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.base.test.util;
6 
7 import android.os.Handler;
8 import android.os.Looper;
9 
10 import org.hamcrest.Matchers;
11 
12 import org.chromium.base.ThreadUtils;
13 
14 import java.lang.reflect.InvocationTargetException;
15 import java.util.concurrent.Callable;
16 import java.util.concurrent.atomic.AtomicBoolean;
17 import java.util.concurrent.atomic.AtomicReference;
18 
19 /**
20  * Helper methods for creating and managing criteria.
21  *
22  * <p>
23  * If possible, use callbacks or testing delegates instead of criteria as they
24  * do not introduce any polling delays.  Should only use criteria if no suitable
25  * other approach exists.
26  *
27  * <p>
28  * The Runnable variation of the CriteriaHelper methods allows a flexible way of verifying any
29  * number of conditions are met prior to proceeding.
30  *
31  * <pre>
32  * Example:
33  * <code>
34  * private void verifyMenuShown() {
35  *     CriteriaHelper.pollUiThread(() -> {
36  *         Criteria.checkThat("App menu was null", getActivity().getAppMenuHandler(),
37  *                 Matchers.notNullValue());
38  *         Criteria.checkThat("App menu was not shown",
39  *                 getActivity().getAppMenuHandler().isAppMenuShowing(), Matchers.is(true));
40  *     });
41  * }
42  * </code>
43  * </pre>
44  *
45  * <p>
46  * To verify simple conditions, the Callback variation can be less verbose.
47  *
48  * <pre>
49  * Example:
50  * <code>
51  * private void assertMenuShown() {
52  *     CriteriaHelper.pollUiThread(() -> getActivity().getAppMenuHandler().isAppMenuShowing(),
53  *             "App menu was not shown");
54  * }
55  * </code>
56  * </pre>
57  */
58 public class CriteriaHelper {
59     /** The default maximum time to wait for a criteria to become valid. */
60     public static final long DEFAULT_MAX_TIME_TO_POLL = 3000L;
61     /** The default polling interval to wait between checking for a satisfied criteria. */
62     public static final long DEFAULT_POLLING_INTERVAL = 50;
63 
64     private static final long DEFAULT_JUNIT_MAX_TIME_TO_POLL = 1000;
65     private static final long DEFAULT_JUNIT_POLLING_INTERVAL = 1;
66 
67     /**
68      * Checks whether the given Runnable completes without exception at a given interval, until
69      * either the Runnable successfully completes, or the maxTimeoutMs number of ms has elapsed.
70      *
71      * <p>
72      * This evaluates the Criteria on the Instrumentation thread, which more often than not is not
73      * correct in an InstrumentationTest. Use
74      * {@link #pollUiThread(Runnable, long, long)} instead.
75      *
76      * @param criteria The Runnable that will be attempted.
77      * @param maxTimeoutMs The maximum number of ms that this check will be performed for
78      *                     before timeout.
79      * @param checkIntervalMs The number of ms between checks.
80      */
pollInstrumentationThread( Runnable criteria, long maxTimeoutMs, long checkIntervalMs)81     public static void pollInstrumentationThread(
82             Runnable criteria, long maxTimeoutMs, long checkIntervalMs) {
83         assert !ThreadUtils.runningOnUiThread();
84         pollThreadInternal(criteria, maxTimeoutMs, checkIntervalMs, false);
85     }
86 
pollThreadInternal( Runnable criteria, long maxTimeoutMs, long checkIntervalMs, boolean shouldNest)87     private static void pollThreadInternal(
88             Runnable criteria, long maxTimeoutMs, long checkIntervalMs, boolean shouldNest) {
89         Throwable throwable;
90         try {
91             criteria.run();
92             return;
93         } catch (Throwable e) {
94             // Espresso catches, wraps, and re-throws the exception we want the CriteriaHelper
95             // to catch.
96             if (e instanceof CriteriaNotSatisfiedException
97                     || e.getCause() instanceof CriteriaNotSatisfiedException) {
98                 throwable = e;
99             } else {
100                 throw e;
101             }
102         }
103         TimeoutTimer timer = new TimeoutTimer(maxTimeoutMs);
104         while (!timer.isTimedOut()) {
105             if (shouldNest) {
106                 nestThread(checkIntervalMs);
107             } else {
108                 sleepThread(checkIntervalMs);
109             }
110             try {
111                 criteria.run();
112                 return;
113             } catch (Throwable e) {
114                 if (e instanceof CriteriaNotSatisfiedException
115                         || e.getCause() instanceof CriteriaNotSatisfiedException) {
116                     throwable = e;
117                 } else {
118                     throw e;
119                 }
120             }
121         }
122         throw new AssertionError(throwable);
123     }
124 
sleepThread(long checkIntervalMs)125     private static void sleepThread(long checkIntervalMs) {
126         try {
127             Thread.sleep(checkIntervalMs);
128         } catch (InterruptedException e) {
129             // Catch the InterruptedException. If the exception occurs before maxTimeoutMs
130             // and the criteria is not satisfied, the while loop will run again.
131         }
132     }
133 
nestThread(long checkIntervalMs)134     private static void nestThread(long checkIntervalMs) {
135         AtomicBoolean called = new AtomicBoolean(false);
136 
137         // Ensure we pump the message handler in case no new tasks arrive.
138         new Handler(Looper.myLooper()).postDelayed(() -> { called.set(true); }, checkIntervalMs);
139 
140         TimeoutTimer timer = new TimeoutTimer(checkIntervalMs);
141         // To allow a checkInterval of 0ms, ensure we at least run a single task, which allows a
142         // test to check conditions between each task run on the thread.
143         do {
144             try {
145                 LooperUtils.runSingleNestedLooperTask();
146             } catch (IllegalArgumentException | IllegalAccessException | SecurityException
147                     | InvocationTargetException e) {
148                 throw new RuntimeException(e);
149             }
150         } while (!timer.isTimedOut() && !called.get());
151     }
152 
153     /**
154      * Checks whether the given Runnable completes without exception at the default interval.
155      *
156      * <p>
157      * This evaluates the Runnable on the test thread, which more often than not is not correct
158      * in an InstrumentationTest.  Use {@link #pollUiThread(Runnable)} instead.
159      *
160      * @param criteria The Runnable that will be attempted.
161      *
162      * @see #pollInstrumentationThread(Criteria, long, long)
163      */
pollInstrumentationThread(Runnable criteria)164     public static void pollInstrumentationThread(Runnable criteria) {
165         pollInstrumentationThread(criteria, DEFAULT_MAX_TIME_TO_POLL, DEFAULT_POLLING_INTERVAL);
166     }
167 
168     /**
169      * Checks whether the given Callable<Boolean> is satisfied at a given interval, until either
170      * the criteria is satisfied, or the specified maxTimeoutMs number of ms has elapsed.
171      *
172      * <p>
173      * This evaluates the Callable<Boolean> on the test thread, which more often than not is not
174      * correct in an InstrumentationTest.  Use {@link #pollUiThread(Callable)} instead.
175      *
176      * @param criteria The Callable<Boolean> that will be checked.
177      * @param failureReason The static failure reason
178      * @param maxTimeoutMs The maximum number of ms that this check will be performed for
179      *                     before timeout.
180      * @param checkIntervalMs The number of ms between checks.
181      */
pollInstrumentationThread(final Callable<Boolean> criteria, String failureReason, long maxTimeoutMs, long checkIntervalMs)182     public static void pollInstrumentationThread(final Callable<Boolean> criteria,
183             String failureReason, long maxTimeoutMs, long checkIntervalMs) {
184         pollInstrumentationThread(
185                 toNotSatisfiedRunnable(criteria, failureReason), maxTimeoutMs, checkIntervalMs);
186     }
187 
188     /**
189      * Checks whether the given Callable<Boolean> is satisfied at a given interval, until either
190      * the criteria is satisfied, or the specified maxTimeoutMs number of ms has elapsed.
191      *
192      * <p>
193      * This evaluates the Callable<Boolean> on the test thread, which more often than not is not
194      * correct in an InstrumentationTest.  Use {@link #pollUiThread(Callable)} instead.
195      *
196      * @param criteria The Callable<Boolean> that will be checked.
197      * @param maxTimeoutMs The maximum number of ms that this check will be performed for
198      *                     before timeout.
199      * @param checkIntervalMs The number of ms between checks.
200      */
pollInstrumentationThread( final Callable<Boolean> criteria, long maxTimeoutMs, long checkIntervalMs)201     public static void pollInstrumentationThread(
202             final Callable<Boolean> criteria, long maxTimeoutMs, long checkIntervalMs) {
203         pollInstrumentationThread(criteria, null, maxTimeoutMs, checkIntervalMs);
204     }
205 
206     /**
207      * Checks whether the given Callable<Boolean> is satisfied polling at a default interval.
208      *
209      * <p>
210      * This evaluates the Callable<Boolean> on the test thread, which more often than not is not
211      * correct in an InstrumentationTest.  Use {@link #pollUiThread(Callable)} instead.
212      *
213      * @param criteria The Callable<Boolean> that will be checked.
214      * @param failureReason The static failure reason
215      */
pollInstrumentationThread(Callable<Boolean> criteria, String failureReason)216     public static void pollInstrumentationThread(Callable<Boolean> criteria, String failureReason) {
217         pollInstrumentationThread(
218                 criteria, failureReason, DEFAULT_MAX_TIME_TO_POLL, DEFAULT_POLLING_INTERVAL);
219     }
220 
221     /**
222      * Checks whether the given Callable<Boolean> is satisfied polling at a default interval.
223      *
224      * <p>
225      * This evaluates the Callable<Boolean> on the test thread, which more often than not is not
226      * correct in an InstrumentationTest.  Use {@link #pollUiThread(Callable)} instead.
227      *
228      * @param criteria The Callable<Boolean> that will be checked.
229      */
pollInstrumentationThread(Callable<Boolean> criteria)230     public static void pollInstrumentationThread(Callable<Boolean> criteria) {
231         pollInstrumentationThread(criteria, null);
232     }
233 
234     /**
235      * Checks whether the given Runnable completes without exception at a given interval on the UI
236      * thread, until either the Runnable successfully completes, or the maxTimeoutMs number of ms
237      * has elapsed.
238      *
239      * @param criteria The Runnable that will be attempted.
240      * @param maxTimeoutMs The maximum number of ms that this check will be performed for
241      *                     before timeout.
242      * @param checkIntervalMs The number of ms between checks.
243      *
244      * @see #pollInstrumentationThread(Runnable)
245      */
pollUiThread( final Runnable criteria, long maxTimeoutMs, long checkIntervalMs)246     public static void pollUiThread(
247             final Runnable criteria, long maxTimeoutMs, long checkIntervalMs) {
248         assert !ThreadUtils.runningOnUiThread();
249         pollInstrumentationThread(() -> {
250             AtomicReference<Throwable> throwableRef = new AtomicReference<>();
251             ThreadUtils.runOnUiThreadBlocking(() -> {
252                 try {
253                     criteria.run();
254                 } catch (Throwable t) {
255                     throwableRef.set(t);
256                 }
257             });
258             Throwable throwable = throwableRef.get();
259             if (throwable != null) {
260                 if (throwable instanceof CriteriaNotSatisfiedException) {
261                     throw new CriteriaNotSatisfiedException(throwable);
262                 } else if (throwable instanceof RuntimeException) {
263                     throw(RuntimeException) throwable;
264                 } else {
265                     throw new RuntimeException(throwable);
266                 }
267             }
268         }, maxTimeoutMs, checkIntervalMs);
269     }
270 
271     /**
272      * Checks whether the given Runnable completes without exception at the default interval on
273      * the UI thread.
274      * @param criteria The Runnable that will be attempted.
275      *
276      * @see #pollInstrumentationThread(Runnable)
277      */
pollUiThread(final Runnable criteria)278     public static void pollUiThread(final Runnable criteria) {
279         pollUiThread(criteria, DEFAULT_MAX_TIME_TO_POLL, DEFAULT_POLLING_INTERVAL);
280     }
281 
282     /**
283      * Checks whether the given Callable<Boolean> is satisfied polling at a given interval on the UI
284      * thread, until either the criteria is satisfied, or the maxTimeoutMs number of ms has elapsed.
285      *
286      * @param criteria The Callable<Boolean> that will be checked.
287      * @param failureReason The static failure reason
288      * @param maxTimeoutMs The maximum number of ms that this check will be performed for
289      *                     before timeout.
290      * @param checkIntervalMs The number of ms between checks.
291      *
292      * @see #pollInstrumentationThread(Criteria)
293      */
pollUiThread(final Callable<Boolean> criteria, String failureReason, long maxTimeoutMs, long checkIntervalMs)294     public static void pollUiThread(final Callable<Boolean> criteria, String failureReason,
295             long maxTimeoutMs, long checkIntervalMs) {
296         pollUiThread(
297                 toNotSatisfiedRunnable(criteria, failureReason), maxTimeoutMs, checkIntervalMs);
298     }
299 
300     /**
301      * Checks whether the given Callable<Boolean> is satisfied polling at a given interval on the UI
302      * thread, until either the criteria is satisfied, or the maxTimeoutMs number of ms has elapsed.
303      *
304      * @param criteria The Callable<Boolean> that will be checked.
305      * @param maxTimeoutMs The maximum number of ms that this check will be performed for
306      *                     before timeout.
307      * @param checkIntervalMs The number of ms between checks.
308      *
309      * @see #pollInstrumentationThread(Criteria)
310      */
pollUiThread( final Callable<Boolean> criteria, long maxTimeoutMs, long checkIntervalMs)311     public static void pollUiThread(
312             final Callable<Boolean> criteria, long maxTimeoutMs, long checkIntervalMs) {
313         pollUiThread(criteria, null, maxTimeoutMs, checkIntervalMs);
314     }
315 
316     /**
317      * Checks whether the given Callable<Boolean> is satisfied polling at a default interval on the
318      * UI thread. A static failure reason is given.
319      * @param criteria The Callable<Boolean> that will be checked.
320      * @param failureReason The static failure reason
321      *
322      * @see #pollInstrumentationThread(Criteria)
323      */
pollUiThread(final Callable<Boolean> criteria, String failureReason)324     public static void pollUiThread(final Callable<Boolean> criteria, String failureReason) {
325         pollUiThread(criteria, failureReason, DEFAULT_MAX_TIME_TO_POLL, DEFAULT_POLLING_INTERVAL);
326     }
327 
328     /**
329      * Checks whether the given Callable<Boolean> is satisfied polling at a default interval on the
330      * UI thread.
331      * @param criteria The Callable<Boolean> that will be checked.
332      *
333      * @see #pollInstrumentationThread(Criteria)
334      */
pollUiThread(final Callable<Boolean> criteria)335     public static void pollUiThread(final Callable<Boolean> criteria) {
336         pollUiThread(criteria, null);
337     }
338 
339     /**
340      * Checks whether the given Runnable completes without exception at a given interval on the UI
341      * thread, until either the Runnable successfully completes, or the maxTimeoutMs number of ms
342      * has elapsed.
343      * This call will nest the Looper in order to wait for the Runnable to complete.
344      *
345      * @param criteria The Runnable that will be attempted.
346      * @param maxTimeoutMs The maximum number of ms that this check will be performed for
347      *                     before timeout.
348      * @param checkIntervalMs The number of ms between checks.
349      *
350      * @see #pollInstrumentationThread(Runnable)
351      */
pollUiThreadNested( Runnable criteria, long maxTimeoutMs, long checkIntervalMs)352     public static void pollUiThreadNested(
353             Runnable criteria, long maxTimeoutMs, long checkIntervalMs) {
354         assert ThreadUtils.runningOnUiThread();
355         pollThreadInternal(criteria, maxTimeoutMs, checkIntervalMs, true);
356     }
357 
358     /**
359      * Checks whether the given Runnable is satisfied polling at a given interval on the UI
360      * thread, until either the criteria is satisfied, or the maxTimeoutMs number of ms has elapsed.
361      * This call will nest the Looper in order to wait for the Criteria to be satisfied.
362      *
363      * @param criteria The Callable<Boolean> that will be checked.
364      * @param maxTimeoutMs The maximum number of ms that this check will be performed for
365      *                     before timeout.
366      * @param checkIntervalMs The number of ms between checks.
367      *
368      * @see #pollInstrumentationThread(Criteria)
369      */
pollUiThreadNested( final Callable<Boolean> criteria, long maxTimeoutMs, long checkIntervalMs)370     public static void pollUiThreadNested(
371             final Callable<Boolean> criteria, long maxTimeoutMs, long checkIntervalMs) {
372         pollUiThreadNested(toNotSatisfiedRunnable(criteria, null), maxTimeoutMs, checkIntervalMs);
373     }
374 
375     /**
376      * Checks whether the given Runnable completes without exception at the default interval on
377      * the UI thread. This call will nest the Looper in order to wait for the Runnable to complete.
378      * @param criteria The Runnable that will be attempted.
379      *
380      * @see #pollInstrumentationThread(Runnable)
381      */
pollUiThreadNested(final Runnable criteria)382     public static void pollUiThreadNested(final Runnable criteria) {
383         pollUiThreadNested(criteria, DEFAULT_MAX_TIME_TO_POLL, DEFAULT_POLLING_INTERVAL);
384     }
385 
386     /**
387      * Checks whether the given Callable<Boolean> is satisfied polling at a default interval on the
388      * UI thread. This call will nest the Looper in order to wait for the Criteria to be satisfied.
389      * @param criteria The Callable<Boolean> that will be checked.
390      *
391      * @see #pollInstrumentationThread(Criteria)
392      */
pollUiThreadNested(final Callable<Boolean> criteria)393     public static void pollUiThreadNested(final Callable<Boolean> criteria) {
394         pollUiThreadNested(toNotSatisfiedRunnable(criteria, null));
395     }
396 
397     /**
398      * Sleeps the JUnit UI thread to wait on the condition. The condition must be met by a
399      * background thread that does not block on the UI thread.
400      *
401      * @param criteria The Callable<Boolean> that will be checked.
402      *
403      * @see #pollInstrumentationThread(Criteria)
404      */
pollUiThreadForJUnit(final Callable<Boolean> criteria)405     public static void pollUiThreadForJUnit(final Callable<Boolean> criteria) {
406         pollUiThreadForJUnit(toNotSatisfiedRunnable(criteria, null));
407     }
408 
409     /**
410      * Sleeps the JUnit UI thread to wait on the criteria. The criteria must be met by a
411      * background thread that does not block on the UI thread.
412      *
413      * @param criteria The Runnable that will be attempted.
414      *
415      * @see #pollInstrumentationThread(Criteria)
416      */
pollUiThreadForJUnit(final Runnable criteria)417     public static void pollUiThreadForJUnit(final Runnable criteria) {
418         assert ThreadUtils.runningOnUiThread();
419         pollThreadInternal(
420                 criteria, DEFAULT_JUNIT_MAX_TIME_TO_POLL, DEFAULT_JUNIT_POLLING_INTERVAL, false);
421     }
422 
toNotSatisfiedRunnable( Callable<Boolean> criteria, String failureReason)423     private static Runnable toNotSatisfiedRunnable(
424             Callable<Boolean> criteria, String failureReason) {
425         return () -> {
426             boolean isSatisfied;
427             try {
428                 isSatisfied = criteria.call();
429             } catch (RuntimeException re) {
430                 throw re;
431             } catch (Exception e) {
432                 throw new RuntimeException(e);
433             }
434             Criteria.checkThat(failureReason, isSatisfied, Matchers.is(true));
435         };
436     }
437 }
438