ICU4J/Libcore: Generalize the IcuTestRunner so it can be used with libcore

Bug: 22023363
Change-Id: Icf4d0f42f07fa719aaa6036f93b7aa7efec214c9
diff --git a/libs/runner/Android.mk b/libs/runner/Android.mk
index 9642f53..15f64a3 100644
--- a/libs/runner/Android.mk
+++ b/libs/runner/Android.mk
@@ -16,9 +16,7 @@
 
 include $(CLEAR_VARS)
 
-LOCAL_SRC_FILES := $(call all-java-files-under, ../../tests/core/runner/src)
-
-LOCAL_STATIC_JAVA_LIBRARIES := android-support-test
+LOCAL_STATIC_JAVA_LIBRARIES := cts-test-runner
 
 LOCAL_MODULE_TAGS := optional
 
diff --git a/tests/core/runner/Android.mk b/tests/core/runner/Android.mk
index 158a1f1..1355512 100644
--- a/tests/core/runner/Android.mk
+++ b/tests/core/runner/Android.mk
@@ -23,12 +23,44 @@
 # include this package in the tests target for continuous testing
 LOCAL_MODULE_TAGS := tests
 
-LOCAL_SRC_FILES := $(call all-java-files-under,src)
-
 LOCAL_PACKAGE_NAME := android.core.tests.runner
 
-LOCAL_STATIC_JAVA_LIBRARIES := android-support-test
+LOCAL_STATIC_JAVA_LIBRARIES := cts-test-runner
 
 LOCAL_JAVA_LANGUAGE_VERSION := 1.8
 
 include $(BUILD_CTSCORE_PACKAGE)
+
+#==========================================================
+# Build the core runner.
+#==========================================================
+
+# Build library
+include $(CLEAR_VARS)
+
+LOCAL_MODULE_TAGS := optional
+LOCAL_SRC_FILES := $(call all-java-files-under,src)
+LOCAL_MODULE := cts-core-test-runner
+LOCAL_STATIC_JAVA_LIBRARIES := \
+    compatibility-device-util \
+    android-support-test \
+    android.test.runner \
+    vogarexpect
+
+LOCAL_JAVA_LANGUAGE_VERSION := 1.8
+
+include $(BUILD_JAVA_LIBRARY)
+
+#==========================================================
+# Build the run listener
+#==========================================================
+
+# Build library
+include $(CLEAR_VARS)
+
+LOCAL_MODULE_TAGS := optional
+LOCAL_SRC_FILES := $(call all-java-files-under,src/com/android/cts/runner)
+LOCAL_MODULE := cts-test-runner
+LOCAL_STATIC_JAVA_LIBRARIES := android-support-test
+
+include $(BUILD_JAVA_LIBRARY)
diff --git a/tests/tests/icu/src/android/icu/cts/AndroidJUnitRunnerConstants.java b/tests/core/runner/src/com/android/cts/core/runner/AndroidJUnitRunnerConstants.java
similarity index 95%
rename from tests/tests/icu/src/android/icu/cts/AndroidJUnitRunnerConstants.java
rename to tests/core/runner/src/com/android/cts/core/runner/AndroidJUnitRunnerConstants.java
index 24d8819..c6c8504 100644
--- a/tests/tests/icu/src/android/icu/cts/AndroidJUnitRunnerConstants.java
+++ b/tests/core/runner/src/com/android/cts/core/runner/AndroidJUnitRunnerConstants.java
@@ -14,14 +14,14 @@
  * limitations under the License.
  */
 
-package android.icu.cts;
+package com.android.cts.core.runner;
 
 import android.support.test.runner.AndroidJUnitRunner;
 
 /**
  * Constants used to communicate to and from {@link AndroidJUnitRunner}.
  */
-interface AndroidJUnitRunnerConstants {
+public interface AndroidJUnitRunnerConstants {
 
     /**
      * The names of the file containing the names of the tests to run.
@@ -82,5 +82,5 @@
     /**
      * An identifier for tests run using this class.
      */
-    String REPORT_VALUE_ID = "IcuTestRunner";
+    String REPORT_VALUE_ID = "CoreTestRunner";
 }
diff --git a/tests/tests/icu/src/android/icu/cts/IcuTestRunner.java b/tests/core/runner/src/com/android/cts/core/runner/CoreTestRunner.java
similarity index 72%
rename from tests/tests/icu/src/android/icu/cts/IcuTestRunner.java
rename to tests/core/runner/src/com/android/cts/core/runner/CoreTestRunner.java
index 51c4999..144e9e2 100644
--- a/tests/tests/icu/src/android/icu/cts/IcuTestRunner.java
+++ b/tests/core/runner/src/com/android/cts/core/runner/CoreTestRunner.java
@@ -14,21 +14,21 @@
  * limitations under the License.
  */
 
-package android.icu.cts;
+package com.android.cts.core.runner;
 
 import android.app.Activity;
 import android.app.Instrumentation;
-import android.icu.junit.IcuTestRunnerBuilder;
+import com.android.cts.core.runner.support.ExtendedAndroidRunnerBuilder;
 import android.os.Bundle;
 import android.os.Debug;
 import android.support.test.internal.util.AndroidRunnerParams;
 import android.util.Log;
+import com.google.common.base.Splitter;
 import java.io.BufferedReader;
 import java.io.FileReader;
 import java.io.IOException;
 import java.util.ArrayList;
 import java.util.Arrays;
-import java.util.Collections;
 import java.util.LinkedHashSet;
 import java.util.List;
 import java.util.Set;
@@ -45,22 +45,26 @@
 import vogar.ExpectationStore;
 import vogar.ModeId;
 
-import static android.icu.cts.AndroidJUnitRunnerConstants.ARGUMENT_COUNT;
-import static android.icu.cts.AndroidJUnitRunnerConstants.ARGUMENT_DEBUG;
-import static android.icu.cts.AndroidJUnitRunnerConstants.ARGUMENT_LOG_ONLY;
-import static android.icu.cts.AndroidJUnitRunnerConstants.ARGUMENT_TEST_CLASS;
-import static android.icu.cts.AndroidJUnitRunnerConstants.ARGUMENT_TEST_FILE;
+import static com.android.cts.core.runner.AndroidJUnitRunnerConstants.ARGUMENT_COUNT;
+import static com.android.cts.core.runner.AndroidJUnitRunnerConstants.ARGUMENT_DEBUG;
+import static com.android.cts.core.runner.AndroidJUnitRunnerConstants.ARGUMENT_LOG_ONLY;
+import static com.android.cts.core.runner.AndroidJUnitRunnerConstants.ARGUMENT_TEST_CLASS;
+import static com.android.cts.core.runner.AndroidJUnitRunnerConstants.ARGUMENT_TEST_FILE;
 
 /**
  * A drop-in replacement for AndroidJUnitTestRunner, which understands the same arguments, and has
- * similar functionality, but runs ICU tests instead of calling the JUnit wrapper.
+ * similar functionality, but can filter by expectations and allows a custom runner-builder to be
+ * provided.
  */
-public final class IcuTestRunner extends Instrumentation {
+public class CoreTestRunner extends Instrumentation {
 
-    public static final String TAG = "IcuTestRunner";
+    public static final String TAG = "LibcoreTestRunner";
 
-    private static final List<String> EXPECTATIONS_PATHS =
-            Collections.singletonList("expectations/icu-known-failures.txt");
+    private static final java.lang.String ARGUMENT_ROOT_CLASSES = "core-root-classes";
+
+    private static final String ARGUMENT_EXPECTATIONS = "core-expectations";
+
+    private static final Splitter CLASS_LIST_SPLITTER = Splitter.on(',').trimResults();
 
     /** The args for the runner. */
     private Bundle args;
@@ -80,10 +84,10 @@
     /**
      * The list of tests to run.
      */
-    private IcuTestList icuTestList;
+    private TestList testList;
 
     @Override
-    public void onCreate(Bundle args) {
+    public void onCreate(final Bundle args) {
         super.onCreate(args);
         this.args = args;
 
@@ -102,7 +106,9 @@
         this.testCountOnly = args.getBoolean(ARGUMENT_COUNT);
 
         try {
-            Set<String> expectationResources = new LinkedHashSet<>(EXPECTATIONS_PATHS);
+            // Get the set of resource names containing the expectations.
+            Set<String> expectationResources = new LinkedHashSet<>(
+                    CLASS_LIST_SPLITTER.splitToList(args.getString(ARGUMENT_EXPECTATIONS)));
             expectationStore = ExpectationStore.parseResources(
                     getClass(), expectationResources, ModeId.DEVICE);
         } catch (IOException e) {
@@ -131,11 +137,16 @@
         }
 
         if (testNameList == null) {
-            icuTestList = IcuTestList.rootList(Arrays.asList(
-                    "android.icu.cts.coverage.TestAll",
-                    "android.icu.dev.test.TestAll"));
+            String rootClasses = args.getString(ARGUMENT_ROOT_CLASSES);
+            if (rootClasses == null) {
+                throw new IllegalStateException(
+                        "No tests specified, neither exclusive list or root class");
+            } else {
+                List<String> roots = CLASS_LIST_SPLITTER.splitToList(rootClasses);
+                testList = TestList.rootList(roots);
+            }
         } else {
-            icuTestList = IcuTestList.exclusiveList(testNameList);
+            testList = TestList.exclusiveList(testNameList);
         }
 
         start();
@@ -157,15 +168,15 @@
         Request request;
         int totalTestCount;
         try {
-            RunnerBuilder runnerBuilder = new IcuTestRunnerBuilder(runnerParams);
-            Class[] classes = icuTestList.getClassesToRun();
+            RunnerBuilder runnerBuilder = new ExtendedAndroidRunnerBuilder(runnerParams);
+            Class[] classes = testList.getClassesToRun();
             Runner suite = new Computer().getSuite(runnerBuilder, classes);
 
             if (suite instanceof Filterable) {
                 Filterable filterable = (Filterable) suite;
 
                 // Filter out all the tests that are expected to fail.
-                Filter filter = new IcuTestFilter(icuTestList, expectationStore);
+                Filter filter = new TestFilter(testList, expectationStore);
 
                 try {
                     filterable.filter(filter);
@@ -183,17 +194,18 @@
             throw new RuntimeException("Could not create a suite", e);
         }
 
-        IcuRunListener icuRunListener = new IcuRunListener(this, runnerParams, totalTestCount);
-        core.addListener(icuRunListener);
+        StatusUpdaterRunListener statusUpdaterRunListener =
+                new StatusUpdaterRunListener(this, runnerParams, totalTestCount);
+        core.addListener(statusUpdaterRunListener);
         core.run(request);
 
         Bundle results;
         if (testCountOnly) {
-            results = icuRunListener.getCountResults();
+            results = statusUpdaterRunListener.getCountResults();
             Log.d(TAG, "test count only: " + results);
         } else {
             // Get the final results to send back.
-            results = icuRunListener.getFinalResults();
+            results = statusUpdaterRunListener.getFinalResults();
         }
 
         Log.d(TAG, "Finished");
diff --git a/tests/tests/icu/src/android/icu/cts/IcuRunListener.java b/tests/core/runner/src/com/android/cts/core/runner/StatusUpdaterRunListener.java
similarity index 92%
rename from tests/tests/icu/src/android/icu/cts/IcuRunListener.java
rename to tests/core/runner/src/com/android/cts/core/runner/StatusUpdaterRunListener.java
index 0a6f355..429ae92 100644
--- a/tests/tests/icu/src/android/icu/cts/IcuRunListener.java
+++ b/tests/core/runner/src/com/android/cts/core/runner/StatusUpdaterRunListener.java
@@ -14,7 +14,7 @@
  * limitations under the License.
  */
 
-package android.icu.cts;
+package com.android.cts.core.runner;
 
 import android.app.Instrumentation;
 import android.os.Bundle;
@@ -30,8 +30,8 @@
 
 import static android.app.Instrumentation.REPORT_KEY_IDENTIFIER;
 import static android.app.Instrumentation.REPORT_KEY_STREAMRESULT;
-import static android.icu.cts.AndroidJUnitRunnerConstants.REPORT_KEY_RUNTIME;
-import static android.icu.cts.AndroidJUnitRunnerConstants.REPORT_VALUE_ID;
+import static com.android.cts.core.runner.AndroidJUnitRunnerConstants.REPORT_KEY_RUNTIME;
+import static com.android.cts.core.runner.AndroidJUnitRunnerConstants.REPORT_VALUE_ID;
 import static android.test.InstrumentationTestRunner.REPORT_KEY_NAME_CLASS;
 import static android.test.InstrumentationTestRunner.REPORT_KEY_NAME_TEST;
 import static android.test.InstrumentationTestRunner.REPORT_KEY_NUM_CURRENT;
@@ -47,7 +47,7 @@
  * Listens to result of running tests, collates details and sends intermediate status information
  * back to the host.
  */
-class IcuRunListener extends RunListener {
+class StatusUpdaterRunListener extends RunListener {
 
     /**
      * The {@link Instrumentation} for which this will report information.
@@ -77,7 +77,8 @@
 
     private long testStartTime;
 
-    public IcuRunListener(Instrumentation instrumentation, AndroidRunnerParams runnerParams,
+    public StatusUpdaterRunListener(Instrumentation instrumentation,
+            AndroidRunnerParams runnerParams,
             int totalTestCount) {
         this.instrumentation = instrumentation;
         this.runnerParams = runnerParams;
@@ -135,6 +136,7 @@
         } else {
             resultStatus = REPORT_VALUE_RESULT_ERROR;
         }
+
         String information = failure.getTrace();
         currentTestResult.putString(REPORT_KEY_STACK, information);
         String output = "Error: " + description + "\n" + information;
@@ -165,7 +167,7 @@
 
     public Bundle getFinalResults() {
         int totalTests = totalFailures + totalSuccess;
-        Log.d(IcuTestRunner.TAG, (runnerParams.isSkipExecution() ? "Skipped " : "Ran ")
+        Log.d(CoreTestRunner.TAG, (runnerParams.isSkipExecution() ? "Skipped " : "Ran ")
                 + totalTests + " tests, " + totalSuccess + " passed, " + totalFailures + " failed");
         Bundle results = new Bundle();
         results.putString(REPORT_KEY_IDENTIFIER, REPORT_VALUE_ID);
@@ -189,7 +191,7 @@
     }
 
     public Bundle getCountResults() {
-        Log.d(IcuTestRunner.TAG, "Counted " + (totalFailures + totalSuccess) + " tests, "
+        Log.d(CoreTestRunner.TAG, "Counted " + (totalFailures + totalSuccess) + " tests, "
                 + totalSuccess + " passed, " + totalFailures + " failed");
         Bundle results = new Bundle();
         results.putString(REPORT_KEY_IDENTIFIER, REPORT_VALUE_ID);
diff --git a/tests/tests/icu/src/android/icu/cts/IcuTestFilter.java b/tests/core/runner/src/com/android/cts/core/runner/TestFilter.java
similarity index 90%
rename from tests/tests/icu/src/android/icu/cts/IcuTestFilter.java
rename to tests/core/runner/src/com/android/cts/core/runner/TestFilter.java
index 3da2976..8cadbcf 100644
--- a/tests/tests/icu/src/android/icu/cts/IcuTestFilter.java
+++ b/tests/core/runner/src/com/android/cts/core/runner/TestFilter.java
@@ -13,7 +13,7 @@
  * See the License for the specific language governing permissions and
  * limitations under the License.
  */
-package android.icu.cts;
+package com.android.cts.core.runner;
 
 import android.util.Log;
 import java.util.List;
@@ -61,15 +61,15 @@
  * {@link ParentRunner}, as that would prevent it from traversing the hierarchy and finding
  * the leaf nodes.
  */
-class IcuTestFilter extends Filter {
+class TestFilter extends Filter {
 
     private final ExpectationStore expectationStore;
 
-    private final IcuTestList icuTestList;
+    private final TestList testList;
 
-    public IcuTestFilter(IcuTestList icuTestList, @Nullable ExpectationStore expectationStore) {
+    public TestFilter(TestList testList, @Nullable ExpectationStore expectationStore) {
         this.expectationStore = expectationStore;
-        this.icuTestList = icuTestList;
+        this.testList = testList;
     }
 
     @Override
@@ -84,14 +84,14 @@
             String testName = className + "#" + methodName;
 
             // If the test isn't in the list of tests to run then do not run it.
-            if (!icuTestList.shouldRunTest(testName)) {
+            if (!testList.shouldRunTest(testName)) {
                 return false;
             }
 
             if (expectationStore != null) {
                 Expectation expectation = expectationStore.get(testName);
                 if (expectation.getResult() != Result.SUCCESS) {
-                    Log.d(IcuTestRunner.TAG, "Excluding test " + description
+                    Log.d(CoreTestRunner.TAG, "Excluding test " + testDescription
                             + " as it matches expectation: " + expectation);
                     return false;
                 }
@@ -126,6 +126,6 @@
 
     @Override
     public String describe() {
-        return "IcuTestFilter";
+        return "TestFilter";
     }
 }
diff --git a/tests/tests/icu/src/android/icu/cts/IcuTestList.java b/tests/core/runner/src/com/android/cts/core/runner/TestList.java
similarity index 84%
rename from tests/tests/icu/src/android/icu/cts/IcuTestList.java
rename to tests/core/runner/src/com/android/cts/core/runner/TestList.java
index a9c3c5f..4b0452d 100644
--- a/tests/tests/icu/src/android/icu/cts/IcuTestList.java
+++ b/tests/core/runner/src/com/android/cts/core/runner/TestList.java
@@ -13,7 +13,7 @@
  * See the License for the specific language governing permissions and
  * limitations under the License.
  */
-package android.icu.cts;
+package com.android.cts.core.runner;
 
 import android.util.Log;
 import java.util.ArrayList;
@@ -25,7 +25,7 @@
 /**
  * A list of the tests to run.
  */
-class IcuTestList {
+class TestList {
 
     /**
      * The names of the set of tests to run, if null then all tests should be run.
@@ -35,7 +35,7 @@
 
     private final List<Class<?>> classesToRun;
 
-    public static IcuTestList exclusiveList(List<String> testNameList) {
+    public static TestList exclusiveList(List<String> testNameList) {
         Set<String> classNamesToRun = new LinkedHashSet<>();
         Set<String> testsToRun = new LinkedHashSet<>(testNameList);
 
@@ -50,19 +50,19 @@
             classNamesToRun.add(className);
         }
 
-        Log.d(IcuTestRunner.TAG, "Running only the following tests: " + testsToRun);
-        return new IcuTestList(getClasses(classNamesToRun), testsToRun);
+        Log.d(CoreTestRunner.TAG, "Running only the following tests: " + testsToRun);
+        return new TestList(getClasses(classNamesToRun), testsToRun);
     }
 
-    public static IcuTestList rootList(List<String> rootList) {
+    public static TestList rootList(List<String> rootList) {
 
         // Run from the root test class.
         Set<String> classNamesToRun = new LinkedHashSet<>(rootList);
-        Log.d(IcuTestRunner.TAG, "Running all tests rooted at " + classNamesToRun);
+        Log.d(CoreTestRunner.TAG, "Running all tests rooted at " + classNamesToRun);
 
         List<Class<?>> classesToRun1 = getClasses(classNamesToRun);
 
-        return new IcuTestList(classesToRun1, null);
+        return new TestList(classesToRun1, null);
     }
 
     private static List<Class<?>> getClasses(Set<String> classNames) {
@@ -83,7 +83,7 @@
      * @param testsToRun The exclusive set of tests to run or null if all tests reachable from the
      * classes are to be run.
      */
-    private IcuTestList(List<Class<?>> classes, Set<String> testsToRun) {
+    private TestList(List<Class<?>> classes, Set<String> testsToRun) {
         this.testsToRun = testsToRun;
         this.classesToRun = classes;
     }
diff --git a/tests/core/runner/src/com/android/cts/core/runner/support/AndroidRunnerBuilder.java b/tests/core/runner/src/com/android/cts/core/runner/support/AndroidRunnerBuilder.java
new file mode 100644
index 0000000..bdb3e57
--- /dev/null
+++ b/tests/core/runner/src/com/android/cts/core/runner/support/AndroidRunnerBuilder.java
@@ -0,0 +1,81 @@
+/*
+ * Copyright (C) 2016 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 com.android.cts.core.runner.support;
+
+import android.support.test.internal.runner.junit3.AndroidJUnit3Builder;
+import android.support.test.internal.runner.junit3.AndroidSuiteBuilder;
+import android.support.test.internal.runner.junit4.AndroidAnnotatedBuilder;
+import android.support.test.internal.runner.junit4.AndroidJUnit4Builder;
+import android.support.test.internal.util.AndroidRunnerParams;
+
+import org.junit.internal.builders.AllDefaultPossibilitiesBuilder;
+import org.junit.internal.builders.AnnotatedBuilder;
+import org.junit.internal.builders.IgnoredBuilder;
+import org.junit.internal.builders.JUnit3Builder;
+import org.junit.internal.builders.JUnit4Builder;
+import org.junit.runners.model.RunnerBuilder;
+
+/**
+ * A {@link RunnerBuilder} that can handle all types of tests.
+ */
+// A copy of package private class android.support.test.internal.runner.AndroidRunnerBuilder.
+// Copied here so that it can be extended.
+class AndroidRunnerBuilder extends AllDefaultPossibilitiesBuilder {
+
+    private final AndroidJUnit3Builder mAndroidJUnit3Builder;
+    private final AndroidJUnit4Builder mAndroidJUnit4Builder;
+    private final AndroidSuiteBuilder mAndroidSuiteBuilder;
+    private final AndroidAnnotatedBuilder mAndroidAnnotatedBuilder;
+    // TODO: customize for Android ?
+    private final IgnoredBuilder mIgnoredBuilder;
+
+    /**
+     * @param runnerParams {@link AndroidRunnerParams} that stores common runner parameters
+     */
+    AndroidRunnerBuilder(AndroidRunnerParams runnerParams) {
+        super(true);
+        mAndroidJUnit3Builder = new AndroidJUnit3Builder(runnerParams);
+        mAndroidJUnit4Builder = new AndroidJUnit4Builder(runnerParams);
+        mAndroidSuiteBuilder = new AndroidSuiteBuilder(runnerParams);
+        mAndroidAnnotatedBuilder = new AndroidAnnotatedBuilder(this, runnerParams);
+        mIgnoredBuilder = new IgnoredBuilder();
+    }
+
+    @Override
+    protected JUnit4Builder junit4Builder() {
+        return mAndroidJUnit4Builder;
+    }
+
+    @Override
+    protected JUnit3Builder junit3Builder() {
+        return mAndroidJUnit3Builder;
+    }
+
+    @Override
+    protected AnnotatedBuilder annotatedBuilder() {
+        return mAndroidAnnotatedBuilder;
+    }
+
+    @Override
+    protected IgnoredBuilder ignoredBuilder() {
+        return mIgnoredBuilder;
+    }
+
+    @Override
+    protected RunnerBuilder suiteMethodBuilder() {
+        return mAndroidSuiteBuilder;
+    }
+}
diff --git a/tests/core/runner/src/com/android/cts/core/runner/support/ExtendedAndroidAnnotatedBuilder.java b/tests/core/runner/src/com/android/cts/core/runner/support/ExtendedAndroidAnnotatedBuilder.java
new file mode 100644
index 0000000..05c6623
--- /dev/null
+++ b/tests/core/runner/src/com/android/cts/core/runner/support/ExtendedAndroidAnnotatedBuilder.java
@@ -0,0 +1,89 @@
+/*
+ * Copyright (C) 2016 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 com.android.cts.core.runner.support;
+
+import android.support.test.internal.runner.junit4.AndroidAnnotatedBuilder;
+import android.support.test.internal.util.AndroidRunnerParams;
+import android.support.test.runner.AndroidJUnit4;
+import junit.framework.TestCase;
+import org.junit.runner.RunWith;
+import org.junit.runner.Runner;
+import org.junit.runners.BlockJUnit4ClassRunner;
+import org.junit.runners.JUnit4;
+import org.junit.runners.model.RunnerBuilder;
+
+/**
+ * Extends {@link AndroidAnnotatedBuilder} to add support for passing the
+ * {@link AndroidRunnerParams} object to the constructor of any {@link RunnerBuilder}
+ * implementation that is not a {@link BlockJUnit4ClassRunner}.
+ *
+ * <p>If {@link AndroidRunnerParams#isSkipExecution()} is {@code true} the super class will create
+ * a {@link RunnerBuilder} that will fire appropriate events as if the tests are being run but will
+ * not actually run the test. Unfortunately, when it does that it appears to assume that the runner
+ * extends {@link BlockJUnit4ClassRunner}, returns a skipping {@link RunnerBuilder} appropriate for
+ * that and ignores the actual {@code runnerClass}. That is a problem because it will not work for
+ * custom {@link RunnerBuilder} instances that do not extend {@link BlockJUnit4ClassRunner}.
+ *
+ * <p>Therefore, when skipping execution this does some additional checks to make sure that the
+ * {@code runnerClass} does extend {@link BlockJUnit4ClassRunner} before calling the overridden
+ * method.
+ *
+ * <p>It then attempts to construct a {@link RunnerBuilder} by calling the constructor with the
+ * signature {@code <init>(Class, AndroidRunnerParams)}. If that doesn't exist it falls back to
+ * the overridden behavior.
+ */
+class ExtendedAndroidAnnotatedBuilder extends AndroidAnnotatedBuilder {
+
+    private final AndroidRunnerParams runnerParams;
+
+    public ExtendedAndroidAnnotatedBuilder(RunnerBuilder suiteBuilder,
+            AndroidRunnerParams runnerParams) {
+        super(suiteBuilder, runnerParams);
+        this.runnerParams = runnerParams;
+    }
+
+    @Override
+    public Runner runnerForClass(Class<?> testClass) throws Exception {
+
+        RunWith annotation = testClass.getAnnotation(RunWith.class);
+        if (annotation != null) {
+            Class<? extends Runner> runnerClass = annotation.value();
+
+            // If the runner is expected to skip execution and it is a JUnit4, AndroidJUnit4 or
+            // a JUnit3 test class then return a special skipping runner.
+            if (runnerParams.isSkipExecution()) {
+                if (runnerClass == AndroidJUnit4.class || runnerClass == JUnit4.class
+                        || TestCase.class.isAssignableFrom(testClass)) {
+                    return super.runnerForClass(testClass);
+                }
+            }
+
+            try {
+                // try to build an AndroidJUnit4 runner
+                Runner runner = buildAndroidRunner(runnerClass, testClass);
+                if (runner != null) {
+                    return runner;
+                }
+            } catch (NoSuchMethodException e) {
+                // let the super class handle the error for us and throw an InitializationError
+                // exception.
+                return super.buildRunner(runnerClass, testClass);
+            }
+        }
+
+        return null;
+    }
+}
diff --git a/tests/core/runner/src/com/android/cts/core/runner/support/ExtendedAndroidRunnerBuilder.java b/tests/core/runner/src/com/android/cts/core/runner/support/ExtendedAndroidRunnerBuilder.java
new file mode 100644
index 0000000..ed16318
--- /dev/null
+++ b/tests/core/runner/src/com/android/cts/core/runner/support/ExtendedAndroidRunnerBuilder.java
@@ -0,0 +1,43 @@
+/*
+ * Copyright (C) 2016 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 com.android.cts.core.runner.support;
+
+import android.support.test.internal.runner.junit4.AndroidAnnotatedBuilder;
+import android.support.test.internal.util.AndroidRunnerParams;
+import org.junit.internal.builders.AnnotatedBuilder;
+import org.junit.runners.model.RunnerBuilder;
+
+/**
+ * Extends {@link AndroidRunnerBuilder} in order to provide alternate {@link RunnerBuilder}
+ * implementations.
+ */
+public class ExtendedAndroidRunnerBuilder extends AndroidRunnerBuilder {
+
+    private final AndroidAnnotatedBuilder mAndroidAnnotatedBuilder;
+
+    /**
+     * @param runnerParams {@link AndroidRunnerParams} that stores common runner parameters
+     */
+    public ExtendedAndroidRunnerBuilder(AndroidRunnerParams runnerParams) {
+        super(runnerParams);
+        mAndroidAnnotatedBuilder = new ExtendedAndroidAnnotatedBuilder(this, runnerParams);
+    }
+
+    @Override
+    protected AnnotatedBuilder annotatedBuilder() {
+        return mAndroidAnnotatedBuilder;
+    }
+}
diff --git a/tests/core/runner/src/com/android/cts/core/runner/support/package-info.java b/tests/core/runner/src/com/android/cts/core/runner/support/package-info.java
new file mode 100644
index 0000000..3ccec3c
--- /dev/null
+++ b/tests/core/runner/src/com/android/cts/core/runner/support/package-info.java
@@ -0,0 +1,26 @@
+/*
+ * Copyright (C) 2016 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.
+ */
+
+/**
+ * Contains all the changes needed to the {@code android.support.test.internal.runner} classes.
+ *
+ * <p>As its name suggests {@code android.support.test.internal.runner} are internal classes that
+ * are not designed to be extended from outside those packages. This package encapsulates all the
+ * workarounds needed to overcome that limitation, from duplicating classes to using reflection.
+ * The intention is that these changes are temporary and they (or a better equivalent) will be
+ * quickly integrated into the internal classes.
+ */
+package com.android.cts.core.runner.support;
diff --git a/tests/tests/icu/Android.mk b/tests/tests/icu/Android.mk
index 39a4b53..6f183d5 100644
--- a/tests/tests/icu/Android.mk
+++ b/tests/tests/icu/Android.mk
@@ -31,9 +31,7 @@
 # The aim of this package is to run tests against the implementation in use by
 # the current android system.
 LOCAL_STATIC_JAVA_LIBRARIES := \
-	compatibility-device-util \
-	android-support-test \
-	vogarexpect \
+	cts-core-test-runner \
 	android-icu4j-tests
 
 # Tag this module as a cts test artifact
diff --git a/tests/tests/icu/AndroidManifest.xml b/tests/tests/icu/AndroidManifest.xml
index 7d99c0a..c743375 100644
--- a/tests/tests/icu/AndroidManifest.xml
+++ b/tests/tests/icu/AndroidManifest.xml
@@ -22,7 +22,7 @@
         <uses-library android:name="android.test.runner" />
     </application>
 
-    <instrumentation android:name="android.icu.cts.IcuTestRunner"
+    <instrumentation android:name="com.android.cts.core.runner.CoreTestRunner"
                      android:targetPackage="android.icu.cts"
-                     android:label="ICU4J library tests."/>
+                     android:label="CTS Repackaged ICU4J library tests."/>
 </manifest>
diff --git a/tests/tests/icu/AndroidTest.xml b/tests/tests/icu/AndroidTest.xml
index 18c95cd..769b789 100644
--- a/tests/tests/icu/AndroidTest.xml
+++ b/tests/tests/icu/AndroidTest.xml
@@ -18,8 +18,13 @@
         <option name="test-file-name" value="CtsIcuTestCases.apk" />
     </target_preparer>
     <test class="com.android.tradefed.testtype.AndroidJUnitTest" >
-        <option name="runner" value="android.icu.cts.IcuTestRunner" /><!-- override AJUR -->
+        <!-- override AJUR -->
+        <option name="runner" value="com.android.cts.core.runner.CoreTestRunner" />
         <option name="package" value="android.icu.cts" />
+        <option name="instrumentation-arg" key="core-root-classes"
+                value="android.icu.cts.coverage.TestAll,android.icu.dev.test.TestAll" />
+        <option name="instrumentation-arg" key="core-expectations"
+                value="/android/icu/cts/expectations/icu-known-failures.txt" />
         <option name="runtime-hint" value="30m19s" />
     </test>
 </configuration>