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