Merge "Fix battery/temperature check when disabled" into pi-dev
diff --git a/src/com/android/tradefed/build/BuildInfo.java b/src/com/android/tradefed/build/BuildInfo.java
index 168f256..5aa54db 100644
--- a/src/com/android/tradefed/build/BuildInfo.java
+++ b/src/com/android/tradefed/build/BuildInfo.java
@@ -258,6 +258,12 @@
         return null;
     }
 
+    /** {@inheritDoc} */
+    @Override
+    public VersionedFile getVersionedFile(String name) {
+        return mVersionedFileMap.get(name);
+    }
+
     /**
      * {@inheritDoc}
      */
diff --git a/src/com/android/tradefed/build/IBuildInfo.java b/src/com/android/tradefed/build/IBuildInfo.java
index 732dee1..4470732 100644
--- a/src/com/android/tradefed/build/IBuildInfo.java
+++ b/src/com/android/tradefed/build/IBuildInfo.java
@@ -147,6 +147,17 @@
     public File getFile(String name);
 
     /**
+     * Helper method to retrieve a {@link VersionedFile} with a given name.
+     *
+     * @param name
+     * @return The versioned file or <code>null</code> if not found
+     */
+    public default VersionedFile getVersionedFile(String name) {
+        // Default implementation for project that don't extends BuildInfo class.
+        return null;
+    }
+
+    /**
      * Returns all {@link VersionedFile}s stored in this {@link BuildInfo}.
      */
     public Collection<VersionedFile> getFiles();
diff --git a/src/com/android/tradefed/build/OtaDeviceBuildInfo.java b/src/com/android/tradefed/build/OtaDeviceBuildInfo.java
index 79904c5..b413bde 100644
--- a/src/com/android/tradefed/build/OtaDeviceBuildInfo.java
+++ b/src/com/android/tradefed/build/OtaDeviceBuildInfo.java
@@ -183,6 +183,12 @@
         return mBaselineBuild.getFile(name);
     }
 
+    /** {@inheritDoc} */
+    @Override
+    public VersionedFile getVersionedFile(String name) {
+        return mBaselineBuild.getVersionedFile(name);
+    }
+
     /**
      * {@inheritDoc}
      */
diff --git a/src/com/android/tradefed/result/LogcatCrashResultForwarder.java b/src/com/android/tradefed/result/LogcatCrashResultForwarder.java
index ab6a546..1640626 100644
--- a/src/com/android/tradefed/result/LogcatCrashResultForwarder.java
+++ b/src/com/android/tradefed/result/LogcatCrashResultForwarder.java
@@ -90,7 +90,7 @@
     /** Attempt to extract the crash from the logcat if the test was seen as started. */
     private String extractCrashAndAddToMessage(String errorMessage, Long startTime) {
         if (errorMessage.contains(ERROR_MESSAGE) && startTime != null) {
-            mLogcatItem = extractLogcat(mDevice, mStartTime);
+            mLogcatItem = extractLogcat(mDevice, startTime);
             errorMessage = addJavaCrashToString(mLogcatItem, errorMessage);
         }
         return errorMessage;
diff --git a/src/com/android/tradefed/result/TestDescription.java b/src/com/android/tradefed/result/TestDescription.java
index dfd1ca8..265167d 100644
--- a/src/com/android/tradefed/result/TestDescription.java
+++ b/src/com/android/tradefed/result/TestDescription.java
@@ -21,12 +21,18 @@
 import java.lang.annotation.Annotation;
 import java.util.Arrays;
 import java.util.Collection;
+import java.util.regex.Matcher;
+import java.util.regex.Pattern;
 
 /** Class representing information about a test case. */
 public final class TestDescription implements Serializable {
 
+    /** Regex for method parameterized. For example: testName[0] */
+    public static final Pattern PARAMETERIZED_TEST_REGEX = Pattern.compile("(.*)?\\[(.*)\\]$");
+
     private final String mClassName;
     private final String mTestName;
+    private final String mTestNameNoParams;
     private Annotation[] mAnnotations;
 
     /**
@@ -42,6 +48,14 @@
         mClassName = className;
         mTestName = testName;
         mAnnotations = new Annotation[0];
+
+        // If the method looks parameterized, track the base non-parameterized name.
+        Matcher m = PARAMETERIZED_TEST_REGEX.matcher(testName);
+        if (m.find()) {
+            mTestNameNoParams = m.group(1);
+        } else {
+            mTestNameNoParams = testName;
+        }
     }
 
     /**
@@ -90,11 +104,19 @@
         return mClassName;
     }
 
-    /** Returns the name of the test. */
+    /**
+     * Returns the name of the test with the parameters, if it's parameterized test. Returns the
+     * regular test name if not a parameterized test.
+     */
     public String getTestName() {
         return mTestName;
     }
 
+    /** Returns the name of the test without any parameters (if it's a parameterized method). */
+    public String getTestNameWithoutParams() {
+        return mTestNameNoParams;
+    }
+
     @Override
     public int hashCode() {
         final int prime = 31;
diff --git a/src/com/android/tradefed/targetprep/multi/MergeMultiBuildTargetPreparer.java b/src/com/android/tradefed/targetprep/multi/MergeMultiBuildTargetPreparer.java
index 7b52078..ec0c69e 100644
--- a/src/com/android/tradefed/targetprep/multi/MergeMultiBuildTargetPreparer.java
+++ b/src/com/android/tradefed/targetprep/multi/MergeMultiBuildTargetPreparer.java
@@ -16,6 +16,7 @@
 package com.android.tradefed.targetprep.multi;
 
 import com.android.tradefed.build.IBuildInfo;
+import com.android.tradefed.build.VersionedFile;
 import com.android.tradefed.config.Option;
 import com.android.tradefed.config.OptionClass;
 import com.android.tradefed.device.DeviceNotAvailableException;
@@ -24,7 +25,6 @@
 import com.android.tradefed.targetprep.BuildError;
 import com.android.tradefed.targetprep.TargetSetupError;
 
-import java.io.File;
 import java.util.HashSet;
 import java.util.Set;
 
@@ -80,12 +80,12 @@
 
         // Move the requested keys.
         for (String key : mKeysToMove) {
-            File toBeMoved = providerInfo.getFile(key);
+            VersionedFile toBeMoved = providerInfo.getVersionedFile(key);
             if (toBeMoved == null) {
                 CLog.w("Key '%s' did not match any files, ignoring.", key);
                 continue;
             }
-            receiverInfo.setFile(key, toBeMoved, providerInfo.getBuildId());
+            receiverInfo.setFile(key, toBeMoved.getFile(), toBeMoved.getVersion());
         }
     }
 }
diff --git a/src/com/android/tradefed/testtype/InstrumentationFileTest.java b/src/com/android/tradefed/testtype/InstrumentationFileTest.java
index dd120cf..07296d6 100644
--- a/src/com/android/tradefed/testtype/InstrumentationFileTest.java
+++ b/src/com/android/tradefed/testtype/InstrumentationFileTest.java
@@ -26,11 +26,15 @@
 import com.android.tradefed.result.TestDescription;
 import com.android.tradefed.util.FileUtil;
 
+import com.google.common.annotations.VisibleForTesting;
+
 import java.io.BufferedWriter;
 import java.io.File;
 import java.io.FileWriter;
 import java.io.IOException;
 import java.util.Collection;
+import java.util.LinkedHashMap;
+import java.util.Map;
 
 /**
  * Runs a set of instrumentation tests by specifying a list of line separated test classes and
@@ -44,8 +48,6 @@
 
     // on device test folder location where the test file should be saved
     private static final String ON_DEVICE_TEST_DIR_LOCATION = "/data/local/tmp/";
-    // used to separate fully-qualified test case class name, and one of its methods
-    private static final char METHOD_SEPARATOR = '#';
 
     private InstrumentationTest mInstrumentationTest = null;
 
@@ -134,8 +136,17 @@
             testFile = FileUtil.createTempFile(
                     "tf_testFile_" + InstrumentationFileTest.class.getCanonicalName(), ".txt");
             try (BufferedWriter bw = new BufferedWriter(new FileWriter(testFile))) {
-                for (TestDescription testToRun : tests) {
-                    bw.write(testToRun.getClassName() + METHOD_SEPARATOR + testToRun.getTestName());
+                // Remove parameterized tests to only re-run their base method.
+                Collection<TestDescription> uniqueMethods = createRerunSet(tests);
+
+                for (TestDescription testToRun : uniqueMethods) {
+                    // We use getTestNameNoParams to avoid attempting re-running individual
+                    // parameterized tests. Instead ask the base method to re-run them all.
+                    bw.write(
+                            String.format(
+                                    "%s#%s",
+                                    testToRun.getClassName(),
+                                    testToRun.getTestNameWithoutParams()));
                     bw.newLine();
                 }
                 CLog.d("Test file %s was successfully created", testFile.getAbsolutePath());
@@ -211,11 +222,25 @@
     }
 
     /**
+     * Returns a new collection of {@link TestDescription} where only one instance of each
+     * parameterized method is in the list.
+     */
+    private Collection<TestDescription> createRerunSet(Collection<TestDescription> tests) {
+        Map<String, TestDescription> uniqueMethods = new LinkedHashMap<>();
+        for (TestDescription test : tests) {
+            uniqueMethods.put(test.getTestNameWithoutParams(), test);
+        }
+        return uniqueMethods.values();
+    }
+
+    /**
      * Util method to push file to a device. Exposed for unit testing.
+     *
      * @return if file was pushed to the device successfully
      * @throws DeviceNotAvailableException
      */
-    boolean pushFileToTestDevice(File file, String destinationPath) throws DeviceNotAvailableException {
+    boolean pushFileToTestDevice(File file, String destinationPath)
+            throws DeviceNotAvailableException {
         return mInstrumentationTest.getDevice().pushFile(file, destinationPath);
     }
 
@@ -230,9 +255,8 @@
         }
     }
 
-    /**
-     * @return the {@link InstrumentationTest} to use. Exposed for unit testing.
-     */
+    /** @return the {@link InstrumentationTest} to use. Exposed for unit testing. */
+    @VisibleForTesting
     InstrumentationTest createInstrumentationTest() {
         return new InstrumentationTest();
     }
diff --git a/src/com/android/tradefed/testtype/junit4/BaseHostJUnit4Test.java b/src/com/android/tradefed/testtype/junit4/BaseHostJUnit4Test.java
index 20c8874..e63c668 100644
--- a/src/com/android/tradefed/testtype/junit4/BaseHostJUnit4Test.java
+++ b/src/com/android/tradefed/testtype/junit4/BaseHostJUnit4Test.java
@@ -162,10 +162,12 @@
      * @param apkFileName The name of the apk file.
      * @param grantPermission whether to pass the grant permission flag when installing the apk.
      * @param userId the user id of the user where to install the apk.
+     * @param options extra options given to the install command
      */
-    public final void installPackageAsUser(String apkFileName, boolean grantPermission, int userId)
+    public final void installPackageAsUser(
+            String apkFileName, boolean grantPermission, int userId, String... options)
             throws DeviceNotAvailableException, TargetSetupError {
-        installPackageAsUser(getDevice(), apkFileName, grantPermission, userId);
+        installPackageAsUser(getDevice(), apkFileName, grantPermission, userId, options);
     }
 
     /**
@@ -175,9 +177,14 @@
      * @param apkFileName The name of the apk file.
      * @param grantPermission whether to pass the grant permission flag when installing the apk.
      * @param userId the user id of the user where to install the apk.
+     * @param options extra options given to the install command
      */
     public final void installPackageAsUser(
-            ITestDevice device, String apkFileName, boolean grantPermission, int userId)
+            ITestDevice device,
+            String apkFileName,
+            boolean grantPermission,
+            int userId,
+            String... options)
             throws DeviceNotAvailableException, TargetSetupError {
         SuiteApkInstaller installer = createSuiteApkInstaller();
         // Force the apk clean up
@@ -188,6 +195,9 @@
         installer.setUserId(userId);
         installer.setShouldGrantPermission(grantPermission);
         installer.setAbi(getAbi());
+        for (String option : options) {
+            installer.addInstallArg(option);
+        }
         installer.setUp(device, mContext.getBuildInfo(device));
     }
 
diff --git a/src/com/android/tradefed/testtype/suite/AtestRunner.java b/src/com/android/tradefed/testtype/suite/AtestRunner.java
index 6b45b2d..d2f2e46 100644
--- a/src/com/android/tradefed/testtype/suite/AtestRunner.java
+++ b/src/com/android/tradefed/testtype/suite/AtestRunner.java
@@ -21,19 +21,13 @@
 import com.android.tradefed.config.Option;
 import com.android.tradefed.config.OptionCopier;
 import com.android.tradefed.config.Option.Importance;
-import com.android.tradefed.device.DeviceNotAvailableException;
 import com.android.tradefed.log.LogUtil.CLog;
 import com.android.tradefed.result.ITestInvocationListener;
 import com.android.tradefed.result.SubprocessResultsReporter;
 import com.android.tradefed.targetprep.ITargetPreparer;
-import com.android.tradefed.testtype.Abi;
-import com.android.tradefed.testtype.IAbi;
-import com.android.tradefed.testtype.IAbiReceiver;
 import com.android.tradefed.testtype.InstrumentationTest;
 import com.android.tradefed.testtype.IRemoteTest;
 import com.android.tradefed.testtype.ITestFilterReceiver;
-import com.android.tradefed.util.AbiFormatter;
-import com.android.tradefed.util.AbiUtils;
 import java.util.ArrayList;
 import java.util.HashSet;
 import java.util.LinkedHashMap;
@@ -66,13 +60,6 @@
     private boolean mSkipTearDown = false;
 
     @Option(
-        name = "abi-name",
-        description =
-                "Abi to pass to tests that require an ABI. The device default will be used if not specified."
-    )
-    private String mabiName;
-
-    @Option(
         name = "subprocess-report-port",
         description = "the port where to connect to send the" + "events."
     )
@@ -89,8 +76,11 @@
 
     @Override
     public LinkedHashMap<String, IConfiguration> loadTests() {
+        // atest only needs to run on primary or specified abi.
+        if (getRequestedAbi() == null) {
+            setPrimaryAbiRun(true);
+        }
         LinkedHashMap<String, IConfiguration> configMap = super.loadTests();
-        IAbi abi = getAbi();
         LinkedHashMap<String, HashSet<String>> includeFilters = getIncludeFilters();
         for (IConfiguration testConfig : configMap.values()) {
             if (mSkipSetUp || mSkipTearDown) {
@@ -99,7 +89,6 @@
             if (mDebug) {
                 addDebugger(testConfig);
             }
-            setTestAbi(testConfig, abi);
 
             // Inject include-filter to test.
             HashSet<String> moduleFilters =
@@ -188,48 +177,6 @@
         return listeners;
     }
 
-    /**
-     * Helper to create the IAbi instance to pass to tests that implement IAbiReceiver.
-     *
-     * @return IAbi instance to use, may be null if not provided and device is unreachable.
-     */
-    private IAbi getAbi() {
-        if (mabiName == null) {
-            if (getDevice() == null) {
-                return null;
-            }
-            try {
-                mabiName = AbiFormatter.getDefaultAbi(getDevice(), "");
-            } catch (DeviceNotAvailableException e) {
-                return null;
-            }
-        }
-        return new Abi(mabiName, AbiUtils.getBitness(mabiName));
-    }
-
-    /**
-     * Set ABI of tests and target preparers that require it to default ABI of device.
-     *
-     * @param testConfig The configuration to set the ABI for.
-     * @param abi The IAbi instance to pass to setAbi() of tests and target preparers.
-     */
-    private void setTestAbi(IConfiguration testConfig, IAbi abi) {
-        if (abi == null) {
-            return;
-        }
-        List<IRemoteTest> tests = testConfig.getTests();
-        for (IRemoteTest test : tests) {
-            if (test instanceof IAbiReceiver) {
-                ((IAbiReceiver) test).setAbi(abi);
-            }
-        }
-        for (ITargetPreparer targetPreparer : testConfig.getTargetPreparers()) {
-            if (targetPreparer instanceof IAbiReceiver) {
-                ((IAbiReceiver) targetPreparer).setAbi(abi);
-            }
-        }
-    }
-
     /** Helper to attach the debugger to any Instrumentation tests in the config. */
     private void addDebugger(IConfiguration testConfig) {
         for (IRemoteTest test : testConfig.getTests()) {
diff --git a/tests/src/com/android/tradefed/result/LogcatCrashResultForwarderTest.java b/tests/src/com/android/tradefed/result/LogcatCrashResultForwarderTest.java
index 6da958c..4a106a3 100644
--- a/tests/src/com/android/tradefed/result/LogcatCrashResultForwarderTest.java
+++ b/tests/src/com/android/tradefed/result/LogcatCrashResultForwarderTest.java
@@ -102,4 +102,47 @@
         mReporter.testRunFailed("Something went wrong.");
         EasyMock.verify(mMockListener, mMockDevice);
     }
+
+    /**
+     * Test that if a crash is note detected at testFailed but later found at testRunFailed, we
+     * still add the extracted information to the failure.
+     */
+    @Test
+    @SuppressWarnings("MustBeClosedChecker")
+    public void testCaptureTestCrash_oneCrashingLogcatAfterTestEnded() {
+        mReporter = new LogcatCrashResultForwarder(mMockDevice, mMockListener);
+        TestDescription test = new TestDescription("com.class", "test");
+
+        mMockListener.testStarted(test, 0L);
+        String logcat =
+                "03-20 09:57:36.709 11 11 E AndroidRuntime: FATAL EXCEPTION: Thread-2\n"
+                        + "03-20 09:57:36.709 11 11 E AndroidRuntime: Process: android.gesture.cts"
+                        + ", PID: 11034\n"
+                        + "03-20 09:57:36.709 11 11 E AndroidRuntime: java.lang.RuntimeException:"
+                        + " Runtime\n"
+                        + "03-20 09:57:36.709 11 11 E AndroidRuntime:    at android.GestureTest$1"
+                        + ".run(GestureTest.java:52)\n"
+                        + "03-20 09:57:36.709 11 11 E AndroidRuntime:    at java.lang.Thread.run"
+                        + "(Thread.java:764)\n"
+                        + "03-20 09:57:36.711 11 11 I TestRunner: started: testGetStrokesCount"
+                        + "(android.gesture.cts.GestureTest)\n";
+
+        EasyMock.expect(mMockDevice.getLogcatSince(0L))
+                .andReturn(new ByteArrayInputStreamSource(logcat.getBytes()));
+        // No crash added at the point of testFailed.
+        mMockListener.testFailed(test, "Something went wrong.");
+        mMockListener.testEnded(test, 5L, new HashMap<String, String>());
+        // If a run failure comes with a crash detected, expect it to contain the additional stack.
+        mMockListener.testRunFailed(
+                EasyMock.contains(
+                        "instrumentation failed. reason: 'Process crashed.'"
+                                + "\nCrash Message:Runtime"));
+
+        EasyMock.replay(mMockListener, mMockDevice);
+        mReporter.testStarted(test, 0L);
+        mReporter.testFailed(test, "Something went wrong.");
+        mReporter.testEnded(test, 5L, new HashMap<String, String>());
+        mReporter.testRunFailed("instrumentation failed. reason: 'Process crashed.'");
+        EasyMock.verify(mMockListener, mMockDevice);
+    }
 }
diff --git a/tests/src/com/android/tradefed/result/TestDescriptionTest.java b/tests/src/com/android/tradefed/result/TestDescriptionTest.java
index cf9290b..4a79d20 100644
--- a/tests/src/com/android/tradefed/result/TestDescriptionTest.java
+++ b/tests/src/com/android/tradefed/result/TestDescriptionTest.java
@@ -15,7 +15,9 @@
  */
 package com.android.tradefed.result;
 
-import static org.junit.Assert.*;
+import static org.junit.Assert.assertEquals;
+import static org.junit.Assert.assertNotNull;
+import static org.junit.Assert.assertNull;
 
 import org.junit.Test;
 import org.junit.runner.RunWith;
@@ -54,6 +56,7 @@
                         "className", "testName", new TestAnnotation(), new TestAnnotation2());
         assertEquals("className", mDescription.getClassName());
         assertEquals("testName", mDescription.getTestName());
+        assertEquals("testName", mDescription.getTestNameWithoutParams());
         assertEquals(2, mDescription.getAnnotations().size());
         assertNotNull(mDescription.getAnnotation(TestAnnotation.class));
         assertNotNull(mDescription.getAnnotation(TestAnnotation2.class));
@@ -68,4 +71,25 @@
         assertEquals(0, mDescription.getAnnotations().size());
         assertNull(mDescription.getAnnotation(TestAnnotation.class));
     }
+
+    /** Create a {@link TestDescription} with a parameterized method. */
+    @Test
+    public void testCreateDescription_parameterized() {
+        mDescription = new TestDescription("className", "testName[0]");
+        assertEquals("className", mDescription.getClassName());
+        assertEquals("testName[0]", mDescription.getTestName());
+        assertEquals("testName", mDescription.getTestNameWithoutParams());
+
+        mDescription = new TestDescription("className", "testName[key=value]");
+        assertEquals("testName[key=value]", mDescription.getTestName());
+        assertEquals("testName", mDescription.getTestNameWithoutParams());
+
+        mDescription = new TestDescription("className", "testName[key=\"quoted value\"]");
+        assertEquals("testName[key=\"quoted value\"]", mDescription.getTestName());
+        assertEquals("testName", mDescription.getTestNameWithoutParams());
+
+        mDescription = new TestDescription("className", "testName[key:22]");
+        assertEquals("testName[key:22]", mDescription.getTestName());
+        assertEquals("testName", mDescription.getTestNameWithoutParams());
+    }
 }
diff --git a/tests/src/com/android/tradefed/targetprep/multi/MergeMultiBuildTargetPreparerTest.java b/tests/src/com/android/tradefed/targetprep/multi/MergeMultiBuildTargetPreparerTest.java
index c9270d7..28003d3 100644
--- a/tests/src/com/android/tradefed/targetprep/multi/MergeMultiBuildTargetPreparerTest.java
+++ b/tests/src/com/android/tradefed/targetprep/multi/MergeMultiBuildTargetPreparerTest.java
@@ -68,16 +68,16 @@
         setter.setOptionValue("dest-device", "device2");
         setter.setOptionValue("key-to-copy", EXAMPLE_KEY);
 
-        mMockInfo1.setFile(EXAMPLE_KEY, new File("fake"), "id1");
-        assertEquals("id1", mMockInfo1.getTestsDirVersion());
+        mMockInfo1.setFile(EXAMPLE_KEY, new File("fake"), "some version");
+        assertEquals("some version", mMockInfo1.getTestsDirVersion());
         assertNotNull(mMockInfo1.getFile(EXAMPLE_KEY));
         assertNull(mMockInfo2.getFile(EXAMPLE_KEY));
 
         mPreparer.setUp(mContext);
         // Now mock info 2 has the file.
         assertNotNull(mMockInfo2.getFile(EXAMPLE_KEY));
-        // The build id of the provider build is set as the version.
-        assertEquals("id1", mMockInfo2.getTestsDirVersion());
+        // The version of the provider build is preserved.
+        assertEquals("some version", mMockInfo2.getTestsDirVersion());
     }
 
     /**
diff --git a/tests/src/com/android/tradefed/testtype/InstrumentationFileTestTest.java b/tests/src/com/android/tradefed/testtype/InstrumentationFileTestTest.java
index 498cb62..52be0f0 100644
--- a/tests/src/com/android/tradefed/testtype/InstrumentationFileTestTest.java
+++ b/tests/src/com/android/tradefed/testtype/InstrumentationFileTestTest.java
@@ -85,7 +85,6 @@
     /**
      * Test normal run scenario with a single test.
      */
-    @SuppressWarnings("unchecked")
     public void testRun_singleSuccessfulTest() throws DeviceNotAvailableException,
             ConfigurationException {
         final Collection<TestDescription> testsList = new ArrayList<>(1);
@@ -101,8 +100,8 @@
                         listener.testRunStarted(TEST_PACKAGE_VALUE, 1);
                         listener.testStarted(TestDescription.convertToIdentifier(test));
                         listener.testEnded(
-                                TestDescription.convertToIdentifier(test), Collections.EMPTY_MAP);
-                        listener.testRunEnded(0, Collections.EMPTY_MAP);
+                                TestDescription.convertToIdentifier(test), Collections.emptyMap());
+                        listener.testRunEnded(0, Collections.emptyMap());
                         return true;
                     }
                 };
@@ -142,7 +141,6 @@
     /**
      * Test re-run scenario when 1 out of 3 tests fails to complete but is successful after re-run
      */
-    @SuppressWarnings("unchecked")
     public void testRun_reRunOneFailedToCompleteTest()
             throws DeviceNotAvailableException, ConfigurationException {
         final Collection<TestDescription> testsList = new ArrayList<>(1);
@@ -163,8 +161,8 @@
                         listener.testRunStarted(TEST_PACKAGE_VALUE, 2);
                         listener.testStarted(TestDescription.convertToIdentifier(test1));
                         listener.testEnded(
-                                TestDescription.convertToIdentifier(test1), Collections.EMPTY_MAP);
-                        listener.testRunEnded(1, Collections.EMPTY_MAP);
+                                TestDescription.convertToIdentifier(test1), Collections.emptyMap());
+                        listener.testRunEnded(1, Collections.emptyMap());
                         // second test started but never finished
                         listener.testStarted(TestDescription.convertToIdentifier(test2));
                         return true;
@@ -182,13 +180,13 @@
                         listener.testRunStarted(TEST_PACKAGE_VALUE, 2);
                         listener.testStarted(TestDescription.convertToIdentifier(test3));
                         listener.testEnded(
-                                TestDescription.convertToIdentifier(test3), Collections.EMPTY_MAP);
-                        listener.testRunEnded(1, Collections.EMPTY_MAP);
+                                TestDescription.convertToIdentifier(test3), Collections.emptyMap());
+                        listener.testRunEnded(1, Collections.emptyMap());
                         // second test is rerun but completed successfully this time
                         listener.testStarted(TestDescription.convertToIdentifier(test2));
                         listener.testEnded(
-                                TestDescription.convertToIdentifier(test2), Collections.EMPTY_MAP);
-                        listener.testRunEnded(1, Collections.EMPTY_MAP);
+                                TestDescription.convertToIdentifier(test2), Collections.emptyMap());
+                        listener.testRunEnded(1, Collections.emptyMap());
                         return true;
                     }
                 };
@@ -243,7 +241,6 @@
     /**
      * Test re-run scenario when 2 remaining tests fail to complete and need to be run serially
      */
-    @SuppressWarnings("unchecked")
     public void testRun_serialReRunOfTwoFailedToCompleteTests()
             throws DeviceNotAvailableException, ConfigurationException {
         final Collection<TestDescription> testsList = new ArrayList<>(1);
@@ -279,8 +276,8 @@
                         listener.testRunStarted(TEST_PACKAGE_VALUE, 1);
                         listener.testStarted(TestDescription.convertToIdentifier(test1));
                         listener.testEnded(
-                                TestDescription.convertToIdentifier(test1), Collections.EMPTY_MAP);
-                        listener.testRunEnded(1, Collections.EMPTY_MAP);
+                                TestDescription.convertToIdentifier(test1), Collections.emptyMap());
+                        listener.testRunEnded(1, Collections.emptyMap());
                         return true;
                     }
                 };
@@ -296,8 +293,8 @@
                         listener.testRunStarted(TEST_PACKAGE_VALUE, 1);
                         listener.testStarted(TestDescription.convertToIdentifier(test2));
                         listener.testEnded(
-                                TestDescription.convertToIdentifier(test2), Collections.EMPTY_MAP);
-                        listener.testRunEnded(1, Collections.EMPTY_MAP);
+                                TestDescription.convertToIdentifier(test2), Collections.emptyMap());
+                        listener.testRunEnded(1, Collections.emptyMap());
                         return true;
                     }
                 };
@@ -353,7 +350,6 @@
     /**
      * Test no serial re-run tests fail to complete.
      */
-    @SuppressWarnings("unchecked")
     public void testRun_noSerialReRun()
             throws DeviceNotAvailableException, ConfigurationException {
         final Collection<TestDescription> testsList = new ArrayList<>(1);
@@ -411,7 +407,6 @@
     /**
      * Test attempting times exceed max attempts.
      */
-    @SuppressWarnings("unchecked")
     public void testRun_exceedMaxAttempts()
             throws DeviceNotAvailableException, ConfigurationException {
         final ArrayList<TestDescription> testsList = new ArrayList<>(1);
@@ -441,8 +436,8 @@
                         // first test started and ended successfully
                         listener.testStarted(TestDescription.convertToIdentifier(test1));
                         listener.testEnded(
-                                TestDescription.convertToIdentifier(test1), Collections.EMPTY_MAP);
-                        listener.testRunEnded(1, Collections.EMPTY_MAP);
+                                TestDescription.convertToIdentifier(test1), Collections.emptyMap());
+                        listener.testRunEnded(1, Collections.emptyMap());
                         // second test started but never finished
                         listener.testStarted(TestDescription.convertToIdentifier(test2));
                         // verify that the content of the testFile contains all expected tests
@@ -462,8 +457,8 @@
                         listener.testRunStarted(TEST_PACKAGE_VALUE, 5);
                         listener.testStarted(TestDescription.convertToIdentifier(test2));
                         listener.testEnded(
-                                TestDescription.convertToIdentifier(test2), Collections.EMPTY_MAP);
-                        listener.testRunEnded(1, Collections.EMPTY_MAP);
+                                TestDescription.convertToIdentifier(test2), Collections.emptyMap());
+                        listener.testRunEnded(1, Collections.emptyMap());
                         // test3 started but never finished
                         listener.testStarted(TestDescription.convertToIdentifier(test3));
                         // verify that the content of the testFile contains all expected tests
@@ -483,8 +478,8 @@
                         listener.testRunStarted(TEST_PACKAGE_VALUE, 4);
                         listener.testStarted(TestDescription.convertToIdentifier(test3));
                         listener.testEnded(
-                                TestDescription.convertToIdentifier(test3), Collections.EMPTY_MAP);
-                        listener.testRunEnded(1, Collections.EMPTY_MAP);
+                                TestDescription.convertToIdentifier(test3), Collections.emptyMap());
+                        listener.testRunEnded(1, Collections.emptyMap());
                         // test4 started but never finished
                         listener.testStarted(TestDescription.convertToIdentifier(test4));
                         // verify that the content of the testFile contains all expected tests
@@ -543,6 +538,102 @@
         assertEquals(mMockTestDevice, mMockITest.getDevice());
     }
 
+    /** Test re-run a test instrumentation when some methods are parameterized. */
+    public void testRun_parameterized() throws DeviceNotAvailableException, ConfigurationException {
+        final Collection<TestDescription> testsList = new ArrayList<>();
+        final TestDescription test = new TestDescription("ClassFoo", "methodBar");
+        final TestDescription test1 = new TestDescription("ClassFoo", "paramMethod[0]");
+        final TestDescription test2 = new TestDescription("ClassFoo", "paramMethod[1]");
+        testsList.add(test);
+        testsList.add(test1);
+        testsList.add(test2);
+
+        // verify the mock listener is passed through to the runner, the first test pass
+        RunTestAnswer runTestResponse =
+                new RunTestAnswer() {
+                    @Override
+                    public Boolean answer(
+                            IRemoteAndroidTestRunner runner, ITestRunListener listener) {
+                        listener.testRunStarted(TEST_PACKAGE_VALUE, 3);
+                        listener.testStarted(TestDescription.convertToIdentifier(test));
+                        listener.testEnded(
+                                TestDescription.convertToIdentifier(test), Collections.emptyMap());
+                        listener.testStarted(TestDescription.convertToIdentifier(test1));
+                        listener.testRunEnded(0, Collections.emptyMap());
+                        return true;
+                    }
+                };
+        setRunTestExpectations(runTestResponse);
+
+        RunTestAnswer secondRunAnswer =
+                new RunTestAnswer() {
+                    @Override
+                    public Boolean answer(
+                            IRemoteAndroidTestRunner runner, ITestRunListener listener) {
+                        // test2 started and ended successfully
+                        listener.testRunStarted(TEST_PACKAGE_VALUE, 2);
+                        listener.testStarted(TestDescription.convertToIdentifier(test1));
+                        listener.testEnded(
+                                TestDescription.convertToIdentifier(test1), Collections.emptyMap());
+                        listener.testStarted(TestDescription.convertToIdentifier(test2));
+                        listener.testEnded(
+                                TestDescription.convertToIdentifier(test2), Collections.emptyMap());
+                        listener.testRunEnded(1, Collections.emptyMap());
+                        return true;
+                    }
+                };
+        setRunTestExpectations(secondRunAnswer);
+
+        mInstrumentationFileTest =
+                new InstrumentationFileTest(mMockITest, testsList, true, -1) {
+                    @Override
+                    InstrumentationTest createInstrumentationTest() {
+                        return mMockITest;
+                    }
+
+                    @Override
+                    boolean pushFileToTestDevice(File file, String destinationPath)
+                            throws DeviceNotAvailableException {
+                        // simulate successful push and store created file
+                        mTestFile = file;
+                        // verify that the content of the testFile contains all expected tests
+                        Collection<TestDescription> updatedList = new ArrayList<>();
+                        updatedList.add(test);
+                        updatedList.add(new TestDescription("ClassFoo", "paramMethod"));
+                        verifyTestFile(updatedList);
+                        return true;
+                    }
+
+                    @Override
+                    void deleteTestFileFromDevice(String pathToFile)
+                            throws DeviceNotAvailableException {
+                        //ignore
+                    }
+                };
+
+        // mock successful test run lifecycle for the first test
+        mMockListener.testRunStarted(TEST_PACKAGE_VALUE, 3);
+        mMockListener.testStarted(EasyMock.eq(test), EasyMock.anyLong());
+        mMockListener.testEnded(
+                EasyMock.eq(test), EasyMock.anyLong(), EasyMock.eq(new HashMap<String, Metric>()));
+        mMockListener.testStarted(EasyMock.eq(test1), EasyMock.anyLong());
+        mMockListener.testRunEnded(0, new HashMap<String, Metric>());
+
+        // Second run:
+        mMockListener.testRunStarted(TEST_PACKAGE_VALUE, 2);
+        mMockListener.testStarted(EasyMock.eq(test1), EasyMock.anyLong());
+        mMockListener.testEnded(
+                EasyMock.eq(test1), EasyMock.anyLong(), EasyMock.eq(new HashMap<String, Metric>()));
+        mMockListener.testStarted(EasyMock.eq(test2), EasyMock.anyLong());
+        mMockListener.testEnded(
+                EasyMock.eq(test2), EasyMock.anyLong(), EasyMock.eq(new HashMap<String, Metric>()));
+        mMockListener.testRunEnded(1, new HashMap<String, Metric>());
+
+        EasyMock.replay(mMockListener, mMockTestDevice);
+        mInstrumentationFileTest.run(mMockListener);
+        assertEquals(mMockTestDevice, mMockITest.getDevice());
+    }
+
     /**
      * Helper class that verifies tetFile's content match the expected list of test to be run
      *