summaryrefslogtreecommitdiff
path: root/base/test/android/javatests/src/org/chromium/base/test/util
diff options
context:
space:
mode:
Diffstat (limited to 'base/test/android/javatests/src/org/chromium/base/test/util')
-rw-r--r--base/test/android/javatests/src/org/chromium/base/test/util/AdvancedMockContext.java118
-rw-r--r--base/test/android/javatests/src/org/chromium/base/test/util/CallbackHelper.java252
-rw-r--r--base/test/android/javatests/src/org/chromium/base/test/util/CommandLineFlags.java188
-rw-r--r--base/test/android/javatests/src/org/chromium/base/test/util/DisableIf.java49
-rw-r--r--base/test/android/javatests/src/org/chromium/base/test/util/DisableIfSkipCheck.java84
-rw-r--r--base/test/android/javatests/src/org/chromium/base/test/util/DisabledTest.java22
-rw-r--r--base/test/android/javatests/src/org/chromium/base/test/util/EnormousTest.java24
-rw-r--r--base/test/android/javatests/src/org/chromium/base/test/util/Feature.java29
-rw-r--r--base/test/android/javatests/src/org/chromium/base/test/util/FlakyTest.java22
-rw-r--r--base/test/android/javatests/src/org/chromium/base/test/util/InMemorySharedPreferences.java238
-rw-r--r--base/test/android/javatests/src/org/chromium/base/test/util/InstrumentationUtils.java32
-rw-r--r--base/test/android/javatests/src/org/chromium/base/test/util/IntegrationTest.java26
-rw-r--r--base/test/android/javatests/src/org/chromium/base/test/util/Manual.java21
-rw-r--r--base/test/android/javatests/src/org/chromium/base/test/util/Matchers.java44
-rw-r--r--base/test/android/javatests/src/org/chromium/base/test/util/MetricsUtils.java43
-rw-r--r--base/test/android/javatests/src/org/chromium/base/test/util/MinAndroidSdkLevel.java19
-rw-r--r--base/test/android/javatests/src/org/chromium/base/test/util/MinAndroidSdkLevelSkipCheck.java43
-rw-r--r--base/test/android/javatests/src/org/chromium/base/test/util/Restriction.java37
-rw-r--r--base/test/android/javatests/src/org/chromium/base/test/util/RestrictionSkipCheck.java78
-rw-r--r--base/test/android/javatests/src/org/chromium/base/test/util/RetryOnFailure.java25
-rw-r--r--base/test/android/javatests/src/org/chromium/base/test/util/ScalableTimeout.java29
-rw-r--r--base/test/android/javatests/src/org/chromium/base/test/util/SkipCheck.java49
-rw-r--r--base/test/android/javatests/src/org/chromium/base/test/util/TestFileUtil.java85
-rw-r--r--base/test/android/javatests/src/org/chromium/base/test/util/TestThread.java143
-rw-r--r--base/test/android/javatests/src/org/chromium/base/test/util/TimeoutScale.java22
-rw-r--r--base/test/android/javatests/src/org/chromium/base/test/util/UrlUtils.java84
-rw-r--r--base/test/android/javatests/src/org/chromium/base/test/util/UserActionTester.java51
-rw-r--r--base/test/android/javatests/src/org/chromium/base/test/util/parameter/CommandLineParameter.java32
-rw-r--r--base/test/android/javatests/src/org/chromium/base/test/util/parameter/SkipCommandLineParameterization.java23
29 files changed, 1912 insertions, 0 deletions
diff --git a/base/test/android/javatests/src/org/chromium/base/test/util/AdvancedMockContext.java b/base/test/android/javatests/src/org/chromium/base/test/util/AdvancedMockContext.java
new file mode 100644
index 0000000000..c8117f7fad
--- /dev/null
+++ b/base/test/android/javatests/src/org/chromium/base/test/util/AdvancedMockContext.java
@@ -0,0 +1,118 @@
+// Copyright 2013 The Chromium Authors. All rights reserved.
+// 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.content.ComponentCallbacks;
+import android.content.ContentResolver;
+import android.content.Context;
+import android.content.ContextWrapper;
+import android.content.SharedPreferences;
+import android.test.mock.MockContentResolver;
+import android.test.mock.MockContext;
+
+import java.util.HashMap;
+import java.util.Map;
+
+/**
+ * ContextWrapper that adds functionality for SharedPreferences and a way to set and retrieve flags.
+ */
+public class AdvancedMockContext extends ContextWrapper {
+
+ private final MockContentResolver mMockContentResolver = new MockContentResolver();
+
+ private final Map<String, SharedPreferences> mSharedPreferences =
+ new HashMap<String, SharedPreferences>();
+
+ private final Map<String, Boolean> mFlags = new HashMap<String, Boolean>();
+
+ public AdvancedMockContext(Context base) {
+ super(base);
+ }
+
+ public AdvancedMockContext() {
+ super(new MockContext());
+ }
+
+ @Override
+ public String getPackageName() {
+ return getBaseContext().getPackageName();
+ }
+
+ @Override
+ public Context getApplicationContext() {
+ return this;
+ }
+
+ @Override
+ public ContentResolver getContentResolver() {
+ return mMockContentResolver;
+ }
+
+ public MockContentResolver getMockContentResolver() {
+ return mMockContentResolver;
+ }
+
+ @Override
+ public SharedPreferences getSharedPreferences(String name, int mode) {
+ synchronized (mSharedPreferences) {
+ if (!mSharedPreferences.containsKey(name)) {
+ // Auto-create shared preferences to mimic Android Context behavior
+ mSharedPreferences.put(name, new InMemorySharedPreferences());
+ }
+ return mSharedPreferences.get(name);
+ }
+ }
+
+ @Override
+ public void registerComponentCallbacks(ComponentCallbacks callback) {
+ getBaseContext().registerComponentCallbacks(callback);
+ }
+
+ @Override
+ public void unregisterComponentCallbacks(ComponentCallbacks callback) {
+ getBaseContext().unregisterComponentCallbacks(callback);
+ }
+
+ public void addSharedPreferences(String name, Map<String, Object> data) {
+ synchronized (mSharedPreferences) {
+ mSharedPreferences.put(name, new InMemorySharedPreferences(data));
+ }
+ }
+
+ public void setFlag(String key) {
+ mFlags.put(key, true);
+ }
+
+ public void clearFlag(String key) {
+ mFlags.remove(key);
+ }
+
+ public boolean isFlagSet(String key) {
+ return mFlags.containsKey(key) && mFlags.get(key);
+ }
+
+ /**
+ * Builder for maps of type Map<String, Object> to be used with
+ * {@link #addSharedPreferences(String, java.util.Map)}.
+ */
+ public static class MapBuilder {
+
+ private final Map<String, Object> mData = new HashMap<String, Object>();
+
+ public static MapBuilder create() {
+ return new MapBuilder();
+ }
+
+ public MapBuilder add(String key, Object value) {
+ mData.put(key, value);
+ return this;
+ }
+
+ public Map<String, Object> build() {
+ return mData;
+ }
+
+ }
+}
diff --git a/base/test/android/javatests/src/org/chromium/base/test/util/CallbackHelper.java b/base/test/android/javatests/src/org/chromium/base/test/util/CallbackHelper.java
new file mode 100644
index 0000000000..bf064c4fce
--- /dev/null
+++ b/base/test/android/javatests/src/org/chromium/base/test/util/CallbackHelper.java
@@ -0,0 +1,252 @@
+// Copyright 2012 The Chromium Authors. All rights reserved.
+// 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 static org.chromium.base.test.util.ScalableTimeout.scaleTimeout;
+
+import org.junit.Assert;
+
+import java.util.concurrent.TimeUnit;
+import java.util.concurrent.TimeoutException;
+
+/**
+ * A helper class that encapsulates listening and blocking for callbacks.
+ *
+ * Sample usage:
+ *
+ * // Let us assume that this interface is defined by some piece of production code and is used
+ * // to communicate events that occur in that piece of code. Let us further assume that the
+ * // production code runs on the main thread test code runs on a separate test thread.
+ * // An instance that implements this interface would be injected by test code to ensure that the
+ * // methods are being called on another thread.
+ * interface Delegate {
+ * void onOperationFailed(String errorMessage);
+ * void onDataPersisted();
+ * }
+ *
+ * // This is the inner class you'd write in your test case to later inject into the production
+ * // code.
+ * class TestDelegate implements Delegate {
+ * // This is the preferred way to create a helper that stores the parameters it receives
+ * // when called by production code.
+ * public static class OnOperationFailedHelper extends CallbackHelper {
+ * private String mErrorMessage;
+ *
+ * public void getErrorMessage() {
+ * assert getCallCount() > 0;
+ * return mErrorMessage;
+ * }
+ *
+ * public void notifyCalled(String errorMessage) {
+ * mErrorMessage = errorMessage;
+ * // It's important to call this after all parameter assignments.
+ * notifyCalled();
+ * }
+ * }
+ *
+ * // There should be one CallbackHelper instance per method.
+ * private OnOperationFailedHelper mOnOperationFailedHelper;
+ * private CallbackHelper mOnDataPersistedHelper;
+ *
+ * public OnOperationFailedHelper getOnOperationFailedHelper() {
+ * return mOnOperationFailedHelper;
+ * }
+ *
+ * public CallbackHelper getOnDataPersistedHelper() {
+ * return mOnDataPersistedHelper;
+ * }
+ *
+ * @Override
+ * public void onOperationFailed(String errorMessage) {
+ * mOnOperationFailedHelper.notifyCalled(errorMessage);
+ * }
+ *
+ * @Override
+ * public void onDataPersisted() {
+ * mOnDataPersistedHelper.notifyCalled();
+ * }
+ * }
+ *
+ * // This is a sample test case.
+ * public void testCase() throws Exception {
+ * // Create the TestDelegate to inject into production code.
+ * TestDelegate delegate = new TestDelegate();
+ * // Create the production class instance that is being tested and inject the test delegate.
+ * CodeUnderTest codeUnderTest = new CodeUnderTest();
+ * codeUnderTest.setDelegate(delegate);
+ *
+ * // Typically you'd get the current call count before performing the operation you expect to
+ * // trigger the callback. There can't be any callbacks 'in flight' at this moment, otherwise
+ * // the call count is unpredictable and the test will be flaky.
+ * int onOperationFailedCallCount = delegate.getOnOperationFailedHelper().getCallCount();
+ * codeUnderTest.doSomethingThatEndsUpCallingOnOperationFailedFromAnotherThread();
+ * // It's safe to do other stuff here, if needed.
+ * ....
+ * // Wait for the callback if it hadn't been called yet, otherwise return immediately. This
+ * // can throw an exception if the callback doesn't arrive within the timeout.
+ * delegate.getOnOperationFailedHelper().waitForCallback(onOperationFailedCallCount);
+ * // Access to method parameters is now safe.
+ * assertEquals("server error", delegate.getOnOperationFailedHelper().getErrorMessage());
+ *
+ * // Being able to pass the helper around lets us build methods which encapsulate commonly
+ * // performed tasks.
+ * doSomeOperationAndWait(codeUnerTest, delegate.getOnOperationFailedHelper());
+ *
+ * // The helper can be reused for as many calls as needed, just be sure to get the count each
+ * // time.
+ * onOperationFailedCallCount = delegate.getOnOperationFailedHelper().getCallCount();
+ * codeUnderTest.doSomethingElseButStillFailOnAnotherThread();
+ * delegate.getOnOperationFailedHelper().waitForCallback(onOperationFailedCallCount);
+ *
+ * // It is also possible to use more than one helper at a time.
+ * onOperationFailedCallCount = delegate.getOnOperationFailedHelper().getCallCount();
+ * int onDataPersistedCallCount = delegate.getOnDataPersistedHelper().getCallCount();
+ * codeUnderTest.doSomethingThatPersistsDataButFailsInSomeOtherWayOnAnotherThread();
+ * delegate.getOnDataPersistedHelper().waitForCallback(onDataPersistedCallCount);
+ * delegate.getOnOperationFailedHelper().waitForCallback(onOperationFailedCallCount);
+ * }
+ *
+ * // Shows how to turn an async operation + completion callback into a synchronous operation.
+ * private void doSomeOperationAndWait(final CodeUnderTest underTest,
+ * CallbackHelper operationHelper) throws InterruptedException, TimeoutException {
+ * final int callCount = operationHelper.getCallCount();
+ * getInstrumentation().runOnMainSync(new Runnable() {
+ * @Override
+ * public void run() {
+ * // This schedules a call to a method on the injected TestDelegate. The TestDelegate
+ * // implementation will then call operationHelper.notifyCalled().
+ * underTest.operation();
+ * }
+ * });
+ * operationHelper.waitForCallback(callCount);
+ * }
+ *
+ */
+public class CallbackHelper {
+ /** The default timeout (in seconds) for a callback to wait. */
+ public static final long WAIT_TIMEOUT_SECONDS = scaleTimeout(5);
+
+ private final Object mLock = new Object();
+ private int mCallCount;
+ private String mFailureString;
+
+ /**
+ * Gets the number of times the callback has been called.
+ *
+ * The call count can be used with the waitForCallback() method, indicating a point
+ * in time after which the caller wishes to record calls to the callback.
+ *
+ * In order to wait for a callback caused by X, the call count should be obtained
+ * before X occurs.
+ *
+ * NOTE: any call to the callback that occurs after the call count is obtained
+ * will result in the corresponding wait call to resume execution. The call count
+ * is intended to 'catch' callbacks that occur after X but before waitForCallback()
+ * is called.
+ */
+ public int getCallCount() {
+ synchronized (mLock) {
+ return mCallCount;
+ }
+ }
+
+ /**
+ * Blocks until the callback is called the specified number of
+ * times or throws an exception if we exceeded the specified time frame.
+ *
+ * This will wait for a callback to be called a specified number of times after
+ * the point in time at which the call count was obtained. The method will return
+ * immediately if a call occurred the specified number of times after the
+ * call count was obtained but before the method was called, otherwise the method will
+ * block until the specified call count is reached.
+ *
+ * @param msg The error message to use if the callback times out.
+ * @param currentCallCount the value obtained by calling getCallCount().
+ * @param numberOfCallsToWaitFor number of calls (counting since
+ * currentCallCount was obtained) that we will wait for.
+ * @param timeout timeout value. We will wait the specified amount of time for a single
+ * callback to occur so the method call may block up to
+ * <code>numberOfCallsToWaitFor * timeout</code> units.
+ * @param unit timeout unit.
+ * @throws InterruptedException
+ * @throws TimeoutException Thrown if the method times out before onPageFinished is called.
+ */
+ public void waitForCallback(String msg, int currentCallCount, int numberOfCallsToWaitFor,
+ long timeout, TimeUnit unit) throws InterruptedException, TimeoutException {
+ assert mCallCount >= currentCallCount;
+ assert numberOfCallsToWaitFor > 0;
+ synchronized (mLock) {
+ int callCountWhenDoneWaiting = currentCallCount + numberOfCallsToWaitFor;
+ while (callCountWhenDoneWaiting > mCallCount) {
+ int callCountBeforeWait = mCallCount;
+ mLock.wait(unit.toMillis(timeout));
+ if (mFailureString != null) {
+ String s = mFailureString;
+ mFailureString = null;
+ Assert.fail(s);
+ }
+ if (callCountBeforeWait == mCallCount) {
+ throw new TimeoutException(msg == null ? "waitForCallback timed out!" : msg);
+ }
+ }
+ }
+ }
+
+ /**
+ * @see #waitForCallback(String, int, int, long, TimeUnit)
+ */
+ public void waitForCallback(int currentCallCount, int numberOfCallsToWaitFor, long timeout,
+ TimeUnit unit) throws InterruptedException, TimeoutException {
+ waitForCallback(null, currentCallCount, numberOfCallsToWaitFor, timeout, unit);
+ }
+
+ /**
+ * @see #waitForCallback(String, int, int, long, TimeUnit)
+ */
+ public void waitForCallback(int currentCallCount, int numberOfCallsToWaitFor)
+ throws InterruptedException, TimeoutException {
+ waitForCallback(null, currentCallCount, numberOfCallsToWaitFor,
+ WAIT_TIMEOUT_SECONDS, TimeUnit.SECONDS);
+ }
+
+ /**
+ * @see #waitForCallback(String, int, int, long, TimeUnit)
+ */
+ public void waitForCallback(String msg, int currentCallCount)
+ throws InterruptedException, TimeoutException {
+ waitForCallback(msg, currentCallCount, 1, WAIT_TIMEOUT_SECONDS, TimeUnit.SECONDS);
+ }
+
+ /**
+ * @see #waitForCallback(String, int, int, long, TimeUnit)
+ */
+ public void waitForCallback(int currentCallCount)
+ throws InterruptedException, TimeoutException {
+ waitForCallback(null, currentCallCount, 1, WAIT_TIMEOUT_SECONDS, TimeUnit.SECONDS);
+ }
+
+ /**
+ * Should be called when the callback associated with this helper object is called.
+ */
+ public void notifyCalled() {
+ synchronized (mLock) {
+ mCallCount++;
+ mLock.notifyAll();
+ }
+ }
+
+ /**
+ * Should be called when the callback associated with this helper object wants to
+ * indicate a failure.
+ *
+ * @param s The failure message.
+ */
+ public void notifyFailed(String s) {
+ synchronized (mLock) {
+ mFailureString = s;
+ mLock.notifyAll();
+ }
+ }
+}
diff --git a/base/test/android/javatests/src/org/chromium/base/test/util/CommandLineFlags.java b/base/test/android/javatests/src/org/chromium/base/test/util/CommandLineFlags.java
new file mode 100644
index 0000000000..71ef8e91ff
--- /dev/null
+++ b/base/test/android/javatests/src/org/chromium/base/test/util/CommandLineFlags.java
@@ -0,0 +1,188 @@
+// Copyright 2015 The Chromium Authors. All rights reserved.
+// 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.text.TextUtils;
+
+import org.junit.Assert;
+import org.junit.Rule;
+
+import org.chromium.base.CommandLine;
+import org.chromium.base.CommandLineInitUtil;
+import org.chromium.base.test.BaseTestResult.PreTestHook;
+
+import java.lang.annotation.ElementType;
+import java.lang.annotation.Inherited;
+import java.lang.annotation.Retention;
+import java.lang.annotation.RetentionPolicy;
+import java.lang.annotation.Target;
+import java.lang.reflect.AnnotatedElement;
+import java.lang.reflect.Field;
+import java.lang.reflect.Method;
+import java.util.Arrays;
+import java.util.Collections;
+import java.util.HashSet;
+import java.util.List;
+import java.util.Set;
+
+/**
+ * Provides annotations related to command-line flag handling.
+ *
+ * Uses of these annotations on a derived class will take precedence over uses on its base classes,
+ * so a derived class can add a command-line flag that a base class has removed (or vice versa).
+ * Similarly, uses of these annotations on a test method will take precedence over uses on the
+ * containing class.
+ * <p>
+ * These annonations may also be used on Junit4 Rule classes and on their base classes. Note,
+ * however that the annotation processor only looks at the declared type of the Rule, not its actual
+ * type, so in, for example:
+ *
+ * <pre>
+ * &#64Rule
+ * TestRule mRule = new ChromeActivityTestRule();
+ * </pre>
+ *
+ * will only look for CommandLineFlags annotations on TestRule, not for CommandLineFlags annotations
+ * on ChromeActivityTestRule.
+ * <p>
+ * In addition a rule may not remove flags added by an independently invoked rule, although it may
+ * remove flags added by its base classes.
+ * <p>
+ * Uses of these annotations on the test class or methods take precedence over uses on Rule classes.
+ * <p>
+ * Note that this class should never be instantiated.
+ */
+public final class CommandLineFlags {
+ private static final String DISABLE_FEATURES = "disable-features";
+ private static final String ENABLE_FEATURES = "enable-features";
+
+ /**
+ * Adds command-line flags to the {@link org.chromium.base.CommandLine} for this test.
+ */
+ @Inherited
+ @Retention(RetentionPolicy.RUNTIME)
+ @Target({ElementType.METHOD, ElementType.TYPE})
+ public @interface Add {
+ String[] value();
+ }
+
+ /**
+ * Removes command-line flags from the {@link org.chromium.base.CommandLine} from this test.
+ *
+ * Note that this can only remove flags added via {@link Add} above.
+ */
+ @Inherited
+ @Retention(RetentionPolicy.RUNTIME)
+ @Target({ElementType.METHOD, ElementType.TYPE})
+ public @interface Remove {
+ String[] value();
+ }
+
+ /**
+ * Sets up the CommandLine with the appropriate flags.
+ *
+ * This will add the difference of the sets of flags specified by {@link CommandLineFlags.Add}
+ * and {@link CommandLineFlags.Remove} to the {@link org.chromium.base.CommandLine}. Note that
+ * trying to remove a flag set externally, i.e. by the command-line flags file, will not work.
+ */
+ public static void setUp(AnnotatedElement element) {
+ CommandLine.reset();
+ CommandLineInitUtil.initCommandLine(getTestCmdLineFile());
+ Set<String> enableFeatures = new HashSet<String>();
+ Set<String> disableFeatures = new HashSet<String>();
+ Set<String> flags = getFlags(element);
+ for (String flag : flags) {
+ String[] parsedFlags = flag.split("=", 2);
+ if (parsedFlags.length == 1) {
+ CommandLine.getInstance().appendSwitch(flag);
+ } else if (ENABLE_FEATURES.equals(parsedFlags[0])) {
+ // We collect enable/disable features flags separately and aggregate them because
+ // they may be specified multiple times, in which case the values will trample each
+ // other.
+ Collections.addAll(enableFeatures, parsedFlags[1].split(","));
+ } else if (DISABLE_FEATURES.equals(parsedFlags[0])) {
+ Collections.addAll(disableFeatures, parsedFlags[1].split(","));
+ } else {
+ CommandLine.getInstance().appendSwitchWithValue(parsedFlags[0], parsedFlags[1]);
+ }
+ }
+
+ if (enableFeatures.size() > 0) {
+ CommandLine.getInstance().appendSwitchWithValue(
+ ENABLE_FEATURES, TextUtils.join(",", enableFeatures));
+ }
+ if (disableFeatures.size() > 0) {
+ CommandLine.getInstance().appendSwitchWithValue(
+ DISABLE_FEATURES, TextUtils.join(",", disableFeatures));
+ }
+ }
+
+ private static Set<String> getFlags(AnnotatedElement type) {
+ Set<String> rule_flags = new HashSet<>();
+ updateFlagsForElement(type, rule_flags);
+ return rule_flags;
+ }
+
+ private static void updateFlagsForElement(AnnotatedElement element, Set<String> flags) {
+ if (element instanceof Class<?>) {
+ // Get flags from rules within the class.
+ for (Field field : ((Class<?>) element).getFields()) {
+ if (field.isAnnotationPresent(Rule.class)) {
+ // The order in which fields are returned is undefined, so, for consistency,
+ // a rule must not remove a flag added by a different rule. Ensure this by
+ // initially getting the flags into a new set.
+ Set<String> rule_flags = getFlags(field.getType());
+ flags.addAll(rule_flags);
+ }
+ }
+ for (Method method : ((Class<?>) element).getMethods()) {
+ if (method.isAnnotationPresent(Rule.class)) {
+ // The order in which methods are returned is undefined, so, for consistency,
+ // a rule must not remove a flag added by a different rule. Ensure this by
+ // initially getting the flags into a new set.
+ Set<String> rule_flags = getFlags(method.getReturnType());
+ flags.addAll(rule_flags);
+ }
+ }
+ }
+
+ // Add the flags from the parent. Override any flags defined by the rules.
+ AnnotatedElement parent = (element instanceof Method)
+ ? ((Method) element).getDeclaringClass()
+ : ((Class<?>) element).getSuperclass();
+ if (parent != null) updateFlagsForElement(parent, flags);
+
+ // Flags on the element itself override all other flag sources.
+ if (element.isAnnotationPresent(CommandLineFlags.Add.class)) {
+ flags.addAll(
+ Arrays.asList(element.getAnnotation(CommandLineFlags.Add.class).value()));
+ }
+
+ if (element.isAnnotationPresent(CommandLineFlags.Remove.class)) {
+ List<String> flagsToRemove =
+ Arrays.asList(element.getAnnotation(CommandLineFlags.Remove.class).value());
+ for (String flagToRemove : flagsToRemove) {
+ // If your test fails here, you have tried to remove a command-line flag via
+ // CommandLineFlags.Remove that was loaded into CommandLine via something other
+ // than CommandLineFlags.Add (probably the command-line flag file).
+ Assert.assertFalse("Unable to remove command-line flag \"" + flagToRemove + "\".",
+ CommandLine.getInstance().hasSwitch(flagToRemove));
+ }
+ flags.removeAll(flagsToRemove);
+ }
+ }
+
+ private CommandLineFlags() {
+ throw new AssertionError("CommandLineFlags is a non-instantiable class");
+ }
+
+ public static PreTestHook getRegistrationHook() {
+ return (targetContext, testMethod) -> CommandLineFlags.setUp(testMethod);
+ }
+
+ public static String getTestCmdLineFile() {
+ return "test-cmdline-file";
+ }
+}
diff --git a/base/test/android/javatests/src/org/chromium/base/test/util/DisableIf.java b/base/test/android/javatests/src/org/chromium/base/test/util/DisableIf.java
new file mode 100644
index 0000000000..c0303b68d4
--- /dev/null
+++ b/base/test/android/javatests/src/org/chromium/base/test/util/DisableIf.java
@@ -0,0 +1,49 @@
+// Copyright 2015 The Chromium Authors. All rights reserved.
+// 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 java.lang.annotation.ElementType;
+import java.lang.annotation.Retention;
+import java.lang.annotation.RetentionPolicy;
+import java.lang.annotation.Target;
+
+/**
+ * Annotations to support conditional test disabling.
+ *
+ * These annotations should only be used to disable tests that are temporarily failing
+ * in some configurations. If a test should never run at all in some configurations, use
+ * {@link Restriction}.
+ */
+public class DisableIf {
+
+ /** Conditional disabling based on {@link android.os.Build}.
+ */
+ @Target({ElementType.METHOD, ElementType.TYPE})
+ @Retention(RetentionPolicy.RUNTIME)
+ public static @interface Build {
+ String message() default "";
+
+ int sdk_is_greater_than() default 0;
+ int sdk_is_less_than() default Integer.MAX_VALUE;
+
+ String supported_abis_includes() default "";
+
+ String hardware_is() default "";
+
+ String product_name_includes() default "";
+ }
+
+ @Target({ElementType.METHOD, ElementType.TYPE})
+ @Retention(RetentionPolicy.RUNTIME)
+ public static @interface Device {
+ /**
+ * @return A list of disabled types.
+ */
+ public String[] type();
+ }
+
+ /* Objects of this type should not be created. */
+ private DisableIf() {}
+}
diff --git a/base/test/android/javatests/src/org/chromium/base/test/util/DisableIfSkipCheck.java b/base/test/android/javatests/src/org/chromium/base/test/util/DisableIfSkipCheck.java
new file mode 100644
index 0000000000..e46b9799a6
--- /dev/null
+++ b/base/test/android/javatests/src/org/chromium/base/test/util/DisableIfSkipCheck.java
@@ -0,0 +1,84 @@
+// Copyright 2015 The Chromium Authors. All rights reserved.
+// 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.Build;
+
+import org.junit.runners.model.FrameworkMethod;
+
+import org.chromium.base.Log;
+
+import java.util.Arrays;
+
+/**
+ * Checks for conditional disables.
+ *
+ * Currently, this only includes checks against a few {@link android.os.Build} values.
+ */
+public class DisableIfSkipCheck extends SkipCheck {
+
+ private static final String TAG = "cr_base_test";
+
+ @Override
+ public boolean shouldSkip(FrameworkMethod method) {
+ if (method == null) return true;
+ for (DisableIf.Build v : AnnotationProcessingUtils.getAnnotations(
+ method.getMethod(), DisableIf.Build.class)) {
+ if (abi(v) && hardware(v) && product(v) && sdk(v)) {
+ if (!v.message().isEmpty()) {
+ Log.i(TAG, "%s is disabled: %s", method.getName(), v.message());
+ }
+ return true;
+ }
+ }
+
+ for (DisableIf.Device d : AnnotationProcessingUtils.getAnnotations(
+ method.getMethod(), DisableIf.Device.class)) {
+ for (String deviceType : d.type()) {
+ if (deviceTypeApplies(deviceType)) {
+ Log.i(TAG, "Test " + method.getDeclaringClass().getName() + "#"
+ + method.getName() + " disabled because of "
+ + d);
+ return true;
+ }
+ }
+ }
+
+ return false;
+ }
+
+ @SuppressWarnings("deprecation")
+ private boolean abi(DisableIf.Build v) {
+ if (v.supported_abis_includes().isEmpty()) return true;
+
+ if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.LOLLIPOP) {
+ return Arrays.asList(Build.SUPPORTED_ABIS).contains(
+ v.supported_abis_includes());
+ } else {
+ return Build.CPU_ABI.equals(v.supported_abis_includes())
+ || Build.CPU_ABI2.equals(v.supported_abis_includes());
+ }
+ }
+
+ private boolean hardware(DisableIf.Build v) {
+ return v.hardware_is().isEmpty() || Build.HARDWARE.equals(v.hardware_is());
+ }
+
+ private boolean product(DisableIf.Build v) {
+ return v.product_name_includes().isEmpty()
+ || Build.PRODUCT.contains(v.product_name_includes());
+ }
+
+ private boolean sdk(DisableIf.Build v) {
+ return Build.VERSION.SDK_INT > v.sdk_is_greater_than()
+ && Build.VERSION.SDK_INT < v.sdk_is_less_than();
+ }
+
+ protected boolean deviceTypeApplies(String type) {
+ return false;
+ }
+
+}
+
diff --git a/base/test/android/javatests/src/org/chromium/base/test/util/DisabledTest.java b/base/test/android/javatests/src/org/chromium/base/test/util/DisabledTest.java
new file mode 100644
index 0000000000..a3e4e8ee7f
--- /dev/null
+++ b/base/test/android/javatests/src/org/chromium/base/test/util/DisabledTest.java
@@ -0,0 +1,22 @@
+// Copyright 2012 The Chromium Authors. All rights reserved.
+// 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 java.lang.annotation.ElementType;
+import java.lang.annotation.Retention;
+import java.lang.annotation.RetentionPolicy;
+import java.lang.annotation.Target;
+
+/**
+ * This annotation is for disabled tests.
+ * <p>
+ * Tests with this annotation will not be run on any of the normal bots.
+ * Please note that they might eventually run on a special bot.
+ */
+@Target({ElementType.METHOD, ElementType.TYPE})
+@Retention(RetentionPolicy.RUNTIME)
+public @interface DisabledTest {
+ String message() default "";
+}
diff --git a/base/test/android/javatests/src/org/chromium/base/test/util/EnormousTest.java b/base/test/android/javatests/src/org/chromium/base/test/util/EnormousTest.java
new file mode 100644
index 0000000000..af483ec3f9
--- /dev/null
+++ b/base/test/android/javatests/src/org/chromium/base/test/util/EnormousTest.java
@@ -0,0 +1,24 @@
+// Copyright 2012 The Chromium Authors. All rights reserved.
+// 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 java.lang.annotation.ElementType;
+import java.lang.annotation.Retention;
+import java.lang.annotation.RetentionPolicy;
+import java.lang.annotation.Target;
+
+/**
+ * This annotation is for enormous tests.
+ * <p>
+ * Examples of enormous tests are tests that depend on external web sites or
+ * tests that are long running.
+ * <p>
+ * Such tests are likely NOT reliable enough to run on tree closing bots and
+ * should only be run on FYI bots.
+ */
+@Target(ElementType.METHOD)
+@Retention(RetentionPolicy.RUNTIME)
+public @interface EnormousTest {
+}
diff --git a/base/test/android/javatests/src/org/chromium/base/test/util/Feature.java b/base/test/android/javatests/src/org/chromium/base/test/util/Feature.java
new file mode 100644
index 0000000000..1bc9226441
--- /dev/null
+++ b/base/test/android/javatests/src/org/chromium/base/test/util/Feature.java
@@ -0,0 +1,29 @@
+// Copyright 2012 The Chromium Authors. All rights reserved.
+// 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 java.lang.annotation.ElementType;
+import java.lang.annotation.Retention;
+import java.lang.annotation.RetentionPolicy;
+import java.lang.annotation.Target;
+
+/**
+ * The java instrumentation tests are normally fairly large (in terms of
+ * dependencies), and the test suite ends up containing a large amount of
+ * tests that are not trivial to filter / group just by their names.
+ * Instead, we use this annotation: each test should be annotated as:
+ * @Feature({"Foo", "Bar"})
+ * in order for the test runner scripts to be able to filter and group
+ * them accordingly (for instance, this enable us to run all tests that exercise
+ * feature Foo).
+ */
+@Target(ElementType.METHOD)
+@Retention(RetentionPolicy.RUNTIME)
+public @interface Feature {
+ /**
+ * @return A list of feature names.
+ */
+ public String[] value();
+}
diff --git a/base/test/android/javatests/src/org/chromium/base/test/util/FlakyTest.java b/base/test/android/javatests/src/org/chromium/base/test/util/FlakyTest.java
new file mode 100644
index 0000000000..83f8e9f43d
--- /dev/null
+++ b/base/test/android/javatests/src/org/chromium/base/test/util/FlakyTest.java
@@ -0,0 +1,22 @@
+// Copyright 2016 The Chromium Authors. All rights reserved.
+// 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 java.lang.annotation.ElementType;
+import java.lang.annotation.Retention;
+import java.lang.annotation.RetentionPolicy;
+import java.lang.annotation.Target;
+
+/**
+ * This annotation is for flaky tests.
+ * <p>
+ * Tests with this annotation will not be run on any of the normal bots.
+ * Please note that they might eventually run on a special bot.
+ */
+@Target({ElementType.METHOD, ElementType.TYPE})
+@Retention(RetentionPolicy.RUNTIME)
+public @interface FlakyTest {
+ String message() default "";
+}
diff --git a/base/test/android/javatests/src/org/chromium/base/test/util/InMemorySharedPreferences.java b/base/test/android/javatests/src/org/chromium/base/test/util/InMemorySharedPreferences.java
new file mode 100644
index 0000000000..2587d724a5
--- /dev/null
+++ b/base/test/android/javatests/src/org/chromium/base/test/util/InMemorySharedPreferences.java
@@ -0,0 +1,238 @@
+// Copyright 2013 The Chromium Authors. All rights reserved.
+// 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.content.SharedPreferences;
+
+import java.util.Collections;
+import java.util.HashMap;
+import java.util.Map;
+import java.util.Set;
+
+/**
+ * An implementation of SharedPreferences that can be used in tests.
+ * <p/>
+ * It keeps all state in memory, and there is no difference between apply() and commit().
+ */
+public class InMemorySharedPreferences implements SharedPreferences {
+
+ // Guarded on its own monitor.
+ private final Map<String, Object> mData;
+
+ public InMemorySharedPreferences() {
+ mData = new HashMap<String, Object>();
+ }
+
+ public InMemorySharedPreferences(Map<String, Object> data) {
+ mData = data;
+ }
+
+ @Override
+ public Map<String, ?> getAll() {
+ synchronized (mData) {
+ return Collections.unmodifiableMap(mData);
+ }
+ }
+
+ @Override
+ public String getString(String key, String defValue) {
+ synchronized (mData) {
+ if (mData.containsKey(key)) {
+ return (String) mData.get(key);
+ }
+ }
+ return defValue;
+ }
+
+ @SuppressWarnings("unchecked")
+ @Override
+ public Set<String> getStringSet(String key, Set<String> defValues) {
+ synchronized (mData) {
+ if (mData.containsKey(key)) {
+ return Collections.unmodifiableSet((Set<String>) mData.get(key));
+ }
+ }
+ return defValues;
+ }
+
+ @Override
+ public int getInt(String key, int defValue) {
+ synchronized (mData) {
+ if (mData.containsKey(key)) {
+ return (Integer) mData.get(key);
+ }
+ }
+ return defValue;
+ }
+
+ @Override
+ public long getLong(String key, long defValue) {
+ synchronized (mData) {
+ if (mData.containsKey(key)) {
+ return (Long) mData.get(key);
+ }
+ }
+ return defValue;
+ }
+
+ @Override
+ public float getFloat(String key, float defValue) {
+ synchronized (mData) {
+ if (mData.containsKey(key)) {
+ return (Float) mData.get(key);
+ }
+ }
+ return defValue;
+ }
+
+ @Override
+ public boolean getBoolean(String key, boolean defValue) {
+ synchronized (mData) {
+ if (mData.containsKey(key)) {
+ return (Boolean) mData.get(key);
+ }
+ }
+ return defValue;
+ }
+
+ @Override
+ public boolean contains(String key) {
+ synchronized (mData) {
+ return mData.containsKey(key);
+ }
+ }
+
+ @Override
+ public SharedPreferences.Editor edit() {
+ return new InMemoryEditor();
+ }
+
+ @Override
+ public void registerOnSharedPreferenceChangeListener(
+ SharedPreferences.OnSharedPreferenceChangeListener
+ listener) {
+ throw new UnsupportedOperationException();
+ }
+
+ @Override
+ public void unregisterOnSharedPreferenceChangeListener(
+ SharedPreferences.OnSharedPreferenceChangeListener listener) {
+ throw new UnsupportedOperationException();
+ }
+
+ private class InMemoryEditor implements SharedPreferences.Editor {
+
+ // All guarded by |mChanges|
+ private boolean mClearCalled;
+ private volatile boolean mApplyCalled;
+ private final Map<String, Object> mChanges = new HashMap<String, Object>();
+
+ @Override
+ public SharedPreferences.Editor putString(String key, String value) {
+ synchronized (mChanges) {
+ if (mApplyCalled) throw new IllegalStateException();
+ mChanges.put(key, value);
+ return this;
+ }
+ }
+
+ @Override
+ public SharedPreferences.Editor putStringSet(String key, Set<String> values) {
+ synchronized (mChanges) {
+ if (mApplyCalled) throw new IllegalStateException();
+ mChanges.put(key, values);
+ return this;
+ }
+ }
+
+ @Override
+ public SharedPreferences.Editor putInt(String key, int value) {
+ synchronized (mChanges) {
+ if (mApplyCalled) throw new IllegalStateException();
+ mChanges.put(key, value);
+ return this;
+ }
+ }
+
+ @Override
+ public SharedPreferences.Editor putLong(String key, long value) {
+ synchronized (mChanges) {
+ if (mApplyCalled) throw new IllegalStateException();
+ mChanges.put(key, value);
+ return this;
+ }
+ }
+
+ @Override
+ public SharedPreferences.Editor putFloat(String key, float value) {
+ synchronized (mChanges) {
+ if (mApplyCalled) throw new IllegalStateException();
+ mChanges.put(key, value);
+ return this;
+ }
+ }
+
+ @Override
+ public SharedPreferences.Editor putBoolean(String key, boolean value) {
+ synchronized (mChanges) {
+ if (mApplyCalled) throw new IllegalStateException();
+ mChanges.put(key, value);
+ return this;
+ }
+ }
+
+ @Override
+ public SharedPreferences.Editor remove(String key) {
+ synchronized (mChanges) {
+ if (mApplyCalled) throw new IllegalStateException();
+ // Magic value for removes
+ mChanges.put(key, this);
+ return this;
+ }
+ }
+
+ @Override
+ public SharedPreferences.Editor clear() {
+ synchronized (mChanges) {
+ if (mApplyCalled) throw new IllegalStateException();
+ mClearCalled = true;
+ return this;
+ }
+ }
+
+ @Override
+ public boolean commit() {
+ apply();
+ return true;
+ }
+
+ @Override
+ public void apply() {
+ synchronized (mData) {
+ synchronized (mChanges) {
+ if (mApplyCalled) throw new IllegalStateException();
+ if (mClearCalled) {
+ mData.clear();
+ }
+ for (Map.Entry<String, Object> entry : mChanges.entrySet()) {
+ String key = entry.getKey();
+ Object value = entry.getValue();
+ if (value == this) {
+ // Special value for removal
+ mData.remove(key);
+ } else {
+ mData.put(key, value);
+ }
+ }
+ // The real shared prefs clears out the temporaries allowing the caller to
+ // reuse the Editor instance, however this is undocumented behavior and subtle
+ // to read, so instead we just ban any future use of this instance.
+ mApplyCalled = true;
+ }
+ }
+ }
+ }
+
+}
diff --git a/base/test/android/javatests/src/org/chromium/base/test/util/InstrumentationUtils.java b/base/test/android/javatests/src/org/chromium/base/test/util/InstrumentationUtils.java
new file mode 100644
index 0000000000..20cfd9d620
--- /dev/null
+++ b/base/test/android/javatests/src/org/chromium/base/test/util/InstrumentationUtils.java
@@ -0,0 +1,32 @@
+// Copyright 2012 The Chromium Authors. All rights reserved.
+// 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.app.Instrumentation;
+
+import java.util.concurrent.Callable;
+import java.util.concurrent.ExecutionException;
+import java.util.concurrent.FutureTask;
+
+/**
+ * Utility methods built around the android.app.Instrumentation class.
+ */
+public final class InstrumentationUtils {
+
+ private InstrumentationUtils() {
+ }
+
+ public static <R> R runOnMainSyncAndGetResult(Instrumentation instrumentation,
+ Callable<R> callable) throws Throwable {
+ FutureTask<R> task = new FutureTask<R>(callable);
+ instrumentation.runOnMainSync(task);
+ try {
+ return task.get();
+ } catch (ExecutionException e) {
+ // Unwrap the cause of the exception and re-throw it.
+ throw e.getCause();
+ }
+ }
+}
diff --git a/base/test/android/javatests/src/org/chromium/base/test/util/IntegrationTest.java b/base/test/android/javatests/src/org/chromium/base/test/util/IntegrationTest.java
new file mode 100644
index 0000000000..8b6550d62d
--- /dev/null
+++ b/base/test/android/javatests/src/org/chromium/base/test/util/IntegrationTest.java
@@ -0,0 +1,26 @@
+// Copyright 2014 The Chromium Authors. All rights reserved.
+// 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 java.lang.annotation.ElementType;
+import java.lang.annotation.Retention;
+import java.lang.annotation.RetentionPolicy;
+import java.lang.annotation.Target;
+
+/**
+ * This annotation is for integration tests.
+ * <p>
+ * Examples of integration tests are tests that rely on real instances of the
+ * application's services and components (e.g. Search) to test the system as
+ * a whole. These tests may use additional command-line flags to configure the
+ * existing backends to use.
+ * <p>
+ * Such tests are likely NOT reliable enough to run on tree closing bots and
+ * should only be run on FYI bots.
+ */
+@Target(ElementType.METHOD)
+@Retention(RetentionPolicy.RUNTIME)
+public @interface IntegrationTest {
+} \ No newline at end of file
diff --git a/base/test/android/javatests/src/org/chromium/base/test/util/Manual.java b/base/test/android/javatests/src/org/chromium/base/test/util/Manual.java
new file mode 100644
index 0000000000..31f3977bef
--- /dev/null
+++ b/base/test/android/javatests/src/org/chromium/base/test/util/Manual.java
@@ -0,0 +1,21 @@
+// Copyright 2012 The Chromium Authors. All rights reserved.
+// 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 java.lang.annotation.ElementType;
+import java.lang.annotation.Retention;
+import java.lang.annotation.RetentionPolicy;
+import java.lang.annotation.Target;
+
+/**
+ * This annotation can be used to mark a test that should only be run manually.
+ * <p>
+ * Tests with this annotation will not be run on bots, because they take too long
+ * or need manual monitoring.
+ */
+@Target(ElementType.METHOD)
+@Retention(RetentionPolicy.RUNTIME)
+public @interface Manual {
+}
diff --git a/base/test/android/javatests/src/org/chromium/base/test/util/Matchers.java b/base/test/android/javatests/src/org/chromium/base/test/util/Matchers.java
new file mode 100644
index 0000000000..fc9d68907b
--- /dev/null
+++ b/base/test/android/javatests/src/org/chromium/base/test/util/Matchers.java
@@ -0,0 +1,44 @@
+// Copyright 2016 The Chromium Authors. All rights reserved.
+// 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 org.hamcrest.CoreMatchers;
+import org.hamcrest.Description;
+import org.hamcrest.Matcher;
+import org.hamcrest.TypeSafeMatcher;
+
+/**
+ * Helper class containing Hamcrest matchers.
+ */
+public class Matchers extends CoreMatchers {
+ private static class GreaterThanOrEqualTo<T extends Comparable<T>>
+ extends TypeSafeMatcher<T> {
+
+ private final T mComparisonValue;
+
+ public GreaterThanOrEqualTo(T comparisonValue) {
+ mComparisonValue = comparisonValue;
+ }
+
+ @Override
+ public void describeTo(Description description) {
+ description.appendText("greater than or equal to ").appendValue(mComparisonValue);
+ }
+
+ @Override
+ protected boolean matchesSafely(T item) {
+ return item.compareTo(mComparisonValue) >= 0;
+ }
+ }
+
+ /**
+ * @param <T> A Comparable type.
+ * @param comparisonValue The value to be compared against.
+ * @return A matcher that expects the value to be greater than the |comparisonValue|.
+ */
+ public static <T extends Comparable<T>> Matcher<T> greaterThanOrEqualTo(T comparisonValue) {
+ return new GreaterThanOrEqualTo<>(comparisonValue);
+ }
+}
diff --git a/base/test/android/javatests/src/org/chromium/base/test/util/MetricsUtils.java b/base/test/android/javatests/src/org/chromium/base/test/util/MetricsUtils.java
new file mode 100644
index 0000000000..c4664d68b0
--- /dev/null
+++ b/base/test/android/javatests/src/org/chromium/base/test/util/MetricsUtils.java
@@ -0,0 +1,43 @@
+// Copyright 2014 The Chromium Authors. All rights reserved.
+// 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 org.chromium.base.metrics.RecordHistogram;
+
+/**
+ * Helpers for testing UMA metrics.
+ */
+public class MetricsUtils {
+ /**
+ * Helper class that snapshots the given bucket of the given UMA histogram on its creation,
+ * allowing to inspect the number of samples recorded during its lifetime.
+ */
+ public static class HistogramDelta {
+ private final String mHistogram;
+ private final int mSampleValue;
+
+ private final int mInitialCount;
+
+ private int get() {
+ return RecordHistogram.getHistogramValueCountForTesting(mHistogram, mSampleValue);
+ }
+
+ /**
+ * Snapshots the given bucket of the given histogram.
+ * @param histogram name of the histogram to snapshot
+ * @param sampleValue the bucket that contains this value will be snapshot
+ */
+ public HistogramDelta(String histogram, int sampleValue) {
+ mHistogram = histogram;
+ mSampleValue = sampleValue;
+ mInitialCount = get();
+ }
+
+ /** Returns the number of samples of the snapshot bucket recorded since creation */
+ public int getDelta() {
+ return get() - mInitialCount;
+ }
+ }
+}
diff --git a/base/test/android/javatests/src/org/chromium/base/test/util/MinAndroidSdkLevel.java b/base/test/android/javatests/src/org/chromium/base/test/util/MinAndroidSdkLevel.java
new file mode 100644
index 0000000000..13e25786a7
--- /dev/null
+++ b/base/test/android/javatests/src/org/chromium/base/test/util/MinAndroidSdkLevel.java
@@ -0,0 +1,19 @@
+// Copyright 2014 The Chromium Authors. All rights reserved.
+// 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 java.lang.annotation.ElementType;
+import java.lang.annotation.Inherited;
+import java.lang.annotation.Retention;
+import java.lang.annotation.RetentionPolicy;
+import java.lang.annotation.Target;
+
+@Inherited
+@Retention(RetentionPolicy.RUNTIME)
+@Target({ElementType.METHOD, ElementType.TYPE})
+public @interface MinAndroidSdkLevel {
+ int value() default 0;
+}
+
diff --git a/base/test/android/javatests/src/org/chromium/base/test/util/MinAndroidSdkLevelSkipCheck.java b/base/test/android/javatests/src/org/chromium/base/test/util/MinAndroidSdkLevelSkipCheck.java
new file mode 100644
index 0000000000..8b07c0f19b
--- /dev/null
+++ b/base/test/android/javatests/src/org/chromium/base/test/util/MinAndroidSdkLevelSkipCheck.java
@@ -0,0 +1,43 @@
+// Copyright 2016 The Chromium Authors. All rights reserved.
+// 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.Build;
+
+import org.junit.runners.model.FrameworkMethod;
+
+import org.chromium.base.Log;
+
+/**
+ * Checks the device's SDK level against any specified minimum requirement.
+ */
+public class MinAndroidSdkLevelSkipCheck extends SkipCheck {
+
+ private static final String TAG = "base_test";
+
+ /**
+ * If {@link MinAndroidSdkLevel} is present, checks its value
+ * against the device's SDK level.
+ *
+ * @param testCase The test to check.
+ * @return true if the device's SDK level is below the specified minimum.
+ */
+ @Override
+ public boolean shouldSkip(FrameworkMethod frameworkMethod) {
+ int minSdkLevel = 0;
+ for (MinAndroidSdkLevel m : AnnotationProcessingUtils.getAnnotations(
+ frameworkMethod.getMethod(), MinAndroidSdkLevel.class)) {
+ minSdkLevel = Math.max(minSdkLevel, m.value());
+ }
+ if (Build.VERSION.SDK_INT < minSdkLevel) {
+ Log.i(TAG, "Test " + frameworkMethod.getDeclaringClass().getName() + "#"
+ + frameworkMethod.getName() + " is not enabled at SDK level "
+ + Build.VERSION.SDK_INT + ".");
+ return true;
+ }
+ return false;
+ }
+
+}
diff --git a/base/test/android/javatests/src/org/chromium/base/test/util/Restriction.java b/base/test/android/javatests/src/org/chromium/base/test/util/Restriction.java
new file mode 100644
index 0000000000..f39bfbd783
--- /dev/null
+++ b/base/test/android/javatests/src/org/chromium/base/test/util/Restriction.java
@@ -0,0 +1,37 @@
+// Copyright 2013 The Chromium Authors. All rights reserved.
+// 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 java.lang.annotation.ElementType;
+import java.lang.annotation.Retention;
+import java.lang.annotation.RetentionPolicy;
+import java.lang.annotation.Target;
+
+/**
+ * An annotation for listing restrictions for a test method. For example, if a test method is only
+ * applicable on a phone with small memory:
+ * @Restriction({RESTRICTION_TYPE_PHONE, RESTRICTION_TYPE_SMALL_MEMORY})
+ * Test classes are free to define restrictions and enforce them using reflection at runtime.
+ */
+@Target({ElementType.METHOD, ElementType.TYPE})
+@Retention(RetentionPolicy.RUNTIME)
+public @interface Restriction {
+ /** Specifies the test is only valid on low end devices that have less memory. */
+ public static final String RESTRICTION_TYPE_LOW_END_DEVICE = "Low_End_Device";
+
+ /** Specifies the test is only valid on non-low end devices. */
+ public static final String RESTRICTION_TYPE_NON_LOW_END_DEVICE = "Non_Low_End_Device";
+
+ /** Specifies the test is only valid on a device that can reach the internet. */
+ public static final String RESTRICTION_TYPE_INTERNET = "Internet";
+
+ /** Specifies the test is only valid on a device that has a camera. */
+ public static final String RESTRICTION_TYPE_HAS_CAMERA = "Has_Camera";
+
+ /**
+ * @return A list of restrictions.
+ */
+ public String[] value();
+}
diff --git a/base/test/android/javatests/src/org/chromium/base/test/util/RestrictionSkipCheck.java b/base/test/android/javatests/src/org/chromium/base/test/util/RestrictionSkipCheck.java
new file mode 100644
index 0000000000..a27dd1fe08
--- /dev/null
+++ b/base/test/android/javatests/src/org/chromium/base/test/util/RestrictionSkipCheck.java
@@ -0,0 +1,78 @@
+// Copyright 2016 The Chromium Authors. All rights reserved.
+// 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.content.Context;
+import android.net.ConnectivityManager;
+import android.net.NetworkInfo;
+import android.text.TextUtils;
+
+import org.junit.runners.model.FrameworkMethod;
+
+import org.chromium.base.Log;
+import org.chromium.base.SysUtils;
+
+/**
+ * Checks if any restrictions exist and skip the test if it meets those restrictions.
+ */
+public class RestrictionSkipCheck extends SkipCheck {
+
+ private static final String TAG = "base_test";
+
+ private final Context mTargetContext;
+
+ public RestrictionSkipCheck(Context targetContext) {
+ mTargetContext = targetContext;
+ }
+
+ protected Context getTargetContext() {
+ return mTargetContext;
+ }
+
+ @Override
+ public boolean shouldSkip(FrameworkMethod frameworkMethod) {
+ if (frameworkMethod == null) return true;
+
+ for (Restriction restriction : AnnotationProcessingUtils.getAnnotations(
+ frameworkMethod.getMethod(), Restriction.class)) {
+ for (String restrictionVal : restriction.value()) {
+ if (restrictionApplies(restrictionVal)) {
+ Log.i(TAG, "Test " + frameworkMethod.getDeclaringClass().getName() + "#"
+ + frameworkMethod.getName() + " skipped because of restriction "
+ + restriction);
+ return true;
+ }
+ }
+ }
+ return false;
+ }
+
+ protected boolean restrictionApplies(String restriction) {
+ if (TextUtils.equals(restriction, Restriction.RESTRICTION_TYPE_LOW_END_DEVICE)
+ && !SysUtils.isLowEndDevice()) {
+ return true;
+ }
+ if (TextUtils.equals(restriction, Restriction.RESTRICTION_TYPE_NON_LOW_END_DEVICE)
+ && SysUtils.isLowEndDevice()) {
+ return true;
+ }
+ if (TextUtils.equals(restriction, Restriction.RESTRICTION_TYPE_INTERNET)
+ && !isNetworkAvailable()) {
+ return true;
+ }
+ if (TextUtils.equals(restriction, Restriction.RESTRICTION_TYPE_HAS_CAMERA)
+ && !SysUtils.hasCamera(mTargetContext)) {
+ return true;
+ }
+ return false;
+ }
+
+ private boolean isNetworkAvailable() {
+ final ConnectivityManager connectivityManager = (ConnectivityManager)
+ mTargetContext.getSystemService(Context.CONNECTIVITY_SERVICE);
+ final NetworkInfo activeNetworkInfo = connectivityManager.getActiveNetworkInfo();
+ return activeNetworkInfo != null && activeNetworkInfo.isConnected();
+ }
+}
diff --git a/base/test/android/javatests/src/org/chromium/base/test/util/RetryOnFailure.java b/base/test/android/javatests/src/org/chromium/base/test/util/RetryOnFailure.java
new file mode 100644
index 0000000000..eb98008d00
--- /dev/null
+++ b/base/test/android/javatests/src/org/chromium/base/test/util/RetryOnFailure.java
@@ -0,0 +1,25 @@
+// Copyright 2016 The Chromium Authors. All rights reserved.
+// 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 java.lang.annotation.ElementType;
+import java.lang.annotation.Retention;
+import java.lang.annotation.RetentionPolicy;
+import java.lang.annotation.Target;
+
+// Note this annotation may be a NOOP. Check http://crbug.com/797002 for latest status (also see
+// http://crbug.com/619055). Current default behavior is to retry all tests on failure.
+/**
+ * Mark a test as flaky and should be retried on failure. The test is
+ * considered passed by the test script if any retry succeeds.
+ *
+ * Long term, this should be merged with @FlakyTest. But @FlakyTest means
+ * has specific meaning that is currently different from RetryOnFailure.
+ */
+@Target({ElementType.METHOD, ElementType.TYPE})
+@Retention(RetentionPolicy.RUNTIME)
+public @interface RetryOnFailure {
+ String message() default "";
+}
diff --git a/base/test/android/javatests/src/org/chromium/base/test/util/ScalableTimeout.java b/base/test/android/javatests/src/org/chromium/base/test/util/ScalableTimeout.java
new file mode 100644
index 0000000000..7a815c09a4
--- /dev/null
+++ b/base/test/android/javatests/src/org/chromium/base/test/util/ScalableTimeout.java
@@ -0,0 +1,29 @@
+// Copyright 2012 The Chromium Authors. All rights reserved.
+// 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;
+
+/**
+ * Utility class for scaling various timeouts by a common factor.
+ * For example, to run tests under slow memory tools, you might do
+ * something like this:
+ * adb shell "echo 20.0 > /data/local/tmp/chrome_timeout_scale"
+ */
+public class ScalableTimeout {
+ private static Double sTimeoutScale;
+ public static final String PROPERTY_FILE = "/data/local/tmp/chrome_timeout_scale";
+
+ public static long scaleTimeout(long timeout) {
+ if (sTimeoutScale == null) {
+ try {
+ char[] data = TestFileUtil.readUtf8File(PROPERTY_FILE, 32);
+ sTimeoutScale = Double.parseDouble(new String(data));
+ } catch (Exception e) {
+ // NumberFormatException, FileNotFoundException, IOException
+ sTimeoutScale = 1.0;
+ }
+ }
+ return (long) (timeout * sTimeoutScale);
+ }
+}
diff --git a/base/test/android/javatests/src/org/chromium/base/test/util/SkipCheck.java b/base/test/android/javatests/src/org/chromium/base/test/util/SkipCheck.java
new file mode 100644
index 0000000000..d1dd7be17f
--- /dev/null
+++ b/base/test/android/javatests/src/org/chromium/base/test/util/SkipCheck.java
@@ -0,0 +1,49 @@
+// Copyright 2015 The Chromium Authors. All rights reserved.
+// 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 junit.framework.TestCase;
+
+import org.junit.runners.model.FrameworkMethod;
+
+import org.chromium.base.Log;
+
+import java.lang.reflect.Method;
+
+/**
+ * Check whether a test case should be skipped.
+ */
+public abstract class SkipCheck {
+
+ private static final String TAG = "base_test";
+
+ /**
+ *
+ * Checks whether the given test method should be skipped.
+ *
+ * @param testMethod The test method to check.
+ * @return Whether the test case should be skipped.
+ */
+ public abstract boolean shouldSkip(FrameworkMethod testMethod);
+
+ /**
+ *
+ * Checks whether the given test case should be skipped.
+ *
+ * @param testCase The test case to check.
+ * @return Whether the test case should be skipped.
+ */
+ public boolean shouldSkip(TestCase testCase) {
+ try {
+ Method m = testCase.getClass().getMethod(testCase.getName(), (Class[]) null);
+ return shouldSkip(new FrameworkMethod(m));
+ } catch (NoSuchMethodException e) {
+ Log.e(TAG, "Unable to find %s in %s", testCase.getName(),
+ testCase.getClass().getName(), e);
+ return false;
+ }
+ }
+}
+
diff --git a/base/test/android/javatests/src/org/chromium/base/test/util/TestFileUtil.java b/base/test/android/javatests/src/org/chromium/base/test/util/TestFileUtil.java
new file mode 100644
index 0000000000..6d891210fc
--- /dev/null
+++ b/base/test/android/javatests/src/org/chromium/base/test/util/TestFileUtil.java
@@ -0,0 +1,85 @@
+// Copyright 2012 The Chromium Authors. All rights reserved.
+// 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 java.io.File;
+import java.io.FileInputStream;
+import java.io.FileNotFoundException;
+import java.io.FileOutputStream;
+import java.io.IOException;
+import java.io.InputStreamReader;
+import java.io.OutputStreamWriter;
+import java.io.Reader;
+import java.io.Writer;
+import java.util.Arrays;
+
+/**
+ * Utility class for dealing with files for test.
+ */
+public class TestFileUtil {
+ public static void createNewHtmlFile(String name, String title, String body)
+ throws IOException {
+ createNewHtmlFile(new File(name), title, body);
+ }
+
+ public static void createNewHtmlFile(File file, String title, String body)
+ throws IOException {
+ if (!file.createNewFile()) {
+ throw new IOException("File \"" + file.getAbsolutePath() + "\" already exists");
+ }
+
+ Writer writer = null;
+ try {
+ writer = new OutputStreamWriter(new FileOutputStream(file), "UTF-8");
+ writer.write("<html><meta charset=\"UTF-8\" />"
+ + " <head><title>" + title + "</title></head>"
+ + " <body>"
+ + (body != null ? body : "")
+ + " </body>"
+ + " </html>");
+ } finally {
+ if (writer != null) {
+ writer.close();
+ }
+ }
+ }
+
+ public static void deleteFile(String name) {
+ deleteFile(new File(name));
+ }
+
+ public static void deleteFile(File file) {
+ boolean deleted = file.delete();
+ assert (deleted || !file.exists());
+ }
+
+ /**
+ * @param fileName the file to read in.
+ * @param sizeLimit cap on the file size: will throw an exception if exceeded
+ * @return Array of chars read from the file
+ * @throws FileNotFoundException file does not exceed
+ * @throws IOException error encountered accessing the file
+ */
+ public static char[] readUtf8File(String fileName, int sizeLimit) throws
+ FileNotFoundException, IOException {
+ Reader reader = null;
+ try {
+ File f = new File(fileName);
+ if (f.length() > sizeLimit) {
+ throw new IOException("File " + fileName + " length " + f.length()
+ + " exceeds limit " + sizeLimit);
+ }
+ char[] buffer = new char[(int) f.length()];
+ reader = new InputStreamReader(new FileInputStream(f), "UTF-8");
+ int charsRead = reader.read(buffer);
+ // Debug check that we've exhausted the input stream (will fail e.g. if the
+ // file grew after we inspected its length).
+ assert !reader.ready();
+ return charsRead < buffer.length ? Arrays.copyOfRange(buffer, 0, charsRead) : buffer;
+ } finally {
+ if (reader != null) reader.close();
+ }
+ }
+}
diff --git a/base/test/android/javatests/src/org/chromium/base/test/util/TestThread.java b/base/test/android/javatests/src/org/chromium/base/test/util/TestThread.java
new file mode 100644
index 0000000000..4f6296924c
--- /dev/null
+++ b/base/test/android/javatests/src/org/chromium/base/test/util/TestThread.java
@@ -0,0 +1,143 @@
+// Copyright 2012 The Chromium Authors. All rights reserved.
+// 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 java.util.concurrent.atomic.AtomicBoolean;
+
+/**
+ * This class is usefull when writing instrumentation tests that exercise code that posts tasks
+ * (to the same thread).
+ * Since the test code is run in a single thread, the posted tasks are never executed.
+ * The TestThread class lets you run that code on a specific thread synchronously and flush the
+ * message loop on that thread.
+ *
+ * Example of test using this:
+ *
+ * public void testMyAwesomeClass() {
+ * TestThread testThread = new TestThread();
+ * testThread.startAndWaitForReadyState();
+ *
+ * testThread.runOnTestThreadSyncAndProcessPendingTasks(new Runnable() {
+ * @Override
+ * public void run() {
+ * MyAwesomeClass.doStuffAsync();
+ * }
+ * });
+ * // Once we get there we know doStuffAsync has been executed and all the tasks it posted.
+ * assertTrue(MyAwesomeClass.stuffWasDone());
+ * }
+ *
+ * Notes:
+ * - this is only for tasks posted to the same thread. Anyway if you were posting to a different
+ * thread, you'd probably need to set that other thread up.
+ * - this only supports tasks posted using Handler.post(), it won't work with postDelayed and
+ * postAtTime.
+ * - if your test instanciates an object and that object is the one doing the posting of tasks, you
+ * probably want to instanciate it on the test thread as it might create the Handler it posts
+ * tasks to in the constructor.
+ */
+
+public class TestThread extends Thread {
+ private final Object mThreadReadyLock;
+ private AtomicBoolean mThreadReady;
+ private Handler mMainThreadHandler;
+ private Handler mTestThreadHandler;
+
+ public TestThread() {
+ mMainThreadHandler = new Handler();
+ // We can't use the AtomicBoolean as the lock or findbugs will freak out...
+ mThreadReadyLock = new Object();
+ mThreadReady = new AtomicBoolean();
+ }
+
+ @Override
+ public void run() {
+ Looper.prepare();
+ mTestThreadHandler = new Handler();
+ mTestThreadHandler.post(new Runnable() {
+ @Override
+ public void run() {
+ synchronized (mThreadReadyLock) {
+ mThreadReady.set(true);
+ mThreadReadyLock.notify();
+ }
+ }
+ });
+ Looper.loop();
+ }
+
+ /**
+ * Starts this TestThread and blocks until it's ready to accept calls.
+ */
+ public void startAndWaitForReadyState() {
+ checkOnMainThread();
+ start();
+ synchronized (mThreadReadyLock) {
+ try {
+ // Note the mThreadReady and while are not really needed.
+ // There are there so findbugs don't report warnings.
+ while (!mThreadReady.get()) {
+ mThreadReadyLock.wait();
+ }
+ } catch (InterruptedException ie) {
+ System.err.println("Error starting TestThread.");
+ ie.printStackTrace();
+ }
+ }
+ }
+
+ /**
+ * Runs the passed Runnable synchronously on the TestThread and returns when all pending
+ * runnables have been excuted.
+ * Should be called from the main thread.
+ */
+ public void runOnTestThreadSyncAndProcessPendingTasks(Runnable r) {
+ checkOnMainThread();
+
+ runOnTestThreadSync(r);
+
+ // Run another task, when it's done it means all pendings tasks have executed.
+ runOnTestThreadSync(null);
+ }
+
+ /**
+ * Runs the passed Runnable on the test thread and blocks until it has finished executing.
+ * Should be called from the main thread.
+ * @param r The runnable to be executed.
+ */
+ public void runOnTestThreadSync(final Runnable r) {
+ checkOnMainThread();
+ final Object lock = new Object();
+ // Task executed is not really needed since we are only on one thread, it is here to appease
+ // findbugs.
+ final AtomicBoolean taskExecuted = new AtomicBoolean();
+ mTestThreadHandler.post(new Runnable() {
+ @Override
+ public void run() {
+ if (r != null) r.run();
+ synchronized (lock) {
+ taskExecuted.set(true);
+ lock.notify();
+ }
+ }
+ });
+ synchronized (lock) {
+ try {
+ while (!taskExecuted.get()) {
+ lock.wait();
+ }
+ } catch (InterruptedException ie) {
+ ie.printStackTrace();
+ }
+ }
+ }
+
+ private void checkOnMainThread() {
+ assert Looper.myLooper() == mMainThreadHandler.getLooper();
+ }
+}
diff --git a/base/test/android/javatests/src/org/chromium/base/test/util/TimeoutScale.java b/base/test/android/javatests/src/org/chromium/base/test/util/TimeoutScale.java
new file mode 100644
index 0000000000..5aee05e73a
--- /dev/null
+++ b/base/test/android/javatests/src/org/chromium/base/test/util/TimeoutScale.java
@@ -0,0 +1,22 @@
+// Copyright 2012 The Chromium Authors. All rights reserved.
+// 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 java.lang.annotation.ElementType;
+import java.lang.annotation.Retention;
+import java.lang.annotation.RetentionPolicy;
+import java.lang.annotation.Target;
+
+/**
+ * This annotation can be used to scale a specific test timeout.
+ */
+@Target(ElementType.METHOD)
+@Retention(RetentionPolicy.RUNTIME)
+public @interface TimeoutScale {
+ /**
+ * @return A number to scale the test timeout.
+ */
+ public int value();
+}
diff --git a/base/test/android/javatests/src/org/chromium/base/test/util/UrlUtils.java b/base/test/android/javatests/src/org/chromium/base/test/util/UrlUtils.java
new file mode 100644
index 0000000000..9ca3fcc33c
--- /dev/null
+++ b/base/test/android/javatests/src/org/chromium/base/test/util/UrlUtils.java
@@ -0,0 +1,84 @@
+// Copyright 2012 The Chromium Authors. All rights reserved.
+// 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 org.junit.Assert;
+
+import org.chromium.base.PathUtils;
+import org.chromium.base.annotations.CalledByNative;
+import org.chromium.base.annotations.MainDex;
+
+/**
+ * Collection of URL utilities.
+ */
+@MainDex
+public class UrlUtils {
+ private static final String DATA_DIR = "/chrome/test/data/";
+
+ /**
+ * Construct the full path of a test data file.
+ * @param path Pathname relative to external/chrome/test/data
+ */
+ public static String getTestFilePath(String path) {
+ // TODO(jbudorick): Remove DATA_DIR once everything has been isolated. crbug/400499
+ return getIsolatedTestFilePath(DATA_DIR + path);
+ }
+
+ // TODO(jbudorick): Remove this function once everything has been isolated and switched back
+ // to getTestFilePath. crbug/400499
+ /**
+ * Construct the full path of a test data file.
+ * @param path Pathname relative to external/
+ */
+ public static String getIsolatedTestFilePath(String path) {
+ return getIsolatedTestRoot() + "/" + path;
+ }
+
+ /**
+ * Returns the root of the test data directory.
+ */
+ @CalledByNative
+ public static String getIsolatedTestRoot() {
+ return PathUtils.getExternalStorageDirectory() + "/chromium_tests_root";
+ }
+
+ /**
+ * Construct a suitable URL for loading a test data file.
+ * @param path Pathname relative to external/chrome/test/data
+ */
+ public static String getTestFileUrl(String path) {
+ return "file://" + getTestFilePath(path);
+ }
+
+ // TODO(jbudorick): Remove this function once everything has been isolated and switched back
+ // to getTestFileUrl. crbug/400499
+ /**
+ * Construct a suitable URL for loading a test data file.
+ * @param path Pathname relative to external/
+ */
+ public static String getIsolatedTestFileUrl(String path) {
+ return "file://" + getIsolatedTestFilePath(path);
+ }
+
+ /**
+ * Construct a data:text/html URI for loading from an inline HTML.
+ * @param html An unencoded HTML
+ * @return String An URI that contains the given HTML
+ */
+ public static String encodeHtmlDataUri(String html) {
+ try {
+ // URLEncoder encodes into application/x-www-form-encoded, so
+ // ' '->'+' needs to be undone and replaced with ' '->'%20'
+ // to match the Data URI requirements.
+ String encoded =
+ "data:text/html;utf-8," + java.net.URLEncoder.encode(html, "UTF-8");
+ encoded = encoded.replace("+", "%20");
+ return encoded;
+ } catch (java.io.UnsupportedEncodingException e) {
+ Assert.fail("Unsupported encoding: " + e.getMessage());
+ return null;
+ }
+ }
+}
diff --git a/base/test/android/javatests/src/org/chromium/base/test/util/UserActionTester.java b/base/test/android/javatests/src/org/chromium/base/test/util/UserActionTester.java
new file mode 100644
index 0000000000..88e3551131
--- /dev/null
+++ b/base/test/android/javatests/src/org/chromium/base/test/util/UserActionTester.java
@@ -0,0 +1,51 @@
+// Copyright 2017 The Chromium Authors. All rights reserved.
+// 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 org.chromium.base.ThreadUtils;
+import org.chromium.base.metrics.RecordUserAction;
+
+import java.util.ArrayList;
+import java.util.List;
+
+/**
+ * A util class that records UserActions.
+ */
+public class UserActionTester implements RecordUserAction.UserActionCallback {
+ private List<String> mActions;
+
+ public UserActionTester() {
+ mActions = new ArrayList<>();
+ ThreadUtils.runOnUiThreadBlocking(new Runnable() {
+ @Override
+ public void run() {
+ RecordUserAction.setActionCallbackForTesting(UserActionTester.this);
+ }
+ });
+ }
+
+ public void tearDown() {
+ ThreadUtils.runOnUiThreadBlocking(new Runnable() {
+ @Override
+ public void run() {
+ RecordUserAction.removeActionCallbackForTesting();
+ }
+ });
+ }
+
+ @Override
+ public void onActionRecorded(String action) {
+ mActions.add(action);
+ }
+
+ public List<String> getActions() {
+ return mActions;
+ }
+
+ @Override
+ public String toString() {
+ return "Actions: " + mActions.toString();
+ }
+}
diff --git a/base/test/android/javatests/src/org/chromium/base/test/util/parameter/CommandLineParameter.java b/base/test/android/javatests/src/org/chromium/base/test/util/parameter/CommandLineParameter.java
new file mode 100644
index 0000000000..e6f5506899
--- /dev/null
+++ b/base/test/android/javatests/src/org/chromium/base/test/util/parameter/CommandLineParameter.java
@@ -0,0 +1,32 @@
+// Copyright 2015 The Chromium Authors. All rights reserved.
+// 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.parameter;
+
+import java.lang.annotation.ElementType;
+import java.lang.annotation.Inherited;
+import java.lang.annotation.Retention;
+import java.lang.annotation.RetentionPolicy;
+import java.lang.annotation.Target;
+
+/**
+ * The annotation for parametering CommandLineFlags in JUnit3 instrumentation tests.
+ *
+ * E.g. if you add the following annotation to your test class:
+ *
+ * <code>
+ * @CommandLineParameter({"", FLAG_A, FLAG_B})
+ * public class MyTestClass
+ * </code>
+ *
+ * The test harness would run the test 3 times with each of the flag added to commandline
+ * file.
+ */
+
+@Inherited
+@Retention(RetentionPolicy.RUNTIME)
+@Target({ElementType.METHOD, ElementType.TYPE})
+public @interface CommandLineParameter {
+ String[] value() default {};
+}
diff --git a/base/test/android/javatests/src/org/chromium/base/test/util/parameter/SkipCommandLineParameterization.java b/base/test/android/javatests/src/org/chromium/base/test/util/parameter/SkipCommandLineParameterization.java
new file mode 100644
index 0000000000..2181031617
--- /dev/null
+++ b/base/test/android/javatests/src/org/chromium/base/test/util/parameter/SkipCommandLineParameterization.java
@@ -0,0 +1,23 @@
+// Copyright 2017 The Chromium Authors. All rights reserved.
+// 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.parameter;
+
+import java.lang.annotation.ElementType;
+import java.lang.annotation.Inherited;
+import java.lang.annotation.Retention;
+import java.lang.annotation.RetentionPolicy;
+import java.lang.annotation.Target;
+
+/**
+ * BaseJUnit4ClassRunner and host side test harness skips commandline parameterization for test
+ * classes or methods annotated with SkipCommandLineParameterization.
+ *
+ * This usually used by test that only runs in WebView javatests that only runs in sandboxed mode
+ * or single process mode.
+ */
+
+@Inherited
+@Retention(RetentionPolicy.RUNTIME)
+@Target({ElementType.METHOD, ElementType.TYPE})
+public @interface SkipCommandLineParameterization {}