Merge "Add include-filter option support to ICU4CTest"
am: 8591213960

Change-Id: I1e7ae43b2b700897101782ca38ea0a66f443e9a8
diff --git a/icu4c/source/test/cintltst/AndroidTest.xml b/icu4c/source/test/cintltst/AndroidTest.xml
index 293235b..b7fd3ff 100644
--- a/icu4c/source/test/cintltst/AndroidTest.xml
+++ b/icu4c/source/test/cintltst/AndroidTest.xml
@@ -38,5 +38,8 @@
         <option name="set-option" value="runtime-hint:20s" />
         <option name="set-option" value="no-fail-data-errors:true" />
         <option name="set-option" value="native-test-timeout:300000" />
+        <option name="set-option" value="command-filter-prefix:/" />
+        <!-- Sample include-filter to run a subset of tests in putiltst-->
+        <!-- <option name="set-option" value="include-filter:cintltst.putiltst" /> -->
     </test>
 </configuration>
diff --git a/icu4c/source/test/intltest/AndroidTest.xml b/icu4c/source/test/intltest/AndroidTest.xml
index 7d90146..327c5b8 100644
--- a/icu4c/source/test/intltest/AndroidTest.xml
+++ b/icu4c/source/test/intltest/AndroidTest.xml
@@ -39,6 +39,8 @@
         <option name="set-option" value="no-fail-data-errors:true" />
         <!-- test-timeout unit is ms, value = 10 minutes -->
         <option name="set-option" value="native-test-timeout:600000" />
+        <!-- Sample include-filter to run a subset of tests in utility-->
+        <!-- <option name="set-option" value="include-filter:intltest.utility" /> -->
     </test>
 
 </configuration>
diff --git a/tools/testing/test_harness/src/com/android/icu/tradefed/testtype/ICU4CTest.java b/tools/testing/test_harness/src/com/android/icu/tradefed/testtype/ICU4CTest.java
index 670843c..5b2c302 100644
--- a/tools/testing/test_harness/src/com/android/icu/tradefed/testtype/ICU4CTest.java
+++ b/tools/testing/test_harness/src/com/android/icu/tradefed/testtype/ICU4CTest.java
@@ -29,6 +29,7 @@
 import com.android.tradefed.testtype.IDeviceTest;
 import com.android.tradefed.testtype.IRemoteTest;
 import com.android.tradefed.testtype.IRuntimeHintProvider;
+import com.android.tradefed.testtype.ITestFilterReceiver;
 import com.android.tradefed.util.CommandResult;
 import com.android.tradefed.util.CommandStatus;
 import com.android.tradefed.util.FileUtil;
@@ -37,8 +38,11 @@
 
 import java.io.File;
 import java.io.IOException;
+import java.util.ArrayList;
+import java.util.LinkedHashSet;
 import java.util.List;
 import java.util.LinkedList;
+import java.util.Set;
 import java.util.concurrent.TimeUnit;
 import java.util.regex.Pattern;
 
@@ -46,6 +50,7 @@
 @OptionClass(alias = "icu4c")
 public class ICU4CTest
         implements IDeviceTest,
+    ITestFilterReceiver,
     IRemoteTest,
     IAbiReceiver,
     IRuntimeHintProvider {
@@ -58,6 +63,10 @@
     @Option(name = "module-name", description = "The name of the native test module to run.")
     private String mTestModule = null;
 
+    @Option(name = "command-filter-prefix",
+        description = "The prefix required for each test filter when running the shell command")
+    private String mCommandFilterPrefix = "";
+
     @Option(
         name = "native-test-timeout",
         description =
@@ -82,6 +91,19 @@
     )
     private boolean mNoFailDataErrors = false;
 
+    @Option(
+        name = "include-filter",
+        description = "The ICU-specific positive filter of the test names to run."
+    )
+    private Set<String> mIncludeFilters = new LinkedHashSet<>();
+
+
+    @Option(
+        name = "exclude-filter",
+        description = "The ICU-specific negative filter of the test names to run."
+    )
+    private Set<String> mExcludeFilters = new LinkedHashSet<>();
+
     private static final String TEST_FLAG_NO_FAIL_DATA_ERRORS = "-w";
     private static final String TEST_FLAG_XML_OUTPUT = "-x";
 
@@ -139,6 +161,62 @@
         return mRuntimeHint;
     }
 
+    /** {@inheritDoc} */
+    @Override
+    public void addIncludeFilter(String filter) {
+        mIncludeFilters.add(filter);
+    }
+
+    /** {@inheritDoc} */
+    @Override
+    public void addAllIncludeFilters(Set<String> filters) {
+        mIncludeFilters.addAll(filters);
+    }
+
+    /** {@inheritDoc} */
+    @Override
+    public void addExcludeFilter(String filter) {
+        mExcludeFilters.add(filter);
+    }
+
+    /** {@inheritDoc} */
+    @Override
+    public void addAllExcludeFilters(Set<String> filters) {
+        mExcludeFilters.addAll(filters);
+    }
+
+    @Override
+    public Set<String> getIncludeFilters() {
+        return mIncludeFilters;
+    }
+
+    @Override
+    public Set<String> getExcludeFilters() {
+        return mExcludeFilters;
+    }
+
+    /** {@inheritDoc} */
+    @Override
+    public void clearExcludeFilters() {
+        mExcludeFilters.clear();
+    }
+
+    @Override
+    public void clearIncludeFilters() {
+        mIncludeFilters.clear();
+    }
+
+    public void setCommandFilterPrefix(String s) {
+        if (s == null) {
+            throw new NullPointerException("CommandFilterPrefix can't be null");
+        }
+        mCommandFilterPrefix = s;
+    }
+
+    public String getCommandFilterPrefix() {
+        return mCommandFilterPrefix;
+    }
+
     /**
      * Gets the path where native tests live on the device.
      *
@@ -226,7 +304,8 @@
             File tmpOutput = FileUtil.createTempFile(testRunName, ".xml");
             testDevice.pullFile(xmlFullPath, tmpOutput);
 
-            ICU4CXmlResultParser parser = new ICU4CXmlResultParser(testRunName, listener);
+            ICU4CXmlResultParser parser = new ICU4CXmlResultParser(mTestModule,
+                testRunName, listener);
 
             parser.parseResult(tmpOutput, commandResult);
         } catch (IOException e) {
@@ -263,9 +342,41 @@
 
         String cmd = String.join(" ", args);
 
+        List<String> includeFilters = preprocessIncludeFilters();
+        if (!includeFilters.isEmpty()) {
+            cmd += " " + String.join(" ", includeFilters);
+        }
+
         return cmd;
     }
 
+    private List<String> preprocessIncludeFilters() {
+        Set<String> includeFilters = mIncludeFilters;
+        List<String> results = new ArrayList<>();
+        for (String filter : includeFilters) {
+            if (!filter.startsWith(mTestModule)) {
+                CLog.i("Ignore positive filter which does not contain module prefix \"%s\":%s",
+                    mTestModule, filter);
+                continue;
+            }
+            String modifiedFilter = filter.substring(mTestModule.length());
+            if (filter.length() == 0) {
+                // Ignore because it intends to run all tests when the filter is the module name.
+                continue;
+            }
+            // Android / tradefed uses '.' as package separator, but ICU4C tests use '/'.
+            modifiedFilter = modifiedFilter.replace('.', '/');
+
+            if (modifiedFilter.charAt(0) != '/' || modifiedFilter.length() == 1) {
+                CLog.i("Ignore invalid filter:%s", filter);
+                continue;
+            }
+            modifiedFilter = mCommandFilterPrefix + modifiedFilter.substring(1);
+            results.add(modifiedFilter);
+        }
+        return results;
+    }
+
     /** {@inheritDoc} */
     @Override
     public void run(ITestInvocationListener listener) throws DeviceNotAvailableException {
@@ -286,6 +397,9 @@
                             "%s exists but is not executable in %s.",
                             testPath, mDevice.getSerialNumber()));
         }
+        if (!mExcludeFilters.isEmpty()) {
+            throw new IllegalStateException("ICU4C test suites do not support exclude filters");
+        }
         runTest(mDevice, testPath, listener);
     }
 }
diff --git a/tools/testing/test_harness/src/com/android/icu/tradefed/testtype/ICU4CXmlResultParser.java b/tools/testing/test_harness/src/com/android/icu/tradefed/testtype/ICU4CXmlResultParser.java
index f9a2007..bed19fd 100644
--- a/tools/testing/test_harness/src/com/android/icu/tradefed/testtype/ICU4CXmlResultParser.java
+++ b/tools/testing/test_harness/src/com/android/icu/tradefed/testtype/ICU4CXmlResultParser.java
@@ -43,6 +43,7 @@
     private static final String TEST_CASE_TAG = "testcase";
 
     private final String mTestRunName;
+    private final String mModuleName;
     private int mNumTestsRun = 0;
     private int mNumTestsExpected = 0;
     private long mTotalRunTime = 0;
@@ -51,11 +52,14 @@
     /**
      * Creates the ICU4CXmlResultParser.
      *
+     * @param moduleName module name
      * @param testRunName the test run name to provide to {@link
      *     ITestInvocationListener#testRunStarted(String, int)}
      * @param listeners informed of test results as the tests are executing
      */
-    public ICU4CXmlResultParser(String testRunName, Collection<ITestInvocationListener> listeners) {
+    public ICU4CXmlResultParser(String moduleName, String testRunName,
+        Collection<ITestInvocationListener> listeners) {
+        mModuleName = moduleName;
         mTestRunName = testRunName;
         mTestListeners = new ArrayList<>(listeners);
     }
@@ -63,11 +67,14 @@
     /**
      * Creates the ICU4CXmlResultParser for a single listener.
      *
+     * @param moduleName module name
      * @param testRunName the test run name to provide to {@link
      *     ITestInvocationListener#testRunStarted(String, int)}
      * @param listener informed of test results as the tests are executing
      */
-    public ICU4CXmlResultParser(String testRunName, ITestInvocationListener listener) {
+    public ICU4CXmlResultParser(String moduleName, String testRunName,
+        ITestInvocationListener listener) {
+        mModuleName = moduleName;
         mTestRunName = testRunName;
         mTestListeners = new ArrayList<>();
         if (listener != null) {
@@ -162,6 +169,10 @@
             classname = classname.substring(1);
         }
 
+        // For test reporting on Android, prefix module name to the class name
+        // and replace '/' with '.'
+        classname = mModuleName + '.' + classname.replace('/', '.');
+
         // TODO: Fix the duplicate test name in the testId
         // Currently, testId is like spoof#spoof or spoof/testBug8654#testBug8654
         // in order to avoid empty test name in the case of spoof#spoof. We should remove