[Android] Support conditional test disabling based on android.os.Build values.

BUG=565324
TBR=thakis@chromium.org

Review URL: https://codereview.chromium.org/1519523002

Cr-Commit-Position: refs/heads/master@{#366465}


CrOS-Libchrome-Original-Commit: ae13c65b3c776429bcc48968855112607db595a0
diff --git a/base/base.gyp b/base/base.gyp
index 7408d05..dc484f4 100644
--- a/base/base.gyp
+++ b/base/base.gyp
@@ -1588,7 +1588,7 @@
           ],
           'variables': {
             'src_paths': [
-              '../base/test/android/junit/',
+              '../base/test/android/junit/src/org/chromium/base/test/shadows/ShadowMultiDex.java',
             ],
           },
           'includes': [ '../build/host_jar.gypi' ]
@@ -1607,6 +1607,7 @@
              'main_class': 'org.chromium.testing.local.JunitTestMain',
              'src_paths': [
                '../base/android/junit/',
+               '../base/test/android/junit/src/org/chromium/base/test/util/DisableIfTest.java',
              ],
            },
           'includes': [ '../build/host_jar.gypi' ],
diff --git a/base/test/android/javatests/src/org/chromium/base/test/BaseInstrumentationTestRunner.java b/base/test/android/javatests/src/org/chromium/base/test/BaseInstrumentationTestRunner.java
index 58e5b1c..8bf3d0f 100644
--- a/base/test/android/javatests/src/org/chromium/base/test/BaseInstrumentationTestRunner.java
+++ b/base/test/android/javatests/src/org/chromium/base/test/BaseInstrumentationTestRunner.java
@@ -19,10 +19,11 @@
 import org.chromium.base.Log;
 import org.chromium.base.SysUtils;
 import org.chromium.base.multidex.ChromiumMultiDex;
-import org.chromium.base.test.BaseTestResult.SkipCheck;
 import org.chromium.base.test.util.CommandLineFlags;
+import org.chromium.base.test.util.DisableIfSkipCheck;
 import org.chromium.base.test.util.MinAndroidSdkLevel;
 import org.chromium.base.test.util.Restriction;
+import org.chromium.base.test.util.SkipCheck;
 import org.chromium.test.reporter.TestStatusListener;
 
 import java.lang.reflect.Method;
@@ -64,24 +65,21 @@
     protected void addTestHooks(BaseTestResult result) {
         result.addSkipCheck(new MinAndroidSdkLevelSkipCheck());
         result.addSkipCheck(new RestrictionSkipCheck());
+        result.addSkipCheck(new DisableIfSkipCheck());
 
         result.addPreTestHook(CommandLineFlags.getRegistrationHook());
     }
 
+
     /**
      * Checks if any restrictions exist and skip the test if it meets those restrictions.
      */
-    public class RestrictionSkipCheck implements SkipCheck {
+    public class RestrictionSkipCheck extends SkipCheck {
         @Override
         public boolean shouldSkip(TestCase testCase) {
-            Method method;
-            try {
-                method = testCase.getClass().getMethod(testCase.getName(), (Class[]) null);
-            } catch (NoSuchMethodException e) {
-                Log.e(TAG, "Unable to find %s in %s", testCase.getName(),
-                        testCase.getClass().getName(), e);
-                return true;
-            }
+            Method method = getTestMethod(testCase);
+            if (method == null) return true;
+
             Restriction restrictions = method.getAnnotation(Restriction.class);
             if (restrictions != null) {
                 for (String restriction : restrictions.value()) {
@@ -120,7 +118,7 @@
     /**
      * Checks the device's SDK level against any specified minimum requirement.
      */
-    public static class MinAndroidSdkLevelSkipCheck implements SkipCheck {
+    public static class MinAndroidSdkLevelSkipCheck extends SkipCheck {
 
         /**
          * If {@link MinAndroidSdkLevel} is present, checks its value
diff --git a/base/test/android/javatests/src/org/chromium/base/test/BaseTestResult.java b/base/test/android/javatests/src/org/chromium/base/test/BaseTestResult.java
index 7ac4bee..1de03ac 100644
--- a/base/test/android/javatests/src/org/chromium/base/test/BaseTestResult.java
+++ b/base/test/android/javatests/src/org/chromium/base/test/BaseTestResult.java
@@ -15,6 +15,7 @@
 
 import org.chromium.base.Log;
 import org.chromium.base.test.util.CommandLineFlags;
+import org.chromium.base.test.util.SkipCheck;
 import org.chromium.base.test.util.parameter.BaseParameter;
 import org.chromium.base.test.util.parameter.Parameter;
 import org.chromium.base.test.util.parameter.Parameterizable;
@@ -53,20 +54,6 @@
     }
 
     /**
-     * An interface for classes that check whether a test case should be skipped.
-     */
-    public interface SkipCheck {
-        /**
-         *
-         * Checks whether the given test case should be skipped.
-         *
-         * @param testCase The test case to check.
-         * @return Whether the test case should be skipped.
-         */
-        boolean shouldSkip(TestCase testCase);
-    }
-
-    /**
      * An interface for classes that have some code to run before a test. They run after
      * {@link SkipCheck}s. Provides access to the test method (and the annotations defined for it)
      * and the instrumentation context.
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 0000000..ee89726
--- /dev/null
+++ b/base/test/android/javatests/src/org/chromium/base/test/util/DisableIf.java
@@ -0,0 +1,38 @@
+// 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 "";
+    }
+
+    /* 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 0000000..7da6de8
--- /dev/null
+++ b/base/test/android/javatests/src/org/chromium/base/test/util/DisableIfSkipCheck.java
@@ -0,0 +1,68 @@
+// 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 junit.framework.TestCase;
+
+import org.chromium.base.Log;
+
+import java.lang.reflect.Method;
+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(TestCase testCase) {
+        Method method = getTestMethod(testCase);
+        if (method == null) return true;
+
+        if (method.isAnnotationPresent(DisableIf.Build.class)) {
+            DisableIf.Build v = method.getAnnotation(DisableIf.Build.class);
+
+            if (abi(v) && hardware(v) && sdk(v)) {
+                if (!v.message().isEmpty()) {
+                    Log.i(TAG, "%s is disabled: %s", testCase.toString(), v.message());
+                }
+                return true;
+            } else {
+                return false;
+            }
+        }
+
+        return false;
+    }
+
+    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 sdk(DisableIf.Build v) {
+        return Build.VERSION.SDK_INT > v.sdk_is_greater_than()
+                && Build.VERSION.SDK_INT < v.sdk_is_less_than();
+    }
+
+}
+
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 0000000..cb21dfd
--- /dev/null
+++ b/base/test/android/javatests/src/org/chromium/base/test/util/SkipCheck.java
@@ -0,0 +1,39 @@
+// 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.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 case should be skipped.
+     *
+     * @param testCase The test case to check.
+     * @return Whether the test case should be skipped.
+     */
+    public abstract boolean shouldSkip(TestCase testCase);
+
+    protected static Method getTestMethod(TestCase testCase) {
+        try {
+            return testCase.getClass().getMethod(testCase.getName(), (Class[]) null);
+        } catch (NoSuchMethodException e) {
+            Log.e(TAG, "Unable to find %s in %s", testCase.getName(),
+                    testCase.getClass().getName(), e);
+            return null;
+        }
+    }
+}
+
diff --git a/base/test/android/junit/src/org/chromium/base/test/util/DisableIfTest.java b/base/test/android/junit/src/org/chromium/base/test/util/DisableIfTest.java
new file mode 100644
index 0000000..c4920a8
--- /dev/null
+++ b/base/test/android/junit/src/org/chromium/base/test/util/DisableIfTest.java
@@ -0,0 +1,160 @@
+// 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 junit.framework.TestCase;
+
+import org.chromium.testing.local.LocalRobolectricTestRunner;
+import org.junit.Assert;
+import org.junit.Test;
+import org.junit.runner.RunWith;
+import org.robolectric.Robolectric;
+import org.robolectric.annotation.Config;
+
+/** Unit tests for the DisableIf annotation and its SkipCheck implementation. */
+@RunWith(LocalRobolectricTestRunner.class)
+@Config(manifest = Config.NONE, reportSdk = 19)
+public class DisableIfTest {
+
+    @Test
+    public void testSdkIsLessThanAndIsLessThan() {
+        TestCase sdkIsLessThan = new TestCase("sdkIsLessThan") {
+            @DisableIf.Build(sdk_is_less_than = 21)
+            public void sdkIsLessThan() {}
+        };
+        Assert.assertTrue(new DisableIfSkipCheck().shouldSkip(sdkIsLessThan));
+    }
+
+    @Test
+    public void testSdkIsLessThanButIsEqual() {
+        TestCase sdkIsEqual = new TestCase("sdkIsEqual") {
+            @DisableIf.Build(sdk_is_less_than = 19)
+            public void sdkIsEqual() {}
+        };
+        Assert.assertFalse(new DisableIfSkipCheck().shouldSkip(sdkIsEqual));
+    }
+
+    @Test
+    public void testSdkIsLessThanButIsGreaterThan() {
+        TestCase sdkIsGreaterThan = new TestCase("sdkIsGreaterThan") {
+            @DisableIf.Build(sdk_is_less_than = 16)
+            public void sdkIsGreaterThan() {}
+        };
+        Assert.assertFalse(new DisableIfSkipCheck().shouldSkip(sdkIsGreaterThan));
+    }
+
+    @Test
+    public void testSdkIsGreaterThanButIsLessThan() {
+        TestCase sdkIsLessThan = new TestCase("sdkIsLessThan") {
+            @DisableIf.Build(sdk_is_greater_than = 21)
+            public void sdkIsLessThan() {}
+        };
+        Assert.assertFalse(new DisableIfSkipCheck().shouldSkip(sdkIsLessThan));
+    }
+
+    @Test
+    public void testSdkIsGreaterThanButIsEqual() {
+        TestCase sdkIsEqual = new TestCase("sdkIsEqual") {
+            @DisableIf.Build(sdk_is_greater_than = 19)
+            public void sdkIsEqual() {}
+        };
+        Assert.assertFalse(new DisableIfSkipCheck().shouldSkip(sdkIsEqual));
+    }
+
+    @Test
+    public void testSdkIsGreaterThanAndIsGreaterThan() {
+        TestCase sdkIsGreaterThan = new TestCase("sdkIsGreaterThan") {
+            @DisableIf.Build(sdk_is_greater_than = 16)
+            public void sdkIsGreaterThan() {}
+        };
+        Assert.assertTrue(new DisableIfSkipCheck().shouldSkip(sdkIsGreaterThan));
+    }
+
+    @Test
+    public void testSupportedAbiIncludesAndCpuAbiMatches() {
+        TestCase supportedAbisCpuAbiMatch = new TestCase("supportedAbisCpuAbiMatch") {
+            @DisableIf.Build(supported_abis_includes = "foo")
+            public void supportedAbisCpuAbiMatch() {}
+        };
+        String originalAbi = Build.CPU_ABI;
+        String originalAbi2 = Build.CPU_ABI2;
+        try {
+            Robolectric.Reflection.setFinalStaticField(Build.class, "CPU_ABI", "foo");
+            Robolectric.Reflection.setFinalStaticField(Build.class, "CPU_ABI2", "bar");
+            Assert.assertTrue(new DisableIfSkipCheck().shouldSkip(supportedAbisCpuAbiMatch));
+        } finally {
+            Robolectric.Reflection.setFinalStaticField(Build.class, "CPU_ABI", originalAbi);
+            Robolectric.Reflection.setFinalStaticField(Build.class, "CPU_ABI2", originalAbi2);
+        }
+    }
+
+    @Test
+    public void testSupportedAbiIncludesAndCpuAbi2Matches() {
+        TestCase supportedAbisCpuAbi2Match = new TestCase("supportedAbisCpuAbi2Match") {
+            @DisableIf.Build(supported_abis_includes = "bar")
+            public void supportedAbisCpuAbi2Match() {}
+        };
+        String originalAbi = Build.CPU_ABI;
+        String originalAbi2 = Build.CPU_ABI2;
+        try {
+            Robolectric.Reflection.setFinalStaticField(Build.class, "CPU_ABI", "foo");
+            Robolectric.Reflection.setFinalStaticField(Build.class, "CPU_ABI2", "bar");
+            Assert.assertTrue(new DisableIfSkipCheck().shouldSkip(supportedAbisCpuAbi2Match));
+        } finally {
+            Robolectric.Reflection.setFinalStaticField(Build.class, "CPU_ABI", originalAbi);
+            Robolectric.Reflection.setFinalStaticField(Build.class, "CPU_ABI2", originalAbi2);
+        }
+    }
+
+    @Test
+    public void testSupportedAbiIncludesButNoMatch() {
+        TestCase supportedAbisNoMatch = new TestCase("supportedAbisNoMatch") {
+            @DisableIf.Build(supported_abis_includes = "baz")
+            public void supportedAbisNoMatch() {}
+        };
+        String originalAbi = Build.CPU_ABI;
+        String originalAbi2 = Build.CPU_ABI2;
+        try {
+            Robolectric.Reflection.setFinalStaticField(Build.class, "CPU_ABI", "foo");
+            Robolectric.Reflection.setFinalStaticField(Build.class, "CPU_ABI2", "bar");
+            Assert.assertFalse(new DisableIfSkipCheck().shouldSkip(supportedAbisNoMatch));
+        } finally {
+            Robolectric.Reflection.setFinalStaticField(Build.class, "CPU_ABI", originalAbi);
+            Robolectric.Reflection.setFinalStaticField(Build.class, "CPU_ABI2", originalAbi2);
+        }
+    }
+
+    @Test
+    public void testHardwareIsMatches() {
+        TestCase hardwareIsMatches = new TestCase("hardwareIsMatches") {
+            @DisableIf.Build(hardware_is = "hammerhead")
+            public void hardwareIsMatches() {}
+        };
+        String originalHardware = Build.HARDWARE;
+        try {
+            Robolectric.Reflection.setFinalStaticField(Build.class, "HARDWARE", "hammerhead");
+            Assert.assertTrue(new DisableIfSkipCheck().shouldSkip(hardwareIsMatches));
+        } finally {
+            Robolectric.Reflection.setFinalStaticField(Build.class, "HARDWARE", originalHardware);
+        }
+    }
+
+    @Test
+    public void testHardwareIsDoesntMatch() {
+        TestCase hardwareIsDoesntMatch = new TestCase("hardwareIsDoesntMatch") {
+            @DisableIf.Build(hardware_is = "hammerhead")
+            public void hardwareIsDoesntMatch() {}
+        };
+        String originalHardware = Build.HARDWARE;
+        try {
+            Robolectric.Reflection.setFinalStaticField(Build.class, "HARDWARE", "mako");
+            Assert.assertFalse(new DisableIfSkipCheck().shouldSkip(hardwareIsDoesntMatch));
+        } finally {
+            Robolectric.Reflection.setFinalStaticField(Build.class, "HARDWARE", originalHardware);
+        }
+    }
+}