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