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