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