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