Merge "Add ability to run tests restricted to given annotation."
diff --git a/test-runner/src/android/test/InstrumentationTestRunner.java b/test-runner/src/android/test/InstrumentationTestRunner.java
index 4ae98e6..e586010 100644
--- a/test-runner/src/android/test/InstrumentationTestRunner.java
+++ b/test-runner/src/android/test/InstrumentationTestRunner.java
@@ -19,6 +19,7 @@
import static android.test.suitebuilder.TestPredicates.REJECT_PERFORMANCE;
import com.android.internal.util.Predicate;
+import com.android.internal.util.Predicates;
import android.app.Activity;
import android.app.Instrumentation;
@@ -31,11 +32,13 @@
import android.test.suitebuilder.TestMethod;
import android.test.suitebuilder.TestPredicates;
import android.test.suitebuilder.TestSuiteBuilder;
+import android.test.suitebuilder.annotation.HasAnnotation;
import android.util.Log;
import java.io.ByteArrayOutputStream;
import java.io.File;
import java.io.PrintStream;
+import java.lang.annotation.Annotation;
import java.lang.reflect.InvocationTargetException;
import java.lang.reflect.Method;
import java.util.ArrayList;
@@ -93,6 +96,18 @@
* -e size large
* com.android.foo/android.test.InstrumentationTestRunner
* <p/>
+ * <b>Filter test run to tests with given annotation:</b> adb shell am instrument -w
+ * -e annotation com.android.foo.MyAnnotation
+ * com.android.foo/android.test.InstrumentationTestRunner
+ * <p/>
+ * If used with other options, the resulting test run will contain the union of the two options.
+ * e.g. "-e size large -e annotation com.android.foo.MyAnnotation" will run only tests with both
+ * the {@link LargeTest} and "com.android.foo.MyAnnotation" annotations.
+ * <p/>
+ * <b>Filter test run to tests <i>without</i> given annotation:</b> adb shell am instrument -w
+ * -e notAnnotation com.android.foo.MyAnnotation
+ * com.android.foo/android.test.InstrumentationTestRunner
+ * <p/>
* <b>Running a single testcase:</b> adb shell am instrument -w
* -e class com.android.foo.FooTest
* com.android.foo/android.test.InstrumentationTestRunner
@@ -161,6 +176,10 @@
private static final String LARGE_SUITE = "large";
private static final String ARGUMENT_LOG_ONLY = "log";
+ /** @hide */
+ static final String ARGUMENT_ANNOTATION = "annotation";
+ /** @hide */
+ static final String ARGUMENT_NOT_ANNOTATION = "notAnnotation";
/**
* This constant defines the maximum allowed runtime (in ms) for a test included in the "small"
@@ -274,6 +293,8 @@
ClassPathPackageInfoSource.setApkPaths(apkPaths);
Predicate<TestMethod> testSizePredicate = null;
+ Predicate<TestMethod> testAnnotationPredicate = null;
+ Predicate<TestMethod> testNotAnnotationPredicate = null;
boolean includePerformance = false;
String testClassesArg = null;
boolean logOnly = false;
@@ -287,6 +308,11 @@
mPackageOfTests = arguments.getString(ARGUMENT_TEST_PACKAGE);
testSizePredicate = getSizePredicateFromArg(
arguments.getString(ARGUMENT_TEST_SIZE_PREDICATE));
+ testAnnotationPredicate = getAnnotationPredicate(
+ arguments.getString(ARGUMENT_ANNOTATION));
+ testNotAnnotationPredicate = getNotAnnotationPredicate(
+ arguments.getString(ARGUMENT_NOT_ANNOTATION));
+
includePerformance = getBooleanArgument(arguments, ARGUMENT_INCLUDE_PERF);
logOnly = getBooleanArgument(arguments, ARGUMENT_LOG_ONLY);
mCoverage = getBooleanArgument(arguments, "coverage");
@@ -306,6 +332,12 @@
if (testSizePredicate != null) {
testSuiteBuilder.addRequirements(testSizePredicate);
}
+ if (testAnnotationPredicate != null) {
+ testSuiteBuilder.addRequirements(testAnnotationPredicate);
+ }
+ if (testNotAnnotationPredicate != null) {
+ testSuiteBuilder.addRequirements(testNotAnnotationPredicate);
+ }
if (!includePerformance) {
testSuiteBuilder.addRequirements(REJECT_PERFORMANCE);
}
@@ -406,6 +438,59 @@
}
}
+ /**
+ * Returns the test predicate object, corresponding to the annotation class value provided via
+ * the {@link ARGUMENT_ANNOTATION} argument.
+ *
+ * @return the predicate or <code>null</code>
+ */
+ private Predicate<TestMethod> getAnnotationPredicate(String annotationClassName) {
+ Class<? extends Annotation> annotationClass = getAnnotationClass(annotationClassName);
+ if (annotationClass != null) {
+ return new HasAnnotation(annotationClass);
+ }
+ return null;
+ }
+
+ /**
+ * Returns the negative test predicate object, corresponding to the annotation class value
+ * provided via the {@link ARGUMENT_NOT_ANNOTATION} argument.
+ *
+ * @return the predicate or <code>null</code>
+ */
+ private Predicate<TestMethod> getNotAnnotationPredicate(String annotationClassName) {
+ Class<? extends Annotation> annotationClass = getAnnotationClass(annotationClassName);
+ if (annotationClass != null) {
+ return Predicates.not(new HasAnnotation(annotationClass));
+ }
+ return null;
+ }
+
+ /**
+ * Helper method to return the annotation class with specified name
+ *
+ * @param annotationClassName the fully qualified name of the class
+ * @return the annotation class or <code>null</code>
+ */
+ private Class<? extends Annotation> getAnnotationClass(String annotationClassName) {
+ if (annotationClassName == null) {
+ return null;
+ }
+ try {
+ Class<?> annotationClass = Class.forName(annotationClassName);
+ if (annotationClass.isAnnotation()) {
+ return (Class<? extends Annotation>)annotationClass;
+ } else {
+ Log.e(LOG_TAG, String.format("Provided annotation value %s is not an Annotation",
+ annotationClassName));
+ }
+ } catch (ClassNotFoundException e) {
+ Log.e(LOG_TAG, String.format("Could not find class for specified annotation %s",
+ annotationClassName));
+ }
+ return null;
+ }
+
@Override
public void onStart() {
Looper.prepare();
@@ -471,7 +556,7 @@
String coverageFilePath = getCoverageFilePath();
java.io.File coverageFile = new java.io.File(coverageFilePath);
try {
- Class emmaRTClass = Class.forName("com.vladium.emma.rt.RT");
+ Class<?> emmaRTClass = Class.forName("com.vladium.emma.rt.RT");
Method dumpCoverageMethod = emmaRTClass.getMethod("dumpCoverageData",
coverageFile.getClass(), boolean.class, boolean.class);
diff --git a/test-runner/tests/src/android/test/InstrumentationTestRunnerTest.java b/test-runner/tests/src/android/test/InstrumentationTestRunnerTest.java
index d9afd54..6db72ad 100644
--- a/test-runner/tests/src/android/test/InstrumentationTestRunnerTest.java
+++ b/test-runner/tests/src/android/test/InstrumentationTestRunnerTest.java
@@ -109,6 +109,33 @@
assertTrue(mStubAndroidTestRunner.isRun());
}
+ /**
+ * Test that the -e {@link InstrumentationTestRunner.ARGUMENT_ANNOTATION} parameter properly
+ * selects tests.
+ */
+ public void testAnnotationParameter() throws Exception {
+ String expectedTestClassName = AnnotationTest.class.getName();
+ Bundle args = new Bundle();
+ args.putString(InstrumentationTestRunner.ARGUMENT_TEST_CLASS, expectedTestClassName);
+ args.putString(InstrumentationTestRunner.ARGUMENT_ANNOTATION, FlakyTest.class.getName());
+ mInstrumentationTestRunner.onCreate(args);
+ assertTestRunnerCalledWithExpectedParameters(expectedTestClassName, "testAnnotated");
+ }
+
+ /**
+ * Test that the -e {@link InstrumentationTestRunner.ARGUMENT_NOT_ANNOTATION} parameter
+ * properly excludes tests.
+ */
+ public void testNotAnnotationParameter() throws Exception {
+ String expectedTestClassName = AnnotationTest.class.getName();
+ Bundle args = new Bundle();
+ args.putString(InstrumentationTestRunner.ARGUMENT_TEST_CLASS, expectedTestClassName);
+ args.putString(InstrumentationTestRunner.ARGUMENT_NOT_ANNOTATION,
+ FlakyTest.class.getName());
+ mInstrumentationTestRunner.onCreate(args);
+ assertTestRunnerCalledWithExpectedParameters(expectedTestClassName, "testNotAnnotated");
+ }
+
private void assertContentsInOrder(List<TestDescriptor> actual, TestDescriptor... source) {
TestDescriptor[] clonedSource = source.clone();
assertEquals("Unexpected number of items.", clonedSource.length, actual.size());
@@ -269,4 +296,17 @@
}
}
+
+ /**
+ * Annotated test used for validation.
+ */
+ public static class AnnotationTest extends TestCase {
+
+ public void testNotAnnotated() throws Exception {
+ }
+
+ @FlakyTest
+ public void testAnnotated() throws Exception {
+ }
+ }
}