Replicate retry logic in external factory

Avoid using compatibilitytest as entry point for the retry
logic.
Provide a new retry.xml configuration to use retry.

Test: run retry --retry <id>
Bug: 36337428
Change-Id: Id67bc92ca4b2536ddf94fc0ba24305b47695a7fe
diff --git a/common/host-side/tradefed/src/com/android/compatibility/common/tradefed/testtype/CompatibilityTest.java b/common/host-side/tradefed/src/com/android/compatibility/common/tradefed/testtype/CompatibilityTest.java
index c0483e2..fc329af 100644
--- a/common/host-side/tradefed/src/com/android/compatibility/common/tradefed/testtype/CompatibilityTest.java
+++ b/common/host-side/tradefed/src/com/android/compatibility/common/tradefed/testtype/CompatibilityTest.java
@@ -676,7 +676,6 @@
     void setupFilters() throws DeviceNotAvailableException {
         if (mRetrySessionId != null) {
             // Load the invocation result
-            IInvocationResult result = null;
             RetryFilterHelper helper = new RetryFilterHelper(mBuildHelper, mRetrySessionId);
             helper.validateBuildFingerprint(mDevice);
             helper.setAllOptionsFrom(this);
@@ -775,4 +774,18 @@
     public void setCollectTestsOnly(boolean collectTestsOnly) {
         mCollectTestsOnly = collectTestsOnly;
     }
+
+    /**
+     * Sets include-filters for the compatibility test
+     */
+    public void setIncludeFilter(Set<String> includeFilters) {
+        mIncludeFilters.addAll(includeFilters);
+    }
+
+    /**
+     * Sets exclude-filters for the compatibility test
+     */
+    public void setExcludeFilter(Set<String> excludeFilters) {
+        mExcludeFilters.addAll(excludeFilters);
+    }
 }
diff --git a/common/host-side/tradefed/src/com/android/compatibility/common/tradefed/testtype/retry/RetryFactoryTest.java b/common/host-side/tradefed/src/com/android/compatibility/common/tradefed/testtype/retry/RetryFactoryTest.java
new file mode 100644
index 0000000..f47363e
--- /dev/null
+++ b/common/host-side/tradefed/src/com/android/compatibility/common/tradefed/testtype/retry/RetryFactoryTest.java
@@ -0,0 +1,161 @@
+/*
+ * Copyright (C) 2017 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.compatibility.common.tradefed.testtype.retry;
+
+import com.android.compatibility.common.tradefed.build.CompatibilityBuildHelper;
+import com.android.compatibility.common.tradefed.testtype.CompatibilityTest;
+import com.android.compatibility.common.tradefed.util.RetryFilterHelper;
+import com.android.compatibility.common.tradefed.util.RetryType;
+import com.android.tradefed.build.IBuildInfo;
+import com.android.tradefed.config.ConfigurationException;
+import com.android.tradefed.config.Option;
+import com.android.tradefed.config.Option.Importance;
+import com.android.tradefed.config.OptionClass;
+import com.android.tradefed.config.OptionSetter;
+import com.android.tradefed.device.DeviceNotAvailableException;
+import com.android.tradefed.device.ITestDevice;
+import com.android.tradefed.result.ITestInvocationListener;
+import com.android.tradefed.suite.checker.ISystemStatusChecker;
+import com.android.tradefed.suite.checker.ISystemStatusCheckerReceiver;
+import com.android.tradefed.testtype.IBuildReceiver;
+import com.android.tradefed.testtype.IDeviceTest;
+import com.android.tradefed.testtype.IRemoteTest;
+
+import java.util.HashSet;
+import java.util.List;
+import java.util.Set;
+
+/**
+ * Runner that creates a {@link CompatibilityTest} to re-run some previous results.
+ * Only the 'cts' plan is supported.
+ * TODO: explore other new way to build the retry (instead of relying on one massive pair of
+ * include/exclude filters)
+ */
+@OptionClass(alias = "compatibility")
+public class RetryFactoryTest implements IRemoteTest, IDeviceTest, IBuildReceiver,
+        ISystemStatusCheckerReceiver {
+
+    /**
+     * Mirror the {@link CompatibilityTest} options in order to pass them to the Helper.
+     */
+    public static final String RETRY_OPTION = "retry";
+    @Option(name = RETRY_OPTION,
+            shortName = 'r',
+            description = "retry a previous session's failed and not executed tests.",
+            mandatory = true)
+    private Integer mRetrySessionId = null;
+
+    @Option(name = CompatibilityTest.SUBPLAN_OPTION,
+            description = "the subplan to run",
+            importance = Importance.IF_UNSET)
+    protected String mSubPlan;
+
+    @Option(name = CompatibilityTest.INCLUDE_FILTER_OPTION,
+            description = "the include module filters to apply.",
+            importance = Importance.ALWAYS)
+    protected Set<String> mIncludeFilters = new HashSet<>();
+
+    @Option(name = CompatibilityTest.EXCLUDE_FILTER_OPTION,
+            description = "the exclude module filters to apply.",
+            importance = Importance.ALWAYS)
+    protected Set<String> mExcludeFilters = new HashSet<>();
+
+    @Option(name = CompatibilityTest.ABI_OPTION,
+            shortName = 'a',
+            description = "the abi to test.",
+            importance = Importance.IF_UNSET)
+    protected String mAbiName = null;
+
+    @Option(name = CompatibilityTest.MODULE_OPTION,
+            shortName = 'm',
+            description = "the test module to run.",
+            importance = Importance.IF_UNSET)
+    protected String mModuleName = null;
+
+    @Option(name = CompatibilityTest.TEST_OPTION,
+            shortName = CompatibilityTest.TEST_OPTION_SHORT_NAME,
+            description = "the test run.",
+            importance = Importance.IF_UNSET)
+    protected String mTestName = null;
+
+    @Option(name = CompatibilityTest.RETRY_TYPE_OPTION,
+            description = "used with " + CompatibilityTest.RETRY_OPTION + ", retry tests"
+            + " of a certain status. Possible values include \"failed\" and \"not_executed\".",
+            importance = Importance.IF_UNSET)
+    protected RetryType mRetryType = null;
+
+    private List<ISystemStatusChecker> mStatusCheckers;
+    private IBuildInfo mBuildInfo;
+    private ITestDevice mDevice;
+
+    @Override
+    public void setSystemStatusChecker(List<ISystemStatusChecker> systemCheckers) {
+        mStatusCheckers = systemCheckers;
+    }
+
+    @Override
+    public void setBuild(IBuildInfo buildInfo) {
+        mBuildInfo = buildInfo;
+    }
+
+    @Override
+    public void setDevice(ITestDevice device) {
+        mDevice = device;
+    }
+
+    @Override
+    public ITestDevice getDevice() {
+        return mDevice;
+    }
+
+    /**
+     * Build a CompatibilityTest with appropriate filters to run only the tests of interests.
+     */
+    @Override
+    public void run(ITestInvocationListener listener) throws DeviceNotAvailableException {
+        // Create a compatibility test and set it to run only what we want.
+        CompatibilityTest test = new CompatibilityTest();
+
+        CompatibilityBuildHelper buildHelper = new CompatibilityBuildHelper(mBuildInfo);
+        RetryFilterHelper helper = new RetryFilterHelper(buildHelper, mRetrySessionId);
+        helper.validateBuildFingerprint(mDevice);
+        helper.setAllOptionsFrom(this);
+        helper.setCommandLineOptionsFor(test);
+        helper.setCommandLineOptionsFor(this);
+        helper.populateRetryFilters();
+
+        try {
+            OptionSetter setter = new OptionSetter(test);
+            setter.setOptionValue("compatibility:test-arg",
+                    "com.android.tradefed.testtype.AndroidJUnitTest:rerun-from-file:true");
+            setter.setOptionValue("compatibility:test-arg",
+                    "com.android.tradefed.testtype.AndroidJUnitTest:fallback-to-serial-rerun:"
+                    + "false");
+        } catch (ConfigurationException e) {
+            throw new RuntimeException(e);
+        }
+
+        test.setIncludeFilter(helper.getIncludeFilters());
+        test.setExcludeFilter(helper.getExcludeFilters());
+        test.setDevice(mDevice);
+        test.setBuild(mBuildInfo);
+        test.setSystemStatusChecker(mStatusCheckers);
+        // clean the helper
+        helper.tearDown();
+        // run the retry run.
+        test.run(listener);
+    }
+}
diff --git a/common/host-side/tradefed/src/com/android/compatibility/common/tradefed/util/RetryFilterHelper.java b/common/host-side/tradefed/src/com/android/compatibility/common/tradefed/util/RetryFilterHelper.java
index 4ff3953..e6771d3 100644
--- a/common/host-side/tradefed/src/com/android/compatibility/common/tradefed/util/RetryFilterHelper.java
+++ b/common/host-side/tradefed/src/com/android/compatibility/common/tradefed/util/RetryFilterHelper.java
@@ -19,8 +19,8 @@
 import com.android.compatibility.common.tradefed.build.CompatibilityBuildHelper;
 import com.android.compatibility.common.tradefed.result.SubPlanHelper;
 import com.android.compatibility.common.tradefed.testtype.CompatibilityTest;
-import com.android.compatibility.common.tradefed.testtype.ModuleRepo;
 import com.android.compatibility.common.tradefed.testtype.ISubPlan;
+import com.android.compatibility.common.tradefed.testtype.ModuleRepo;
 import com.android.compatibility.common.util.IInvocationResult;
 import com.android.compatibility.common.util.LightInvocationResult;
 import com.android.compatibility.common.util.ResultHandler;
@@ -33,7 +33,6 @@
 import com.android.tradefed.config.OptionSetter;
 import com.android.tradefed.device.DeviceNotAvailableException;
 import com.android.tradefed.device.ITestDevice;
-import com.android.tradefed.log.LogUtil.CLog;
 import com.android.tradefed.util.ArrayUtil;
 
 import java.io.FileNotFoundException;
@@ -127,7 +126,7 @@
 
     /**
      * Set a single option on this instance of RetryFilterHelper
-     * @throws {@link ConfigurationException} if the option cannot be set.
+     * @throw {@link ConfigurationException} if the option cannot be set.
      */
     public void setOption(String option, String value) throws ConfigurationException {
         OptionSetter setter = new OptionSetter(this);
diff --git a/tools/cts-tradefed/res/config/retry.xml b/tools/cts-tradefed/res/config/retry.xml
new file mode 100644
index 0000000..c4a7ced
--- /dev/null
+++ b/tools/cts-tradefed/res/config/retry.xml
@@ -0,0 +1,42 @@
+<?xml version="1.0" encoding="utf-8"?>
+<!-- Copyright (C) 2017 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.
+-->
+<configuration description="Runs a retry of a previous CTS session.">
+
+    <device_recovery class="com.android.tradefed.device.WaitDeviceRecovery" />
+    <build_provider class="com.android.compatibility.common.tradefed.build.CompatibilityBuildProvider" />
+    <test class="com.android.compatibility.common.tradefed.testtype.retry.RetryFactoryTest" />
+    <logger class="com.android.tradefed.log.FileLogger">
+        <option name="log-level-display" value="WARN" />
+    </logger>
+
+    <include name="cts-preconditions" />
+    <include name="cts-system-checkers" />
+    <include name="cts-known-failures" />
+
+    <option name="plan" value="cts-retry" />
+    <option name="test-tag" value="cts" />
+    <option name="skip-device-info" value="true" />
+    <option name="enable-root" value="false" />
+
+    <result_reporter class="com.android.compatibility.common.tradefed.result.ConsoleReporter" />
+    <result_reporter class="com.android.compatibility.common.tradefed.result.ResultReporter" />
+
+    <template-include name="reporters" default="basic-reporters" />
+
+    <!-- Include additional test metadata output. -->
+    <template-include name="metadata-reporters" default="empty" />
+
+</configuration>