blob: 1677bf8074679828237cb35caa49d54ec67ced7b [file] [log] [blame]
// Copyright 2012 The Chromium Authors
// Use of this source code is governed by a BSD-style license that can be
// found in the LICENSE file.
package org.chromium.base.test.util;
import android.os.Handler;
import android.os.Looper;
import org.hamcrest.Matchers;
import org.chromium.base.ThreadUtils;
import java.lang.reflect.InvocationTargetException;
import java.util.concurrent.Callable;
import java.util.concurrent.atomic.AtomicBoolean;
import java.util.concurrent.atomic.AtomicReference;
/**
* Helper methods for creating and managing criteria.
*
* <p>
* If possible, use callbacks or testing delegates instead of criteria as they
* do not introduce any polling delays. Should only use criteria if no suitable
* other approach exists.
*
* <p>
* The Runnable variation of the CriteriaHelper methods allows a flexible way of verifying any
* number of conditions are met prior to proceeding.
*
* <pre>
* Example:
* <code>
* private void verifyMenuShown() {
* CriteriaHelper.pollUiThread(() -> {
* Criteria.checkThat("App menu was null", getActivity().getAppMenuHandler(),
* Matchers.notNullValue());
* Criteria.checkThat("App menu was not shown",
* getActivity().getAppMenuHandler().isAppMenuShowing(), Matchers.is(true));
* });
* }
* </code>
* </pre>
*
* <p>
* To verify simple conditions, the Callback variation can be less verbose.
*
* <pre>
* Example:
* <code>
* private void assertMenuShown() {
* CriteriaHelper.pollUiThread(() -> getActivity().getAppMenuHandler().isAppMenuShowing(),
* "App menu was not shown");
* }
* </code>
* </pre>
*/
public class CriteriaHelper {
/** The default maximum time to wait for a criteria to become valid. */
public static final long DEFAULT_MAX_TIME_TO_POLL = 3000L;
/** The default polling interval to wait between checking for a satisfied criteria. */
public static final long DEFAULT_POLLING_INTERVAL = 50;
private static final long DEFAULT_JUNIT_MAX_TIME_TO_POLL = 1000;
private static final long DEFAULT_JUNIT_POLLING_INTERVAL = 1;
/**
* Checks whether the given Runnable completes without exception at a given interval, until
* either the Runnable successfully completes, or the maxTimeoutMs number of ms has elapsed.
*
* <p>
* This evaluates the Criteria on the Instrumentation thread, which more often than not is not
* correct in an InstrumentationTest. Use
* {@link #pollUiThread(Runnable, long, long)} instead.
*
* @param criteria The Runnable that will be attempted.
* @param maxTimeoutMs The maximum number of ms that this check will be performed for
* before timeout.
* @param checkIntervalMs The number of ms between checks.
*/
public static void pollInstrumentationThread(
Runnable criteria, long maxTimeoutMs, long checkIntervalMs) {
assert !ThreadUtils.runningOnUiThread();
pollThreadInternal(criteria, maxTimeoutMs, checkIntervalMs, false);
}
private static void pollThreadInternal(
Runnable criteria, long maxTimeoutMs, long checkIntervalMs, boolean shouldNest) {
Throwable throwable;
try {
criteria.run();
return;
} catch (Throwable e) {
// Espresso catches, wraps, and re-throws the exception we want the CriteriaHelper
// to catch.
if (e instanceof CriteriaNotSatisfiedException
|| e.getCause() instanceof CriteriaNotSatisfiedException) {
throwable = e;
} else {
throw e;
}
}
TimeoutTimer timer = new TimeoutTimer(maxTimeoutMs);
while (!timer.isTimedOut()) {
if (shouldNest) {
nestThread(checkIntervalMs);
} else {
sleepThread(checkIntervalMs);
}
try {
criteria.run();
return;
} catch (Throwable e) {
if (e instanceof CriteriaNotSatisfiedException
|| e.getCause() instanceof CriteriaNotSatisfiedException) {
throwable = e;
} else {
throw e;
}
}
}
throw new AssertionError(throwable);
}
private static void sleepThread(long checkIntervalMs) {
try {
Thread.sleep(checkIntervalMs);
} catch (InterruptedException e) {
// Catch the InterruptedException. If the exception occurs before maxTimeoutMs
// and the criteria is not satisfied, the while loop will run again.
}
}
private static void nestThread(long checkIntervalMs) {
AtomicBoolean called = new AtomicBoolean(false);
// Ensure we pump the message handler in case no new tasks arrive.
new Handler(Looper.myLooper())
.postDelayed(
() -> {
called.set(true);
},
checkIntervalMs);
TimeoutTimer timer = new TimeoutTimer(checkIntervalMs);
// To allow a checkInterval of 0ms, ensure we at least run a single task, which allows a
// test to check conditions between each task run on the thread.
do {
try {
LooperUtils.runSingleNestedLooperTask();
} catch (IllegalArgumentException
| IllegalAccessException
| SecurityException
| InvocationTargetException e) {
throw new RuntimeException(e);
}
} while (!timer.isTimedOut() && !called.get());
}
/**
* Checks whether the given Runnable completes without exception at the default interval.
*
* <p>
* This evaluates the Runnable on the test thread, which more often than not is not correct
* in an InstrumentationTest. Use {@link #pollUiThread(Runnable)} instead.
*
* @param criteria The Runnable that will be attempted.
*
* @see #pollInstrumentationThread(Criteria, long, long)
*/
public static void pollInstrumentationThread(Runnable criteria) {
pollInstrumentationThread(criteria, DEFAULT_MAX_TIME_TO_POLL, DEFAULT_POLLING_INTERVAL);
}
/**
* Checks whether the given Callable<Boolean> is satisfied at a given interval, until either the
* criteria is satisfied, or the specified maxTimeoutMs number of ms has elapsed.
*
* <p>This evaluates the Callable<Boolean> on the test thread, which more often than not is not
* correct in an InstrumentationTest. Use {@link #pollUiThread(Callable)} instead.
*
* @param criteria The Callable<Boolean> that will be checked.
* @param failureReason The static failure reason
* @param maxTimeoutMs The maximum number of ms that this check will be performed for before
* timeout.
* @param checkIntervalMs The number of ms between checks.
*/
public static void pollInstrumentationThread(
final Callable<Boolean> criteria,
String failureReason,
long maxTimeoutMs,
long checkIntervalMs) {
pollInstrumentationThread(
toNotSatisfiedRunnable(criteria, failureReason), maxTimeoutMs, checkIntervalMs);
}
/**
* Checks whether the given Callable<Boolean> is satisfied at a given interval, until either
* the criteria is satisfied, or the specified maxTimeoutMs number of ms has elapsed.
*
* <p>
* This evaluates the Callable<Boolean> on the test thread, which more often than not is not
* correct in an InstrumentationTest. Use {@link #pollUiThread(Callable)} instead.
*
* @param criteria The Callable<Boolean> that will be checked.
* @param maxTimeoutMs The maximum number of ms that this check will be performed for
* before timeout.
* @param checkIntervalMs The number of ms between checks.
*/
public static void pollInstrumentationThread(
final Callable<Boolean> criteria, long maxTimeoutMs, long checkIntervalMs) {
pollInstrumentationThread(criteria, null, maxTimeoutMs, checkIntervalMs);
}
/**
* Checks whether the given Callable<Boolean> is satisfied polling at a default interval.
*
* <p>
* This evaluates the Callable<Boolean> on the test thread, which more often than not is not
* correct in an InstrumentationTest. Use {@link #pollUiThread(Callable)} instead.
*
* @param criteria The Callable<Boolean> that will be checked.
* @param failureReason The static failure reason
*/
public static void pollInstrumentationThread(Callable<Boolean> criteria, String failureReason) {
pollInstrumentationThread(
criteria, failureReason, DEFAULT_MAX_TIME_TO_POLL, DEFAULT_POLLING_INTERVAL);
}
/**
* Checks whether the given Callable<Boolean> is satisfied polling at a default interval.
*
* <p>
* This evaluates the Callable<Boolean> on the test thread, which more often than not is not
* correct in an InstrumentationTest. Use {@link #pollUiThread(Callable)} instead.
*
* @param criteria The Callable<Boolean> that will be checked.
*/
public static void pollInstrumentationThread(Callable<Boolean> criteria) {
pollInstrumentationThread(criteria, null);
}
/**
* Checks whether the given Runnable completes without exception at a given interval on the UI
* thread, until either the Runnable successfully completes, or the maxTimeoutMs number of ms
* has elapsed.
*
* @param criteria The Runnable that will be attempted.
* @param maxTimeoutMs The maximum number of ms that this check will be performed for
* before timeout.
* @param checkIntervalMs The number of ms between checks.
*
* @see #pollInstrumentationThread(Runnable)
*/
public static void pollUiThread(
final Runnable criteria, long maxTimeoutMs, long checkIntervalMs) {
assert !ThreadUtils.runningOnUiThread();
pollInstrumentationThread(
() -> {
AtomicReference<Throwable> throwableRef = new AtomicReference<>();
ThreadUtils.runOnUiThreadBlocking(
() -> {
try {
criteria.run();
} catch (Throwable t) {
throwableRef.set(t);
}
});
Throwable throwable = throwableRef.get();
if (throwable != null) {
if (throwable instanceof CriteriaNotSatisfiedException) {
throw new CriteriaNotSatisfiedException(throwable);
} else if (throwable instanceof RuntimeException) {
throw (RuntimeException) throwable;
} else {
throw new RuntimeException(throwable);
}
}
},
maxTimeoutMs,
checkIntervalMs);
}
/**
* Checks whether the given Runnable completes without exception at the default interval on
* the UI thread.
* @param criteria The Runnable that will be attempted.
*
* @see #pollInstrumentationThread(Runnable)
*/
public static void pollUiThread(final Runnable criteria) {
pollUiThread(criteria, DEFAULT_MAX_TIME_TO_POLL, DEFAULT_POLLING_INTERVAL);
}
/**
* Checks whether the given Callable<Boolean> is satisfied polling at a given interval on the UI
* thread, until either the criteria is satisfied, or the maxTimeoutMs number of ms has elapsed.
*
* @param criteria The Callable<Boolean> that will be checked.
* @param failureReason The static failure reason
* @param maxTimeoutMs The maximum number of ms that this check will be performed for before
* timeout.
* @param checkIntervalMs The number of ms between checks.
* @see #pollInstrumentationThread(Criteria)
*/
public static void pollUiThread(
final Callable<Boolean> criteria,
String failureReason,
long maxTimeoutMs,
long checkIntervalMs) {
pollUiThread(
toNotSatisfiedRunnable(criteria, failureReason), maxTimeoutMs, checkIntervalMs);
}
/**
* Checks whether the given Callable<Boolean> is satisfied polling at a given interval on the UI
* thread, until either the criteria is satisfied, or the maxTimeoutMs number of ms has elapsed.
*
* @param criteria The Callable<Boolean> that will be checked.
* @param maxTimeoutMs The maximum number of ms that this check will be performed for
* before timeout.
* @param checkIntervalMs The number of ms between checks.
*
* @see #pollInstrumentationThread(Criteria)
*/
public static void pollUiThread(
final Callable<Boolean> criteria, long maxTimeoutMs, long checkIntervalMs) {
pollUiThread(criteria, null, maxTimeoutMs, checkIntervalMs);
}
/**
* Checks whether the given Callable<Boolean> is satisfied polling at a default interval on the
* UI thread. A static failure reason is given.
* @param criteria The Callable<Boolean> that will be checked.
* @param failureReason The static failure reason
*
* @see #pollInstrumentationThread(Criteria)
*/
public static void pollUiThread(final Callable<Boolean> criteria, String failureReason) {
pollUiThread(criteria, failureReason, DEFAULT_MAX_TIME_TO_POLL, DEFAULT_POLLING_INTERVAL);
}
/**
* Checks whether the given Callable<Boolean> is satisfied polling at a default interval on the
* UI thread.
* @param criteria The Callable<Boolean> that will be checked.
*
* @see #pollInstrumentationThread(Criteria)
*/
public static void pollUiThread(final Callable<Boolean> criteria) {
pollUiThread(criteria, null);
}
/**
* Checks whether the given Runnable completes without exception at a given interval on the UI
* thread, until either the Runnable successfully completes, or the maxTimeoutMs number of ms
* has elapsed.
* This call will nest the Looper in order to wait for the Runnable to complete.
*
* @param criteria The Runnable that will be attempted.
* @param maxTimeoutMs The maximum number of ms that this check will be performed for
* before timeout.
* @param checkIntervalMs The number of ms between checks.
*
* @see #pollInstrumentationThread(Runnable)
*/
public static void pollUiThreadNested(
Runnable criteria, long maxTimeoutMs, long checkIntervalMs) {
assert ThreadUtils.runningOnUiThread();
pollThreadInternal(criteria, maxTimeoutMs, checkIntervalMs, true);
}
/**
* Checks whether the given Runnable is satisfied polling at a given interval on the UI
* thread, until either the criteria is satisfied, or the maxTimeoutMs number of ms has elapsed.
* This call will nest the Looper in order to wait for the Criteria to be satisfied.
*
* @param criteria The Callable<Boolean> that will be checked.
* @param maxTimeoutMs The maximum number of ms that this check will be performed for
* before timeout.
* @param checkIntervalMs The number of ms between checks.
*
* @see #pollInstrumentationThread(Criteria)
*/
public static void pollUiThreadNested(
final Callable<Boolean> criteria, long maxTimeoutMs, long checkIntervalMs) {
pollUiThreadNested(toNotSatisfiedRunnable(criteria, null), maxTimeoutMs, checkIntervalMs);
}
/**
* Checks whether the given Runnable completes without exception at the default interval on
* the UI thread. This call will nest the Looper in order to wait for the Runnable to complete.
* @param criteria The Runnable that will be attempted.
*
* @see #pollInstrumentationThread(Runnable)
*/
public static void pollUiThreadNested(final Runnable criteria) {
pollUiThreadNested(criteria, DEFAULT_MAX_TIME_TO_POLL, DEFAULT_POLLING_INTERVAL);
}
/**
* Checks whether the given Callable<Boolean> is satisfied polling at a default interval on the
* UI thread. This call will nest the Looper in order to wait for the Criteria to be satisfied.
* @param criteria The Callable<Boolean> that will be checked.
*
* @see #pollInstrumentationThread(Criteria)
*/
public static void pollUiThreadNested(final Callable<Boolean> criteria) {
pollUiThreadNested(toNotSatisfiedRunnable(criteria, null));
}
/**
* Sleeps the JUnit UI thread to wait on the condition. The condition must be met by a
* background thread that does not block on the UI thread.
*
* @param criteria The Callable<Boolean> that will be checked.
*
* @see #pollInstrumentationThread(Criteria)
*/
public static void pollUiThreadForJUnit(final Callable<Boolean> criteria) {
pollUiThreadForJUnit(toNotSatisfiedRunnable(criteria, null));
}
/**
* Sleeps the JUnit UI thread to wait on the criteria. The criteria must be met by a
* background thread that does not block on the UI thread.
*
* @param criteria The Runnable that will be attempted.
*
* @see #pollInstrumentationThread(Criteria)
*/
public static void pollUiThreadForJUnit(final Runnable criteria) {
assert ThreadUtils.runningOnUiThread();
pollThreadInternal(
criteria, DEFAULT_JUNIT_MAX_TIME_TO_POLL, DEFAULT_JUNIT_POLLING_INTERVAL, false);
}
private static Runnable toNotSatisfiedRunnable(
Callable<Boolean> criteria, String failureReason) {
return () -> {
boolean isSatisfied;
try {
isSatisfied = criteria.call();
} catch (RuntimeException re) {
throw re;
} catch (Exception e) {
throw new RuntimeException(e);
}
Criteria.checkThat(failureReason, isSatisfied, Matchers.is(true));
};
}
}