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