/* * Copyright (C) 2009 The Android Open Source Project * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. * You may obtain a copy of the License at * * http://www.apache.org/licenses/LICENSE-2.0 * * Unless required by applicable law or agreed to in writing, software * distributed under the License is distributed on an "AS IS" BASIS, * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. * See the License for the specific language governing permissions and * limitations under the License. */ package com.android.common; import android.content.SharedPreferences; import android.test.AndroidTestCase; import android.test.suitebuilder.annotation.MediumTest; import android.test.suitebuilder.annotation.SmallTest; public class OperationSchedulerTest extends AndroidTestCase { /** * OperationScheduler subclass which uses an artificial time. * Set {@link #timeMillis} to whatever value you like. */ private class TimeTravelScheduler extends OperationScheduler { static final long DEFAULT_TIME = 1250146800000L; // 13-Aug-2009, 12:00:00 am public long timeMillis = DEFAULT_TIME; @Override protected long currentTimeMillis() { return timeMillis; } public TimeTravelScheduler() { super(getFreshStorage()); } } private SharedPreferences getFreshStorage() { SharedPreferences sp = getContext().getSharedPreferences("OperationSchedulerTest", 0); sp.edit().clear().commit(); return sp; } @MediumTest public void testScheduler() throws Exception { TimeTravelScheduler scheduler = new TimeTravelScheduler(); OperationScheduler.Options options = new OperationScheduler.Options(); assertEquals(Long.MAX_VALUE, scheduler.getNextTimeMillis(options)); assertEquals(0, scheduler.getLastSuccessTimeMillis()); assertEquals(0, scheduler.getLastAttemptTimeMillis()); long beforeTrigger = scheduler.timeMillis; scheduler.setTriggerTimeMillis(beforeTrigger + 1000000); assertEquals(beforeTrigger + 1000000, scheduler.getNextTimeMillis(options)); // It will schedule for the later of the trigger and the moratorium... scheduler.setMoratoriumTimeMillis(beforeTrigger + 500000); assertEquals(beforeTrigger + 1000000, scheduler.getNextTimeMillis(options)); scheduler.setMoratoriumTimeMillis(beforeTrigger + 1500000); assertEquals(beforeTrigger + 1500000, scheduler.getNextTimeMillis(options)); // Test enable/disable toggle scheduler.setEnabledState(false); assertEquals(Long.MAX_VALUE, scheduler.getNextTimeMillis(options)); scheduler.setEnabledState(true); assertEquals(beforeTrigger + 1500000, scheduler.getNextTimeMillis(options)); // Backoff interval after an error long beforeError = (scheduler.timeMillis += 100); scheduler.onTransientError(); assertEquals(0, scheduler.getLastSuccessTimeMillis()); assertEquals(beforeError, scheduler.getLastAttemptTimeMillis()); assertEquals(beforeTrigger + 1500000, scheduler.getNextTimeMillis(options)); options.backoffFixedMillis = 1000000; options.backoffIncrementalMillis = 500000; assertEquals(beforeError + 1500000, scheduler.getNextTimeMillis(options)); // Two errors: backoff interval increases beforeError = (scheduler.timeMillis += 100); scheduler.onTransientError(); assertEquals(beforeError, scheduler.getLastAttemptTimeMillis()); assertEquals(beforeError + 2000000, scheduler.getNextTimeMillis(options)); // Reset transient error: no backoff interval scheduler.resetTransientError(); assertEquals(0, scheduler.getLastSuccessTimeMillis()); assertEquals(beforeTrigger + 1500000, scheduler.getNextTimeMillis(options)); assertEquals(beforeError, scheduler.getLastAttemptTimeMillis()); // Permanent error holds true even if transient errors are reset // However, we remember that the transient error was reset... scheduler.onPermanentError(); assertEquals(Long.MAX_VALUE, scheduler.getNextTimeMillis(options)); scheduler.resetTransientError(); assertEquals(Long.MAX_VALUE, scheduler.getNextTimeMillis(options)); scheduler.resetPermanentError(); assertEquals(beforeTrigger + 1500000, scheduler.getNextTimeMillis(options)); // Success resets the trigger long beforeSuccess = (scheduler.timeMillis += 100); scheduler.onSuccess(); assertEquals(beforeSuccess, scheduler.getLastAttemptTimeMillis()); assertEquals(beforeSuccess, scheduler.getLastSuccessTimeMillis()); assertEquals(Long.MAX_VALUE, scheduler.getNextTimeMillis(options)); // The moratorium is not reset by success! scheduler.setTriggerTimeMillis(0); assertEquals(beforeTrigger + 1500000, scheduler.getNextTimeMillis(options)); scheduler.setMoratoriumTimeMillis(0); assertEquals(beforeSuccess, scheduler.getNextTimeMillis(options)); // Periodic interval after success options.periodicIntervalMillis = 250000; scheduler.setTriggerTimeMillis(Long.MAX_VALUE); assertEquals(beforeSuccess + 250000, scheduler.getNextTimeMillis(options)); // Trigger minimum is also since the last success options.minTriggerMillis = 1000000; assertEquals(beforeSuccess + 1000000, scheduler.getNextTimeMillis(options)); } @MediumTest public void testExponentialBackoff() throws Exception { TimeTravelScheduler scheduler = new TimeTravelScheduler(); OperationScheduler.Options options = new OperationScheduler.Options(); options.backoffFixedMillis = 100; options.backoffIncrementalMillis = 1000; options.backoffExponentialMillis = 10000; scheduler.setTriggerTimeMillis(0); scheduler.setEnabledState(true); // Backoff interval after an error long beforeError = (scheduler.timeMillis += 10); scheduler.onTransientError(); assertEquals(0, scheduler.getLastSuccessTimeMillis()); assertEquals(beforeError, scheduler.getLastAttemptTimeMillis()); assertEquals(beforeError + 11100, scheduler.getNextTimeMillis(options)); // Second error beforeError = (scheduler.timeMillis += 10); scheduler.onTransientError(); assertEquals(beforeError, scheduler.getLastAttemptTimeMillis()); assertEquals(beforeError + 22100, scheduler.getNextTimeMillis(options)); // Third error beforeError = (scheduler.timeMillis += 10); scheduler.onTransientError(); assertEquals(beforeError, scheduler.getLastAttemptTimeMillis()); assertEquals(beforeError + 43100, scheduler.getNextTimeMillis(options)); // Fourth error beforeError = (scheduler.timeMillis += 10); scheduler.onTransientError(); assertEquals(beforeError, scheduler.getLastAttemptTimeMillis()); assertEquals(beforeError + 84100, scheduler.getNextTimeMillis(options)); } @SmallTest public void testParseOptions() throws Exception { OperationScheduler.Options options = new OperationScheduler.Options(); assertEquals( "OperationScheduler.Options[backoff=0.0+5.0 max=86400.0 min=0.0 period=3600.0]", OperationScheduler.parseOptions("3600", options).toString()); assertEquals( "OperationScheduler.Options[backoff=0.0+2.5 max=86400.0 min=0.0 period=3700.0]", OperationScheduler.parseOptions("backoff=+2.5 3700", options).toString()); assertEquals( "OperationScheduler.Options[backoff=10.0+2.5 max=12345.6 min=7.0 period=3800.0]", OperationScheduler.parseOptions("max=12345.6 min=7 backoff=10 period=3800", options).toString()); assertEquals( "OperationScheduler.Options[backoff=10.0+2.5 max=12345.6 min=7.0 period=3800.0]", OperationScheduler.parseOptions("", options).toString()); assertEquals( "OperationScheduler.Options[backoff=5.0+2.5+10.0 max=12345.6 min=7.0 period=3600.0]", OperationScheduler.parseOptions("backoff=5.0++10.0 3600", options).toString()); } @SmallTest public void testMoratoriumWithHttpDate() throws Exception { TimeTravelScheduler scheduler = new TimeTravelScheduler(); OperationScheduler.Options options = new OperationScheduler.Options(); long beforeTrigger = scheduler.timeMillis; scheduler.setTriggerTimeMillis(beforeTrigger + 1000000); assertEquals(beforeTrigger + 1000000, scheduler.getNextTimeMillis(options)); scheduler.setMoratoriumTimeMillis(beforeTrigger + 2000000); assertEquals(beforeTrigger + 2000000, scheduler.getNextTimeMillis(options)); long beforeMoratorium = scheduler.timeMillis; assertTrue(scheduler.setMoratoriumTimeHttp("3000")); long afterMoratorium = scheduler.timeMillis; assertTrue(beforeMoratorium + 3000000 <= scheduler.getNextTimeMillis(options)); assertTrue(afterMoratorium + 3000000 >= scheduler.getNextTimeMillis(options)); options.maxMoratoriumMillis = Long.MAX_VALUE / 2; assertTrue(scheduler.setMoratoriumTimeHttp("Fri, 31 Dec 2030 23:59:59 GMT")); assertEquals(1924991999000L, scheduler.getNextTimeMillis(options)); assertFalse(scheduler.setMoratoriumTimeHttp("not actually a date")); } @SmallTest public void testClockRollbackScenario() throws Exception { TimeTravelScheduler scheduler = new TimeTravelScheduler(); OperationScheduler.Options options = new OperationScheduler.Options(); options.minTriggerMillis = 2000; // First, set up a scheduler with reasons to wait: a transient // error with backoff and a moratorium for a few minutes. long beforeTrigger = scheduler.timeMillis; long triggerTime = beforeTrigger - 10000000; scheduler.setTriggerTimeMillis(triggerTime); assertEquals(triggerTime, scheduler.getNextTimeMillis(options)); assertEquals(0, scheduler.getLastAttemptTimeMillis()); long beforeSuccess = (scheduler.timeMillis += 100); scheduler.onSuccess(); scheduler.setTriggerTimeMillis(triggerTime); assertEquals(beforeSuccess, scheduler.getLastAttemptTimeMillis()); assertEquals(beforeSuccess + 2000, scheduler.getNextTimeMillis(options)); long beforeError = (scheduler.timeMillis += 100); scheduler.onTransientError(); assertEquals(beforeError, scheduler.getLastAttemptTimeMillis()); assertEquals(beforeError + 5000, scheduler.getNextTimeMillis(options)); long beforeMoratorium = (scheduler.timeMillis += 100); scheduler.setMoratoriumTimeMillis(beforeTrigger + 1000000); assertEquals(beforeTrigger + 1000000, scheduler.getNextTimeMillis(options)); // Now set the time back a few seconds. // The moratorium time should still be honored. long beforeRollback = (scheduler.timeMillis = beforeTrigger - 10000); assertEquals(beforeTrigger + 1000000, scheduler.getNextTimeMillis(options)); // The rollback also moved the last-attempt clock back to the rollback time. assertEquals(scheduler.timeMillis, scheduler.getLastAttemptTimeMillis()); // But if we set the time back more than a day, the moratorium // resets to the maximum moratorium (a day, by default), exposing // the original trigger time. beforeRollback = (scheduler.timeMillis = beforeTrigger - 100000000); assertEquals(triggerTime, scheduler.getNextTimeMillis(options)); assertEquals(beforeRollback, scheduler.getLastAttemptTimeMillis()); // If we roll forward until after the re-set moratorium, then it expires. scheduler.timeMillis = triggerTime + 5000000; assertEquals(triggerTime, scheduler.getNextTimeMillis(options)); assertEquals(beforeRollback, scheduler.getLastAttemptTimeMillis()); assertEquals(beforeRollback, scheduler.getLastSuccessTimeMillis()); } }