Merge "Isolate the force allocate logic"
diff --git a/atest/atest.py b/atest/atest.py
index 9271a32..2ee41c2 100755
--- a/atest/atest.py
+++ b/atest/atest.py
@@ -599,6 +599,10 @@
         atest_execution_info.print_test_result(constants.ATEST_RESULT_ROOT,
                                                args.history)
         return constants.EXIT_CODE_SUCCESS
+    if args.latest_result:
+        atest_execution_info.print_test_result_by_path(
+            constants.LATEST_RESULT_FILE)
+        return constants.EXIT_CODE_SUCCESS
     mod_info = module_info.ModuleInfo(force_build=args.rebuild_module_info)
     if args.rebuild_module_info:
         _run_extra_tasks(join=True)
diff --git a/atest/atest_arg_parser.py b/atest/atest_arg_parser.py
index 6e5079c..c497de4 100644
--- a/atest/atest_arg_parser.py
+++ b/atest/atest_arg_parser.py
@@ -53,6 +53,7 @@
            'Note: Nothing\'s going to run if it\'s not an Instant App test and '
            '"--instant" is passed.')
 ITERATION = 'Loop-run tests until the max iteration is reached. (10 by default)'
+LATEST_RESULT = 'Print latest test result.'
 LIST_MODULES = 'List testable modules for the given suite.'
 REBUILD_MODULE_INFO = ('Forces a rebuild of the module-info.json file. '
                        'This may be necessary following a repo sync or '
@@ -205,10 +206,14 @@
                            type=_positive_int, const=10, default=0,
                            metavar='MAX_ITERATIONS', help=RETRY_ANY_FAILURE)
 
-        # Option for test result history.
-        group.add_argument('--history', nargs='?',
-                           type=_positive_int, const=1000, default=0,
-                           help=HISTORY)
+        # A group of options for history. They are mutually exclusive
+        # in a command line.
+        history_group = self.add_mutually_exclusive_group()
+        # History related options.
+        history_group.add_argument('--latest-result', action='store_true',
+                                   help=LATEST_RESULT)
+        history_group.add_argument('--history', nargs='?', const='99999',
+                                   help=HISTORY)
 
         # This arg actually doesn't consume anything, it's primarily used for
         # the help description and creating custom_args in the NameSpace object.
@@ -252,6 +257,7 @@
                                          INSTALL=INSTALL,
                                          INSTANT=INSTANT,
                                          ITERATION=ITERATION,
+                                         LATEST_RESULT=LATEST_RESULT,
                                          LIST_MODULES=LIST_MODULES,
                                          REBUILD_MODULE_INFO=REBUILD_MODULE_INFO,
                                          RERUN_UNTIL_FAILURE=RERUN_UNTIL_FAILURE,
@@ -346,6 +352,9 @@
         -L, --list-modules
             {LIST_MODULES}
 
+        --latest-result
+            {LATEST_RESULT}
+
         -v, --verbose
             {VERBOSE}
 
diff --git a/atest/atest_execution_info.py b/atest/atest_execution_info.py
index 39cd3c1..0c67e19 100644
--- a/atest/atest_execution_info.py
+++ b/atest/atest_execution_info.py
@@ -23,6 +23,7 @@
 import os
 import sys
 
+import atest_utils as au
 import constants
 
 from metrics import metrics_utils
@@ -40,6 +41,10 @@
 _TEST_RESULT_NAME = 'test_result'
 _EXIT_CODE_ATTR = 'EXIT_CODE'
 _MAIN_MODULE_KEY = '__main__'
+_UUID_LEN = 30
+_RESULT_LEN = 35
+_COMMAND_LEN = 50
+_LOGCAT_FMT = '{}/log/invocation_*/{}*logcat-on-failure*'
 
 _SUMMARY_MAP_TEMPLATE = {_STATUS_PASSED_KEY : 0,
                          _STATUS_FAILED_KEY : 0,
@@ -72,18 +77,30 @@
     os.symlink(test_result_dir, symlink)
 
 
-def print_test_result(root, num):
+def print_test_result(root, history_arg):
     """Make a list of latest n test result.
 
     Args:
         root: A string of the test result root path.
-        num: An integer, the number of latest results.
+        history_arg: A string of an integer or uuid. If it's an integer string,
+                     the number of lines of test result will be given; else it
+                     will be treated a uuid and print test result accordingly
+                     in detail.
     """
+    if not history_arg.isdigit():
+        path = os.path.join(constants.ATEST_RESULT_ROOT, history_arg,
+                            'test_result')
+        print_test_result_by_path(path)
+        return
     target = '%s/20*_*_*' % root
     paths = glob.glob(target)
     paths.sort(reverse=True)
-    print('{:-^22}    {:-^35}    {:-^50}'.format('uuid', 'result', 'command'))
-    for path in paths[0: num+1]:
+    print('{:-^{uuid_len}} {:-^{result_len}} {:-^{command_len}}'
+          .format('uuid', 'result', 'command',
+                  uuid_len=_UUID_LEN,
+                  result_len=_RESULT_LEN,
+                  command_len=_COMMAND_LEN))
+    for path in paths[0: int(history_arg)+1]:
         result_path = os.path.join(path, 'test_result')
         if os.path.isfile(result_path):
             try:
@@ -92,14 +109,58 @@
                     total_summary = result.get(_TOTAL_SUMMARY_KEY, {})
                     summary_str = ', '.join([k+':'+str(v)
                                              for k, v in total_summary.items()])
-                    print('{:<22}    {:<35}    {:<50}'
+                    print('{:<{uuid_len}} {:<{result_len}} {:<{command_len}}'
                           .format(os.path.basename(path),
                                   summary_str,
-                                  'atest '+result.get(_ARGS_KEY, '')))
+                                  'atest '+result.get(_ARGS_KEY, ''),
+                                  uuid_len=_UUID_LEN,
+                                  result_len=_RESULT_LEN,
+                                  command_len=_COMMAND_LEN))
             except ValueError:
                 pass
 
 
+def print_test_result_by_path(path):
+    """Print latest test result.
+
+    Args:
+        path: A string of test result path.
+    """
+    if os.path.isfile(path):
+        with open(path) as json_file:
+            result = json.load(json_file)
+            print("\natest {}".format(result.get(_ARGS_KEY, '')))
+            print('\nTotal Summary:\n--------------')
+            total_summary = result.get(_TOTAL_SUMMARY_KEY, {})
+            print(', '.join([(k+':'+str(v))
+                             for k, v in total_summary.items()]))
+            fail_num = total_summary.get(_STATUS_FAILED_KEY)
+            if fail_num > 0:
+                message = '%d test failed' % fail_num
+                print('\n')
+                print(au.colorize(message, constants.RED))
+                print('-' * len(message))
+                test_runner = result.get(_TEST_RUNNER_KEY, {})
+                for runner_name in test_runner.keys():
+                    test_dict = test_runner.get(runner_name, {})
+                    for test_name in test_dict:
+                        test_details = test_dict.get(test_name, {})
+                        for fail in test_details.get(_STATUS_FAILED_KEY):
+                            print(au.colorize('{}'.format(
+                                fail.get(_TEST_NAME_KEY)), constants.RED))
+                            failure_files = glob.glob(_LOGCAT_FMT.format(
+                                os.path.dirname(path), fail.get(_TEST_NAME_KEY)
+                                ))
+                            if failure_files:
+                                print('{} {}'.format(
+                                    au.colorize('LOGCAT-ON-FAILURES:',
+                                                constants.CYAN),
+                                    failure_files[0]))
+                            print('{} {}'.format(
+                                au.colorize('STACKTRACE:\n', constants.CYAN),
+                                fail.get(_TEST_DETAILS_KEY)))
+
+
 def has_non_test_options(args):
     """
     check whether non-test option in the args.
@@ -116,7 +177,8 @@
             or args.help
             or args.history
             or args.info
-            or args.version)
+            or args.version
+            or args.latest_result)
 
 
 class AtestExecutionInfo(object):
diff --git a/atest/constants_default.py b/atest/constants_default.py
index 1df9e2d..f730b60 100644
--- a/atest/constants_default.py
+++ b/atest/constants_default.py
@@ -233,3 +233,4 @@
                                r'(?P<package>[^(;|\s)]+)\s*')
 
 ATEST_RESULT_ROOT = '/tmp/atest_result'
+LATEST_RESULT_FILE = os.path.join(ATEST_RESULT_ROOT, 'LATEST', 'test_result')
diff --git a/device_build_interfaces/com/android/tradefed/build/IBuildInfo.java b/device_build_interfaces/com/android/tradefed/build/IBuildInfo.java
index 8fd2026..85b4a30 100644
--- a/device_build_interfaces/com/android/tradefed/build/IBuildInfo.java
+++ b/device_build_interfaces/com/android/tradefed/build/IBuildInfo.java
@@ -298,9 +298,6 @@
         return false;
     }
 
-    /** Set the build as test resource build. */
-    public default void setTestResourceBuild(boolean testResourceBuild) {}
-
     /** Get the paths for build artifacts that are delayed download. */
     public default Set<File> getRemoteFiles() {
         return null;
diff --git a/device_build_interfaces/com/android/tradefed/device/INativeDevice.java b/device_build_interfaces/com/android/tradefed/device/INativeDevice.java
index ee6e900..3ceb944 100644
--- a/device_build_interfaces/com/android/tradefed/device/INativeDevice.java
+++ b/device_build_interfaces/com/android/tradefed/device/INativeDevice.java
@@ -1402,22 +1402,12 @@
     /**
      * Extra steps for device specific required setup that will be executed on the device prior to
      * the invocation flow.
-     */
-    public default void preInvocationSetup(IBuildInfo info)
-            throws TargetSetupError, DeviceNotAvailableException {
-        preInvocationSetup(info, null);
-    }
-
-    /**
-     * Extra steps for device specific required setup that will be executed on the device prior to
-     * the invocation flow.
      *
      * @param info The {@link IBuildInfo} of the device.
-     * @param testResourceBuildInfos The list of test resources.
      * @throws TargetSetupError
      * @throws DeviceNotAvailableException
      */
-    public default void preInvocationSetup(IBuildInfo info, List<IBuildInfo> testResourceBuildInfos)
+    public default void preInvocationSetup(IBuildInfo info)
             throws TargetSetupError, DeviceNotAvailableException {
         // Empty default implementation.
     }
diff --git a/src/com/android/tradefed/build/BuildInfo.java b/src/com/android/tradefed/build/BuildInfo.java
index 2069b2a..04b2135 100644
--- a/src/com/android/tradefed/build/BuildInfo.java
+++ b/src/com/android/tradefed/build/BuildInfo.java
@@ -134,12 +134,6 @@
         return mTestResourceBuild;
     }
 
-    /** {@inheritDoc} */
-    @Override
-    public void setTestResourceBuild(boolean testResourceBuild) {
-        mTestResourceBuild = testResourceBuild;
-    }
-
     /**
      * {@inheritDoc}
      */
diff --git a/src/com/android/tradefed/device/DeviceSelectionOptions.java b/src/com/android/tradefed/device/DeviceSelectionOptions.java
index eae78ea..6dbae6d 100644
--- a/src/com/android/tradefed/device/DeviceSelectionOptions.java
+++ b/src/com/android/tradefed/device/DeviceSelectionOptions.java
@@ -499,12 +499,12 @@
         }
         // If battery check is required and we have a min/max battery requested
         if (mRequireBatteryCheck) {
-            if (((mMinBattery != null) || (mMaxBattery != null))
-                    && (!(device instanceof StubDevice) || (device instanceof FastbootDevice))) {
+            if ((mMinBattery != null || mMaxBattery != null)) {
                 // Only check battery on physical device. (FastbootDevice placeholder is always for
                 // a physical device
-                if (device instanceof FastbootDevice) {
-                    // Ready battery of fastboot device does not work and could lead to weird log.
+                if (device instanceof StubDevice || device instanceof FastbootDevice) {
+                    // Reading battery of fastboot and StubDevice device does not work and could
+                    // lead to weird log.
                     return false;
                 }
                 Integer deviceBattery = getBatteryLevel(device);
diff --git a/src/com/android/tradefed/device/LocalAndroidVirtualDevice.java b/src/com/android/tradefed/device/LocalAndroidVirtualDevice.java
index 66a92fb..ea45061 100644
--- a/src/com/android/tradefed/device/LocalAndroidVirtualDevice.java
+++ b/src/com/android/tradefed/device/LocalAndroidVirtualDevice.java
@@ -35,9 +35,11 @@
 import com.android.tradefed.util.RunUtil;
 import com.android.tradefed.util.TarUtil;
 import com.android.tradefed.util.ZipUtil;
+
 import com.google.common.annotations.VisibleForTesting;
 import com.google.common.base.Strings;
 import com.google.common.net.HostAndPort;
+
 import java.io.File;
 import java.io.IOException;
 import java.util.ArrayList;
@@ -76,10 +78,10 @@
 
     /** Execute common setup procedure and launch the virtual device. */
     @Override
-    public void preInvocationSetup(IBuildInfo info, List<IBuildInfo> testResourceBuildInfos)
+    public void preInvocationSetup(IBuildInfo info)
             throws TargetSetupError, DeviceNotAvailableException {
         // The setup method in super class does not require the device to be online.
-        super.preInvocationSetup(info, testResourceBuildInfos);
+        super.preInvocationSetup(info);
 
         createTempDirs((IDeviceBuildInfo) info);
 
diff --git a/src/com/android/tradefed/device/NativeDevice.java b/src/com/android/tradefed/device/NativeDevice.java
index 45124bd..e730a83 100644
--- a/src/com/android/tradefed/device/NativeDevice.java
+++ b/src/com/android/tradefed/device/NativeDevice.java
@@ -447,8 +447,7 @@
         String propValue = getProperty(propName);
         if (propValue != null) {
             return propValue;
-        } else if (TestDeviceState.FASTBOOT.equals(getDeviceState()) &&
-                fastbootVar != null) {
+        } else if (isStateBootloaderOrFastbootd() && fastbootVar != null) {
             CLog.i("%s for device %s is null, re-querying in fastboot", description,
                     getSerialNumber());
             return getFastbootVariable(fastbootVar);
@@ -2936,11 +2935,11 @@
             throws DeviceNotAvailableException, UnsupportedOperationException {
         rebootIntoFastbootInternal(false);
     }
-    
+
     /**
      * Reboots the device into bootloader or fastbootd mode.
      *
-     * @param isBootloader: true to boot the device into bootloader mode, false to boot the device
+     * @param isBootloader true to boot the device into bootloader mode, false to boot the device
      *     into fastbootd mode.
      * @throws DeviceNotAvailableException if connection with device is lost and cannot be
      *     recovered.
@@ -2948,7 +2947,7 @@
     private void rebootIntoFastbootInternal(boolean isBootloader)
             throws DeviceNotAvailableException {
         final RebootMode mode =
-                isBootloader ? RebootMode.REBOOT_INTO_BOOTLOADER : RebootMode.REBOOT_INTO_FASTBOOT;
+                isBootloader ? RebootMode.REBOOT_INTO_BOOTLOADER : RebootMode.REBOOT_INTO_FASTBOOTD;
         if (!mFastbootEnabled) {
             throw new UnsupportedOperationException(
                     String.format("Fastboot is not available and cannot reboot into %s", mode));
@@ -2958,13 +2957,14 @@
         CLog.i(
                 "Rebooting device %s in state %s into %s",
                 getSerialNumber(), getDeviceState(), mode);
-        if (TestDeviceState.FASTBOOT.equals(getDeviceState())) {
+        if (isStateBootloaderOrFastbootd()) {
             CLog.i("device %s already in fastboot. Rebooting anyway", getSerialNumber());
             executeFastbootCommand(String.format("reboot-%s", mode));
         } else {
             CLog.i("Booting device %s into %s", getSerialNumber(), mode);
             doAdbReboot(mode, null);
         }
+
         if (!mStateMonitor.waitForDeviceBootloader(mOptions.getFastbootTimeout())) {
             recoverDeviceFromBootloader();
         }
@@ -3120,7 +3120,7 @@
     protected enum RebootMode {
         REBOOT_FULL(""),
         REBOOT_USERSPACE("userspace"),
-        REBOOT_INTO_FASTBOOT("fastboot"),
+        REBOOT_INTO_FASTBOOTD("fastboot"),
         REBOOT_INTO_BOOTLOADER("bootloader"),
         REBOOT_INTO_SIDELOAD("sideload"),
         REBOOT_INTO_SIDELOAD_AUTO_REBOOT("sideload-auto-reboot"),
@@ -4392,7 +4392,7 @@
 
     /** {@inheritDoc} */
     @Override
-    public void preInvocationSetup(IBuildInfo info, List<IBuildInfo> testResourceBuildInfos)
+    public void preInvocationSetup(IBuildInfo info)
             throws TargetSetupError, DeviceNotAvailableException {
         // Default implementation
         mContentProvider = null;
diff --git a/src/com/android/tradefed/device/cloud/ManagedRemoteDevice.java b/src/com/android/tradefed/device/cloud/ManagedRemoteDevice.java
index 282c9bd..cabd21f 100644
--- a/src/com/android/tradefed/device/cloud/ManagedRemoteDevice.java
+++ b/src/com/android/tradefed/device/cloud/ManagedRemoteDevice.java
@@ -44,7 +44,6 @@
 
 import java.io.File;
 import java.io.IOException;
-import java.util.List;
 
 /**
  * A device running inside a virtual machine that we manage remotely via a Tradefed instance inside
@@ -72,9 +71,9 @@
     }
 
     @Override
-    public void preInvocationSetup(IBuildInfo info, List<IBuildInfo> testResourceBuildInfos)
+    public void preInvocationSetup(IBuildInfo info)
             throws TargetSetupError, DeviceNotAvailableException {
-        super.preInvocationSetup(info, testResourceBuildInfos);
+        super.preInvocationSetup(info);
         mGceAvd = null;
         // First get the options
         TestDeviceOptions options = getOptions();
diff --git a/src/com/android/tradefed/device/cloud/NestedRemoteDevice.java b/src/com/android/tradefed/device/cloud/NestedRemoteDevice.java
index 9ae0988..b362b34 100644
--- a/src/com/android/tradefed/device/cloud/NestedRemoteDevice.java
+++ b/src/com/android/tradefed/device/cloud/NestedRemoteDevice.java
@@ -161,7 +161,7 @@
         // Reset recovery since it's a new device
         setRecoveryMode(RecoveryMode.AVAILABLE);
         try {
-            preInvocationSetup(info, null);
+            preInvocationSetup(info);
         } catch (TargetSetupError e) {
             CLog.e("Failed to re-init the device %s", getSerialNumber());
             CLog.e(e);
diff --git a/src/com/android/tradefed/device/cloud/RemoteAndroidVirtualDevice.java b/src/com/android/tradefed/device/cloud/RemoteAndroidVirtualDevice.java
index 641fe31..5042966 100644
--- a/src/com/android/tradefed/device/cloud/RemoteAndroidVirtualDevice.java
+++ b/src/com/android/tradefed/device/cloud/RemoteAndroidVirtualDevice.java
@@ -84,9 +84,9 @@
 
     /** {@inheritDoc} */
     @Override
-    public void preInvocationSetup(IBuildInfo info, List<IBuildInfo> testResourceBuildInfos)
+    public void preInvocationSetup(IBuildInfo info)
             throws TargetSetupError, DeviceNotAvailableException {
-        super.preInvocationSetup(info, testResourceBuildInfos);
+        super.preInvocationSetup(info);
         try {
             mGceAvd = null;
             mGceSshMonitor = null;
diff --git a/src/com/android/tradefed/device/metric/FilePullerDeviceMetricCollector.java b/src/com/android/tradefed/device/metric/FilePullerDeviceMetricCollector.java
index 3bbf52e..6b5fa18 100644
--- a/src/com/android/tradefed/device/metric/FilePullerDeviceMetricCollector.java
+++ b/src/com/android/tradefed/device/metric/FilePullerDeviceMetricCollector.java
@@ -28,6 +28,7 @@
 import java.io.IOException;
 import java.util.AbstractMap.SimpleEntry;
 import java.util.Arrays;
+import java.util.HashMap;
 import java.util.HashSet;
 import java.util.Map;
 import java.util.Map.Entry;
@@ -121,9 +122,11 @@
             return;
         }
         for (String key : mKeys) {
-            Entry<String, File> pulledMetrics = pullMetricFile(key, currentMetrics);
-            if (pulledMetrics != null) {
-                processMetricFile(pulledMetrics.getKey(), pulledMetrics.getValue(), data);
+            Map<String, File> pulledMetrics = pullMetricFile(key, currentMetrics);
+
+            // Process all the metric files that matched the key pattern.
+            for (Map.Entry<String, File> entry : pulledMetrics.entrySet()) {
+                processMetricFile(entry.getKey(), entry.getValue(), data);
             }
         }
 
@@ -137,8 +140,9 @@
     }
 
 
-    private Entry<String, File> pullMetricFile(
+    private Map<String, File> pullMetricFile(
             String pattern, final Map<String, String> currentMetrics) {
+        Map<String, File> matchedFiles = new HashMap<>();
         Pattern p = Pattern.compile(pattern);
         for (Entry<String, String> entry : currentMetrics.entrySet()) {
             if (p.matcher(entry.getKey()).find()) {
@@ -153,8 +157,9 @@
                             if (mCleanUp) {
                                 device.deleteFile(entry.getValue());
                             }
-                            // Return the actual key and the file associated
-                            return new SimpleEntry<String, File>(entry.getKey(), attemptPull);
+                            // Store all the keys that matches the pattern and the corresponding
+                            // files pulled from the device.
+                            matchedFiles.put(entry.getKey(), attemptPull);
                         }
                     } catch (DeviceNotAvailableException e) {
                         CLog.e(
@@ -165,9 +170,13 @@
                 }
             }
         }
-        // Not a hard failure, just nice to know
-        CLog.d("Could not find a device file associated to pattern '%s'.", pattern);
-        return null;
+
+        if (matchedFiles.isEmpty()) {
+            // Not a hard failure, just nice to know
+            CLog.d("Could not find a device file associated to pattern '%s'.", pattern);
+
+        }
+        return matchedFiles;
     }
 
     /**
diff --git a/src/com/android/tradefed/invoker/InvocationExecution.java b/src/com/android/tradefed/invoker/InvocationExecution.java
index 395fe6e..abbb144 100644
--- a/src/com/android/tradefed/invoker/InvocationExecution.java
+++ b/src/com/android/tradefed/invoker/InvocationExecution.java
@@ -89,7 +89,6 @@
 import java.util.concurrent.Callable;
 import java.util.concurrent.ConcurrentHashMap;
 import java.util.concurrent.TimeUnit;
-import java.util.stream.Collectors;
 
 /**
  * Class that describes all the invocation steps: build download, target_prep, run tests, clean up.
@@ -337,12 +336,7 @@
             if (device instanceof ITestLoggerReceiver) {
                 ((ITestLoggerReceiver) context.getDevice(deviceName)).setTestLogger(logger);
             }
-            device.preInvocationSetup(
-                    context.getBuildInfo(deviceName),
-                    context.getBuildInfos()
-                            .stream()
-                            .filter(buildInfo -> buildInfo.isTestResourceBuild())
-                            .collect(Collectors.toList()));
+            device.preInvocationSetup(context.getBuildInfo(deviceName));
         }
     }
 
diff --git a/src/com/android/tradefed/result/suite/XmlSuiteResultFormatter.java b/src/com/android/tradefed/result/suite/XmlSuiteResultFormatter.java
index d6a4be6..772075b 100644
--- a/src/com/android/tradefed/result/suite/XmlSuiteResultFormatter.java
+++ b/src/com/android/tradefed/result/suite/XmlSuiteResultFormatter.java
@@ -98,6 +98,7 @@
     private static final String MODULE_TAG = "Module";
     private static final String MODULES_DONE_ATTR = "modules_done";
     private static final String MODULES_TOTAL_ATTR = "modules_total";
+    private static final String MODULES_NOT_DONE_REASON = "Reason";
     private static final String NAME_ATTR = "name";
     private static final String OS_ARCH_ATTR = "os_arch";
     private static final String OS_NAME_ATTR = "os_name";
@@ -297,6 +298,15 @@
                     NS, PASS_ATTR, Integer.toString(module.getNumTestsInState(TestStatus.PASSED)));
             serializer.attribute(NS, TOTAL_TESTS_ATTR, Integer.toString(module.getNumTests()));
 
+            if (!isDone) {
+                String message = module.getRunFailureMessage();
+                if (message == null) {
+                    message = "Run was incomplete. Some tests might not have finished.";
+                }
+                serializer.startTag(NS, MODULES_NOT_DONE_REASON);
+                serializer.attribute(NS, MESSAGE_ATTR, message);
+                serializer.endTag(NS, MODULES_NOT_DONE_REASON);
+            }
             serializeTestCases(serializer, module.getTestResults());
             serializer.endTag(NS, MODULE_TAG);
         }
@@ -603,6 +613,13 @@
             module.testRunStarted(moduleId, totalTests);
             // TestCase level information parsing
             while (parser.nextTag() == XmlPullParser.START_TAG) {
+                // If a reason for not done exists, handle it.
+                if (parser.getName().equals(MODULES_NOT_DONE_REASON)) {
+                    parser.require(XmlPullParser.START_TAG, NS, MODULES_NOT_DONE_REASON);
+                    parser.nextTag();
+                    parser.require(XmlPullParser.END_TAG, NS, MODULES_NOT_DONE_REASON);
+                    continue;
+                }
                 parser.require(XmlPullParser.START_TAG, NS, CASE_TAG);
                 String className = parser.getAttributeValue(NS, NAME_ATTR);
                 // Test level information parsing
diff --git a/src/com/android/tradefed/testtype/UsbResetTest.java b/src/com/android/tradefed/testtype/UsbResetTest.java
index 1be0792..40b7047 100644
--- a/src/com/android/tradefed/testtype/UsbResetTest.java
+++ b/src/com/android/tradefed/testtype/UsbResetTest.java
@@ -47,6 +47,7 @@
                 } else {
                     CLog.d("Resetting USB port for device '%s'", serial);
                     usbDevice.reset();
+                    device.waitForDeviceOnline();
                     // If device fails to reboot it will throw an exception and be left unavailable
                     // again.
                     device.reboot();
diff --git a/test_framework/com/android/tradefed/targetprep/RunHostScriptTargetPreparer.java b/test_framework/com/android/tradefed/targetprep/RunHostScriptTargetPreparer.java
new file mode 100644
index 0000000..0245523
--- /dev/null
+++ b/test_framework/com/android/tradefed/targetprep/RunHostScriptTargetPreparer.java
@@ -0,0 +1,178 @@
+/*
+ * Copyright (C) 2020 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.tradefed.targetprep;
+
+import com.android.tradefed.config.GlobalConfiguration;
+import com.android.tradefed.config.Option;
+import com.android.tradefed.config.OptionClass;
+import com.android.tradefed.device.DeviceNotAvailableException;
+import com.android.tradefed.device.IDeviceManager;
+import com.android.tradefed.device.ITestDevice;
+import com.android.tradefed.invoker.ExecutionFiles.FilesKey;
+import com.android.tradefed.invoker.TestInformation;
+import com.android.tradefed.log.LogUtil.CLog;
+import com.android.tradefed.util.CommandResult;
+import com.android.tradefed.util.IRunUtil;
+import com.android.tradefed.util.RunUtil;
+
+import com.google.common.annotations.VisibleForTesting;
+
+import java.io.File;
+import java.io.FileNotFoundException;
+import java.time.Duration;
+import java.util.ArrayList;
+import java.util.List;
+import java.util.stream.Collectors;
+
+import javax.annotation.Nullable;
+
+/**
+ * Target preparer which executes a script before running a test. The script can reference the
+ * device's serial number using the ANDROID_SERIAL environment variable.
+ */
+@OptionClass(alias = "run-host-script")
+public class RunHostScriptTargetPreparer extends BaseTargetPreparer {
+
+    @Option(name = "script-file", description = "Path to the script to execute.")
+    private String mScriptPath = null;
+
+    @Option(name = "work-dir", description = "Working directory to use when executing script.")
+    private File mWorkDir = null;
+
+    @Option(name = "script-timeout", description = "Script execution timeout.")
+    private Duration mTimeout = Duration.ofMinutes(1L);
+
+    private IRunUtil mRunUtil;
+
+    @Override
+    public void setUp(TestInformation testInfo)
+            throws TargetSetupError, BuildError, DeviceNotAvailableException {
+        if (mScriptPath == null) {
+            CLog.w("No script to execute.");
+            return;
+        }
+        ITestDevice device = testInfo.getDevice();
+
+        // Find script and verify it exists and is executable
+        File scriptFile = findScriptFile(testInfo);
+        if (scriptFile == null || !scriptFile.isFile()) {
+            throw new TargetSetupError(
+                    String.format("File %s not found.", mScriptPath), device.getDeviceDescriptor());
+        }
+        if (!scriptFile.canExecute()) {
+            scriptFile.setExecutable(true);
+        }
+
+        // Set working directory and environment variables
+        getRunUtil().setWorkingDir(mWorkDir);
+        getRunUtil().setEnvVariable("ANDROID_SERIAL", device.getSerialNumber());
+        setPathVariable(testInfo);
+
+        // Execute script and handle result
+        CommandResult result =
+                getRunUtil().runTimedCmd(mTimeout.toMillis(), scriptFile.getAbsolutePath());
+        switch (result.getStatus()) {
+            case SUCCESS:
+                CLog.i("Script executed successfully, stdout = [%s].", result.getStdout());
+                break;
+            case FAILED:
+                throw new TargetSetupError(
+                        String.format(
+                                "Script execution failed, stdout = [%s], stderr = [%s].",
+                                result.getStdout(), result.getStderr()),
+                        device.getDeviceDescriptor());
+            case TIMED_OUT:
+                throw new TargetSetupError(
+                        "Script execution timed out.", device.getDeviceDescriptor());
+            case EXCEPTION:
+                throw new TargetSetupError(
+                        String.format(
+                                "Exception during script execution, stdout = [%s], stderr = [%s].",
+                                result.getStdout(), result.getStderr()),
+                        device.getDeviceDescriptor());
+        }
+    }
+
+    /** @return {@link IRunUtil} instance to use */
+    @VisibleForTesting
+    IRunUtil getRunUtil() {
+        if (mRunUtil == null) {
+            mRunUtil = new RunUtil();
+        }
+        return mRunUtil;
+    }
+
+    /** @return {@link IDeviceManager} instance used to fetch the configured adb/fastboot paths */
+    @VisibleForTesting
+    IDeviceManager getDeviceManager() {
+        return GlobalConfiguration.getDeviceManagerInstance();
+    }
+
+    /** Find the script file to execute. */
+    @Nullable
+    private File findScriptFile(TestInformation testInfo) {
+        File scriptFile = new File(mScriptPath);
+        if (scriptFile.isAbsolute()) {
+            return scriptFile;
+        }
+        // Try to find the script in the working directory if it was set
+        if (mWorkDir != null) {
+            scriptFile = new File(mWorkDir, mScriptPath);
+            if (scriptFile.isFile()) {
+                return scriptFile;
+            }
+        }
+        // Otherwise, search for it in the test information
+        try {
+            return testInfo.getDependencyFile(mScriptPath, false);
+        } catch (FileNotFoundException e) {
+            return null;
+        }
+    }
+
+    /** Update $PATH if necessary to use consistent adb and fastboot binaries. */
+    private void setPathVariable(TestInformation testInfo) {
+        List<String> paths = new ArrayList<>();
+        // Use the test's adb binary or the globally defined one
+        File adbBinary = testInfo.executionFiles().get(FilesKey.ADB_BINARY);
+        if (adbBinary == null) {
+            String adbPath = getDeviceManager().getAdbPath();
+            if (!adbPath.equals("adb")) { // ignore default binary
+                adbBinary = new File(adbPath);
+            }
+        }
+        if (adbBinary != null && adbBinary.isFile()) {
+            paths.add(adbBinary.getParentFile().getAbsolutePath());
+        }
+        // Use the globally defined fastboot binary
+        String fastbootPath = getDeviceManager().getFastbootPath();
+        if (!fastbootPath.equals("fastboot")) { // ignore default binary
+            File fastbootBinary = new File(fastbootPath);
+            if (fastbootBinary.isFile()) {
+                paths.add(fastbootBinary.getParentFile().getAbsolutePath());
+            }
+        }
+        // Prepend additional paths to the PATH variable
+        if (!paths.isEmpty()) {
+            String separator = System.getProperty("path.separator");
+            paths.add(System.getenv("PATH"));
+            String path = paths.stream().distinct().collect(Collectors.joining(separator));
+            CLog.d("Using updated $PATH: %s", path);
+            getRunUtil().setEnvVariable("PATH", path);
+        }
+    }
+}
diff --git a/test_framework/com/android/tradefed/testtype/GTestListener.java b/test_framework/com/android/tradefed/testtype/GTestListener.java
index c967d06..38c6498 100644
--- a/test_framework/com/android/tradefed/testtype/GTestListener.java
+++ b/test_framework/com/android/tradefed/testtype/GTestListener.java
@@ -22,8 +22,10 @@
 import com.android.tradefed.result.TestDescription;
 import com.android.tradefed.result.proto.TestRecordProto.FailureStatus;
 
+import java.util.ArrayList;
 import java.util.HashMap;
 import java.util.HashSet;
+import java.util.List;
 import java.util.Set;
 
 /**
@@ -32,6 +34,7 @@
  */
 final class GTestListener extends ResultForwarder {
 
+    private static final int MAX_PARTIAL_SET_SIZE = 20;
     private Set<TestDescription> mTests = new HashSet<>();
     private Set<TestDescription> mDuplicateTests = new HashSet<>();
 
@@ -50,11 +53,19 @@
     @Override
     public void testRunEnded(long elapsedTime, HashMap<String, Metric> runMetrics) {
         if (!mDuplicateTests.isEmpty()) {
-            FailureDescription error =
-                    FailureDescription.create(
-                            String.format(
-                                    "The following tests ran more than once: %s.",
-                                    mDuplicateTests));
+            StringBuilder errorMessage = new StringBuilder();
+            errorMessage.append(
+                    String.format("%s tests ran more than once.", mDuplicateTests.size()));
+            if (mDuplicateTests.size() > MAX_PARTIAL_SET_SIZE) {
+                List<TestDescription> partialDuplicateSet = new ArrayList<>(mDuplicateTests);
+                while (partialDuplicateSet.size() > MAX_PARTIAL_SET_SIZE) {
+                    partialDuplicateSet.remove(0);
+                }
+                errorMessage.append(String.format(" Partial list: %s", partialDuplicateSet));
+            } else {
+                errorMessage.append(String.format(" Full list: %s", mDuplicateTests));
+            }
+            FailureDescription error = FailureDescription.create(errorMessage.toString());
             error.setFailureStatus(FailureStatus.TEST_FAILURE);
             super.testRunFailed(error);
         }
diff --git a/test_framework/com/android/tradefed/testtype/GoogleBenchmarkTest.java b/test_framework/com/android/tradefed/testtype/GoogleBenchmarkTest.java
index 163b4fb..373844a 100644
--- a/test_framework/com/android/tradefed/testtype/GoogleBenchmarkTest.java
+++ b/test_framework/com/android/tradefed/testtype/GoogleBenchmarkTest.java
@@ -16,6 +16,7 @@
 package com.android.tradefed.testtype;
 
 import com.android.ddmlib.FileListingService;
+import com.android.ddmlib.IShellOutputReceiver;
 import com.android.tradefed.config.Option;
 import com.android.tradefed.config.OptionClass;
 import com.android.tradefed.device.CollectingOutputReceiver;
@@ -26,24 +27,28 @@
 import com.android.tradefed.result.ITestInvocationListener;
 import com.android.tradefed.util.proto.TfMetricProtoUtil;
 
+import com.google.common.annotations.VisibleForTesting;
+
 import java.util.ArrayList;
 import java.util.Arrays;
 import java.util.HashMap;
+import java.util.Iterator;
+import java.util.LinkedHashSet;
 import java.util.List;
 import java.util.Map;
+import java.util.Set;
 import java.util.concurrent.TimeUnit;
 
-/**
- * A Test that runs a Google benchmark test package on given device.
- */
+/** A Test that runs a Google benchmark test package on given device. */
 @OptionClass(alias = "gbenchmark")
-public class GoogleBenchmarkTest implements IDeviceTest, IRemoteTest {
+public class GoogleBenchmarkTest implements IDeviceTest, IRemoteTest, ITestFilterReceiver {
 
     static final String DEFAULT_TEST_PATH = "/data/benchmarktest";
+    static final String GBENCHMARK_FILTER_OPTION = "--benchmark_filter";
+    static final int ADB_CMD_CHAR_LIMIT = 4000; // May apply to some old device models.
 
     private static final String GBENCHMARK_JSON_OUTPUT_FORMAT = "--benchmark_format=json";
     private static final String GBENCHMARK_LIST_TESTS_OPTION = "--benchmark_list_tests=true";
-
     private static final List<String> DEFAULT_FILE_EXCLUDE_FILTERS = new ArrayList<>();
 
     static {
@@ -75,6 +80,16 @@
             "The maximum time to allow for each benchmark run in ms.", isTimeVal=true)
     private long mMaxRunTime = 15 * 60 * 1000;
 
+    @Option(
+            name = "include-filter",
+            description = "The benchmark (regex) filters used to match benchmarks to include.")
+    private Set<String> mIncludeFilters = new LinkedHashSet<>();
+
+    @Option(
+            name = "exclude-filter",
+            description = "The benchmark (regex) filters used to match benchmarks to exclude.")
+    private Set<String> mExcludeFilters = new LinkedHashSet<>();
+
     private ITestDevice mDevice = null;
 
     /**
@@ -168,8 +183,11 @@
             }
             long startTime = System.currentTimeMillis();
 
+            Set<String> filteredTests = getFilteredTests(testDevice, root);
+            CLog.d("List that will be used: %s", Arrays.asList(filteredTests));
+
             // Count expected number of tests
-            int numTests = countExpectedTests(testDevice, root);
+            int numTests = filteredTests.size();
             if (numTests == 0) {
                 CLog.d("No tests to run.");
                 return;
@@ -180,11 +198,15 @@
             GoogleBenchmarkResultParser resultParser = createResultParser(runName, listener);
             listener.testRunStarted(runName, numTests);
             try {
-                String cmd = String.format("%s %s", root, GBENCHMARK_JSON_OUTPUT_FORMAT);
+                String cmd =
+                        String.format(
+                                "%s%s %s",
+                                root,
+                                getFilterFlagForTests(filteredTests),
+                                GBENCHMARK_JSON_OUTPUT_FORMAT);
                 CLog.i(String.format("Running google benchmark test on %s: %s",
                         mDevice.getSerialNumber(), cmd));
-                testDevice.executeShellCommand(cmd, outputCollector,
-                        mMaxRunTime, TimeUnit.MILLISECONDS, 0);
+                executeCommand(testDevice, cmd, outputCollector);
                 metricMap = resultParser.parse(outputCollector);
             } catch (DeviceNotAvailableException e) {
                 listener.testRunFailed(e.getMessage());
@@ -196,17 +218,60 @@
         }
     }
 
-    private int countExpectedTests(ITestDevice testDevice, String fullBinaryPath)
+    /**
+     * Returns benchmark tests matching current filters.
+     *
+     * @param testDevice the device on which to run the command
+     * @param fullBinaryPath the full binary path
+     * @return matching benchmark tests.
+     * @throws DeviceNotAvailableException
+     */
+    private Set<String> getFilteredTests(ITestDevice testDevice, String fullBinaryPath)
             throws DeviceNotAvailableException {
-        if (!testDevice.isExecutable(fullBinaryPath)) {
-            CLog.d("%s does not look like an executable", fullBinaryPath);
-            return 0;
+        Set<String> filteredTests = getTestsForFilters(testDevice, fullBinaryPath, mIncludeFilters);
+        if (!mExcludeFilters.isEmpty()) {
+            filteredTests.removeAll(
+                    getTestsForFilters(testDevice, fullBinaryPath, mExcludeFilters));
         }
-        String cmd = String.format("%s %s", fullBinaryPath, GBENCHMARK_LIST_TESTS_OPTION);
-        String list_output = testDevice.executeShellCommand(cmd);
+        return filteredTests;
+    }
+
+    /**
+     * Returns benchmark tests matching the filters.
+     *
+     * @param testDevice the device on which to run the command
+     * @param fullBinaryPath the full binary path
+     * @param filters filters for matching benchmark tests
+     * @return matching benchmark tests.
+     * @throws DeviceNotAvailableException
+     */
+    private Set<String> getTestsForFilters(
+            ITestDevice testDevice, String fullBinaryPath, Set<String> filters)
+            throws DeviceNotAvailableException {
+        String cmd =
+                String.format(
+                        "%s%s %s",
+                        fullBinaryPath,
+                        getFilterFlagForFilters(filters),
+                        GBENCHMARK_LIST_TESTS_OPTION);
+        String list_output = executeCommand(testDevice, cmd, null /* outputReceiver */);
         String[] list = list_output.trim().split("\n");
-        CLog.d("List that will be used: %s", Arrays.asList(list));
-        return list.length;
+        if (noMatchesFound(list)) {
+            list = new String[0];
+        }
+        return new LinkedHashSet<String>(Arrays.asList(list));
+    }
+
+    // ** Returns true if no matches found. */
+    private boolean noMatchesFound(String[] list) {
+        if (list.length == 0) {
+            return true;
+        }
+
+        // A benchmark name is a single word.
+        // A no-match output is "Failed to match any benchmarks ..."
+        // Consider no matches found if the output line has multiple-words.
+        return (list[0].indexOf(' ') >= 0);
     }
 
     /**
@@ -262,4 +327,162 @@
         }
         doRunAllTestsInSubdirectory(testPath, mDevice, listener);
     }
+
+    /*
+     * Conforms filters using a {@link TestDescription} format to be recognized by the
+     * GoogleBenchmarkTest executable.
+     */
+    public String cleanFilter(String filter) {
+        Integer index = filter.indexOf('#');
+        if (index >= 0) {
+            // For "class#method", takes the "method" part as the benchmark name.
+            String benchmark = filter.substring(index + 1);
+            if (benchmark.isEmpty()) {
+                CLog.e("Invalid filter %s. Result could be unexpected.", filter);
+            } else {
+                return benchmark;
+            }
+        }
+        return filter;
+    }
+
+    /** {@inheritDoc} */
+    @Override
+    public void addIncludeFilter(String filter) {
+        mIncludeFilters.add(cleanFilter(filter));
+    }
+
+    /** {@inheritDoc} */
+    @Override
+    public void addAllIncludeFilters(Set<String> filters) {
+        for (String filter : filters) {
+            mIncludeFilters.add(cleanFilter(filter));
+        }
+    }
+
+    /** {@inheritDoc} */
+    @Override
+    public void addExcludeFilter(String filter) {
+        mExcludeFilters.add(cleanFilter(filter));
+    }
+
+    /** {@inheritDoc} */
+    @Override
+    public void addAllExcludeFilters(Set<String> filters) {
+        for (String filter : filters) {
+            mExcludeFilters.add(cleanFilter(filter));
+        }
+    }
+
+    /** {@inheritDoc} */
+    @Override
+    public Set<String> getIncludeFilters() {
+        return mIncludeFilters;
+    }
+
+    /** {@inheritDoc} */
+    @Override
+    public Set<String> getExcludeFilters() {
+        return mExcludeFilters;
+    }
+
+    /** {@inheritDoc} */
+    @Override
+    public void clearIncludeFilters() {
+        mIncludeFilters.clear();
+    }
+
+    /** {@inheritDoc} */
+    @Override
+    public void clearExcludeFilters() {
+        mExcludeFilters.clear();
+    }
+
+    @VisibleForTesting
+    protected String getFilterFlagForFilters(Set<String> filters) {
+        // Use single filter as only the last "--benchmark_filter" is recognized.
+        StringBuilder filterFlag = new StringBuilder();
+        Iterator<String> iterator = filters.iterator();
+        if (iterator.hasNext()) {
+            filterFlag.append(String.format(" %s=%s", GBENCHMARK_FILTER_OPTION, iterator.next()));
+            while (iterator.hasNext()) {
+                filterFlag.append(String.format("\\|%s", iterator.next()));
+            }
+        }
+        return filterFlag.toString();
+    }
+
+    @VisibleForTesting
+    protected String getFilterFlagForTests(Set<String> fitlererTests) {
+        // Pass the list of tests as filter since BenchmarkTest can't handle negative filters.
+        StringBuilder filterFlag = new StringBuilder();
+        Iterator<String> iterator = fitlererTests.iterator();
+        if (iterator.hasNext()) {
+            // Format benchmark as "^benchmark$" to avoid unintended regex partial matching.
+            filterFlag.append(String.format(" %s=^%s$", GBENCHMARK_FILTER_OPTION, iterator.next()));
+            while (iterator.hasNext()) {
+                filterFlag.append(String.format("\\|^%s$", iterator.next()));
+            }
+        }
+        return filterFlag.toString();
+    }
+
+    /**
+     * Helper method to run a benchmarktest command. If the command is too long to be run directly
+     * by adb, it runs from a temporary script.
+     *
+     * @param testDevice the device on which to run the command
+     * @param cmd the command string to run
+     * @param outputReceiver the output receiver for reading test results
+     * @return shell output if outputReceiver is null
+     */
+    protected String executeCommand(
+            final ITestDevice testDevice,
+            final String cmd,
+            final IShellOutputReceiver outputReceiver)
+            throws DeviceNotAvailableException {
+        // Ensure that command is not too long for adb
+        if (cmd.length() < ADB_CMD_CHAR_LIMIT) {
+            if (outputReceiver == null) {
+                return testDevice.executeShellCommand(cmd);
+            }
+
+            testDevice.executeShellCommand(
+                    cmd,
+                    outputReceiver,
+                    mMaxRunTime /* maxTimeToShellOutputResponse */,
+                    TimeUnit.MILLISECONDS,
+                    0 /* retryAttempts */);
+            return null;
+        }
+
+        // Wrap adb shell command in script if command is too long for direct execution
+        return executeCommandByScript(testDevice, cmd, outputReceiver);
+    }
+
+    /** Runs a command from a temporary script. */
+    private String executeCommandByScript(
+            final ITestDevice testDevice,
+            final String cmd,
+            final IShellOutputReceiver outputReceiver)
+            throws DeviceNotAvailableException {
+        String tmpFileDevice = "/data/local/tmp/gbenchmarktest_script.sh";
+        testDevice.pushString(String.format("#!/bin/bash\n%s", cmd), tmpFileDevice);
+        String shellOutput = null;
+        try {
+            if (outputReceiver == null) {
+                shellOutput = testDevice.executeShellCommand(String.format("sh %s", tmpFileDevice));
+            } else {
+                testDevice.executeShellCommand(
+                        String.format("sh %s", tmpFileDevice),
+                        outputReceiver,
+                        mMaxRunTime /* maxTimeToShellOutputResponse */,
+                        TimeUnit.MILLISECONDS,
+                        0 /* retry attempts */);
+            }
+        } finally {
+            testDevice.deleteFile(tmpFileDevice);
+        }
+        return shellOutput;
+    }
 }
diff --git a/tests/src/com/android/tradefed/UnitTests.java b/tests/src/com/android/tradefed/UnitTests.java
index 45594a4..101caa8 100644
--- a/tests/src/com/android/tradefed/UnitTests.java
+++ b/tests/src/com/android/tradefed/UnitTests.java
@@ -235,6 +235,7 @@
 import com.android.tradefed.targetprep.RootTargetPreparerTest;
 import com.android.tradefed.targetprep.RunCommandTargetPreparerTest;
 import com.android.tradefed.targetprep.RunHostCommandTargetPreparerTest;
+import com.android.tradefed.targetprep.RunHostScriptTargetPreparerTest;
 import com.android.tradefed.targetprep.StopServicesSetupTest;
 import com.android.tradefed.targetprep.SwitchUserTargetPreparerTest;
 import com.android.tradefed.targetprep.SystemUpdaterDeviceFlasherTest;
@@ -674,6 +675,7 @@
     RootTargetPreparerTest.class,
     RunCommandTargetPreparerTest.class,
     RunHostCommandTargetPreparerTest.class,
+    RunHostScriptTargetPreparerTest.class,
     StopServicesSetupTest.class,
     SystemUpdaterDeviceFlasherTest.class,
     TargetSetupErrorTest.class,
diff --git a/tests/src/com/android/tradefed/device/DeviceSelectionOptionsTest.java b/tests/src/com/android/tradefed/device/DeviceSelectionOptionsTest.java
index c263704..12d849a 100644
--- a/tests/src/com/android/tradefed/device/DeviceSelectionOptionsTest.java
+++ b/tests/src/com/android/tradefed/device/DeviceSelectionOptionsTest.java
@@ -538,15 +538,22 @@
         assertFalse(mDeviceSelection.matches(mMockDevice));
     }
 
-    /**
-     * Test that min-battery is not used to check non physical devices otherwise they will never
-     * match.
-     */
+    /** Test that if min-battery is used for a StubDevice it will not match. */
     @Test
     public void testStubDevice_minBattery() throws Exception {
         OptionSetter setter = new OptionSetter(mDeviceSelection);
+        setter.setOptionValue("require-battery-check", "true");
         setter.setOptionValue("min-battery", "20");
         setter.setOptionValue("null-device", "true");
+        assertFalse(mDeviceSelection.matches(new NullDevice("test")));
+    }
+
+    /** Test that if we require a battery check but no minimal or max, StubDevice can be matched. */
+    @Test
+    public void testStubDevice_requireBatteryCheck() throws Exception {
+        OptionSetter setter = new OptionSetter(mDeviceSelection);
+        setter.setOptionValue("require-battery-check", "true");
+        setter.setOptionValue("null-device", "true");
         assertTrue(mDeviceSelection.matches(new NullDevice("test")));
     }
 
diff --git a/tests/src/com/android/tradefed/device/LocalAndroidVirtualDeviceTest.java b/tests/src/com/android/tradefed/device/LocalAndroidVirtualDeviceTest.java
index c123833..70a20f2 100644
--- a/tests/src/com/android/tradefed/device/LocalAndroidVirtualDeviceTest.java
+++ b/tests/src/com/android/tradefed/device/LocalAndroidVirtualDeviceTest.java
@@ -18,7 +18,6 @@
 
 import com.android.ddmlib.IDevice;
 import com.android.ddmlib.Log.LogLevel;
-import com.android.tradefed.build.IBuildInfo;
 import com.android.tradefed.build.IDeviceBuildInfo;
 import com.android.tradefed.log.ITestLogger;
 import com.android.tradefed.result.LogDataType;
@@ -29,14 +28,7 @@
 import com.android.tradefed.util.IRunUtil;
 import com.android.tradefed.util.StreamUtil;
 import com.android.tradefed.util.ZipUtil;
-import java.io.BufferedOutputStream;
-import java.io.File;
-import java.io.FileOutputStream;
-import java.io.IOException;
-import java.io.OutputStream;
-import java.util.ArrayList;
-import java.util.List;
-import java.util.zip.GZIPOutputStream;
+
 import org.apache.commons.compress.archivers.tar.TarArchiveEntry;
 import org.apache.commons.compress.archivers.tar.TarArchiveOutputStream;
 import org.easymock.Capture;
@@ -49,6 +41,14 @@
 import org.junit.runner.RunWith;
 import org.junit.runners.JUnit4;
 
+import java.io.BufferedOutputStream;
+import java.io.File;
+import java.io.FileOutputStream;
+import java.io.IOException;
+import java.io.OutputStream;
+import java.util.ArrayList;
+import java.util.zip.GZIPOutputStream;
+
 /** Unit tests for {@link LocalAndroidVirtualDevice}. */
 @RunWith(JUnit4.class)
 public class LocalAndroidVirtualDeviceTest {
@@ -351,7 +351,7 @@
         mLocalAvd.setTestLogger(testLogger);
         mLocalAvd.currentRunUtil = acloudCreateRunUtil;
         mLocalAvd.expectToConnect = true;
-        mLocalAvd.preInvocationSetup(mMockDeviceBuildInfo, null);
+        mLocalAvd.preInvocationSetup(mMockDeviceBuildInfo);
 
         Assert.assertEquals(ONLINE_SERIAL_NUMBER, mLocalAvd.getIDevice().getSerialNumber());
 
@@ -407,7 +407,7 @@
         mLocalAvd.setTestLogger(testLogger);
         mLocalAvd.currentRunUtil = acloudCreateRunUtil;
         try {
-            mLocalAvd.preInvocationSetup(mMockDeviceBuildInfo, null);
+            mLocalAvd.preInvocationSetup(mMockDeviceBuildInfo);
             Assert.fail("TargetSetupError is not thrown");
         } catch (TargetSetupError e) {
             expectedException = e;
@@ -449,7 +449,7 @@
         mLocalAvd.setTestLogger(testLogger);
         mLocalAvd.currentRunUtil = acloudCreateRunUtil;
         try {
-            mLocalAvd.preInvocationSetup(mMockDeviceBuildInfo, null);
+            mLocalAvd.preInvocationSetup(mMockDeviceBuildInfo);
             Assert.fail("TargetSetupError is not thrown");
         } catch (TargetSetupError e) {
             expectedException = e;
diff --git a/tests/src/com/android/tradefed/device/metric/FilePullerDeviceMetricCollectorTest.java b/tests/src/com/android/tradefed/device/metric/FilePullerDeviceMetricCollectorTest.java
index 27e9203..ec04f22 100644
--- a/tests/src/com/android/tradefed/device/metric/FilePullerDeviceMetricCollectorTest.java
+++ b/tests/src/com/android/tradefed/device/metric/FilePullerDeviceMetricCollectorTest.java
@@ -95,6 +95,35 @@
     }
 
     /**
+     * Test when a multiple file is found matching the matching key, then pulled and {@link
+     * FilePullerDeviceMetricCollector#processMetricFile(String, File, DeviceMetricData)} is called
+     * multiple times.
+     */
+    @Test
+    public void testPullMultipleMatchingKeyInMetrics() throws Exception {
+        OptionSetter setter = new OptionSetter(mFilePuller);
+        setter.setOptionValue("pull-pattern-keys", "coverageFile");
+        HashMap<String, Metric> currentMetrics = new HashMap<>();
+        currentMetrics.put("coverageFile", TfMetricProtoUtil.stringToMetric("/data/coverage1"));
+        currentMetrics.put("coverageFileAnother",
+                TfMetricProtoUtil.stringToMetric("/data/coverage2"));
+
+        Mockito.when(mMockDevice.pullFile(Mockito.eq("/data/coverage1")))
+                .thenReturn(new File("fake1"));
+        Mockito.when(mMockDevice.pullFile(Mockito.eq("/data/coverage2")))
+                .thenReturn(new File("fake2"));
+
+        mFilePuller.testRunStarted("fakeRun", 5);
+        mFilePuller.testRunEnded(500, currentMetrics);
+
+        Mockito.verify(mMockListener)
+                .testLog(Mockito.eq("coverageFile"), Mockito.eq(LogDataType.TEXT), Mockito.any());
+        Mockito.verify(mMockListener)
+                .testLog(Mockito.eq("coverageFileAnother"), Mockito.eq(LogDataType.TEXT),
+                        Mockito.any());
+    }
+
+    /**
      * Test when a file is found matching the key using a pattern matching, then pulled and {@link
      * FilePullerDeviceMetricCollector#processMetricFile(String, File, DeviceMetricData)} is called.
      */
diff --git a/tests/src/com/android/tradefed/invoker/SandboxedInvocationExecutionTest.java b/tests/src/com/android/tradefed/invoker/SandboxedInvocationExecutionTest.java
index 654055e..a29e7ae 100644
--- a/tests/src/com/android/tradefed/invoker/SandboxedInvocationExecutionTest.java
+++ b/tests/src/com/android/tradefed/invoker/SandboxedInvocationExecutionTest.java
@@ -322,7 +322,7 @@
         mContext.addInvocationAttribute("test", "test");
         // Device early preInvocationSetup was called and even if no tests run we still call tear
         // down
-        Mockito.verify(mMockDevice).preInvocationSetup(any(), any());
+        Mockito.verify(mMockDevice).preInvocationSetup(any());
         Mockito.verify(mMockDevice).postInvocationTearDown(null);
     }
 
@@ -379,7 +379,7 @@
         doReturn(info).when(mMockProvider).getBuild();
 
         DeviceNotAvailableException exception = new DeviceNotAvailableException("reason", "serial");
-        doThrow(exception).when(mMockDevice).preInvocationSetup(eq(info), any());
+        doThrow(exception).when(mMockDevice).preInvocationSetup(eq(info));
 
         mInvocation.invoke(mContext, mConfig, mMockRescheduler, mMockListener);
         // No tests to run but we still call start/end
@@ -390,7 +390,7 @@
         mContext.addInvocationAttribute("test", "test");
         // Device early preInvocationSetup was called and even if no tests run we still call tear
         // down
-        Mockito.verify(mMockDevice).preInvocationSetup(any(), any());
+        Mockito.verify(mMockDevice).preInvocationSetup(any());
         Mockito.verify(mMockDevice).postInvocationTearDown(exception);
     }
 
diff --git a/tests/src/com/android/tradefed/invoker/TestInvocationTest.java b/tests/src/com/android/tradefed/invoker/TestInvocationTest.java
index 1925909..3d56501 100644
--- a/tests/src/com/android/tradefed/invoker/TestInvocationTest.java
+++ b/tests/src/com/android/tradefed/invoker/TestInvocationTest.java
@@ -221,8 +221,7 @@
         EasyMock.expect(mMockDevice.getDeviceState()).andStubReturn(TestDeviceState.NOT_AVAILABLE);
         mMockDevice.setRecoveryMode(RecoveryMode.AVAILABLE);
         mMockDevice.setRecovery(mMockRecovery);
-        mMockDevice.preInvocationSetup(
-                (IBuildInfo) EasyMock.anyObject(), EasyMock.<List<IBuildInfo>>anyObject());
+        mMockDevice.preInvocationSetup((IBuildInfo) EasyMock.anyObject());
         EasyMock.expectLastCall().anyTimes();
         mFakeDescriptor =
                 new DeviceDescriptor(
@@ -1582,8 +1581,6 @@
         mMockBuildInfo = EasyMock.createMock(IDeviceBuildInfo.class);
         EasyMock.expect(mMockBuildInfo.getProperties()).andStubReturn(new HashSet<>());
         EasyMock.expect(mMockBuildInfo.isTestResourceBuild()).andStubReturn(false);
-        mMockBuildInfo.setTestResourceBuild(EasyMock.anyBoolean());
-        EasyMock.expectLastCall().anyTimes();
 
         IRemoteTest test = EasyMock.createNiceMock(IRemoteTest.class);
         mMockPreparer.tearDown(EasyMock.anyObject(), EasyMock.isNull());
@@ -1683,8 +1680,6 @@
                     .andReturn(tmpTestsDir);
             EasyMock.expect(mMockBuildInfo.getProperties()).andStubReturn(new HashSet<>());
             EasyMock.expect(mMockBuildInfo.isTestResourceBuild()).andStubReturn(false);
-            mMockBuildInfo.setTestResourceBuild(EasyMock.anyBoolean());
-            EasyMock.expectLastCall().anyTimes();
 
             setupMockSuccessListeners();
             setupNormalInvoke(test);
@@ -1777,8 +1772,6 @@
             prop.add(BuildInfoProperties.DO_NOT_LINK_TESTS_DIR);
             EasyMock.expect(mMockBuildInfo.getProperties()).andStubReturn(prop);
             EasyMock.expect(mMockBuildInfo.isTestResourceBuild()).andStubReturn(false);
-            mMockBuildInfo.setTestResourceBuild(EasyMock.anyBoolean());
-            EasyMock.expectLastCall().anyTimes();
 
             setupMockSuccessListeners();
             setupNormalInvoke(test);
diff --git a/tests/src/com/android/tradefed/result/suite/XmlSuiteResultFormatterTest.java b/tests/src/com/android/tradefed/result/suite/XmlSuiteResultFormatterTest.java
index 7eb74c6..647db0b 100644
--- a/tests/src/com/android/tradefed/result/suite/XmlSuiteResultFormatterTest.java
+++ b/tests/src/com/android/tradefed/result/suite/XmlSuiteResultFormatterTest.java
@@ -16,6 +16,7 @@
 package com.android.tradefed.result.suite;
 
 import static org.junit.Assert.assertEquals;
+import static org.junit.Assert.assertFalse;
 import static org.junit.Assert.assertNotNull;
 import static org.junit.Assert.assertTrue;
 
@@ -184,6 +185,62 @@
         assertEquals(holder.runResults.size(), mResultHolder.runResults.size());
     }
 
+    @Test
+    public void testFailuresReporting_notDone() throws Exception {
+        mResultHolder.context = mContext;
+
+        List<TestRunResult> runResults = new ArrayList<>();
+        runResults.add(createFakeResult("module1", 2, 1, 0, 0));
+        runResults.get(0).testRunFailed("Failed module");
+        mResultHolder.runResults = runResults;
+
+        Map<String, IAbi> modulesAbi = new HashMap<>();
+        modulesAbi.put("module1", new Abi("armeabi-v7a", "32"));
+        mResultHolder.modulesAbi = modulesAbi;
+
+        mResultHolder.completeModules = 2;
+        mResultHolder.totalModules = 1;
+        mResultHolder.passedTests = 2;
+        mResultHolder.failedTests = 1;
+        mResultHolder.startTime = 0L;
+        mResultHolder.endTime = 10L;
+        File res = mFormatter.writeResults(mResultHolder, mResultDir);
+        String content = FileUtil.readStringFromFile(res);
+
+        assertXmlContainsNode(content, "Result/Module");
+        assertXmlContainsAttribute(content, "Result/Module", "done", "false");
+        assertXmlContainsNode(content, "Result/Module/Reason");
+        assertXmlContainsAttribute(content, "Result/Module/TestCase", "name", "com.class.module1");
+        assertXmlContainsAttribute(
+                content, "Result/Module/TestCase/Test", "name", "module1.method0");
+        assertXmlContainsAttribute(
+                content, "Result/Module/TestCase/Test", "name", "module1.method1");
+        // Check that failures are showing in the xml for the test cases
+        assertXmlContainsAttribute(
+                content, "Result/Module/TestCase/Test", "name", "module1.failed0");
+        assertXmlContainsAttribute(content, "Result/Module/TestCase/Test", "result", "fail");
+        assertXmlContainsAttribute(
+                content, "Result/Module/TestCase/Test/Failure", "message", "module1 failed.");
+        assertXmlContainsValue(
+                content,
+                "Result/Module/TestCase/Test/Failure/StackTrace",
+                XmlSuiteResultFormatter.sanitizeXmlContent("module1 failed.\nstack\nstack\0"));
+        // Test that we can read back the informations
+        SuiteResultHolder holder = mFormatter.parseResults(mResultDir, false);
+        assertEquals(holder.completeModules, mResultHolder.completeModules);
+        assertEquals(holder.totalModules, mResultHolder.totalModules);
+        assertEquals(holder.passedTests, mResultHolder.passedTests);
+        assertEquals(holder.failedTests, mResultHolder.failedTests);
+        assertEquals(holder.startTime, mResultHolder.startTime);
+        assertEquals(holder.endTime, mResultHolder.endTime);
+        assertEquals(
+                holder.modulesAbi.get("armeabi-v7a module1"),
+                mResultHolder.modulesAbi.get("module1"));
+        assertEquals(1, holder.runResults.size());
+        TestRunResult result = new ArrayList<>(holder.runResults).get(0);
+        assertFalse(result.isRunComplete());
+    }
+
     /** Test that assumption failures and ignored tests are correctly reported in the xml. */
     @Test
     public void testAssumptionFailures_Ignore_Reporting() throws Exception {
diff --git a/tests/src/com/android/tradefed/targetprep/RunHostScriptTargetPreparerTest.java b/tests/src/com/android/tradefed/targetprep/RunHostScriptTargetPreparerTest.java
new file mode 100644
index 0000000..4125fa1
--- /dev/null
+++ b/tests/src/com/android/tradefed/targetprep/RunHostScriptTargetPreparerTest.java
@@ -0,0 +1,172 @@
+/*
+ * Copyright (C) 2020 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.tradefed.targetprep;
+
+import static org.junit.Assert.assertTrue;
+import static org.mockito.ArgumentMatchers.any;
+import static org.mockito.ArgumentMatchers.anyBoolean;
+import static org.mockito.ArgumentMatchers.anyLong;
+import static org.mockito.ArgumentMatchers.eq;
+import static org.mockito.Mockito.never;
+import static org.mockito.Mockito.verify;
+import static org.mockito.Mockito.when;
+
+import com.android.tradefed.config.ConfigurationException;
+import com.android.tradefed.config.OptionSetter;
+import com.android.tradefed.device.IDeviceManager;
+import com.android.tradefed.invoker.ExecutionFiles.FilesKey;
+import com.android.tradefed.invoker.TestInformation;
+import com.android.tradefed.util.CommandResult;
+import com.android.tradefed.util.CommandStatus;
+import com.android.tradefed.util.FileUtil;
+import com.android.tradefed.util.IRunUtil;
+
+import org.junit.After;
+import org.junit.Before;
+import org.junit.Rule;
+import org.junit.Test;
+import org.junit.runner.RunWith;
+import org.junit.runners.JUnit4;
+import org.mockito.Answers;
+import org.mockito.Mock;
+import org.mockito.junit.MockitoJUnit;
+import org.mockito.junit.MockitoRule;
+
+import java.io.File;
+import java.io.IOException;
+import java.nio.file.Files;
+import java.nio.file.Path;
+
+/** Unit test for {@link RunHostScriptTargetPreparer}. */
+@RunWith(JUnit4.class)
+public final class RunHostScriptTargetPreparerTest {
+
+    private static final String DEVICE_SERIAL = "DEVICE_SERIAL";
+
+    @Rule public final MockitoRule mockito = MockitoJUnit.rule();
+
+    @Mock(answer = Answers.RETURNS_DEEP_STUBS)
+    private TestInformation mTestInfo;
+
+    @Mock private IRunUtil mRunUtil;
+    @Mock private IDeviceManager mDeviceManager;
+    private RunHostScriptTargetPreparer mPreparer;
+    private OptionSetter mOptionSetter;
+    private File mWorkDir;
+    private File mScriptFile;
+
+    @Before
+    public void setUp() throws IOException, ConfigurationException {
+        // Initialize preparer and test information
+        mPreparer =
+                new RunHostScriptTargetPreparer() {
+                    @Override
+                    IRunUtil getRunUtil() {
+                        return mRunUtil;
+                    }
+
+                    @Override
+                    IDeviceManager getDeviceManager() {
+                        return mDeviceManager;
+                    }
+                };
+        mOptionSetter = new OptionSetter(mPreparer);
+        when(mTestInfo.getDevice().getSerialNumber()).thenReturn(DEVICE_SERIAL);
+        when(mTestInfo.executionFiles().get(any(FilesKey.class))).thenReturn(null);
+
+        // Create temporary working directory and script file
+        mWorkDir = FileUtil.createTempDir(this.getClass().getSimpleName());
+        mScriptFile = File.createTempFile("script", ".sh", mWorkDir);
+
+        // Default to default adb/fastboot paths
+        when(mDeviceManager.getAdbPath()).thenReturn("adb");
+        when(mDeviceManager.getFastbootPath()).thenReturn("fastboot");
+
+        // Default to successful execution
+        CommandResult result = new CommandResult(CommandStatus.SUCCESS);
+        when(mRunUtil.runTimedCmd(anyLong(), any())).thenReturn(result);
+    }
+
+    @After
+    public void tearDown() {
+        FileUtil.recursiveDelete(mWorkDir);
+    }
+
+    @Test
+    public void testSetUp() throws Exception {
+        mOptionSetter.setOptionValue("script-file", mScriptFile.getAbsolutePath());
+        mOptionSetter.setOptionValue("script-timeout", "10");
+        // Verify environment, timeout, and script path
+        mPreparer.setUp(mTestInfo);
+        verify(mRunUtil).setEnvVariable("ANDROID_SERIAL", DEVICE_SERIAL);
+        verify(mRunUtil, never()).setEnvVariable(eq("PATH"), any()); // uses default PATH
+        verify(mRunUtil).runTimedCmd(10L, mScriptFile.getAbsolutePath());
+        // Verify that script is executable
+        assertTrue(mScriptFile.canExecute());
+    }
+
+    @Test
+    public void testSetUp_workingDir() throws Exception {
+        mOptionSetter.setOptionValue("work-dir", mWorkDir.getAbsolutePath());
+        mOptionSetter.setOptionValue("script-file", mScriptFile.getName()); // relative
+        // Verify that the working directory is set and script's path is resolved
+        mPreparer.setUp(mTestInfo);
+        verify(mRunUtil).setWorkingDir(mWorkDir);
+        verify(mRunUtil).runTimedCmd(anyLong(), eq(mScriptFile.getAbsolutePath()));
+    }
+
+    @Test
+    public void testSetUp_findFile() throws Exception {
+        mOptionSetter.setOptionValue("script-file", mScriptFile.getName()); // relative
+        when(mTestInfo.getDependencyFile(any(), anyBoolean())).thenReturn(mScriptFile);
+        // Verify that the script is found in the test information
+        mPreparer.setUp(mTestInfo);
+        verify(mRunUtil).runTimedCmd(anyLong(), eq(mScriptFile.getAbsolutePath()));
+    }
+
+    @Test(expected = TargetSetupError.class)
+    public void testSetUp_fileNotFound() throws Exception {
+        mOptionSetter.setOptionValue("script-file", "unknown.sh");
+        mPreparer.setUp(mTestInfo);
+    }
+
+    @Test(expected = TargetSetupError.class)
+    public void testSetUp_executionError() throws Exception {
+        mOptionSetter.setOptionValue("script-file", mScriptFile.getAbsolutePath());
+        CommandResult result = new CommandResult(CommandStatus.FAILED);
+        when(mRunUtil.runTimedCmd(anyLong(), any())).thenReturn(result);
+        mPreparer.setUp(mTestInfo);
+    }
+
+    @Test
+    public void testSetUp_pathVariable() throws Exception {
+        mOptionSetter.setOptionValue("script-file", mScriptFile.getAbsolutePath());
+        // Create and set dummy adb binary
+        Path adbDir = Files.createTempDirectory(mWorkDir.toPath(), "adb");
+        File adbBinary = File.createTempFile("adb", ".sh", adbDir.toFile());
+        when(mTestInfo.executionFiles().get(eq(FilesKey.ADB_BINARY))).thenReturn(adbBinary);
+        // Create and set dummy fastboot binary
+        Path fastbootDir = Files.createTempDirectory(mWorkDir.toPath(), "fastboot");
+        File fastbootBinary = File.createTempFile("fastboot", ".sh", fastbootDir.toFile());
+        when(mDeviceManager.getFastbootPath()).thenReturn(fastbootBinary.getAbsolutePath());
+        // Verify that binary paths were prepended to the path variable
+        String separator = System.getProperty("path.separator");
+        String expectedPath = adbDir + separator + fastbootDir + separator + System.getenv("PATH");
+        mPreparer.setUp(mTestInfo);
+        verify(mRunUtil).setEnvVariable("PATH", expectedPath);
+    }
+}
diff --git a/tests/src/com/android/tradefed/testtype/GTestListenerTest.java b/tests/src/com/android/tradefed/testtype/GTestListenerTest.java
index 392ee75..d05f3fb 100644
--- a/tests/src/com/android/tradefed/testtype/GTestListenerTest.java
+++ b/tests/src/com/android/tradefed/testtype/GTestListenerTest.java
@@ -96,7 +96,7 @@
         String moduleName = "testWithDuplicateTests";
         String testClass = "testClass";
         String testName1 = "testName1";
-        String duplicateTestsMessage = "The following tests ran more than once: ";
+        String duplicateTestsMessage = "1 tests ran more than once. Full list:";
         TestDescription testId1 = new TestDescription(testClass, testName1);
 
         mMockListener.testRunStarted(EasyMock.eq(moduleName), EasyMock.eq(2));
diff --git a/tests/src/com/android/tradefed/testtype/GoogleBenchmarkTestTest.java b/tests/src/com/android/tradefed/testtype/GoogleBenchmarkTestTest.java
index 9fbfa13..51877ce 100644
--- a/tests/src/com/android/tradefed/testtype/GoogleBenchmarkTestTest.java
+++ b/tests/src/com/android/tradefed/testtype/GoogleBenchmarkTestTest.java
@@ -22,14 +22,18 @@
 import com.android.tradefed.invoker.TestInformation;
 import com.android.tradefed.metrics.proto.MetricMeasurement.Metric;
 import com.android.tradefed.result.ITestInvocationListener;
+import com.android.tradefed.result.TestDescription;
 
 import junit.framework.TestCase;
 
 import org.easymock.EasyMock;
 
+import java.util.Arrays;
 import java.util.Collections;
 import java.util.HashMap;
+import java.util.LinkedHashSet;
 import java.util.Map;
+import java.util.Set;
 import java.util.concurrent.TimeUnit;
 
 /**
@@ -42,6 +46,7 @@
     private ITestDevice mMockITestDevice = null;
     private GoogleBenchmarkTest mGoogleBenchmarkTest;
     private TestInformation mTestInfo;
+    private TestDescription mDummyTest;
 
     /**
      * Helper to initialize the various EasyMocks we'll need.
@@ -52,23 +57,28 @@
         mMockInvocationListener = EasyMock.createMock(ITestInvocationListener.class);
         mMockReceiver = new CollectingOutputReceiver();
         mMockITestDevice = EasyMock.createMock(ITestDevice.class);
+        mDummyTest = new TestDescription("Class", "method");
         EasyMock.expect(mMockITestDevice.getSerialNumber()).andStubReturn("serial");
-        mGoogleBenchmarkTest = new GoogleBenchmarkTest() {
-            @Override
-            CollectingOutputReceiver createOutputCollector() {
-                return mMockReceiver;
-            }
-            @Override
-            GoogleBenchmarkResultParser createResultParser(String runName,
-                    ITestInvocationListener listener) {
-                return new GoogleBenchmarkResultParser(runName, listener) {
+        mGoogleBenchmarkTest =
+                new GoogleBenchmarkTest() {
                     @Override
-                    public Map<String, String> parse(CollectingOutputReceiver output) {
-                        return Collections.emptyMap();
+                    CollectingOutputReceiver createOutputCollector() {
+                        return mMockReceiver;
+                    }
+
+                    @Override
+                    GoogleBenchmarkResultParser createResultParser(
+                            String runName, ITestInvocationListener listener) {
+                        return new GoogleBenchmarkResultParser(runName, listener) {
+                            @Override
+                            public Map<String, String> parse(CollectingOutputReceiver output) {
+                                listener.testStarted(mDummyTest);
+                                listener.testEnded(mDummyTest, Collections.emptyMap());
+                                return Collections.emptyMap();
+                            }
+                        };
                     }
                 };
-            }
-        };
         mGoogleBenchmarkTest.setDevice(mMockITestDevice);
         mTestInfo = TestInformation.newBuilder().build();
     }
@@ -108,13 +118,11 @@
         mMockITestDevice.executeShellCommand(EasyMock.contains(test2), EasyMock.same(mMockReceiver),
                 EasyMock.anyLong(), (TimeUnit)EasyMock.anyObject(), EasyMock.anyInt());
 
-        EasyMock.expect(mMockITestDevice.isExecutable(nativeTestPath + "/test1")).andReturn(true);
         EasyMock.expect(
                         mMockITestDevice.executeShellCommand(
                                 String.format(
                                         "%s/test1 --benchmark_list_tests=true", nativeTestPath)))
                 .andReturn("method1\nmethod2\nmethod3");
-        EasyMock.expect(mMockITestDevice.isExecutable(nativeTestPath + "/test2")).andReturn(true);
 
         EasyMock.expect(
                         mMockITestDevice.executeShellCommand(
@@ -122,7 +130,13 @@
                                         "%s/test2 --benchmark_list_tests=true", nativeTestPath)))
                 .andReturn("method1\nmethod2\n");
         mMockInvocationListener.testRunStarted(test1, 3);
+        mMockInvocationListener.testStarted(mDummyTest);
+        mMockInvocationListener.testEnded(
+                EasyMock.eq(mDummyTest), EasyMock.<HashMap<String, String>>anyObject());
         mMockInvocationListener.testRunStarted(test2, 2);
+        mMockInvocationListener.testStarted(mDummyTest);
+        mMockInvocationListener.testEnded(
+                EasyMock.eq(mDummyTest), EasyMock.<HashMap<String, String>>anyObject());
         mMockInvocationListener.testRunEnded(
                 EasyMock.anyLong(), EasyMock.<HashMap<String, Metric>>anyObject());
         EasyMock.expectLastCall().times(2);
@@ -185,20 +199,24 @@
                 EasyMock.anyLong(), (TimeUnit)EasyMock.anyObject(), EasyMock.anyInt());
         mMockITestDevice.executeShellCommand(EasyMock.contains(test2), EasyMock.same(mMockReceiver),
                 EasyMock.anyLong(), (TimeUnit)EasyMock.anyObject(), EasyMock.anyInt());
-        EasyMock.expect(mMockITestDevice.isExecutable(nativeTestPath + "/test1")).andReturn(true);
         EasyMock.expect(
                         mMockITestDevice.executeShellCommand(
                                 String.format(
                                         "%s/test1 --benchmark_list_tests=true", nativeTestPath)))
                 .andReturn("\nmethod1\nmethod2\nmethod3\n\n");
-        EasyMock.expect(mMockITestDevice.isExecutable(nativeTestPath + "/test2")).andReturn(true);
         EasyMock.expect(
                         mMockITestDevice.executeShellCommand(
                                 String.format(
                                         "%s/test2 --benchmark_list_tests=true", nativeTestPath)))
                 .andReturn("method1\nmethod2\n");
         mMockInvocationListener.testRunStarted(test1, 3);
+        mMockInvocationListener.testStarted(mDummyTest);
+        mMockInvocationListener.testEnded(
+                EasyMock.eq(mDummyTest), EasyMock.<HashMap<String, String>>anyObject());
         mMockInvocationListener.testRunStarted(test2, 2);
+        mMockInvocationListener.testStarted(mDummyTest);
+        mMockInvocationListener.testEnded(
+                EasyMock.eq(mDummyTest), EasyMock.<HashMap<String, String>>anyObject());
         mMockInvocationListener.testRunEnded(
                 EasyMock.anyLong(), EasyMock.<HashMap<String, Metric>>anyObject());
         EasyMock.expectLastCall().times(2);
@@ -226,7 +244,6 @@
                 .andReturn("");
         mMockITestDevice.executeShellCommand(EasyMock.contains(test1), EasyMock.same(mMockReceiver),
                 EasyMock.anyLong(), (TimeUnit)EasyMock.anyObject(), EasyMock.anyInt());
-        EasyMock.expect(mMockITestDevice.isExecutable(nativeTestPath + "/test1")).andReturn(true);
         EasyMock.expect(
                         mMockITestDevice.executeShellCommand(
                                 String.format(
@@ -234,6 +251,9 @@
                 .andReturn("method1\nmethod2\nmethod3");
         // Expect reportName instead of test name
         mMockInvocationListener.testRunStarted(reportName, 3);
+        mMockInvocationListener.testStarted(mDummyTest);
+        mMockInvocationListener.testEnded(
+                EasyMock.eq(mDummyTest), EasyMock.<HashMap<String, String>>anyObject());
         mMockInvocationListener.testRunEnded(
                 EasyMock.anyLong(), EasyMock.<HashMap<String, Metric>>anyObject());
         EasyMock.expectLastCall();
@@ -260,7 +280,6 @@
         mMockITestDevice.executeShellCommand(EasyMock.contains(test1), EasyMock.same(mMockReceiver),
                 EasyMock.anyLong(), (TimeUnit)EasyMock.anyObject(), EasyMock.anyInt());
         EasyMock.expectLastCall().andThrow(new DeviceNotAvailableException("dnae", "serial"));
-        EasyMock.expect(mMockITestDevice.isExecutable(nativeTestPath + "/test1")).andReturn(true);
         EasyMock.expect(
                         mMockITestDevice.executeShellCommand(
                                 String.format(
@@ -322,4 +341,217 @@
         assertFalse(mGoogleBenchmarkTest.shouldSkipFile("/some/path/file/test.txt"));
 
     }
+
+    /** Test getFilterFlagForFilters. */
+    public void testGetFilterFlagForFilters() {
+        Set<String> filters = new LinkedHashSet<>(Arrays.asList("filter1", "filter2"));
+        String filterFlag = mGoogleBenchmarkTest.getFilterFlagForFilters(filters);
+        assertEquals(
+                String.format(
+                        " %s=%s",
+                        GoogleBenchmarkTest.GBENCHMARK_FILTER_OPTION, "filter1\\|filter2"),
+                filterFlag);
+    }
+
+    /** Test getFilterFlagForFilters - no filters. */
+    public void testGetFilterFlagForFilters_noFilters() {
+        Set<String> filters = new LinkedHashSet<>();
+        String filterFlag = mGoogleBenchmarkTest.getFilterFlagForFilters(filters);
+        assertEquals("", filterFlag);
+    }
+
+    /** Test getFilterFlagForTests. */
+    public void testGetFilterFlagForTests() {
+        Set<String> tests = new LinkedHashSet<>(Arrays.asList("test1", "test2"));
+        String filterFlag = mGoogleBenchmarkTest.getFilterFlagForTests(tests);
+        assertEquals(
+                String.format(
+                        " %s=%s",
+                        GoogleBenchmarkTest.GBENCHMARK_FILTER_OPTION, "^test1$\\|^test2$"),
+                filterFlag);
+    }
+
+    /** Test getFilterFlagForTests - no tests. */
+    public void testGetFilterFlagForTests_noFilters() {
+        Set<String> tests = new LinkedHashSet<>();
+        String filterFlag = mGoogleBenchmarkTest.getFilterFlagForTests(tests);
+        assertEquals("", filterFlag);
+    }
+
+    /**
+     * Helper function to do the actual filtering test.
+     *
+     * @param incFilter filter flag for querying tests to include
+     * @param incTests tests to include
+     * @param excFilter filter flag for querying tests to exclude
+     * @param excTests tests to exclude
+     * @param testFilter filter flag for running tests
+     * @throws DeviceNotAvailableException
+     */
+    private void doTestFilter(String incTests, String excTests, Set<String> filteredTests)
+            throws DeviceNotAvailableException {
+        String nativeTestPath = GoogleBenchmarkTest.DEFAULT_TEST_PATH;
+        String testPath = nativeTestPath + "/test1";
+        // configure the mock file system to have a single test
+        MockFileUtil.setMockDirContents(mMockITestDevice, nativeTestPath, "test1");
+        EasyMock.expect(mMockITestDevice.doesFileExist(nativeTestPath)).andReturn(true);
+        EasyMock.expect(mMockITestDevice.isDirectory(nativeTestPath)).andReturn(true);
+        EasyMock.expect(mMockITestDevice.isDirectory(testPath)).andReturn(false);
+        EasyMock.expect(mMockITestDevice.executeShellCommand(EasyMock.contains("chmod")))
+                .andReturn("");
+        String[] files = new String[] {"test1"};
+        EasyMock.expect(mMockITestDevice.getChildren(nativeTestPath)).andReturn(files);
+
+        // List tests to include
+        if (mGoogleBenchmarkTest.getIncludeFilters().size() > 0) {
+            String incFilterFlag =
+                    mGoogleBenchmarkTest.getFilterFlagForFilters(
+                            mGoogleBenchmarkTest.getIncludeFilters());
+            EasyMock.expect(mMockITestDevice.executeShellCommand(EasyMock.contains(incFilterFlag)))
+                    .andReturn(incTests);
+        } else {
+            EasyMock.expect(
+                            mMockITestDevice.executeShellCommand(
+                                    EasyMock.not(
+                                            EasyMock.contains(
+                                                    GoogleBenchmarkTest.GBENCHMARK_FILTER_OPTION))))
+                    .andReturn(incTests);
+        }
+        if (mGoogleBenchmarkTest.getExcludeFilters().size() > 0) {
+            // List tests to exclude
+            String excFilterFlag =
+                    mGoogleBenchmarkTest.getFilterFlagForFilters(
+                            mGoogleBenchmarkTest.getExcludeFilters());
+            EasyMock.expect(mMockITestDevice.executeShellCommand(EasyMock.contains(excFilterFlag)))
+                    .andReturn(excTests);
+        }
+        if (filteredTests != null && filteredTests.size() > 0) {
+            // Runningt filtered tests
+            String testFilterFlag = mGoogleBenchmarkTest.getFilterFlagForTests(filteredTests);
+            mMockITestDevice.executeShellCommand(
+                    EasyMock.contains(testFilterFlag),
+                    EasyMock.same(mMockReceiver),
+                    EasyMock.anyLong(),
+                    (TimeUnit) EasyMock.anyObject(),
+                    EasyMock.anyInt());
+            mMockInvocationListener.testRunStarted("test1", filteredTests.size());
+            mMockInvocationListener.testStarted(mDummyTest);
+            mMockInvocationListener.testEnded(
+                    EasyMock.eq(mDummyTest), EasyMock.<HashMap<String, String>>anyObject());
+            mMockInvocationListener.testRunEnded(
+                    EasyMock.anyLong(), EasyMock.<HashMap<String, Metric>>anyObject());
+            EasyMock.expectLastCall();
+        }
+        replayMocks();
+
+        mGoogleBenchmarkTest.run(mTestInfo, mMockInvocationListener);
+        verifyMocks();
+    }
+
+    /** Test no matching tests for the filters. */
+    public void testNoMatchingTests() throws DeviceNotAvailableException {
+        Set<String> incFilters = new LinkedHashSet<>(Arrays.asList("X", "Y"));
+        String incTests = "Failed to match any benchmarks against regex: X|Y";
+
+        mGoogleBenchmarkTest.addAllIncludeFilters(incFilters);
+        doTestFilter(incTests, null /* excTests */, null /* testFilter */);
+    }
+
+    /** Test the include filtering of test methods. */
+    public void testIncludeFilter() throws DeviceNotAvailableException {
+        Set<String> incFilters = new LinkedHashSet<>(Arrays.asList("A", "B"));
+        String incTests = "A\nAa\nB\nBb";
+        Set<String> filteredTests = new LinkedHashSet<>(Arrays.asList("A", "Aa", "B", "Bb"));
+
+        mGoogleBenchmarkTest.addAllIncludeFilters(incFilters);
+        doTestFilter(incTests, null /* excTests */, filteredTests);
+    }
+
+    /** Test the exclude filtering of test methods. */
+    public void testExcludeFilter() throws DeviceNotAvailableException {
+        String incTests = "A\nAa\nB\nBb\nC\nCc";
+        Set<String> excFilters = new LinkedHashSet<>(Arrays.asList("Bb", "C"));
+        String excTests = "Bb\nC\nCc";
+        Set<String> filteredTests = new LinkedHashSet<>(Arrays.asList("A", "Aa", "B"));
+
+        mGoogleBenchmarkTest.addAllExcludeFilters(excFilters);
+        doTestFilter(incTests, excTests, filteredTests);
+    }
+
+    /** Test the include & exclude filtering of test methods. */
+    public void testIncludeAndExcludeFilter() throws DeviceNotAvailableException {
+        Set<String> incFilters = new LinkedHashSet<>(Arrays.asList("A", "B"));
+        String incTests = "A\nAa\nB\nBb";
+        Set<String> excFilters = new LinkedHashSet<>(Arrays.asList("Bb", "C"));
+        String excTests = "Bb\nC\nCc";
+        Set<String> filteredTests = new LinkedHashSet<>(Arrays.asList("A", "Aa", "B"));
+
+        mGoogleBenchmarkTest.addAllIncludeFilters(incFilters);
+        mGoogleBenchmarkTest.addAllExcludeFilters(excFilters);
+        doTestFilter(incTests, excTests, filteredTests);
+    }
+
+    /** Test the ITestDescription filter format "class#method". */
+    public void testClearFilter() throws DeviceNotAvailableException {
+        Set<String> incFilters = new LinkedHashSet<>(Arrays.asList("X#A", "X#B"));
+        Set<String> expectedIncFilters = new LinkedHashSet<>(Arrays.asList("A", "B"));
+        mGoogleBenchmarkTest.addAllIncludeFilters(incFilters);
+        assertEquals(expectedIncFilters, mGoogleBenchmarkTest.getIncludeFilters());
+    }
+
+    /** Test behavior for command lines too long to be run by ADB */
+    public void testCommandTooLong() throws DeviceNotAvailableException {
+        String deviceScriptPath = "/data/local/tmp/gbenchmarktest_script.sh";
+        StringBuilder testNameBuilder = new StringBuilder();
+        for (int i = 0; i < GoogleBenchmarkTest.ADB_CMD_CHAR_LIMIT; i++) {
+            testNameBuilder.append("a");
+        }
+        String testName = testNameBuilder.toString();
+        // filter string will be longer than GTest.ADB_CMD_CHAR_LIMIT
+
+        String nativeTestPath = GoogleBenchmarkTest.DEFAULT_TEST_PATH;
+        String testPath = nativeTestPath + "/" + testName;
+        // configure the mock file system to have a single test
+        MockFileUtil.setMockDirContents(mMockITestDevice, nativeTestPath, "test1");
+        EasyMock.expect(mMockITestDevice.doesFileExist(nativeTestPath)).andReturn(true);
+        EasyMock.expect(mMockITestDevice.isDirectory(nativeTestPath)).andReturn(true);
+        EasyMock.expect(mMockITestDevice.isDirectory(testPath)).andReturn(false);
+        EasyMock.expect(mMockITestDevice.executeShellCommand(EasyMock.contains("chmod")))
+                .andReturn("");
+        String[] files = new String[] {testName};
+        EasyMock.expect(mMockITestDevice.getChildren(nativeTestPath)).andReturn(files);
+        // List tests
+        EasyMock.expect(
+                        mMockITestDevice.pushString(
+                                EasyMock.<String>anyObject(), EasyMock.eq(deviceScriptPath)))
+                .andReturn(Boolean.TRUE);
+        EasyMock.expect(
+                        mMockITestDevice.executeShellCommand(
+                                EasyMock.eq(String.format("sh %s", deviceScriptPath))))
+                .andReturn("test");
+        mMockITestDevice.deleteFile(deviceScriptPath);
+        // Run tests
+        EasyMock.expect(
+                        mMockITestDevice.pushString(
+                                EasyMock.<String>anyObject(), EasyMock.eq(deviceScriptPath)))
+                .andReturn(Boolean.TRUE);
+        mMockITestDevice.executeShellCommand(
+                EasyMock.eq(String.format("sh %s", deviceScriptPath)),
+                EasyMock.same(mMockReceiver),
+                EasyMock.anyLong(),
+                (TimeUnit) EasyMock.anyObject(),
+                EasyMock.anyInt());
+        mMockITestDevice.deleteFile(deviceScriptPath);
+        mMockInvocationListener.testRunStarted(testName, 1);
+        mMockInvocationListener.testStarted(mDummyTest);
+        mMockInvocationListener.testEnded(
+                EasyMock.eq(mDummyTest), EasyMock.<HashMap<String, String>>anyObject());
+        mMockInvocationListener.testRunEnded(
+                EasyMock.anyLong(), EasyMock.<HashMap<String, Metric>>anyObject());
+        EasyMock.expectLastCall();
+        replayMocks();
+
+        mGoogleBenchmarkTest.run(mTestInfo, mMockInvocationListener);
+        verifyMocks();
+    }
 }
diff --git a/tests/src/com/android/tradefed/testtype/UsbResetTestTest.java b/tests/src/com/android/tradefed/testtype/UsbResetTestTest.java
index 6d166e3..d8c199c 100644
--- a/tests/src/com/android/tradefed/testtype/UsbResetTestTest.java
+++ b/tests/src/com/android/tradefed/testtype/UsbResetTestTest.java
@@ -68,6 +68,7 @@
         mTest.run(mTestInfo, null);
 
         verify(usbDevice).reset();
+        verify(mDevice).waitForDeviceOnline();
         verify(mDevice).reboot();
     }