1 /* 2 * Copyright (C) 2009 The Android Open Source Project 3 * 4 * Licensed under the Apache License, Version 2.0 (the "License"); 5 * you may not use this file except in compliance with the License. 6 * You may obtain a copy of the License at 7 * 8 * http://www.apache.org/licenses/LICENSE-2.0 9 * 10 * Unless required by applicable law or agreed to in writing, software 11 * distributed under the License is distributed on an "AS IS" BASIS, 12 * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 13 * See the License for the specific language governing permissions and 14 * limitations under the License. 15 */ 16 17 package com.android.common; 18 19 import android.content.SharedPreferences; 20 import android.test.AndroidTestCase; 21 import android.test.suitebuilder.annotation.MediumTest; 22 import android.test.suitebuilder.annotation.SmallTest; 23 24 public class OperationSchedulerTest extends AndroidTestCase { 25 /** 26 * OperationScheduler subclass which uses an artificial time. 27 * Set {@link #timeMillis} to whatever value you like. 28 */ 29 private class TimeTravelScheduler extends OperationScheduler { 30 static final long DEFAULT_TIME = 1250146800000L; // 13-Aug-2009, 12:00:00 am 31 public long timeMillis = DEFAULT_TIME; 32 33 @Override currentTimeMillis()34 protected long currentTimeMillis() { return timeMillis; } TimeTravelScheduler()35 public TimeTravelScheduler() { super(getFreshStorage()); } 36 } 37 getFreshStorage()38 private SharedPreferences getFreshStorage() { 39 SharedPreferences sp = getContext().getSharedPreferences("OperationSchedulerTest", 0); 40 sp.edit().clear().commit(); 41 return sp; 42 } 43 44 @MediumTest testScheduler()45 public void testScheduler() throws Exception { 46 TimeTravelScheduler scheduler = new TimeTravelScheduler(); 47 OperationScheduler.Options options = new OperationScheduler.Options(); 48 assertEquals(Long.MAX_VALUE, scheduler.getNextTimeMillis(options)); 49 assertEquals(0, scheduler.getLastSuccessTimeMillis()); 50 assertEquals(0, scheduler.getLastAttemptTimeMillis()); 51 52 long beforeTrigger = scheduler.timeMillis; 53 scheduler.setTriggerTimeMillis(beforeTrigger + 1000000); 54 assertEquals(beforeTrigger + 1000000, scheduler.getNextTimeMillis(options)); 55 56 // It will schedule for the later of the trigger and the moratorium... 57 scheduler.setMoratoriumTimeMillis(beforeTrigger + 500000); 58 assertEquals(beforeTrigger + 1000000, scheduler.getNextTimeMillis(options)); 59 scheduler.setMoratoriumTimeMillis(beforeTrigger + 1500000); 60 assertEquals(beforeTrigger + 1500000, scheduler.getNextTimeMillis(options)); 61 62 // Test enable/disable toggle 63 scheduler.setEnabledState(false); 64 assertEquals(Long.MAX_VALUE, scheduler.getNextTimeMillis(options)); 65 scheduler.setEnabledState(true); 66 assertEquals(beforeTrigger + 1500000, scheduler.getNextTimeMillis(options)); 67 68 // Backoff interval after an error 69 long beforeError = (scheduler.timeMillis += 100); 70 scheduler.onTransientError(); 71 assertEquals(0, scheduler.getLastSuccessTimeMillis()); 72 assertEquals(beforeError, scheduler.getLastAttemptTimeMillis()); 73 assertEquals(beforeTrigger + 1500000, scheduler.getNextTimeMillis(options)); 74 options.backoffFixedMillis = 1000000; 75 options.backoffIncrementalMillis = 500000; 76 assertEquals(beforeError + 1500000, scheduler.getNextTimeMillis(options)); 77 78 // Two errors: backoff interval increases 79 beforeError = (scheduler.timeMillis += 100); 80 scheduler.onTransientError(); 81 assertEquals(beforeError, scheduler.getLastAttemptTimeMillis()); 82 assertEquals(beforeError + 2000000, scheduler.getNextTimeMillis(options)); 83 84 // Reset transient error: no backoff interval 85 scheduler.resetTransientError(); 86 assertEquals(0, scheduler.getLastSuccessTimeMillis()); 87 assertEquals(beforeTrigger + 1500000, scheduler.getNextTimeMillis(options)); 88 assertEquals(beforeError, scheduler.getLastAttemptTimeMillis()); 89 90 // Permanent error holds true even if transient errors are reset 91 // However, we remember that the transient error was reset... 92 scheduler.onPermanentError(); 93 assertEquals(Long.MAX_VALUE, scheduler.getNextTimeMillis(options)); 94 scheduler.resetTransientError(); 95 assertEquals(Long.MAX_VALUE, scheduler.getNextTimeMillis(options)); 96 scheduler.resetPermanentError(); 97 assertEquals(beforeTrigger + 1500000, scheduler.getNextTimeMillis(options)); 98 99 // Success resets the trigger 100 long beforeSuccess = (scheduler.timeMillis += 100); 101 scheduler.onSuccess(); 102 assertEquals(beforeSuccess, scheduler.getLastAttemptTimeMillis()); 103 assertEquals(beforeSuccess, scheduler.getLastSuccessTimeMillis()); 104 assertEquals(Long.MAX_VALUE, scheduler.getNextTimeMillis(options)); 105 106 // The moratorium is not reset by success! 107 scheduler.setTriggerTimeMillis(0); 108 assertEquals(beforeTrigger + 1500000, scheduler.getNextTimeMillis(options)); 109 scheduler.setMoratoriumTimeMillis(0); 110 assertEquals(beforeSuccess, scheduler.getNextTimeMillis(options)); 111 112 // Periodic interval after success 113 options.periodicIntervalMillis = 250000; 114 scheduler.setTriggerTimeMillis(Long.MAX_VALUE); 115 assertEquals(beforeSuccess + 250000, scheduler.getNextTimeMillis(options)); 116 117 // Trigger minimum is also since the last success 118 options.minTriggerMillis = 1000000; 119 assertEquals(beforeSuccess + 1000000, scheduler.getNextTimeMillis(options)); 120 } 121 122 @MediumTest testExponentialBackoff()123 public void testExponentialBackoff() throws Exception { 124 TimeTravelScheduler scheduler = new TimeTravelScheduler(); 125 OperationScheduler.Options options = new OperationScheduler.Options(); 126 options.backoffFixedMillis = 100; 127 options.backoffIncrementalMillis = 1000; 128 options.backoffExponentialMillis = 10000; 129 scheduler.setTriggerTimeMillis(0); 130 scheduler.setEnabledState(true); 131 132 // Backoff interval after an error 133 long beforeError = (scheduler.timeMillis += 10); 134 scheduler.onTransientError(); 135 assertEquals(0, scheduler.getLastSuccessTimeMillis()); 136 assertEquals(beforeError, scheduler.getLastAttemptTimeMillis()); 137 assertEquals(beforeError + 11100, scheduler.getNextTimeMillis(options)); 138 139 // Second error 140 beforeError = (scheduler.timeMillis += 10); 141 scheduler.onTransientError(); 142 assertEquals(beforeError, scheduler.getLastAttemptTimeMillis()); 143 assertEquals(beforeError + 22100, scheduler.getNextTimeMillis(options)); 144 145 // Third error 146 beforeError = (scheduler.timeMillis += 10); 147 scheduler.onTransientError(); 148 assertEquals(beforeError, scheduler.getLastAttemptTimeMillis()); 149 assertEquals(beforeError + 43100, scheduler.getNextTimeMillis(options)); 150 151 // Fourth error 152 beforeError = (scheduler.timeMillis += 10); 153 scheduler.onTransientError(); 154 assertEquals(beforeError, scheduler.getLastAttemptTimeMillis()); 155 assertEquals(beforeError + 84100, scheduler.getNextTimeMillis(options)); 156 } 157 158 @MediumTest testExponentialBackoffBoundedByMoratorium()159 public void testExponentialBackoffBoundedByMoratorium() throws Exception { 160 TimeTravelScheduler scheduler = new TimeTravelScheduler(); 161 scheduler.setTriggerTimeMillis(0); 162 scheduler.setEnabledState(true); 163 scheduler.timeMillis = System.currentTimeMillis(); 164 165 OperationScheduler.Options options = new OperationScheduler.Options(); 166 options.backoffFixedMillis = 100; 167 options.backoffIncrementalMillis = 1000; 168 options.backoffExponentialMillis = 10000; 169 options.maxMoratoriumMillis = 24 * 3600 * 1000; 170 171 for(int i = 1; i < 31; i++) { 172 // report an error - increments the errorCount 173 scheduler.onTransientError(); 174 final long nextTime = scheduler.getNextTimeMillis(options); 175 final long timeAfterOperation = System.currentTimeMillis(); 176 assertTrue("Backoff is not bounded by max moratorium for iteration " + i, 177 nextTime < timeAfterOperation + options.maxMoratoriumMillis); 178 } 179 } 180 181 @SmallTest testParseOptions()182 public void testParseOptions() throws Exception { 183 OperationScheduler.Options options = new OperationScheduler.Options(); 184 assertEquals( 185 "OperationScheduler.Options[backoff=0.0+5.0 max=86400.0 min=0.0 period=3600.0]", 186 OperationScheduler.parseOptions("3600", options).toString()); 187 188 assertEquals( 189 "OperationScheduler.Options[backoff=0.0+2.5 max=86400.0 min=0.0 period=3700.0]", 190 OperationScheduler.parseOptions("backoff=+2.5 3700", options).toString()); 191 192 assertEquals( 193 "OperationScheduler.Options[backoff=10.0+2.5 max=12345.6 min=7.0 period=3800.0]", 194 OperationScheduler.parseOptions("max=12345.6 min=7 backoff=10 period=3800", 195 options).toString()); 196 197 assertEquals( 198 "OperationScheduler.Options[backoff=10.0+2.5 max=12345.6 min=7.0 period=3800.0]", 199 OperationScheduler.parseOptions("", options).toString()); 200 201 assertEquals( 202 "OperationScheduler.Options[backoff=5.0+2.5+10.0 max=12345.6 min=7.0 period=3600.0]", 203 OperationScheduler.parseOptions("backoff=5.0++10.0 3600", options).toString()); 204 } 205 206 @SmallTest testMoratoriumWithHttpDate()207 public void testMoratoriumWithHttpDate() throws Exception { 208 TimeTravelScheduler scheduler = new TimeTravelScheduler(); 209 OperationScheduler.Options options = new OperationScheduler.Options(); 210 211 long beforeTrigger = scheduler.timeMillis; 212 scheduler.setTriggerTimeMillis(beforeTrigger + 1000000); 213 assertEquals(beforeTrigger + 1000000, scheduler.getNextTimeMillis(options)); 214 215 scheduler.setMoratoriumTimeMillis(beforeTrigger + 2000000); 216 assertEquals(beforeTrigger + 2000000, scheduler.getNextTimeMillis(options)); 217 218 long beforeMoratorium = scheduler.timeMillis; 219 assertTrue(scheduler.setMoratoriumTimeHttp("3000")); 220 long afterMoratorium = scheduler.timeMillis; 221 assertTrue(beforeMoratorium + 3000000 <= scheduler.getNextTimeMillis(options)); 222 assertTrue(afterMoratorium + 3000000 >= scheduler.getNextTimeMillis(options)); 223 224 options.maxMoratoriumMillis = Long.MAX_VALUE / 2; 225 assertTrue(scheduler.setMoratoriumTimeHttp("Fri, 31 Dec 2030 23:59:59 GMT")); 226 assertEquals(1924991999000L, scheduler.getNextTimeMillis(options)); 227 228 assertFalse(scheduler.setMoratoriumTimeHttp("not actually a date")); 229 } 230 231 @SmallTest testClockRollbackScenario()232 public void testClockRollbackScenario() throws Exception { 233 TimeTravelScheduler scheduler = new TimeTravelScheduler(); 234 OperationScheduler.Options options = new OperationScheduler.Options(); 235 options.minTriggerMillis = 2000; 236 237 // First, set up a scheduler with reasons to wait: a transient 238 // error with backoff and a moratorium for a few minutes. 239 240 long beforeTrigger = scheduler.timeMillis; 241 long triggerTime = beforeTrigger - 10000000; 242 scheduler.setTriggerTimeMillis(triggerTime); 243 assertEquals(triggerTime, scheduler.getNextTimeMillis(options)); 244 assertEquals(0, scheduler.getLastAttemptTimeMillis()); 245 246 long beforeSuccess = (scheduler.timeMillis += 100); 247 scheduler.onSuccess(); 248 scheduler.setTriggerTimeMillis(triggerTime); 249 assertEquals(beforeSuccess, scheduler.getLastAttemptTimeMillis()); 250 assertEquals(beforeSuccess + 2000, scheduler.getNextTimeMillis(options)); 251 252 long beforeError = (scheduler.timeMillis += 100); 253 scheduler.onTransientError(); 254 assertEquals(beforeError, scheduler.getLastAttemptTimeMillis()); 255 assertEquals(beforeError + 5000, scheduler.getNextTimeMillis(options)); 256 257 long beforeMoratorium = (scheduler.timeMillis += 100); 258 scheduler.setMoratoriumTimeMillis(beforeTrigger + 1000000); 259 assertEquals(beforeTrigger + 1000000, scheduler.getNextTimeMillis(options)); 260 261 // Now set the time back a few seconds. 262 // The moratorium time should still be honored. 263 long beforeRollback = (scheduler.timeMillis = beforeTrigger - 10000); 264 assertEquals(beforeTrigger + 1000000, scheduler.getNextTimeMillis(options)); 265 266 // The rollback also moved the last-attempt clock back to the rollback time. 267 assertEquals(scheduler.timeMillis, scheduler.getLastAttemptTimeMillis()); 268 269 // But if we set the time back more than a day, the moratorium 270 // resets to the maximum moratorium (a day, by default), exposing 271 // the original trigger time. 272 beforeRollback = (scheduler.timeMillis = beforeTrigger - 100000000); 273 assertEquals(triggerTime, scheduler.getNextTimeMillis(options)); 274 assertEquals(beforeRollback, scheduler.getLastAttemptTimeMillis()); 275 276 // If we roll forward until after the re-set moratorium, then it expires. 277 scheduler.timeMillis = triggerTime + 5000000; 278 assertEquals(triggerTime, scheduler.getNextTimeMillis(options)); 279 assertEquals(beforeRollback, scheduler.getLastAttemptTimeMillis()); 280 assertEquals(beforeRollback, scheduler.getLastSuccessTimeMillis()); 281 } 282 } 283