Skip to content

Instantly share code, notes, and snippets.

@operando
Created September 8, 2016 23:42
Show Gist options
  • Save operando/ae4d18d264dae3851da7aa539e48ffc6 to your computer and use it in GitHub Desktop.
Save operando/ae4d18d264dae3851da7aa539e48ffc6 to your computer and use it in GitHub Desktop.
com.android.support.test:rules rules0.5-0.6-alpha-diff
diff -ru ../0.5/android/support/test/annotation/UiThreadTest.java android/support/test/annotation/UiThreadTest.java
--- ../0.5/android/support/test/annotation/UiThreadTest.java 2016-02-22 20:52:48.000000000 +0900
+++ android/support/test/annotation/UiThreadTest.java 2016-08-02 22:18:50.000000000 +0900
@@ -22,18 +22,14 @@
import java.lang.annotation.Target;
/**
- * This annotation should be used along with {@link android.support.test.rule.UiThreadTestRule}
- * or with any rule that inherits from it. When the annotation is present, the test method is
- * executed on the application's UI thread (or main thread).
+ * Methods annotated with this annotation will be executed on the application's UI thread
+ * (or main thread).
* <p/>
- * Note, due to current JUnit limitation, methods annotated with
- * <a href="http://junit.sourceforge.net/javadoc/org/junit/Before.html"><code>Before</code></a> and
- * <a href="http://junit.sourceforge.net/javadoc/org/junit/After.html"><code>After</code></a> will
- * also be executed on the UI Thread. Consider using
- * {@link android.support.test.rule.UiThreadTestRule#runOnUiThread(Runnable)} if this is an
- * issue.
- *
- * @see android.support.test.rule.UiThreadTestRule#runOnUiThread(Runnable)
+ * This annotation will only take effect for {@link org.junit.Test}, {@link org.junit.Before}
+ * or {@link org.junit.After} methods.
+ * <p/>
+ * @see android.support.test.rule.ActivityTestRule#runOnUiThread(Runnable) if you need to switch in
+ * and out of the UI thread within your method.
*/
@Target(ElementType.METHOD)
@Retention(RetentionPolicy.RUNTIME)
Only in ../0.5/android/support/test: internal
diff -ru ../0.5/android/support/test/rule/ActivityTestRule.java android/support/test/rule/ActivityTestRule.java
--- ../0.5/android/support/test/rule/ActivityTestRule.java 2016-02-22 20:52:48.000000000 +0900
+++ android/support/test/rule/ActivityTestRule.java 2016-08-02 22:18:50.000000000 +0900
@@ -16,16 +16,23 @@
package android.support.test.rule;
+import org.junit.rules.TestRule;
import org.junit.runner.Description;
import org.junit.runners.model.Statement;
import android.app.Activity;
import android.app.Instrumentation;
+import android.content.Context;
import android.content.Intent;
import android.os.Bundle;
+import android.support.annotation.NonNull;
import android.support.annotation.Nullable;
import android.support.annotation.VisibleForTesting;
import android.support.test.InstrumentationRegistry;
+import android.support.test.annotation.UiThreadTest;
+import android.support.test.internal.runner.junit4.statement.UiThreadStatement;
+import android.support.test.runner.intercepting.SingleActivityFactory;
+import android.support.test.runner.MonitoringInstrumentation;
import android.util.Log;
import static android.support.test.internal.util.Checks.checkNotNull;
@@ -42,12 +49,18 @@
*
* @param <T> The activity to test
*/
-public class ActivityTestRule<T extends Activity> extends UiThreadTestRule {
+public class ActivityTestRule<T extends Activity> implements TestRule {
private static final String TAG = "ActivityTestRule";
+ private static final int NO_FLAGS_SET = 0;
+
private final Class<T> mActivityClass;
+ private final String mTargetPackage;
+
+ private final int mLaunchFlags;
+
private Instrumentation mInstrumentation;
private boolean mInitialTouchMode = false;
@@ -56,8 +69,10 @@
private T mActivity;
+ private SingleActivityFactory<T> mActivityFactory;
+
/**
- * Similar to {@link #ActivityTestRule(Class, boolean, boolean)} but with "touch mode" disabled.
+ * Similar to {@link #ActivityTestRule(Class, boolean)} but with "touch mode" disabled.
*
* @param activityClass The activity under test. This must be a class in the instrumentation
* targetPackage specified in the AndroidManifest.xml
@@ -87,7 +102,10 @@
}
/**
- * Creates an {@link ActivityTestRule} for the Activity under test.
+ * Similar to {@link #ActivityTestRule(Class, String, int, boolean, boolean)} but defaults to
+ * launch the Activity with the default target package name
+ * {@link InstrumentationRegistry#getTargetContext()#getPackageName} and
+ * {@link Intent#FLAG_ACTIVITY_NEW_TASK} launch flag.
*
* @param activityClass The activity under test. This must be a class in the instrumentation
* targetPackage specified in the AndroidManifest.xml
@@ -102,25 +120,81 @@
*/
public ActivityTestRule(Class<T> activityClass, boolean initialTouchMode,
boolean launchActivity) {
+ this(activityClass, InstrumentationRegistry.getTargetContext().getPackageName(),
+ Intent.FLAG_ACTIVITY_NEW_TASK, initialTouchMode, launchActivity);
+ }
+
+ /**
+ * Creates an {@link ActivityTestRule} for the Activity under test.
+ *
+ * @param activityFactory factory to be used for creating Activity instance
+ * @param initialTouchMode true if the Activity should be placed into "touch mode" when started
+ * @param launchActivity true if the Activity should be launched once per
+ * <a href="http://junit.org/javadoc/latest/org/junit/Test.html">
+ * <code>Test</code></a> method. It will be launched before the first
+ * <a href="http://junit.sourceforge.net/javadoc/org/junit/Before.html">
+ * <code>Before</code></a> method, and terminated after the last
+ * <a href="http://junit.sourceforge.net/javadoc/org/junit/After.html">
+ * <code>After</code></a> method.
+ */
+ public ActivityTestRule(SingleActivityFactory<T> activityFactory,
+ boolean initialTouchMode, boolean launchActivity) {
+ this(activityFactory.getActivityClassToIntercept(), initialTouchMode, launchActivity);
+ mActivityFactory = activityFactory;
+ }
+
+ /**
+ * Creates an {@link ActivityTestRule} for the Activity under test.
+ *
+ * @param activityClass The activity under test. This must be a class in the instrumentation
+ * targetPackage specified in the AndroidManifest.xml
+ * @param initialTouchMode true if the Activity should be placed into "touch mode" when started
+ * @param launchActivity true if the Activity should be launched once per
+ * <a href="http://junit.org/javadoc/latest/org/junit/Test.html">
+ * <code>Test</code></a> method. It will be launched before the first
+ * <a href="http://junit.sourceforge.net/javadoc/org/junit/Before.html">
+ * <code>Before</code></a> method, and terminated after the last
+ * <a href="http://junit.sourceforge.net/javadoc/org/junit/After.html">
+ * <code>After</code></a> method.
+ * @param targetPackage The name of the target package that the Activity is started
+ * under. This value is passed down to the start Intent using
+ * {@link Intent#setClassName(Context, String)}. Can not be null.
+ * @param launchFlags launch flags to start the Activity under test.
+ */
+ public ActivityTestRule(Class<T> activityClass, @NonNull String targetPackage, int launchFlags,
+ boolean initialTouchMode, boolean launchActivity) {
+ mInstrumentation = InstrumentationRegistry.getInstrumentation();
mActivityClass = activityClass;
+ mTargetPackage = checkNotNull(targetPackage, "targetPackage cannot be null!");
+ mLaunchFlags = launchFlags;
mInitialTouchMode = initialTouchMode;
mLaunchActivity = launchActivity;
- mInstrumentation = InstrumentationRegistry.getInstrumentation();
}
/**
- * Override this method to set up Intent as if supplied to
- * {@link android.content.Context#startActivity}.
+ * Override this method to set up a custom Intent as if supplied to
+ * {@link android.content.Context#startActivity}. Custom Intents provided by this method will
+ * take precedence over default Intents that where created in the constructor but be overridden
+ * by any Intents passed in through {@link #launchActivity(Intent)}.
* <p>
* The default Intent (if this method returns null or is not overwritten) is:
* action = {@link Intent#ACTION_MAIN}
* flags = {@link Intent#FLAG_ACTIVITY_NEW_TASK}
* All other intent fields are null or empty.
- *
+ * <p>
+ * If the custom Intent provided by this methods overrides any of the following fields:
+ * <ul>
+ * <li>componentName through {@link Intent#setClassName(String, String)}</li>
+ * <li>launch flags through {@link Intent#setFlags(int)}</li>
+ * </ul>
+ * <p>
+ * These custom values will be used to start the Activity. However, if some of these values are
+ * not set, the default values documented in
+ * {@link #ActivityTestRule(Class, String, int, boolean, boolean)} are supplemented.
* @return The Intent as if supplied to {@link android.content.Context#startActivity}.
*/
protected Intent getActivityIntent() {
- return new Intent(Intent.ACTION_MAIN);
+ return null;
}
/**
@@ -168,9 +242,8 @@
return mActivity;
}
- @Override
public Statement apply(final Statement base, Description description) {
- return new ActivityStatement(super.apply(base, description));
+ return new ActivityStatement(base);
}
/**
@@ -178,7 +251,7 @@
* <p>
* Don't call this method directly, unless you explicitly requested not to lazily launch the
* Activity manually using the launchActivity flag in
- * {@link ActivityTestRule#ActivityTestRule(Class, boolean, boolean)}.
+ * {@link #ActivityTestRule(Class, boolean, boolean)}.
* <p>
* Usage:
* <pre>
@@ -188,17 +261,19 @@
* mActivity = mActivityRule.launchActivity(intent);
* }
* </pre>
+ * Note: Custom start Intents provided through this method will take precedence over default
+ * Intents that where created in the constructor and any Intent returned from
+ * {@link #getActivityIntent()}. The same override rules documented in
+ * {@link #getActivityIntent()} apply.
* @param startIntent The Intent that will be used to start the Activity under test. If
* {@code startIntent} is null, the Intent returned by
* {@link ActivityTestRule#getActivityIntent()} is used.
* @return the Activity launched by this rule.
- * @see ActivityTestRule#getActivityIntent()
*/
public T launchActivity(@Nullable Intent startIntent) {
// set initial touch mode
mInstrumentation.setInTouchMode(mInitialTouchMode);
- final String targetPackage = mInstrumentation.getTargetContext().getPackageName();
// inject custom intent, if provided
if (null == startIntent) {
startIntent = getActivityIntent();
@@ -208,10 +283,18 @@
startIntent = new Intent(Intent.ACTION_MAIN);
}
}
- startIntent.setClassName(targetPackage, mActivityClass.getName());
- startIntent.addFlags(Intent.FLAG_ACTIVITY_NEW_TASK);
- Log.d(TAG, String.format("Launching activity %s",
- mActivityClass.getName()));
+
+ // Set target component if not set Intent
+ if (null == startIntent.getComponent()) {
+ startIntent.setClassName(mTargetPackage, mActivityClass.getName());
+ }
+
+ // Set launch flags where if not set Intent
+ if (NO_FLAGS_SET == startIntent.getFlags()) {
+ startIntent.addFlags(mLaunchFlags);
+ }
+
+ Log.i(TAG, String.format("Launching activity: %s", startIntent.getComponent()));
beforeActivityLaunched();
// The following cast is correct because the activity we're creating is of the same type as
@@ -226,7 +309,7 @@
} else {
// Log an error message to logcat/instrumentation, that the Activity failed to launch
String errorMessage = String.format("Activity %s, failed to launch",
- mActivityClass.getName());
+ startIntent.getComponent());
Bundle bundle = new Bundle();
bundle.putString(Instrumentation.REPORT_KEY_STREAMRESULT, TAG + " " + errorMessage);
mInstrumentation.sendStatus(0, bundle);
@@ -263,14 +346,40 @@
@Override
public void evaluate() throws Throwable {
+ MonitoringInstrumentation instrumentation
+ = ActivityTestRule.this.mInstrumentation instanceof MonitoringInstrumentation
+ ? (MonitoringInstrumentation) ActivityTestRule.this.mInstrumentation
+ : null;
try {
+ if (mActivityFactory != null && instrumentation != null) {
+ instrumentation.interceptActivityUsing(mActivityFactory);
+ }
if (mLaunchActivity) {
mActivity = launchActivity(getActivityIntent());
}
mBase.evaluate();
} finally {
+ if (instrumentation != null) {
+ instrumentation.useDefaultInterceptingActivityFactory();
+ }
finishActivity();
}
}
}
-}
+
+ /**
+ * Helper method for running part of a method on the UI thread.
+ * <p/>
+ * Note: In most cases it is simpler to annotate the test method with
+ * {@link UiThreadTest}.
+ * <p/>
+ * Use this method if you need to switch in and out of the UI thread within your method.
+ *
+ * @param runnable runnable containing test code in the {@link Runnable#run()} method
+ *
+ * @see android.support.test.annotation.UiThreadTest
+ */
+ public void runOnUiThread(final Runnable runnable) throws Throwable {
+ UiThreadStatement.runOnUiThread(runnable);
+ }
+}
\ No newline at end of file
diff -ru ../0.5/android/support/test/rule/UiThreadTestRule.java android/support/test/rule/UiThreadTestRule.java
--- ../0.5/android/support/test/rule/UiThreadTestRule.java 2016-02-22 20:52:48.000000000 +0900
+++ android/support/test/rule/UiThreadTestRule.java 2016-08-02 22:18:50.000000000 +0900
@@ -16,21 +16,15 @@
package android.support.test.rule;
-import android.os.Looper;
import android.support.test.annotation.UiThreadTest;
-import android.support.test.annotation.Beta;
-import android.support.test.internal.statement.UiThreadStatement;
+import android.support.test.internal.runner.junit4.statement.UiThreadStatement;
import android.util.Log;
+import org.junit.internal.runners.statements.FailOnTimeout;
import org.junit.rules.TestRule;
import org.junit.runner.Description;
import org.junit.runners.model.Statement;
-import java.util.concurrent.ExecutionException;
-import java.util.concurrent.FutureTask;
-
-import static android.support.test.InstrumentationRegistry.getInstrumentation;
-
/**
* This rule allows the test method annotated with {@link UiThreadTest} to execute on the
* application's main thread (or UI thread).
@@ -40,49 +34,53 @@
* <a href="http://junit.sourceforge.net/javadoc/org/junit/After.html"><code>After</code></a> will
* also be executed on the UI thread.
*
- * @see android.support.test.annotation.UiThreadTest
+ * @see android.support.test.annotation.UiThreadTest if you need to switch in and out of the
+ * UI thread within your method.
+ *
+ * @deprecated use {@link UiThreadTest} directly without this rule. {@link UiThreadTest} is now
+ * supported as part of the core Android test runner to provide the ability to run methods
+ * annotated with <code>@Before</code> and <code>@After</code> on the UI thread regardless of what
+ * <code>@Test</code> is annotated with.
*/
public class UiThreadTestRule implements TestRule {
- private static final String LOG_TAG = "UiThreadTestRule";
+ private static final String TAG = "UiThreadTestRule";
@Override
public Statement apply(final Statement base, Description description) {
+ if (base instanceof FailOnTimeout ||
+ (base instanceof UiThreadStatement &&
+ !((UiThreadStatement) base).isRunOnUiThread())) {
+ // In upstream junit code Rules Statements are handled last. Since we now handle
+ // @UiThreadTest as part of the core Android runner, there is a chance that
+ // UiThreadStatement was already applied on the current statement.
+ // This is mainly for compatibility reasons to deprecated this rule.
+ return base;
+ }
return new UiThreadStatement(base, shouldRunOnUiThread(description));
}
protected boolean shouldRunOnUiThread(Description description) {
+ if (description.getAnnotation(android.test.UiThreadTest.class) != null) {
+ Log.w(TAG, "Deprecated android.test.UiThreadTest annotation is used! please switch " +
+ "to using android.support.test.annotation.UiThreadTest instead.");
+ return true;
+ }
return description.getAnnotation(UiThreadTest.class) != null;
}
/**
- * Helper for running portions of a test on the UI thread.
+ * Helper method for running part of a method on the UI thread.
* <p/>
- * Note, in most cases it is simpler to annotate the test method with
- * {@link UiThreadTest}, which will run the entire test method including methods annotated with
- * <a href="http://junit.sourceforge.net/javadoc/org/junit/Before.html"><code>Before</code></a>
- * and <a href="http://junit.sourceforge.net/javadoc/org/junit/After.html">
- * <code>After</code></a> on the UI thread.
+ * Note: In most cases it is simpler to annotate the test method with
+ * {@link UiThreadTest}.
* <p/>
- * Use this method if you need to switch in and out of the UI thread to perform your test.
+ * Use this method if you need to switch in and out of the UI thread within your method.
*
* @param runnable runnable containing test code in the {@link Runnable#run()} method
*
* @see android.support.test.annotation.UiThreadTest
*/
public void runOnUiThread(final Runnable runnable) throws Throwable {
- if (Looper.myLooper() == Looper.getMainLooper()) {
- Log.w(LOG_TAG, "Already on the UI thread, this method should not be called from the " +
- "main application thread");
- runnable.run();
- } else {
- FutureTask<Void> task = new FutureTask<>(runnable, null);
- getInstrumentation().runOnMainSync(task);
- try {
- task.get();
- } catch (ExecutionException e) {
- // Expose the original exception
- throw e.getCause();
- }
- }
+ UiThreadStatement.runOnUiThread(runnable);
}
}
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment