summaryrefslogtreecommitdiff
diff options
context:
space:
mode:
authorBrett Chabot <brettchabot@google.com>2014-02-04 21:24:19 -0600
committerBrett Chabot <brettchabot@google.com>2014-02-24 10:44:28 -0800
commitac6aefffc4296202d709cbe8bd160a15ffeccbaf (patch)
tree7a956713d6ca8b3f762195686004399edb4927f0
parenta1898a7e8a25d9044ac39179f6b3e72dc1e778de (diff)
downloadtesting-ac6aefffc4296202d709cbe8bd160a15ffeccbaf.tar.gz
Add SdkSuppress and RequiresDevice filtering annotations.
Bug: 12873809 Change-Id: If7211c19f7c8b8f896a3d0a151402cc4ecef038a
-rw-r--r--support/src/android/support/test/filters/RequiresDevice.java31
-rw-r--r--support/src/android/support/test/filters/SdkSuppress.java32
-rw-r--r--support/src/android/support/test/internal/runner/TestRequestBuilder.java151
-rw-r--r--support/tests/src/android/support/test/internal/runner/TestRequestBuilderTest.java92
4 files changed, 273 insertions, 33 deletions
diff --git a/support/src/android/support/test/filters/RequiresDevice.java b/support/src/android/support/test/filters/RequiresDevice.java
new file mode 100644
index 0000000..fce8f27
--- /dev/null
+++ b/support/src/android/support/test/filters/RequiresDevice.java
@@ -0,0 +1,31 @@
+/*
+ * Copyright (C) 2014 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+package android.support.test.filters;
+
+import java.lang.annotation.ElementType;
+import java.lang.annotation.Retention;
+import java.lang.annotation.RetentionPolicy;
+import java.lang.annotation.Target;
+
+/**
+ * Indicates that a specific test should not be run on emulator.
+ * <p/>
+ * It will be executed only if the test is running on the physical android device.
+ */
+@Retention(RetentionPolicy.RUNTIME)
+@Target({ElementType.TYPE, ElementType.METHOD})
+public @interface RequiresDevice {
+}
diff --git a/support/src/android/support/test/filters/SdkSuppress.java b/support/src/android/support/test/filters/SdkSuppress.java
new file mode 100644
index 0000000..573cc4b
--- /dev/null
+++ b/support/src/android/support/test/filters/SdkSuppress.java
@@ -0,0 +1,32 @@
+/*
+ * Copyright (C) 2014 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+package android.support.test.filters;
+
+import java.lang.annotation.ElementType;
+import java.lang.annotation.Retention;
+import java.lang.annotation.RetentionPolicy;
+import java.lang.annotation.Target;
+
+/**
+ * Indicates that a specific test or class requires a minimum API Level to execute.
+ * <p/>
+ * Test(s) will be skipped when executed on android platforms less than specified level.
+ */
+@Retention(RetentionPolicy.RUNTIME)
+@Target({ElementType.TYPE, ElementType.METHOD})
+public @interface SdkSuppress {
+ int minSdkVersion();
+}
diff --git a/support/src/android/support/test/internal/runner/TestRequestBuilder.java b/support/src/android/support/test/internal/runner/TestRequestBuilder.java
index 0d7cd8c..a4d9c01 100644
--- a/support/src/android/support/test/internal/runner/TestRequestBuilder.java
+++ b/support/src/android/support/test/internal/runner/TestRequestBuilder.java
@@ -17,6 +17,8 @@ package android.support.test.internal.runner;
import android.app.Instrumentation;
import android.os.Bundle;
+import android.support.test.filters.RequiresDevice;
+import android.support.test.filters.SdkSuppress;
import android.support.test.internal.runner.ClassPathScanner.ChainedClassNameFilter;
import android.support.test.internal.runner.ClassPathScanner.ExcludePackageNameFilter;
import android.support.test.internal.runner.ClassPathScanner.ExternalClassNameFilter;
@@ -58,24 +60,51 @@ public class TestRequestBuilder {
public static final String MEDIUM_SIZE = "medium";
public static final String SMALL_SIZE = "small";
+ static final String EMULATOR_HARDWARE = "goldfish";
+
private String[] mApkPaths;
private TestLoader mTestLoader;
- private Filter mFilter = new AnnotationExclusionFilter(Suppress.class);
+ private Filter mFilter = new AnnotationExclusionFilter(Suppress.class).intersect(
+ new SdkSuppressFilter()).intersect(new RequiresDeviceFilter());
+
private PrintStream mWriter;
private boolean mSkipExecution = false;
private String mTestPackageName = null;
+ private final DeviceBuild mDeviceBuild;
/**
- * Filter that only runs tests whose method or class has been annotated with given filter.
+ * Accessor interface for retrieving device build properties.
+ * <p/>
+ * Used so unit tests can mock calls
*/
- private static class AnnotationInclusionFilter extends Filter {
+ static interface DeviceBuild {
+ /**
+ * Returns the SDK API level for current device.
+ */
+ int getSdkVersionInt();
- private final Class<? extends Annotation> mAnnotationClass;
+ /**
+ * Returns the hardware type of the current device.
+ */
+ String getHardware();
+ }
- AnnotationInclusionFilter(Class<? extends Annotation> annotation) {
- mAnnotationClass = annotation;
+ private static class DeviceBuildImpl implements DeviceBuild {
+ @Override
+ public int getSdkVersionInt() {
+ return android.os.Build.VERSION.SDK_INT;
+ }
+
+ @Override
+ public String getHardware() {
+ return android.os.Build.HARDWARE;
}
+ }
+ /**
+ * Helper parent class for {@link Filter} that allows suites to run if any child matches.
+ */
+ private abstract static class ParentFilter extends Filter {
/**
* {@inheritDoc}
*/
@@ -100,6 +129,27 @@ public class TestRequestBuilder {
* @param description the {@link Description} describing the test
* @return <code>true</code> if matched
*/
+ protected abstract boolean evaluateTest(Description description);
+ }
+
+ /**
+ * Filter that only runs tests whose method or class has been annotated with given filter.
+ */
+ private static class AnnotationInclusionFilter extends ParentFilter {
+
+ private final Class<? extends Annotation> mAnnotationClass;
+
+ AnnotationInclusionFilter(Class<? extends Annotation> annotation) {
+ mAnnotationClass = annotation;
+ }
+
+ /**
+ * Determine if given test description matches filter.
+ *
+ * @param description the {@link Description} describing the test
+ * @return <code>true</code> if matched
+ */
+ @Override
protected boolean evaluateTest(Description description) {
return description.getAnnotation(mAnnotationClass) != null ||
description.getTestClass().isAnnotationPresent(mAnnotationClass);
@@ -157,7 +207,7 @@ public class TestRequestBuilder {
/**
* Filter out tests whose method or class has been annotated with given filter.
*/
- private static class AnnotationExclusionFilter extends Filter {
+ private static class AnnotationExclusionFilter extends ParentFilter {
private final Class<? extends Annotation> mAnnotationClass;
@@ -165,27 +215,46 @@ public class TestRequestBuilder {
mAnnotationClass = annotation;
}
- /**
- * {@inheritDoc}
- */
@Override
- public boolean shouldRun(Description description) {
+ protected boolean evaluateTest(Description description) {
final Class<?> testClass = description.getTestClass();
if ((testClass != null && testClass.isAnnotationPresent(mAnnotationClass))
|| (description.getAnnotation(mAnnotationClass) != null)) {
return false;
}
- if (description.isTest() ) {
- return true;
+ return true;
+ }
+
+ /**
+ * {@inheritDoc}
+ */
+ @Override
+ public String describe() {
+ return String.format("not annotation %s", mAnnotationClass.getName());
+ }
+ }
+
+ private class SdkSuppressFilter extends ParentFilter {
+
+ @Override
+ protected boolean evaluateTest(Description description) {
+ final SdkSuppress s = getAnnotationForTest(description);
+ if (s != null && getDeviceSdkInt() < s.minSdkVersion()) {
+ return false;
}
- // this is a suite, explicitly check if any children want to run
- for (Description each : description.getChildren()) {
- if (shouldRun(each)) {
- return true;
- }
+ return true;
+ }
+
+ private SdkSuppress getAnnotationForTest(Description description) {
+ final SdkSuppress s = description.getAnnotation(SdkSuppress.class);
+ if (s != null) {
+ return s;
}
- // no children to run, filter this out
- return false;
+ final Class<?> testClass = description.getTestClass();
+ if (testClass != null) {
+ return testClass.getAnnotation(SdkSuppress.class);
+ }
+ return null;
}
/**
@@ -193,7 +262,34 @@ public class TestRequestBuilder {
*/
@Override
public String describe() {
- return String.format("not annotation %s", mAnnotationClass.getName());
+ return String.format("skip tests annotated with SdkSuppress if necessary");
+ }
+ }
+
+ /**
+ * Class that filters out tests annotated with {@link RequestDevice} when running on emulator
+ */
+ private class RequiresDeviceFilter extends AnnotationExclusionFilter {
+
+ RequiresDeviceFilter() {
+ super(RequiresDevice.class);
+ }
+
+ @Override
+ protected boolean evaluateTest(Description description) {
+ if (!super.evaluateTest(description)) {
+ // annotation is present - check if device is an emulator
+ return !EMULATOR_HARDWARE.equals(getDeviceHardware());
+ }
+ return true;
+ }
+
+ /**
+ * {@inheritDoc}
+ */
+ @Override
+ public String describe() {
+ return String.format("skip tests annotated with RequiresDevice if necessary");
}
}
@@ -272,6 +368,11 @@ public class TestRequestBuilder {
}
public TestRequestBuilder(PrintStream writer, String... apkPaths) {
+ this(new DeviceBuildImpl(), writer, apkPaths);
+ }
+
+ TestRequestBuilder(DeviceBuild deviceBuildAccessor, PrintStream writer, String... apkPaths) {
+ mDeviceBuild = deviceBuildAccessor;
mApkPaths = apkPaths;
mTestLoader = new TestLoader(writer);
}
@@ -490,4 +591,12 @@ public class TestRequestBuilder {
}
return null;
}
+
+ private int getDeviceSdkInt() {
+ return mDeviceBuild.getSdkVersionInt();
+ }
+
+ private String getDeviceHardware() {
+ return mDeviceBuild.getHardware();
+ }
}
diff --git a/support/tests/src/android/support/test/internal/runner/TestRequestBuilderTest.java b/support/tests/src/android/support/test/internal/runner/TestRequestBuilderTest.java
index 9da0ba3..6c80146 100644
--- a/support/tests/src/android/support/test/internal/runner/TestRequestBuilderTest.java
+++ b/support/tests/src/android/support/test/internal/runner/TestRequestBuilderTest.java
@@ -19,6 +19,9 @@ import android.app.Instrumentation;
import android.os.Bundle;
import android.support.test.InjectBundle;
import android.support.test.InjectInstrumentation;
+import android.support.test.filters.RequiresDevice;
+import android.support.test.filters.SdkSuppress;
+import android.support.test.internal.runner.TestRequestBuilder.DeviceBuild;
import android.test.suitebuilder.annotation.MediumTest;
import android.test.suitebuilder.annotation.SmallTest;
import android.test.suitebuilder.annotation.Suppress;
@@ -31,6 +34,9 @@ import org.junit.runner.Description;
import org.junit.runner.JUnitCore;
import org.junit.runner.Result;
import org.junit.runner.notification.RunListener;
+import org.mockito.Mock;
+import org.mockito.Mockito;
+import org.mockito.MockitoAnnotations;
import java.io.ByteArrayOutputStream;
import java.io.PrintStream;
@@ -134,10 +140,14 @@ public class TestRequestBuilderTest {
}
}
- public static class SampleAllSuppressed extends TestCase {
+ public static class SampleSizeWithSuppress extends TestCase {
+
+ public void testNoSize() {
+ }
+ @SmallTest
@Suppress
- public void testSuppressed2() {
+ public void testSmallAndSuppressed() {
}
@Suppress
@@ -145,10 +155,10 @@ public class TestRequestBuilderTest {
}
}
- public static class SampleSizeAndSuppress extends TestCase {
+ public static class SampleAllSuppressed extends TestCase {
- @MediumTest
- public void testMedium() {
+ @Suppress
+ public void testSuppressed2() {
}
@Suppress
@@ -156,14 +166,10 @@ public class TestRequestBuilderTest {
}
}
- public static class SampleSizeWithSuppress extends TestCase {
-
- public void testNoSize() {
- }
+ public static class SampleSizeAndSuppress extends TestCase {
- @SmallTest
- @Suppress
- public void testSmallAndSuppressed() {
+ @MediumTest
+ public void testMedium() {
}
@Suppress
@@ -214,12 +220,41 @@ public class TestRequestBuilderTest {
}
}
+ public static class SampleRequiresDevice {
+ @RequiresDevice
+ @Test
+ public void skipThis() {}
+
+ @Test
+ public void runMe() {}
+
+ @Test
+ public void runMe2() {}
+ }
+
+ public static class SampleSdkSuppress {
+ @SdkSuppress(minSdkVersion=15)
+ @Test
+ public void skipThis() {}
+
+ @SdkSuppress(minSdkVersion=16)
+ @Test
+ public void runMe() {}
+
+ @SdkSuppress(minSdkVersion=17)
+ @Test
+ public void runMe2() {}
+ }
+
@InjectInstrumentation
public Instrumentation mInstr;
@InjectBundle
public Bundle mBundle;
+ @Mock
+ private DeviceBuild mMockDeviceBuild;
+
/**
* Test initial condition for size filtering - that all tests run when no filter is attached
*/
@@ -563,4 +598,37 @@ public class TestRequestBuilderTest {
Result result = testRunner.run(request.getRequest());
Assert.assertEquals(0, result.getRunCount());
}
+
+ /**
+ * Test that {@link SdkSuppress} filters tests as appropriate
+ */
+ @Test
+ public void testSdkSuppress() {
+ MockitoAnnotations.initMocks(this);
+ TestRequestBuilder b = new TestRequestBuilder(mMockDeviceBuild,
+ new PrintStream(new ByteArrayOutputStream()));
+ Mockito.when(mMockDeviceBuild.getSdkVersionInt()).thenReturn(16);
+ b.addTestClass(SampleSdkSuppress.class.getName());
+ TestRequest request = b.build(mInstr, mBundle);
+ JUnitCore testRunner = new JUnitCore();
+ Result result = testRunner.run(request.getRequest());
+ Assert.assertEquals(2, result.getRunCount());
+ }
+
+ /**
+ * Test that {@link RequiresDevuce} filters tests as appropriate
+ */
+ @Test
+ public void testRequiresDevice() {
+ MockitoAnnotations.initMocks(this);
+ TestRequestBuilder b = new TestRequestBuilder(mMockDeviceBuild,
+ new PrintStream(new ByteArrayOutputStream()));
+ Mockito.when(mMockDeviceBuild.getHardware()).thenReturn(
+ TestRequestBuilder.EMULATOR_HARDWARE);
+ b.addTestClass(SampleRequiresDevice.class.getName());
+ TestRequest request = b.build(mInstr, mBundle);
+ JUnitCore testRunner = new JUnitCore();
+ Result result = testRunner.run(request.getRequest());
+ Assert.assertEquals(2, result.getRunCount());
+ }
}