CTS implementation of ITestSuite

- basic CTS implementation of the new ITestSuite to start
scheduling some tests and get results.
- config using the implementation to be used to run.
- Fix CTS monkey module to ensure it does not run out of
memory in case of failures.

Test: unit tests, manual
Bug: 37211399
Change-Id: I78416b3475b2a5eeeb804054ad43273cd4dddca7
diff --git a/common/host-side/tradefed/src/com/android/compatibility/common/tradefed/build/CompatibilityBuildProvider.java b/common/host-side/tradefed/src/com/android/compatibility/common/tradefed/build/CompatibilityBuildProvider.java
index df3cfbc..286f53f 100644
--- a/common/host-side/tradefed/src/com/android/compatibility/common/tradefed/build/CompatibilityBuildProvider.java
+++ b/common/host-side/tradefed/src/com/android/compatibility/common/tradefed/build/CompatibilityBuildProvider.java
@@ -56,6 +56,14 @@
     @Option(name="branch", description="build branch name to supply.")
     private String mBranch = null;
 
+    @Option(name = "build-id",
+            description =
+                    "build version number to supply. Override the default cts version number.")
+    private String mBuildId = null;
+
+    @Option(name="build-flavor", description="build flavor name to supply.")
+    private String mBuildFlavor = null;
+
     @Option(name="use-device-build-info", description="Bootstrap build info from device")
     private boolean mUseDeviceBuildInfo = false;
 
@@ -78,14 +86,22 @@
     @Override
     public IBuildInfo getBuild() {
         // Create a blank BuildInfo which will get populated later.
-        String version = getSuiteInfoBuildNumber();
-        if (version == null) {
-            version = IBuildInfo.UNKNOWN_BUILD_ID;
+        String version = null;
+        if (mBuildId != null) {
+            version = mBuildId;
+        } else {
+            version = getSuiteInfoBuildNumber();
+            if (version == null) {
+                version = IBuildInfo.UNKNOWN_BUILD_ID;
+            }
         }
-        IBuildInfo ctsBuild = new BuildInfo(version, mTestTag, mTestTag);
+        IBuildInfo ctsBuild = new BuildInfo(version, mTestTag);
         if (mBranch  != null) {
             ctsBuild.setBuildBranch(mBranch);
         }
+        if (mBuildFlavor != null) {
+            ctsBuild.setBuildFlavor(mBuildFlavor);
+        }
         addCompatibilitySuiteInfo(ctsBuild);
         return ctsBuild;
     }
@@ -103,7 +119,7 @@
         } else {
             String buildId = device.getBuildId();
             String buildFlavor = device.getBuildFlavor();
-            IBuildInfo info = new DeviceBuildInfo(buildId, mTestTag, buildFlavor);
+            IBuildInfo info = new DeviceBuildInfo(buildId, mTestTag);
             if (mBranch == null) {
                 // if branch is not specified via param, make a pseudo branch name based on platform
                 // version and product info from device
diff --git a/common/host-side/tradefed/src/com/android/compatibility/common/tradefed/targetprep/FilePusher.java b/common/host-side/tradefed/src/com/android/compatibility/common/tradefed/targetprep/FilePusher.java
index 70dc125..3eb93d4 100644
--- a/common/host-side/tradefed/src/com/android/compatibility/common/tradefed/targetprep/FilePusher.java
+++ b/common/host-side/tradefed/src/com/android/compatibility/common/tradefed/targetprep/FilePusher.java
@@ -61,6 +61,14 @@
      * {@inheritDoc}
      */
     @Override
+    public IAbi getAbi() {
+        return mAbi;
+    }
+
+    /**
+     * {@inheritDoc}
+     */
+    @Override
     public File resolveRelativeFilePath(IBuildInfo buildInfo, String fileName) {
         try {
             File f = new File(getTestsDir(buildInfo),
diff --git a/common/host-side/tradefed/src/com/android/compatibility/common/tradefed/targetprep/NetworkConnectivityChecker.java b/common/host-side/tradefed/src/com/android/compatibility/common/tradefed/targetprep/NetworkConnectivityChecker.java
index d77d931..8e038fb 100644
--- a/common/host-side/tradefed/src/com/android/compatibility/common/tradefed/targetprep/NetworkConnectivityChecker.java
+++ b/common/host-side/tradefed/src/com/android/compatibility/common/tradefed/targetprep/NetworkConnectivityChecker.java
@@ -37,7 +37,8 @@
     public boolean postExecutionCheck(ITestDevice device) throws DeviceNotAvailableException {
         if (!MonitoringUtils.checkDeviceConnectivity(device)) {
             if (mIsFailed) {
-                CLog.w("NetworkConnectivityChecker is still failing.");
+                CLog.w("NetworkConnectivityChecker is still failing on %s.",
+                        device.getSerialNumber());
                 return true;
             }
             mIsFailed = true;
diff --git a/common/host-side/tradefed/src/com/android/compatibility/common/tradefed/targetprep/TokenRequirement.java b/common/host-side/tradefed/src/com/android/compatibility/common/tradefed/targetprep/TokenRequirement.java
index 102d9c8..f1faee9 100644
--- a/common/host-side/tradefed/src/com/android/compatibility/common/tradefed/targetprep/TokenRequirement.java
+++ b/common/host-side/tradefed/src/com/android/compatibility/common/tradefed/targetprep/TokenRequirement.java
@@ -20,6 +20,7 @@
 import com.android.tradefed.config.Option;
 import com.android.tradefed.device.DeviceNotAvailableException;
 import com.android.tradefed.device.ITestDevice;
+import com.android.tradefed.log.LogUtil.CLog;
 import com.android.tradefed.targetprep.BuildError;
 import com.android.tradefed.targetprep.ITargetPreparer;
 import com.android.tradefed.targetprep.TargetSetupError;
@@ -45,8 +46,7 @@
     @Override
     public void setUp(ITestDevice device, IBuildInfo buildInfo) throws TargetSetupError,
             BuildError, DeviceNotAvailableException {
-        throw new TargetSetupError("TokenRequirement is not expected to run",
-                device.getDeviceDescriptor());
+        CLog.e("TokenRequirement is not expected to run");
     }
 
     /**
diff --git a/common/host-side/tradefed/src/com/android/compatibility/common/tradefed/testtype/CompatibilityHostTestBase.java b/common/host-side/tradefed/src/com/android/compatibility/common/tradefed/testtype/CompatibilityHostTestBase.java
index b2e2a33..bd4653b 100644
--- a/common/host-side/tradefed/src/com/android/compatibility/common/tradefed/testtype/CompatibilityHostTestBase.java
+++ b/common/host-side/tradefed/src/com/android/compatibility/common/tradefed/testtype/CompatibilityHostTestBase.java
@@ -77,6 +77,11 @@
     }
 
     @Override
+    public IAbi getAbi() {
+        return mAbi;
+    }
+
+    @Override
     public void setBuild(IBuildInfo buildInfo) {
         // Get the build, this is used to access the APK.
         mBuild = buildInfo;
diff --git a/common/host-side/tradefed/src/com/android/compatibility/common/tradefed/testtype/suite/CompatibilityTestSuite.java b/common/host-side/tradefed/src/com/android/compatibility/common/tradefed/testtype/suite/CompatibilityTestSuite.java
new file mode 100644
index 0000000..aecdd0e
--- /dev/null
+++ b/common/host-side/tradefed/src/com/android/compatibility/common/tradefed/testtype/suite/CompatibilityTestSuite.java
@@ -0,0 +1,274 @@
+/*
+ * 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.suite;
+
+import com.android.compatibility.SuiteInfo;
+import com.android.compatibility.common.tradefed.build.CompatibilityBuildHelper;
+import com.android.compatibility.common.tradefed.testtype.ISubPlan;
+import com.android.compatibility.common.tradefed.testtype.ModuleRepo;
+import com.android.compatibility.common.tradefed.testtype.SubPlan;
+import com.android.compatibility.common.util.TestFilter;
+import com.android.tradefed.build.IBuildInfo;
+import com.android.tradefed.config.IConfiguration;
+import com.android.tradefed.config.Option;
+import com.android.tradefed.config.Option.Importance;
+import com.android.tradefed.config.OptionClass;
+import com.android.tradefed.device.DeviceNotAvailableException;
+import com.android.tradefed.device.ITestDevice;
+import com.android.tradefed.log.LogUtil.CLog;
+import com.android.tradefed.testtype.Abi;
+import com.android.tradefed.testtype.IAbi;
+import com.android.tradefed.testtype.suite.ITestSuite;
+import com.android.tradefed.util.AbiFormatter;
+import com.android.tradefed.util.AbiUtils;
+import com.android.tradefed.util.ArrayUtil;
+import com.android.tradefed.util.xml.AbstractXmlParser.ParseException;
+
+import java.io.File;
+import java.io.FileInputStream;
+import java.io.FileNotFoundException;
+import java.io.InputStream;
+import java.util.ArrayList;
+import java.util.Arrays;
+import java.util.HashSet;
+import java.util.LinkedHashMap;
+import java.util.LinkedHashSet;
+import java.util.List;
+import java.util.Set;
+
+/**
+ * A Test for running Compatibility Test Suite with new suite system.
+ */
+@OptionClass(alias = "compatibility")
+public class CompatibilityTestSuite extends ITestSuite {
+
+    private static final String INCLUDE_FILTER_OPTION = "include-filter";
+    private static final String EXCLUDE_FILTER_OPTION = "exclude-filter";
+    private static final String SUBPLAN_OPTION = "subplan";
+    private static final String MODULE_OPTION = "module";
+    private static final String TEST_OPTION = "test";
+    private static final String PRECONDITION_ARG_OPTION = "precondition-arg";
+    private static final String MODULE_ARG_OPTION = "module-arg";
+    private static final String TEST_ARG_OPTION = "test-arg";
+    private static final String ABI_OPTION = "abi";
+    private static final String SKIP_HOST_ARCH_CHECK = "skip-host-arch-check";
+    private static final String PRIMARY_ABI_RUN = "primary-abi-only";
+    private static final String PRODUCT_CPU_ABI_KEY = "ro.product.cpu.abi";
+    
+    @Option(name = SUBPLAN_OPTION,
+            description = "the subplan to run",
+            importance = Importance.IF_UNSET)
+    private String mSubPlan;
+
+    @Option(name = INCLUDE_FILTER_OPTION,
+            description = "the include module filters to apply.",
+            importance = Importance.ALWAYS)
+    private Set<String> mIncludeFilters = new HashSet<>();
+
+    @Option(name = EXCLUDE_FILTER_OPTION,
+            description = "the exclude module filters to apply.",
+            importance = Importance.ALWAYS)
+    private Set<String> mExcludeFilters = new HashSet<>();
+
+    @Option(name = MODULE_OPTION,
+            shortName = 'm',
+            description = "the test module to run.",
+            importance = Importance.IF_UNSET)
+    private String mModuleName = null;
+
+    @Option(name = TEST_OPTION,
+            shortName = 't',
+            description = "the test to run.",
+            importance = Importance.IF_UNSET)
+    private String mTestName = null;
+
+    @Option(name = PRECONDITION_ARG_OPTION,
+            description = "the arguments to pass to a precondition. The expected format is "
+                    + "\"<arg-name>:<arg-value>\"",
+            importance = Importance.ALWAYS)
+    private List<String> mPreconditionArgs = new ArrayList<>();
+
+    @Option(name = MODULE_ARG_OPTION,
+            description = "the arguments to pass to a module. The expected format is "
+                    + "\"<module-name>:<arg-name>:<arg-value>\"",
+            importance = Importance.ALWAYS)
+    private List<String> mModuleArgs = new ArrayList<>();
+
+    @Option(name = TEST_ARG_OPTION,
+            description = "the arguments to pass to a test. The expected format is "
+                    + "\"<test-class>:<arg-name>:<arg-value>\"",
+            importance = Importance.ALWAYS)
+    private List<String> mTestArgs = new ArrayList<>();
+
+    @Option(name = ABI_OPTION,
+            shortName = 'a',
+            description = "the abi to test.",
+            importance = Importance.IF_UNSET)
+    private String mAbiName = null;
+
+    @Option(name = SKIP_HOST_ARCH_CHECK,
+            description = "Whether host architecture check should be skipped")
+    private boolean mSkipHostArchCheck = false;
+
+    @Option(name = PRIMARY_ABI_RUN,
+            description = "Whether to run tests with only the device primary abi. "
+                    + "This override the --abi option.")
+    private boolean mPrimaryAbiRun = false;
+
+    private ModuleRepoSuite mModuleRepo = new ModuleRepoSuite();
+    private CompatibilityBuildHelper mBuildHelper;
+
+    /**
+     * {@inheritDoc}
+     */
+    @Override
+    public LinkedHashMap<String, IConfiguration> loadTests() {
+        try {
+            setupFilters();
+            Set<IAbi> abis = getAbis(getDevice());
+            // Initialize the repository, {@link CompatibilityBuildHelper#getTestsDir} can
+            // throw a {@link FileNotFoundException}
+            return mModuleRepo.loadConfigs(mBuildHelper.getTestsDir(),
+                    abis, mTestArgs, mModuleArgs, mIncludeFilters,
+                    mExcludeFilters);
+        } catch (DeviceNotAvailableException | FileNotFoundException e) {
+            throw new RuntimeException(e);
+        }
+    }
+
+    /**
+     * {@inheritDoc}
+     */
+    @Override
+    public void setBuild(IBuildInfo buildInfo) {
+        super.setBuild(buildInfo);
+        mBuildHelper = new CompatibilityBuildHelper(buildInfo);
+    }
+
+    /**
+     * Gets the set of ABIs supported by both Compatibility and the device under test
+     *
+     * @return The set of ABIs to run the tests on
+     * @throws DeviceNotAvailableException
+     */
+    Set<IAbi> getAbis(ITestDevice device) throws DeviceNotAvailableException {
+        Set<IAbi> abis = new LinkedHashSet<>();
+        Set<String> archAbis = getAbisForBuildTargetArch();
+        if (mPrimaryAbiRun) {
+            if (mAbiName == null) {
+                // Get the primary from the device and make it the --abi to run.
+                mAbiName = device.getProperty(PRODUCT_CPU_ABI_KEY).trim();
+            } else {
+                CLog.d("Option --%s supersedes the option --%s, using abi: %s", ABI_OPTION,
+                        PRIMARY_ABI_RUN, mAbiName);
+            }
+        }
+        if (mAbiName != null) {
+            // A particular abi was requested, it still needs to be supported by the build.
+            if ((!mSkipHostArchCheck && !archAbis.contains(mAbiName)) ||
+                    !AbiUtils.isAbiSupportedByCompatibility(mAbiName)) {
+                throw new IllegalArgumentException(String.format("Your CTS hasn't been built with "
+                        + "abi '%s' support, this CTS currently supports '%s'.",
+                        mAbiName, archAbis));
+            } else {
+                abis.add(new Abi(mAbiName, AbiUtils.getBitness(mAbiName)));
+                return abis;
+            }
+        } else {
+            // Run on all abi in common between the device and CTS.
+            List<String> deviceAbis = Arrays.asList(AbiFormatter.getSupportedAbis(device, ""));
+            for (String abi : deviceAbis) {
+                if ((mSkipHostArchCheck || archAbis.contains(abi)) &&
+                        AbiUtils.isAbiSupportedByCompatibility(abi)) {
+                    abis.add(new Abi(abi, AbiUtils.getBitness(abi)));
+                } else {
+                    CLog.d("abi '%s' is supported by device but not by this CTS build (%s), tests "
+                            + "will not run against it.", abi, archAbis);
+                }
+            }
+            if (abis.isEmpty()) {
+                throw new IllegalArgumentException(String.format("None of the abi supported by this"
+                       + " CTS build ('%s') are supported by the device ('%s').",
+                       archAbis, deviceAbis));
+            }
+            return abis;
+        }
+    }
+
+    /**
+     * Return the abis supported by the Host build target architecture.
+     * Exposed for testing.
+     */
+    protected Set<String> getAbisForBuildTargetArch() {
+        return AbiUtils.getAbisForArch(SuiteInfo.TARGET_ARCH);
+    }
+
+    /**
+     * Sets the include/exclude filters up based on if a module name was given or whether this is a
+     * retry run.
+     */
+    void setupFilters() throws FileNotFoundException {
+        if (mSubPlan != null) {
+            try {
+                File subPlanFile = new File(mBuildHelper.getSubPlansDir(), mSubPlan + ".xml");
+                if (!subPlanFile.exists()) {
+                    throw new IllegalArgumentException(
+                            String.format("Could not retrieve subplan \"%s\"", mSubPlan));
+                }
+                InputStream subPlanInputStream = new FileInputStream(subPlanFile);
+                ISubPlan subPlan = new SubPlan();
+                subPlan.parse(subPlanInputStream);
+                mIncludeFilters.addAll(subPlan.getIncludeFilters());
+                mExcludeFilters.addAll(subPlan.getExcludeFilters());
+            } catch (ParseException e) {
+                throw new RuntimeException(
+                        String.format("Unable to find or parse subplan %s", mSubPlan), e);
+            }
+        }
+        if (mModuleName != null) {
+            List<String> modules = ModuleRepo.getModuleNamesMatching(
+                    mBuildHelper.getTestsDir(), mModuleName);
+            if (modules.size() == 0) {
+                throw new IllegalArgumentException(
+                        String.format("No modules found matching %s", mModuleName));
+            } else if (modules.size() > 1) {
+                throw new IllegalArgumentException(String.format(
+                        "Multiple modules found matching %s:\n%s\nWhich one did you mean?\n",
+                        mModuleName, ArrayUtil.join("\n", modules)));
+            } else {
+                String moduleName = modules.get(0);
+                checkFilters(mIncludeFilters, moduleName);
+                checkFilters(mExcludeFilters, moduleName);
+                mIncludeFilters.add(new TestFilter(mAbiName, moduleName, mTestName).toString());
+            }
+        } else if (mTestName != null) {
+            throw new IllegalArgumentException(
+                    "Test name given without module name. Add --module <module-name>");
+        }
+    }
+
+    /* Helper method designed to remove filters in a list not applicable to the given module */
+    private static void checkFilters(Set<String> filters, String moduleName) {
+        Set<String> cleanedFilters = new HashSet<String>();
+        for (String filter : filters) {
+            if (moduleName.equals(TestFilter.createFrom(filter).getName())) {
+                cleanedFilters.add(filter); // Module name matches, filter passes
+            }
+        }
+        filters.clear();
+        filters.addAll(cleanedFilters);
+    }
+}
diff --git a/common/host-side/tradefed/src/com/android/compatibility/common/tradefed/testtype/suite/ModuleRepoSuite.java b/common/host-side/tradefed/src/com/android/compatibility/common/tradefed/testtype/suite/ModuleRepoSuite.java
new file mode 100644
index 0000000..acfb80b
--- /dev/null
+++ b/common/host-side/tradefed/src/com/android/compatibility/common/tradefed/testtype/suite/ModuleRepoSuite.java
@@ -0,0 +1,300 @@
+/*
+ * 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.suite;
+
+import com.android.compatibility.common.util.TestFilter;
+import com.android.tradefed.config.ConfigurationException;
+import com.android.tradefed.config.ConfigurationFactory;
+import com.android.tradefed.config.IConfiguration;
+import com.android.tradefed.config.IConfigurationFactory;
+import com.android.tradefed.log.LogUtil.CLog;
+import com.android.tradefed.targetprep.ITargetPreparer;
+import com.android.tradefed.testtype.IAbi;
+import com.android.tradefed.testtype.IAbiReceiver;
+import com.android.tradefed.testtype.IRemoteTest;
+import com.android.tradefed.testtype.ITestFileFilterReceiver;
+import com.android.tradefed.testtype.ITestFilterReceiver;
+import com.android.tradefed.util.AbiUtils;
+import com.android.tradefed.util.FileUtil;
+import com.android.tradefed.util.StreamUtil;
+
+import java.io.File;
+import java.io.FilenameFilter;
+import java.io.IOException;
+import java.io.PrintWriter;
+import java.util.ArrayList;
+import java.util.Collection;
+import java.util.HashMap;
+import java.util.LinkedHashMap;
+import java.util.List;
+import java.util.Map;
+import java.util.Map.Entry;
+import java.util.Set;
+
+/**
+ * Retrieves Compatibility test module definitions from the repository.
+ */
+public class ModuleRepoSuite {
+
+    private static final String CONFIG_EXT = ".config";
+    private Map<String, Map<String, List<String>>> mTestArgs = new HashMap<>();
+    private Map<String, Map<String, List<String>>> mModuleArgs = new HashMap<>();
+    private boolean mIncludeAll;
+    private Map<String, List<TestFilter>> mIncludeFilters = new HashMap<>();
+    private Map<String, List<TestFilter>> mExcludeFilters = new HashMap<>();
+    private IConfigurationFactory mConfigFactory = ConfigurationFactory.getInstance();
+
+    /**
+     * Main loading of configurations, looking into testcases/ folder
+     */
+    public LinkedHashMap<String, IConfiguration> loadConfigs(File testsDir, Set<IAbi> abis,
+            List<String> testArgs, List<String> moduleArgs,
+            Set<String> includeFilters, Set<String> excludeFilters) {
+        CLog.d("Initializing ModuleRepo\nTests Dir:%s\nABIs:%s\n" +
+                "Test Args:%s\nModule Args:%s\nIncludes:%s\nExcludes:%s",
+                testsDir.getAbsolutePath(), abis, testArgs, moduleArgs,
+                includeFilters, excludeFilters);
+
+        LinkedHashMap<String, IConfiguration> toRun = new LinkedHashMap<>();
+
+        putArgs(testArgs, mTestArgs);
+        putArgs(moduleArgs, mModuleArgs);
+        mIncludeAll = includeFilters.isEmpty();
+        // Include all the inclusions
+        addFilters(includeFilters, mIncludeFilters, abis);
+        // Exclude all the exclusions
+        addFilters(excludeFilters, mExcludeFilters, abis);
+
+        File[] configFiles = testsDir.listFiles(new ConfigFilter());
+        if (configFiles.length == 0) {
+            throw new IllegalArgumentException(
+                    String.format("No config files found in %s", testsDir.getAbsolutePath()));
+        }
+        for (File configFile : configFiles) {
+            final String name = configFile.getName().replace(CONFIG_EXT, "");
+            final String[] pathArg = new String[] { configFile.getAbsolutePath() };
+            try {
+                // Invokes parser to process the test module config file
+                // Need to generate a different config for each ABI as we cannot guarantee the
+                // configs are idempotent. This however means we parse the same file multiple times
+                for (IAbi abi : abis) {
+                    IConfiguration config = mConfigFactory.createConfigurationFromArgs(pathArg);
+                    String id = AbiUtils.createId(abi.getName(), name);
+                    if (!shouldRunModule(id)) {
+                        // If the module should not run tests based on the state of filters,
+                        // skip this name/abi combination.
+                        continue;
+                    }
+
+                    Map<String, List<String>> args = new HashMap<>();
+                    if (mModuleArgs.containsKey(name)) {
+                        args.putAll(mModuleArgs.get(name));
+                    }
+                    if (mModuleArgs.containsKey(id)) {
+                        args.putAll(mModuleArgs.get(id));
+                    }
+                    for (Entry<String, List<String>> entry : args.entrySet()) {
+                        for (String value : entry.getValue()) {
+                            // Collection-type options can be injected with multiple values
+                            config.injectOptionValue(entry.getKey(), value);
+                        }
+                    }
+
+                    List<IRemoteTest> tests = config.getTests();
+                    for (IRemoteTest test : tests) {
+                        String className = test.getClass().getName();
+                        Map<String, List<String>> testArgsMap = new HashMap<>();
+                        if (mTestArgs.containsKey(className)) {
+                            testArgsMap.putAll(mTestArgs.get(className));
+                        }
+                        for (Entry<String, List<String>> entry : testArgsMap.entrySet()) {
+                            for (String value : entry.getValue()) {
+                                config.injectOptionValue(entry.getKey(), value);
+                            }
+                        }
+                        addFiltersToTest(test, abi, name);
+                        if (test instanceof IAbiReceiver) {
+                            ((IAbiReceiver)test).setAbi(abi);
+                        }
+                    }
+                    List<ITargetPreparer> preparers = config.getTargetPreparers();
+                    for (ITargetPreparer preparer : preparers) {
+                        if (preparer instanceof IAbiReceiver) {
+                            ((IAbiReceiver)preparer).setAbi(abi);
+                        }
+                    }
+                    toRun.put(id, config);
+                }
+            } catch (ConfigurationException e) {
+                throw new RuntimeException(String.format("Error parsing config file: %s",
+                        configFile.getName()), e);
+            }
+        }
+        return toRun;
+    }
+
+    private void addFilters(Set<String> stringFilters,
+            Map<String, List<TestFilter>> filters, Set<IAbi> abis) {
+        for (String filterString : stringFilters) {
+            TestFilter filter = TestFilter.createFrom(filterString);
+            String abi = filter.getAbi();
+            if (abi == null) {
+                for (IAbi a : abis) {
+                    addFilter(a.getName(), filter, filters);
+                }
+            } else {
+                addFilter(abi, filter, filters);
+            }
+        }
+    }
+
+    private void addFilter(String abi, TestFilter filter,
+            Map<String, List<TestFilter>> filters) {
+        getFilterList(filters, AbiUtils.createId(abi, filter.getName())).add(filter);
+    }
+
+    private List<TestFilter> getFilterList(Map<String, List<TestFilter>> filters, String id) {
+        List<TestFilter> fs = filters.get(id);
+        if (fs == null) {
+            fs = new ArrayList<>();
+            filters.put(id, fs);
+        }
+        return fs;
+    }
+
+    private void addFiltersToTest(IRemoteTest test, IAbi abi, String name) {
+        String moduleId = AbiUtils.createId(abi.getName(), name);
+        if (!(test instanceof ITestFilterReceiver)) {
+            throw new IllegalArgumentException(String.format(
+                    "Test in module %s must implement ITestFilterReceiver.", moduleId));
+        }
+        List<TestFilter> mdIncludes = getFilterList(mIncludeFilters, moduleId);
+        List<TestFilter> mdExcludes = getFilterList(mExcludeFilters, moduleId);
+        if (!mdIncludes.isEmpty()) {
+            addTestIncludes((ITestFilterReceiver) test, mdIncludes, name);
+        }
+        if (!mdExcludes.isEmpty()) {
+            addTestExcludes((ITestFilterReceiver) test, mdExcludes, name);
+        }
+    }
+
+    private boolean shouldRunModule(String moduleId) {
+        List<TestFilter> mdIncludes = getFilterList(mIncludeFilters, moduleId);
+        List<TestFilter> mdExcludes = getFilterList(mExcludeFilters, moduleId);
+        // if including all modules or includes exist for this module, and there are not excludes
+        // for the entire module, this module should be run.
+        return (mIncludeAll || !mdIncludes.isEmpty()) && !containsModuleExclude(mdExcludes);
+    }
+
+    private void addTestIncludes(ITestFilterReceiver test, List<TestFilter> includes,
+            String name) {
+        if (test instanceof ITestFileFilterReceiver) {
+            File includeFile = createFilterFile(name, ".include", includes);
+            ((ITestFileFilterReceiver)test).setIncludeTestFile(includeFile);
+        } else {
+            // add test includes one at a time
+            for (TestFilter include : includes) {
+                String filterTestName = include.getTest();
+                if (filterTestName != null) {
+                    test.addIncludeFilter(filterTestName);
+                }
+            }
+        }
+    }
+
+    private void addTestExcludes(ITestFilterReceiver test, List<TestFilter> excludes,
+            String name) {
+        if (test instanceof ITestFileFilterReceiver) {
+            File excludeFile = createFilterFile(name, ".exclude", excludes);
+            ((ITestFileFilterReceiver)test).setExcludeTestFile(excludeFile);
+        } else {
+            // add test excludes one at a time
+            for (TestFilter exclude : excludes) {
+                test.addExcludeFilter(exclude.getTest());
+            }
+        }
+    }
+
+    private File createFilterFile(String prefix, String suffix, List<TestFilter> filters) {
+        File filterFile = null;
+        PrintWriter out = null;
+        try {
+            filterFile = FileUtil.createTempFile(prefix, suffix);
+            out = new PrintWriter(filterFile);
+            for (TestFilter filter : filters) {
+                String filterTest = filter.getTest();
+                if (filterTest != null) {
+                    out.println(filterTest);
+                }
+            }
+            out.flush();
+        } catch (IOException e) {
+            throw new RuntimeException("Failed to create filter file");
+        } finally {
+            StreamUtil.close(out);
+        }
+        filterFile.deleteOnExit();
+        return filterFile;
+    }
+
+    /**
+     * Returns true iff one or more test filters in excludes apply to the entire module.
+     */
+    private boolean containsModuleExclude(Collection<TestFilter> excludes) {
+        for (TestFilter exclude : excludes) {
+            if (exclude.getTest() == null) {
+                return true;
+            }
+        }
+        return false;
+    }
+
+    /**
+     * A {@link FilenameFilter} to find all the config files in a directory.
+     */
+    public static class ConfigFilter implements FilenameFilter {
+
+        /**
+         * {@inheritDoc}
+         */
+        @Override
+        public boolean accept(File dir, String name) {
+            CLog.d("%s/%s", dir.getAbsolutePath(), name);
+            return name.endsWith(CONFIG_EXT);
+        }
+    }
+
+    private static void putArgs(List<String> args,
+            Map<String, Map<String, List<String>>> argsMap) {
+        for (String arg : args) {
+            String[] parts = arg.split(":");
+            String target = parts[0];
+            String key = parts[1];
+            String value = parts[2];
+            Map<String, List<String>> map = argsMap.get(target);
+            if (map == null) {
+                map = new HashMap<>();
+                argsMap.put(target, map);
+            }
+            List<String> valueList = map.get(key);
+            if (valueList == null) {
+                valueList = new ArrayList<>();
+                map.put(key, valueList);
+            }
+            valueList.add(value);
+        }
+    }
+}
diff --git a/common/host-side/tradefed/tests/src/com/android/compatibility/common/tradefed/UnitTests.java b/common/host-side/tradefed/tests/src/com/android/compatibility/common/tradefed/UnitTests.java
index c42c410..fc04085 100644
--- a/common/host-side/tradefed/tests/src/com/android/compatibility/common/tradefed/UnitTests.java
+++ b/common/host-side/tradefed/tests/src/com/android/compatibility/common/tradefed/UnitTests.java
@@ -36,6 +36,7 @@
 import com.android.compatibility.common.tradefed.testtype.ModuleRepoTest;
 import com.android.compatibility.common.tradefed.testtype.SubPlanTest;
 import com.android.compatibility.common.tradefed.testtype.retry.RetryFactoryTestTest;
+import com.android.compatibility.common.tradefed.testtype.suite.CompatibilityTestSuiteTest;
 import com.android.compatibility.common.tradefed.util.CollectorUtilTest;
 import com.android.compatibility.common.tradefed.util.OptionHelperTest;
 import com.android.compatibility.common.tradefed.util.RetryFilterHelperTest;
@@ -89,6 +90,9 @@
     // testtype.retry
     RetryFactoryTestTest.class,
 
+    // testype.suite
+    CompatibilityTestSuiteTest.class,
+
     // util
     CollectorUtilTest.class,
     OptionHelperTest.class,
diff --git a/common/host-side/tradefed/tests/src/com/android/compatibility/common/tradefed/testtype/ModuleDefTest.java b/common/host-side/tradefed/tests/src/com/android/compatibility/common/tradefed/testtype/ModuleDefTest.java
index 4aa67ac..356207e 100644
--- a/common/host-side/tradefed/tests/src/com/android/compatibility/common/tradefed/testtype/ModuleDefTest.java
+++ b/common/host-side/tradefed/tests/src/com/android/compatibility/common/tradefed/testtype/ModuleDefTest.java
@@ -104,6 +104,11 @@
         }
 
         @Override
+        public IAbi getAbi() {
+            return null;
+        }
+
+        @Override
         public long getRuntimeHint() {
             return 1L;
         }
diff --git a/common/host-side/tradefed/tests/src/com/android/compatibility/common/tradefed/testtype/ModuleRepoTest.java b/common/host-side/tradefed/tests/src/com/android/compatibility/common/tradefed/testtype/ModuleRepoTest.java
index 504d5e9..edee5eb 100644
--- a/common/host-side/tradefed/tests/src/com/android/compatibility/common/tradefed/testtype/ModuleRepoTest.java
+++ b/common/host-side/tradefed/tests/src/com/android/compatibility/common/tradefed/testtype/ModuleRepoTest.java
@@ -430,6 +430,8 @@
         public void setCollectTestsOnly(boolean arg0) {}
         @Override
         public void setAbi(IAbi arg0) {}
+        @Override
+        public IAbi getAbi() {return null;}
     }
 
     /**
diff --git a/common/host-side/tradefed/tests/src/com/android/compatibility/common/tradefed/testtype/ShardableTestStub.java b/common/host-side/tradefed/tests/src/com/android/compatibility/common/tradefed/testtype/ShardableTestStub.java
index 1a63b03..7b43972 100644
--- a/common/host-side/tradefed/tests/src/com/android/compatibility/common/tradefed/testtype/ShardableTestStub.java
+++ b/common/host-side/tradefed/tests/src/com/android/compatibility/common/tradefed/testtype/ShardableTestStub.java
@@ -90,6 +90,11 @@
     }
 
     @Override
+    public IAbi getAbi() {
+        return null;
+    }
+
+    @Override
     public long getRuntimeHint() {
         return 1L;
     }
diff --git a/common/host-side/tradefed/tests/src/com/android/compatibility/common/tradefed/testtype/TestStub.java b/common/host-side/tradefed/tests/src/com/android/compatibility/common/tradefed/testtype/TestStub.java
index 6f199ae..a154f31 100644
--- a/common/host-side/tradefed/tests/src/com/android/compatibility/common/tradefed/testtype/TestStub.java
+++ b/common/host-side/tradefed/tests/src/com/android/compatibility/common/tradefed/testtype/TestStub.java
@@ -132,6 +132,11 @@
     }
 
     @Override
+    public IAbi getAbi() {
+        return null;
+    }
+
+    @Override
     public long getRuntimeHint() {
         return 1L;
     }
diff --git a/common/host-side/tradefed/tests/src/com/android/compatibility/common/tradefed/testtype/suite/CompatibilityTestSuiteTest.java b/common/host-side/tradefed/tests/src/com/android/compatibility/common/tradefed/testtype/suite/CompatibilityTestSuiteTest.java
new file mode 100644
index 0000000..86569ef
--- /dev/null
+++ b/common/host-side/tradefed/tests/src/com/android/compatibility/common/tradefed/testtype/suite/CompatibilityTestSuiteTest.java
@@ -0,0 +1,179 @@
+/*
+ * 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.suite;
+
+import static org.junit.Assert.assertEquals;
+import static org.junit.Assert.assertTrue;
+import static org.junit.Assert.fail;
+
+import com.android.compatibility.common.tradefed.testtype.CompatibilityTest;
+import com.android.tradefed.config.OptionSetter;
+import com.android.tradefed.device.DeviceNotAvailableException;
+import com.android.tradefed.device.ITestDevice;
+import com.android.tradefed.testtype.IAbi;
+import com.android.tradefed.util.AbiUtils;
+
+import org.easymock.EasyMock;
+import org.junit.Before;
+import org.junit.Test;
+
+import java.util.HashSet;
+import java.util.Set;
+
+/**
+ * Unit tests for {@link CompatibilityTestSuite}.
+ */
+public class CompatibilityTestSuiteTest {
+
+    private static final String FAKE_HOST_ARCH = "arm";
+    private CompatibilityTestSuite mTest;
+    private ITestDevice mMockDevice;
+
+    @Before
+    public void setUp() throws Exception {
+        mTest = new CompatibilityTestSuite() {
+            @Override
+            protected Set<String> getAbisForBuildTargetArch() {
+                return AbiUtils.getAbisForArch(FAKE_HOST_ARCH);
+            }
+        };
+        mMockDevice = EasyMock.createMock(ITestDevice.class);
+        mTest.setDevice(mMockDevice);
+    }
+
+    /**
+     * Test that {@link CompatibilityTestSuite#getAbis(ITestDevice)} is returning a proper
+     * intersection of CTS supported architectures and Device supported architectures.
+     */
+    @Test
+    public void testGetAbis() throws DeviceNotAvailableException {
+        EasyMock.expect(mMockDevice.getProperty(EasyMock.eq("ro.product.cpu.abilist")))
+                .andReturn("arm64-v8a,armeabi-v7a,armeabi");
+        Set<String> expectedAbis = new HashSet<>();
+        expectedAbis.add("arm64-v8a");
+        expectedAbis.add("armeabi-v7a");
+        EasyMock.replay(mMockDevice);
+        Set<IAbi> res = mTest.getAbis(mMockDevice);
+        assertEquals(2, res.size());
+        for (IAbi abi : res) {
+            assertTrue(expectedAbis.contains(abi.getName()));
+        }
+        EasyMock.verify(mMockDevice);
+    }
+
+    /**
+     * Test that {@link CompatibilityTestSuite#getAbis(ITestDevice)} is throwing an exception when
+     * none of the CTS build supported abi match the device abi.
+     */
+    @Test
+    public void testGetAbis_notSupported() throws DeviceNotAvailableException {
+        EasyMock.expect(mMockDevice.getProperty(EasyMock.eq("ro.product.cpu.abilist")))
+                .andReturn("armeabi");
+        EasyMock.replay(mMockDevice);
+        try {
+            mTest.getAbis(mMockDevice);
+            fail("Should have thrown an exception");
+        } catch (IllegalArgumentException e) {
+            assertEquals("None of the abi supported by this CTS build ('[armeabi-v7a, arm64-v8a]')"
+                    + " are supported by the device ('[armeabi]').", e.getMessage());
+        }
+        EasyMock.verify(mMockDevice);
+    }
+
+    /**
+     * Test that {@link CompatibilityTestSuite#getAbis(ITestDevice)} is returning only the device
+     * primary abi.
+     */
+    @Test
+    public void testGetAbis_primaryAbiOnly() throws Exception {
+        OptionSetter setter = new OptionSetter(mTest);
+        setter.setOptionValue(CompatibilityTest.PRIMARY_ABI_RUN, "true");
+        EasyMock.expect(mMockDevice.getProperty(EasyMock.eq("ro.product.cpu.abi")))
+                .andReturn("arm64-v8a");
+        Set<String> expectedAbis = new HashSet<>();
+        expectedAbis.add("arm64-v8a");
+        EasyMock.replay(mMockDevice);
+        Set<IAbi> res = mTest.getAbis(mMockDevice);
+        assertEquals(1, res.size());
+        for (IAbi abi : res) {
+            assertTrue(expectedAbis.contains(abi.getName()));
+        }
+        EasyMock.verify(mMockDevice);
+    }
+
+    /**
+     * Test that {@link CompatibilityTestSuite#getAbis(ITestDevice)} is throwing an exception if
+     * the primary abi is not supported.
+     */
+    @Test
+    public void testGetAbis_primaryAbiOnly_NotSupported() throws Exception {
+        OptionSetter setter = new OptionSetter(mTest);
+        setter.setOptionValue(CompatibilityTest.PRIMARY_ABI_RUN, "true");
+        EasyMock.expect(mMockDevice.getProperty(EasyMock.eq("ro.product.cpu.abi")))
+                .andReturn("armeabi");
+        EasyMock.replay(mMockDevice);
+        try {
+            mTest.getAbis(mMockDevice);
+            fail("Should have thrown an exception");
+        } catch (IllegalArgumentException e) {
+            assertEquals("Your CTS hasn't been built with abi 'armeabi' support, "
+                    + "this CTS currently supports '[armeabi-v7a, arm64-v8a]'.", e.getMessage());
+        }
+        EasyMock.verify(mMockDevice);
+    }
+
+    /**
+     * Test that {@link CompatibilityTestSuite#getAbis(ITestDevice)} is returning the list of abi
+     * supported by Compatibility and the device, and not the particular CTS build.
+     */
+    @Test
+    public void testGetAbis_skipCtsArchCheck() throws Exception {
+        OptionSetter setter = new OptionSetter(mTest);
+        setter.setOptionValue(CompatibilityTest.SKIP_HOST_ARCH_CHECK, "true");
+        EasyMock.expect(mMockDevice.getProperty(EasyMock.eq("ro.product.cpu.abilist")))
+                .andReturn("x86_64,x86,armeabi");
+        Set<String> expectedAbis = new HashSet<>();
+        expectedAbis.add("x86_64");
+        expectedAbis.add("x86");
+        EasyMock.replay(mMockDevice);
+        Set<IAbi> res = mTest.getAbis(mMockDevice);
+        assertEquals(2, res.size());
+        for (IAbi abi : res) {
+            assertTrue(expectedAbis.contains(abi.getName()));
+        }
+        EasyMock.verify(mMockDevice);
+    }
+
+    /**
+     * Test {@link CompatibilityTestSuite#getAbis(ITestDevice)} when we skip the Cts side
+     * architecture check and want to run x86 abi.
+     */
+    @Test
+    public void testGetAbis_skipCtsArchCheck_abiSpecified() throws Exception {
+        OptionSetter setter = new OptionSetter(mTest);
+        setter.setOptionValue(CompatibilityTest.SKIP_HOST_ARCH_CHECK, "true");
+        setter.setOptionValue(CompatibilityTest.ABI_OPTION, "x86");
+        Set<String> expectedAbis = new HashSet<>();
+        expectedAbis.add("x86");
+        EasyMock.replay(mMockDevice);
+        Set<IAbi> res = mTest.getAbis(mMockDevice);
+        assertEquals(1, res.size());
+        for (IAbi abi : res) {
+            assertTrue(expectedAbis.contains(abi.getName()));
+        }
+        EasyMock.verify(mMockDevice);
+    }
+}
diff --git a/common/host-side/util/src/com/android/compatibility/common/util/MonitoringUtils.java b/common/host-side/util/src/com/android/compatibility/common/util/MonitoringUtils.java
index e24ca40..148a9a5 100644
--- a/common/host-side/util/src/com/android/compatibility/common/util/MonitoringUtils.java
+++ b/common/host-side/util/src/com/android/compatibility/common/util/MonitoringUtils.java
@@ -42,7 +42,8 @@
                 return true;
             } else {
                 CLog.logAndDisplay(LogLevel.INFO,
-                        "Connectivity check failed, retrying in %dms",
+                        "Connectivity check failed on %s, retrying in %dms",
+                        device.getSerialNumber(),
                         CONNECTIVITY_CHECK_INTERVAL_MS);
                 RunUtil.getDefault().sleep(CONNECTIVITY_CHECK_INTERVAL_MS);
             }
diff --git a/hostsidetests/jvmti/base/host/src/android/jvmti/cts/JvmtiHostTest.java b/hostsidetests/jvmti/base/host/src/android/jvmti/cts/JvmtiHostTest.java
index 6894a21..f042a89 100644
--- a/hostsidetests/jvmti/base/host/src/android/jvmti/cts/JvmtiHostTest.java
+++ b/hostsidetests/jvmti/base/host/src/android/jvmti/cts/JvmtiHostTest.java
@@ -31,6 +31,7 @@
 import java.util.LinkedList;
 import java.util.List;
 import java.util.Map;
+import java.util.concurrent.TimeUnit;
 import java.util.zip.ZipFile;
 
 /**
@@ -80,6 +81,8 @@
 
         RemoteAndroidTestRunner runner = new RemoteAndroidTestRunner(mTestPackageName, RUNNER,
                 device.getIDevice());
+        // set a max deadline limit to avoid hanging forever
+        runner.setMaxTimeToOutputResponse(2, TimeUnit.MINUTES);
 
         TestResults tr = new TestResults(new AttachAgent(device, mTestPackageName, mTestApk));
 
diff --git a/hostsidetests/monkey/src/com/android/cts/monkey/CategoryTest.java b/hostsidetests/monkey/src/com/android/cts/monkey/CategoryTest.java
index ff04f6c..f3bb5d9 100644
--- a/hostsidetests/monkey/src/com/android/cts/monkey/CategoryTest.java
+++ b/hostsidetests/monkey/src/com/android/cts/monkey/CategoryTest.java
@@ -16,31 +16,73 @@
 
 package com.android.cts.monkey;
 
+import com.android.tradefed.device.CollectingOutputReceiver;
+
+import java.util.concurrent.TimeUnit;
+
 public class CategoryTest extends AbstractMonkeyTest {
 
+    private static final long MAX_TIMEOUT = 5 * 60 * 1000; // 5 min
+
     public void testDefaultCategories() throws Exception {
-        String out = mDevice.executeShellCommand(MONKEY_CMD + " -v -p " + PKGS[0] + " 5000");
-        assertTrue(out.contains("cmp=com.android.cts.monkey/.MonkeyActivity"));
-        assertTrue(out.contains("cmp=com.android.cts.monkey/.BaboonActivity"));
+        String cmd = MONKEY_CMD + " -v -p " + PKGS[0] + " 5000";
+        CollectingOutputReceiver receiver = new CollectingOutputReceiver();
+        try {
+            mDevice.executeShellCommand(cmd, receiver, MAX_TIMEOUT, TimeUnit.MILLISECONDS, 0);
+            String out = receiver.getOutput();
+            assertTrue(out.contains("cmp=com.android.cts.monkey/.MonkeyActivity"));
+            assertTrue(out.contains("cmp=com.android.cts.monkey/.BaboonActivity"));
+        } finally {
+            receiver.cancel();
+            receiver.clearBuffer();
+            receiver = null;
+        }
     }
 
     public void testSingleCategory() throws Exception {
-        String out = mDevice.executeShellCommand(MONKEY_CMD + " -v -p " + PKGS[0]
-                + " -c android.intent.category.LAUNCHER 5000");
-        assertTrue(out.contains("cmp=com.android.cts.monkey/.MonkeyActivity"));
-        assertFalse(out.contains("cmp=com.android.cts.monkey/.BaboonActivity"));
+        String cmd = MONKEY_CMD + " -v -p " + PKGS[0]
+                + " -c android.intent.category.LAUNCHER 5000";
+        CollectingOutputReceiver receiver = new CollectingOutputReceiver();
+        try {
+            mDevice.executeShellCommand(cmd, receiver, MAX_TIMEOUT, TimeUnit.MILLISECONDS, 0);
+            String out = receiver.getOutput();
+            assertTrue(out.contains("cmp=com.android.cts.monkey/.MonkeyActivity"));
+            assertFalse(out.contains("cmp=com.android.cts.monkey/.BaboonActivity"));
+        } finally {
+            receiver.cancel();
+            receiver.clearBuffer();
+            receiver = null;
+        }
 
-        out = mDevice.executeShellCommand(MONKEY_CMD + " -v -p " + PKGS[0]
-                + " -c android.intent.category.MONKEY 5000");
-        assertFalse(out.contains("cmp=com.android.cts.monkey/.MonkeyActivity"));
-        assertTrue(out.contains("cmp=com.android.cts.monkey/.BaboonActivity"));
+        CollectingOutputReceiver receiver2 = new CollectingOutputReceiver();
+        try {
+            mDevice.executeShellCommand(MONKEY_CMD + " -v -p " + PKGS[0]
+                    + " -c android.intent.category.MONKEY 5000", receiver2, MAX_TIMEOUT,
+                    TimeUnit.MILLISECONDS, 0);
+            String out = receiver2.getOutput();
+            assertFalse(out.contains("cmp=com.android.cts.monkey/.MonkeyActivity"));
+            assertTrue(out.contains("cmp=com.android.cts.monkey/.BaboonActivity"));
+        } finally {
+            receiver2.cancel();
+            receiver2.clearBuffer();
+            receiver2 = null;
+        }
     }
 
     public void testMultipleCategories() throws Exception {
-        String out = mDevice.executeShellCommand(MONKEY_CMD + " -v -p " + PKGS[0]
+        String cmd = MONKEY_CMD + " -v -p " + PKGS[0]
                 + " -c android.intent.category.LAUNCHER"
-                + " -c android.intent.category.MONKEY 5000");
-        assertTrue(out.contains("cmp=com.android.cts.monkey/.MonkeyActivity"));
-        assertTrue(out.contains("cmp=com.android.cts.monkey/.BaboonActivity"));
+                + " -c android.intent.category.MONKEY 5000";
+        CollectingOutputReceiver receiver = new CollectingOutputReceiver();
+        try {
+            mDevice.executeShellCommand(cmd, receiver, MAX_TIMEOUT, TimeUnit.MILLISECONDS, 0);
+            String out = receiver.getOutput();
+            assertTrue(out.contains("cmp=com.android.cts.monkey/.MonkeyActivity"));
+            assertTrue(out.contains("cmp=com.android.cts.monkey/.BaboonActivity"));
+        } finally {
+            receiver.cancel();
+            receiver.clearBuffer();
+            receiver = null;
+        }
     }
 }
diff --git a/hostsidetests/monkey/src/com/android/cts/monkey/MonkeyTest.java b/hostsidetests/monkey/src/com/android/cts/monkey/MonkeyTest.java
index 997f7c6..0241879 100644
--- a/hostsidetests/monkey/src/com/android/cts/monkey/MonkeyTest.java
+++ b/hostsidetests/monkey/src/com/android/cts/monkey/MonkeyTest.java
@@ -16,6 +16,7 @@
 
 package com.android.cts.monkey;
 
+import com.android.ddmlib.NullOutputReceiver;
 import com.android.tradefed.device.DeviceNotAvailableException;
 
 import java.util.Scanner;
@@ -32,7 +33,8 @@
 
     public void testNotMonkey() throws Exception {
         mDevice.executeShellCommand("am start -W -a android.intent.action.MAIN "
-                + "-n com.android.cts.monkey/com.android.cts.monkey.MonkeyActivity");
+                + "-n com.android.cts.monkey/com.android.cts.monkey.MonkeyActivity",
+                new NullOutputReceiver());
         assertIsUserAMonkey(false);
     }
 
diff --git a/hostsidetests/monkey/src/com/android/cts/monkey/PackageTest.java b/hostsidetests/monkey/src/com/android/cts/monkey/PackageTest.java
index 986304c..b3a22d6 100644
--- a/hostsidetests/monkey/src/com/android/cts/monkey/PackageTest.java
+++ b/hostsidetests/monkey/src/com/android/cts/monkey/PackageTest.java
@@ -16,10 +16,14 @@
 
 package com.android.cts.monkey;
 
+import com.android.tradefed.device.CollectingOutputReceiver;
+
+import java.util.concurrent.TimeUnit;
 import java.util.regex.Pattern;
 
 public class PackageTest extends AbstractMonkeyTest {
 
+    private static final long MAX_TIMEOUT = 5 * 60 * 1000; // 5 min
     private static final int MAX_ERROR_LENGTH = 256;
     private static final Pattern ALLOW_MONKEY =
             Pattern.compile("^.*Allowing.*cmp=com\\.android\\.cts\\.monkey/\\.MonkeyActivity.*$",
@@ -30,23 +34,49 @@
                     Pattern.MULTILINE);
 
     public void testSinglePackage() throws Exception {
-        String out = mDevice.executeShellCommand(MONKEY_CMD + " -v -p " + PKGS[0] + " 5000");
-        String error = truncateError(out);
-        assertTrue("Monkey not found in: " + error, ALLOW_MONKEY.matcher(out).find());
-        assertFalse("Chimp found in: " + error, ALLOW_CHIMP.matcher(out).find());
+        String cmd = MONKEY_CMD + " -v -p " + PKGS[0] + " 5000";
+        CollectingOutputReceiver receiver = new CollectingOutputReceiver();
+        try {
+            mDevice.executeShellCommand(cmd, receiver, MAX_TIMEOUT, TimeUnit.MILLISECONDS, 0);
+            String out = receiver.getOutput();
+            String error = truncateError(out);
+            assertTrue("Monkey not found in: " + error, ALLOW_MONKEY.matcher(out).find());
+            assertFalse("Chimp found in: " + error, ALLOW_CHIMP.matcher(out).find());
+        } finally {
+            receiver.cancel();
+            receiver.clearBuffer();
+            receiver = null;
+        }
 
-        out = mDevice.executeShellCommand(MONKEY_CMD + " -v -p " + PKGS[1] + " 5000");
-        error = truncateError(out);
-        assertFalse("Monkey found in: " + error, ALLOW_MONKEY.matcher(out).find());
-        assertTrue("Chimp not found in: " + error, ALLOW_CHIMP.matcher(out).find());
+        String cmd2 = MONKEY_CMD + " -v -p " + PKGS[1] + " 5000";
+        CollectingOutputReceiver receiver2 = new CollectingOutputReceiver();
+        try {
+            mDevice.executeShellCommand(cmd2, receiver2, MAX_TIMEOUT, TimeUnit.MILLISECONDS, 0);
+            String out = receiver2.getOutput();
+            String error = truncateError(out);
+            assertFalse("Monkey found in: " + error, ALLOW_MONKEY.matcher(out).find());
+            assertTrue("Chimp not found in: " + error, ALLOW_CHIMP.matcher(out).find());
+        } finally {
+            receiver2.cancel();
+            receiver2.clearBuffer();
+            receiver2 = null;
+        }
     }
 
     public void testMultiplePackages() throws Exception {
-        String out = mDevice.executeShellCommand(MONKEY_CMD + " -v -p " + PKGS[0]
-                + " -p " + PKGS[1] + " 5000");
-        String error = truncateError(out);
-        assertTrue("Monkey not found in: " + error, ALLOW_MONKEY.matcher(out).find());
-        assertTrue("Chimp not found in: " + error, ALLOW_CHIMP.matcher(out).find());
+        String cmd = MONKEY_CMD + " -v -p " + PKGS[0] + " -p " + PKGS[1] + " 5000";
+        CollectingOutputReceiver receiver = new CollectingOutputReceiver();
+        try {
+            mDevice.executeShellCommand(cmd, receiver, MAX_TIMEOUT, TimeUnit.MILLISECONDS, 0);
+            String out = receiver.getOutput();
+            String error = truncateError(out);
+            assertTrue("Monkey not found in: " + error, ALLOW_MONKEY.matcher(out).find());
+            assertTrue("Chimp not found in: " + error, ALLOW_CHIMP.matcher(out).find());
+        } finally {
+            receiver.cancel();
+            receiver.clearBuffer();
+            receiver = null;
+        }
     }
 
     private static final String truncateError(String input) {
diff --git a/hostsidetests/monkey/src/com/android/cts/monkey/SeedTest.java b/hostsidetests/monkey/src/com/android/cts/monkey/SeedTest.java
index a0016e2..973e986 100644
--- a/hostsidetests/monkey/src/com/android/cts/monkey/SeedTest.java
+++ b/hostsidetests/monkey/src/com/android/cts/monkey/SeedTest.java
@@ -25,7 +25,9 @@
         String out1 = mDevice.executeShellCommand(cmd1);
         String out2 = mDevice.executeShellCommand(cmd1);
         assertOutputs(out1, out2);
+    }
 
+    public void testSeed2() throws Exception {
         String cmd2 = MONKEY_CMD + " -s 3007 -v -p " + PKGS[0] + " 125";
         String out3 = mDevice.executeShellCommand(cmd2);
         String out4 = mDevice.executeShellCommand(cmd2);
diff --git a/tests/jdwp/runner/host-side/src/com/android/compatibility/testtype/DalvikTest.java b/tests/jdwp/runner/host-side/src/com/android/compatibility/testtype/DalvikTest.java
index fb86862..6abc6d7 100644
--- a/tests/jdwp/runner/host-side/src/com/android/compatibility/testtype/DalvikTest.java
+++ b/tests/jdwp/runner/host-side/src/com/android/compatibility/testtype/DalvikTest.java
@@ -182,6 +182,14 @@
      * {@inheritDoc}
      */
     @Override
+    public IAbi getAbi() {
+        return mAbi;
+    }
+
+    /**
+     * {@inheritDoc}
+     */
+    @Override
     public void setBuild(IBuildInfo build) {
         mBuildHelper = new CompatibilityBuildHelper(build);
     }
@@ -464,6 +472,7 @@
         // A DalvikTest to run any tests not contained in packages from TEST_PACKAGES, may be empty
         DalvikTest catchAll = new DalvikTest();
         OptionCopier.copyOptionsNoThrow(this, catchAll);
+        catchAll.mAbi = mAbi;
         shards.add(catchAll);
         // estimate catchAll's runtime to be that of a single package in TEST_PACKAGES
         long runtimeHint = mRuntimeHint / TEST_PACKAGES.size();
@@ -474,7 +483,8 @@
             DalvikTest test = new DalvikTest();
             OptionCopier.copyOptionsNoThrow(this, test);
             test.addIncludeFilter(packageName);
-            test.mRuntimeHint = runtimeHint;
+            test.mRuntimeHint = runtimeHint / TEST_PACKAGES.size();
+            test.mAbi = mAbi;
             shards.add(test);
         }
         // return a shard for each package in TEST_PACKAGE, plus a shard for any other tests
diff --git a/tests/libcore/javautilcollections/AndroidTest.xml b/tests/libcore/javautilcollections/AndroidTest.xml
index 016285f..40800cd 100644
--- a/tests/libcore/javautilcollections/AndroidTest.xml
+++ b/tests/libcore/javautilcollections/AndroidTest.xml
@@ -1,3 +1,4 @@
+<?xml version="1.0" encoding="utf-8"?>
 <!-- Copyright (C) 2016 The Android Open Source Project
 
      Licensed under the Apache License, Version 2.0 (the "License");
@@ -13,6 +14,7 @@
      limitations under the License.
 -->
 <configuration description="Config for CTS Libcore java.util Collection test cases">
+    <option name="not-shardable" value="true" />
     <target_preparer class="com.android.tradefed.targetprep.suite.SuiteApkInstaller">
         <option name="cleanup-apks" value="true" />
         <option name="test-file-name" value="CtsLibcoreJavaUtilCollectionsTestCases.apk" />
@@ -66,4 +68,4 @@
         <option name="test-timeout" value="1200000" />
         <option name="shell-timeout" value="1400000" />
     </test>
-</configuration>
\ No newline at end of file
+</configuration>
diff --git a/tools/cts-tradefed/res/config/cts-suite.xml b/tools/cts-tradefed/res/config/cts-suite.xml
new file mode 100644
index 0000000..c75072c
--- /dev/null
+++ b/tools/cts-tradefed/res/config/cts-suite.xml
@@ -0,0 +1,53 @@
+<?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 CTS as a suite">
+    <!-- running config -->
+    <build_provider class="com.android.compatibility.common.tradefed.build.CompatibilityBuildProvider" />
+    <test class="com.android.compatibility.common.tradefed.testtype.suite.CompatibilityTestSuite" />
+
+    <option name="compatibility:test-arg" value="com.android.tradefed.testtype.AndroidJUnitTest:rerun-from-file:true" />
+    <option name="compatibility:test-arg" value="com.android.tradefed.testtype.AndroidJUnitTest:fallback-to-serial-rerun:false" />
+
+    <logger class="com.android.tradefed.log.FileLogger">
+        <option name="log-level-display" value="WARN" />
+    </logger>
+    <!-- setup configs -->
+    <include name="cts-preconditions" />
+    <include name="cts-system-checkers" />
+    <include name="cts-known-failures" />
+
+    <option name="plan" value="cts-suite" />
+    <option name="test-tag" value="cts-suite" />
+
+    <option name="enable-root" value="false" />
+
+    <target_preparer class="com.android.compatibility.common.tradefed.targetprep.PropertyCheck">
+        <option name="property-name" value="ro.build.type" />
+        <option name="expected-value" value="user"/> <!-- Device should have user build -->
+        <option name="throw-error" value="false"/> <!-- Only print warning if not user build -->
+    </target_preparer>
+
+    <target_preparer class="com.android.compatibility.common.tradefed.targetprep.PropertyCheck">
+        <option name="property-name" value="ro.product.locale" />
+        <option name="expected-value" value="en-US"/> <!-- Device locale should be US English -->
+        <option name="throw-error" value="false"/> <!-- Only print warning if not en-US -->
+    </target_preparer>
+
+    <template-include name="reporters" default="basic-reporters" />
+
+    <result_reporter class="com.android.compatibility.common.tradefed.result.ResultReporter" />
+    <result_reporter class="com.android.tradefed.result.suite.SuiteResultReporter" />
+</configuration>