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