diff options
author | Brett Chabot <brettchabot@google.com> | 2014-02-04 21:24:19 -0600 |
---|---|---|
committer | Brett Chabot <brettchabot@google.com> | 2014-02-24 10:44:28 -0800 |
commit | ac6aefffc4296202d709cbe8bd160a15ffeccbaf (patch) | |
tree | 7a956713d6ca8b3f762195686004399edb4927f0 | |
parent | a1898a7e8a25d9044ac39179f6b3e72dc1e778de (diff) | |
download | testing-ac6aefffc4296202d709cbe8bd160a15ffeccbaf.tar.gz |
Add SdkSuppress and RequiresDevice filtering annotations.
Bug: 12873809
Change-Id: If7211c19f7c8b8f896a3d0a151402cc4ecef038a
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()); + } } |