1 /* 2 * Copyright (C) 2010 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.email; 18 19 import android.os.Handler; 20 import android.os.Message; 21 import android.test.AndroidTestCase; 22 import android.test.suitebuilder.annotation.SmallTest; 23 24 import com.android.mail.utils.Clock; 25 import com.android.mail.utils.Throttle; 26 27 import java.util.Timer; 28 import java.util.TimerTask; 29 import java.util.concurrent.BlockingQueue; 30 import java.util.concurrent.LinkedBlockingQueue; 31 32 @SmallTest 33 public class ThrottleTest extends AndroidTestCase { 34 private static final int MIN_TIMEOUT = 100; 35 private static final int MAX_TIMEOUT = 500; 36 37 private final CountingRunnable mRunnable = new CountingRunnable(); 38 private final MockClock mClock = new MockClock(); 39 private final MockTimer mTimer = new MockTimer(mClock); 40 private final Throttle mTarget = new Throttle("test", mRunnable, new CallItNowHandler(), 41 MIN_TIMEOUT, MAX_TIMEOUT, mClock, mTimer); 42 43 /** 44 * Advance the clock. 45 */ 46 private void advanceClock(int milliseconds) { 47 mClock.advance(milliseconds); 48 mTimer.runExpiredTasks(); 49 } 50 51 /** 52 * Gets two events. They're far apart enough that the timeout won't be extended. 53 */ 54 public void testSingleCalls() { 55 // T + 0 56 mTarget.onEvent(); 57 advanceClock(0); 58 assertEquals(0, mRunnable.mCounter); 59 60 // T + 99 61 advanceClock(99); 62 assertEquals(0, mRunnable.mCounter); // Still not called 63 64 // T + 100 65 advanceClock(1); 66 assertEquals(1, mRunnable.mCounter); // Called 67 68 // T + 10100 69 advanceClock(10000); 70 assertEquals(1, mRunnable.mCounter); 71 72 // Do the same thing again. Should work in the same way. 73 74 // T + 0 75 mTarget.onEvent(); 76 advanceClock(0); 77 assertEquals(1, mRunnable.mCounter); 78 79 // T + 99 80 advanceClock(99); 81 assertEquals(1, mRunnable.mCounter); // Still not called 82 83 // T + 100 84 advanceClock(1); 85 assertEquals(2, mRunnable.mCounter); // Called 86 87 // T + 10100 88 advanceClock(10000); 89 assertEquals(2, mRunnable.mCounter); 90 } 91 92 /** 93 * Gets 5 events in a row in a short period. 94 * 95 * We only roughly check the consequence, as the detailed spec isn't really important. 96 * Here, we check if the timeout is extended, and the callback get called less than 97 * 5 times. 98 */ 99 public void testMultiCalls() { 100 mTarget.onEvent(); 101 advanceClock(1); 102 mTarget.onEvent(); 103 advanceClock(1); 104 mTarget.onEvent(); 105 advanceClock(1); 106 mTarget.onEvent(); 107 advanceClock(1); 108 mTarget.onEvent(); 109 110 // Timeout should be extended 111 assertTrue(mTarget.getTimeoutForTest() > 100); 112 113 // Shouldn't result in 5 callback calls. 114 advanceClock(2000); 115 assertTrue(mRunnable.mCounter < 5); 116 } 117 118 public void testUpdateTimeout() { 119 // Check initial value 120 assertEquals(100, mTarget.getTimeoutForTest()); 121 122 // First call -- won't change the timeout 123 mTarget.updateTimeout(); 124 assertEquals(100, mTarget.getTimeoutForTest()); 125 126 // Call again in 10 ms -- will extend timeout. 127 mClock.advance(10); 128 mTarget.updateTimeout(); 129 assertEquals(200, mTarget.getTimeoutForTest()); 130 131 // Call again in TIMEOUT_EXTEND_INTERAVL ms -- will extend timeout. 132 mClock.advance(Throttle.TIMEOUT_EXTEND_INTERVAL); 133 mTarget.updateTimeout(); 134 assertEquals(400, mTarget.getTimeoutForTest()); 135 136 // Again -- timeout reaches max. 137 mClock.advance(Throttle.TIMEOUT_EXTEND_INTERVAL); 138 mTarget.updateTimeout(); 139 assertEquals(500, mTarget.getTimeoutForTest()); 140 141 // Call in TIMEOUT_EXTEND_INTERAVL + 1 ms -- timeout will get reset. 142 mClock.advance(Throttle.TIMEOUT_EXTEND_INTERVAL + 1); 143 mTarget.updateTimeout(); 144 assertEquals(100, mTarget.getTimeoutForTest()); 145 } 146 147 private static class CountingRunnable implements Runnable { 148 public int mCounter; 149 150 @Override 151 public void run() { 152 mCounter++; 153 } 154 } 155 156 /** 157 * Dummy {@link Handler} that executes {@link Runnable}s passed to {@link Handler#post} 158 * immediately on the current thread. 159 */ 160 private static class CallItNowHandler extends Handler { 161 @Override 162 public boolean sendMessageAtTime(Message msg, long uptimeMillis) { 163 msg.getCallback().run(); 164 return true; 165 } 166 } 167 168 /** 169 * Substitute for {@link Timer} that works based on the provided {@link Clock}. 170 */ 171 private static class MockTimer extends Timer { 172 private final Clock mClock; 173 174 private static class Entry { 175 public long mScheduledTime; 176 public TimerTask mTask; 177 } 178 179 private final BlockingQueue<Entry> mTasks = new LinkedBlockingQueue<Entry>(); 180 181 public MockTimer(Clock clock) { 182 mClock = clock; 183 } 184 185 @Override 186 public void schedule(TimerTask task, long delay) { 187 if (delay == 0) { 188 task.run(); 189 } else { 190 Entry e = new Entry(); 191 e.mScheduledTime = mClock.getTime() + delay; 192 e.mTask = task; 193 mTasks.offer(e); 194 } 195 } 196 197 /** 198 * {@link MockTimer} can't know when the clock advances. This method must be called 199 * whenever the (mock) current time changes. 200 */ 201 public void runExpiredTasks() { 202 while (!mTasks.isEmpty()) { 203 Entry e = mTasks.peek(); 204 if (e.mScheduledTime > mClock.getTime()) { 205 break; 206 } 207 e.mTask.run(); 208 mTasks.poll(); 209 } 210 } 211 } 212 } 213