- add sources.
[platform/framework/web/crosswalk.git] / src / content / public / test / android / javatests / src / org / chromium / content / browser / test / util / CallbackHelper.java
1 // Copyright (c) 2012 The Chromium Authors. All rights reserved.
2 // Use of this source code is governed by a BSD-style license that can be
3 // found in the LICENSE file.
4
5 package org.chromium.content.browser.test.util;
6
7 import java.util.concurrent.TimeUnit;
8 import java.util.concurrent.TimeoutException;
9
10 /**
11  * A helper class that encapsulates listening and blocking for callbacks.
12  *
13  * Sample usage:
14  *
15  * // Let us assume that this interface is defined by some piece of production code and is used
16  * // to communicate events that occur in that piece of code. Let us further assume that the
17  * // production code runs on the main thread test code runs on a separate test thread.
18  * // An instance that implements this interface would be injected by test code to ensure that the
19  * // methods are being called on another thread.
20  * interface Delegate {
21  *     void onOperationFailed(String errorMessage);
22  *     void onDataPersisted();
23  * }
24  *
25  * // This is the inner class you'd write in your test case to later inject into the production
26  * // code.
27  * class TestDelegate implements Delegate {
28  *     // This is the preferred way to create a helper that stores the parameters it receives
29  *     // when called by production code.
30  *     public static class OnOperationFailedHelper extends CallbackHelper {
31  *         private String mErrorMessage;
32  *
33  *         public void getErrorMessage() {
34  *             assert getCallCount() > 0;
35  *             return mErrorMessage;
36  *         }
37  *
38  *         public void notifyCalled(String errorMessage) {
39  *             mErrorMessage = errorMessage;
40  *             // It's important to call this after all parameter assignments.
41  *             notifyCalled();
42  *         }
43  *     }
44  *
45  *     // There should be one CallbackHelper instance per method.
46  *     private OnOperationFailedHelper mOnOperationFailedHelper;
47  *     private CallbackHelper mOnDataPersistedHelper;
48  *
49  *     public OnOperationFailedHelper getOnOperationFailedHelper() {
50  *         return mOnOperationFailedHelper;
51  *     }
52  *
53  *     public CallbackHelper getOnDataPersistedHelper() {
54  *         return mOnDataPersistedHelper;
55  *     }
56  *
57  *     @Override
58  *     public void onOperationFailed(String errorMessage) {
59  *         mOnOperationFailedHelper.notifyCalled(errorMessage);
60  *     }
61  *
62  *     @Override
63  *     public void onDataPersisted() {
64  *         mOnDataPersistedHelper.notifyCalled();
65  *     }
66  * }
67  *
68  * // This is a sample test case.
69  * public void testCase() throws Exception {
70  *     // Create the TestDelegate to inject into production code.
71  *     TestDelegate delegate = new TestDelegate();
72  *     // Create the production class instance that is being tested and inject the test delegate.
73  *     CodeUnderTest codeUnderTest = new CodeUnderTest();
74  *     codeUnderTest.setDelegate(delegate);
75  *
76  *     // Typically you'd get the current call count before performing the operation you expect to
77  *     // trigger the callback. There can't be any callbacks 'in flight' at this moment, otherwise
78  *     // the call count is unpredictable and the test will be flaky.
79  *     int onOperationFailedCallCount = delegate.getOnOperationFailedHelper().getCallCount();
80  *     codeUnderTest.doSomethingThatEndsUpCallingOnOperationFailedFromAnotherThread();
81  *     // It's safe to do other stuff here, if needed.
82  *     ....
83  *     // Wait for the callback if it hadn't been called yet, otherwise return immediately. This
84  *     // can throw an exception if the callback doesn't arrive within the timeout.
85  *     delegate.getOnOperationFailedHelper().waitForCallback(onOperationFailedCallCount);
86  *     // Access to method parameters is now safe.
87  *     assertEquals("server error", delegate.getOnOperationFailedHelper().getErrorMessage());
88  *
89  *     // Being able to pass the helper around lets us build methods which encapsulate commonly
90  *     // performed tasks.
91  *     doSomeOperationAndWait(codeUnerTest, delegate.getOnOperationFailedHelper());
92  *
93  *     // The helper can be resued for as many calls as needed, just be sure to get the count each
94  *     // time.
95  *     onOperationFailedCallCount = delegate.getOnOperationFailedHelper().getCallCount();
96  *     codeUnderTest.doSomethingElseButStillFailOnAnotherThread();
97  *     delegate.getOnOperationFailedHelper().waitForCallback(onOperationFailedCallCount);
98  *
99  *     // It is also possible to use more than one helper at a time.
100  *     onOperationFailedCallCount = delegate.getOnOperationFailedHelper().getCallCount();
101  *     int onDataPersistedCallCount = delegate.getOnDataPersistedHelper().getCallCount();
102  *     codeUnderTest.doSomethingThatPersistsDataButFailsInSomeOtherWayOnAnotherThread();
103  *     delegate.getOnDataPersistedHelper().waitForCallback(onDataPersistedCallCount);
104  *     delegate.getOnOperationFailedHelper().waitForCallback(onOperationFailedCallCount);
105  * }
106  *
107  * // Shows how to turn an async operation + completion callback into a synchronous operation.
108  * private void doSomeOperationAndWait(final CodeUnderTest underTest,
109  *         CallbackHelper operationHelper) throws InterruptedException, TimeoutException {
110  *     final int callCount = operaitonHelper.getCallCount();
111  *     getInstrumentaiton().runOnMainSync(new Runnable() {
112  *         @Override
113  *         public void run() {
114  *             // This schedules a call to a method on the injected TestDelegate. The TestDelegate
115  *             // implementation will then call operationHelper.notifyCalled().
116  *             underTest.operation();
117  *         }
118  *      });
119  *      operationHelper.waitForCallback(callCount);
120  * }
121  *
122  */
123 public class CallbackHelper {
124     protected static final int WAIT_TIMEOUT_SECONDS = 5;
125
126     private final Object mLock = new Object();
127     private int mCallCount = 0;
128
129     /**
130      * Gets the number of times the callback has been called.
131      *
132      * The call count can be used with the waitForCallback() method, indicating a point
133      * in time after which the caller wishes to record calls to the callback.
134      *
135      * In order to wait for a callback caused by X, the call count should be obtained
136      * before X occurs.
137      *
138      * NOTE: any call to the callback that occurs after the call count is obtained
139      * will result in the corresponding wait call to resume execution. The call count
140      * is intended to 'catch' callbacks that occur after X but before waitForCallback()
141      * is called.
142      */
143     public int getCallCount() {
144         synchronized(mLock) {
145             return mCallCount;
146         }
147     }
148
149     /**
150      * Blocks until the callback is called the specified number of
151      * times or throws an exception if we exceeded the specified time frame.
152      *
153      * This will wait for a callback to be called a specified number of times after
154      * the point in time at which the call count was obtained.  The method will return
155      * immediately if a call occurred the specified number of times after the
156      * call count was obtained but before the method was called, otherwise the method will
157      * block until the specified call count is reached.
158      *
159      * @param currentCallCount the value obtained by calling getCallCount().
160      * @param numberOfCallsToWaitFor number of calls (counting since
161      *                               currentCallCount was obtained) that we will wait for.
162      * @param timeout timeout value. We will wait the specified amount of time for a single
163      *                callback to occur so the method call may block up to
164      *                <code>numberOfCallsToWaitFor * timeout</code> units.
165      * @param unit timeout unit.
166      * @throws InterruptedException
167      * @throws TimeoutException Thrown if the method times out before onPageFinished is called.
168      */
169     public void waitForCallback(int currentCallCount, int numberOfCallsToWaitFor, long timeout,
170             TimeUnit unit) throws InterruptedException, TimeoutException {
171         assert mCallCount >= currentCallCount;
172         assert numberOfCallsToWaitFor > 0;
173         synchronized(mLock) {
174             int callCountWhenDoneWaiting = currentCallCount + numberOfCallsToWaitFor;
175             while (callCountWhenDoneWaiting > mCallCount) {
176                 int callCountBeforeWait = mCallCount;
177                 mLock.wait(unit.toMillis(timeout));
178                 if (callCountBeforeWait == mCallCount) {
179                     throw new TimeoutException("waitForCallback timed out!");
180                 }
181             }
182         }
183     }
184
185     public void waitForCallback(int currentCallCount, int numberOfCallsToWaitFor)
186             throws InterruptedException, TimeoutException {
187         waitForCallback(currentCallCount, numberOfCallsToWaitFor,
188                 WAIT_TIMEOUT_SECONDS, TimeUnit.SECONDS);
189     }
190
191     public void waitForCallback(int currentCallCount)
192             throws InterruptedException, TimeoutException {
193         waitForCallback(currentCallCount, 1);
194     }
195
196     /**
197      * Blocks until the criteria is satisfied or throws an exception
198      * if the specified time frame is exceeded.
199      * @param timeout timeout value.
200      * @param unit timeout unit.
201      */
202     public void waitUntilCriteria(Criteria criteria, long timeout, TimeUnit unit)
203             throws InterruptedException, TimeoutException {
204         synchronized(mLock) {
205             final long startTime = System.currentTimeMillis();
206             boolean isSatisfied = criteria.isSatisfied();
207             while (!isSatisfied &&
208                     System.currentTimeMillis() - startTime < unit.toMillis(timeout)) {
209                 mLock.wait(unit.toMillis(timeout));
210                 isSatisfied = criteria.isSatisfied();
211             }
212             if (!isSatisfied) throw new TimeoutException("waitUntilCriteria timed out!");
213         }
214     }
215
216     public void waitUntilCriteria(Criteria criteria)
217             throws InterruptedException, TimeoutException {
218         waitUntilCriteria(criteria, WAIT_TIMEOUT_SECONDS, TimeUnit.SECONDS);
219     }
220
221     /**
222      * Should be called when the callback associated with this helper object is called.
223      */
224     public void notifyCalled() {
225         synchronized(mLock) {
226             mCallCount++;
227             mLock.notifyAll();
228         }
229     }
230 }