Merge "RootTargetPreparer: add option to ignore failure and carry on with setup"
diff --git a/invocation_interfaces/com/android/tradefed/config/ConfigurationDescriptor.java b/invocation_interfaces/com/android/tradefed/config/ConfigurationDescriptor.java
index bc6d30c..e97233f 100644
--- a/invocation_interfaces/com/android/tradefed/config/ConfigurationDescriptor.java
+++ b/invocation_interfaces/com/android/tradefed/config/ConfigurationDescriptor.java
@@ -48,7 +48,7 @@
     /** Metadata key for a config to specify that it was sharded. */
     public static final String LOCAL_SHARDED_KEY = "sharded";
     /** Metadata key for a config parameterization, optional. */
-    public static final String PARAMETER_KEY = "parameter";
+    public static final String ACTIVE_PARAMETER_KEY = "active-parameter";
 
     @Option(name = "test-suite-tag", description = "A membership tag to suite. Can be repeated.")
     private List<String> mSuiteTags = new ArrayList<>();
@@ -136,7 +136,7 @@
      * @param key {@link String} of the key to add values to.
      * @param values a list of {@link String} of the additional values.
      */
-    public void addMetaData(String key, List<String> values) {
+    public void addMetadata(String key, List<String> values) {
         for (String source : values) {
             mMetaData.put(key, source);
         }
diff --git a/invocation_interfaces/com/android/tradefed/result/TestRunResult.java b/invocation_interfaces/com/android/tradefed/result/TestRunResult.java
index 91e934e..234ef2b 100644
--- a/invocation_interfaces/com/android/tradefed/result/TestRunResult.java
+++ b/invocation_interfaces/com/android/tradefed/result/TestRunResult.java
@@ -298,6 +298,10 @@
         updateTestResult(test, TestStatus.FAILURE, FailureDescription.create(trace));
     }
 
+    public void testFailed(TestDescription test, FailureDescription failure) {
+        updateTestResult(test, TestStatus.FAILURE, failure);
+    }
+
     public void testAssumptionFailure(TestDescription test, String trace) {
         updateTestResult(test, TestStatus.ASSUMPTION_FAILURE, FailureDescription.create(trace));
     }
diff --git a/src/com/android/tradefed/result/CollectingTestListener.java b/src/com/android/tradefed/result/CollectingTestListener.java
index 7f46ee1..e12902a 100644
--- a/src/com/android/tradefed/result/CollectingTestListener.java
+++ b/src/com/android/tradefed/result/CollectingTestListener.java
@@ -298,6 +298,12 @@
     }
 
     @Override
+    public void testFailed(TestDescription test, FailureDescription failure) {
+        setCountDirty();
+        mCurrentTestRunResult.testFailed(test, failure);
+    }
+
+    @Override
     public void testAssumptionFailure(TestDescription test, String trace) {
         setCountDirty();
         mCurrentTestRunResult.testAssumptionFailure(test, trace);
diff --git a/src/com/android/tradefed/result/ConsoleResultReporter.java b/src/com/android/tradefed/result/ConsoleResultReporter.java
index 6cedd57..1955b7d 100644
--- a/src/com/android/tradefed/result/ConsoleResultReporter.java
+++ b/src/com/android/tradefed/result/ConsoleResultReporter.java
@@ -125,28 +125,33 @@
     public void invocationEnded(long elapsedTime) {
         int[] results = mResultCountListener.getResultCounts();
         StringBuilder sb = new StringBuilder();
+        sb.append("========== Result Summary ==========");
         sb.append(String.format("\nResults summary for test-tag '%s': ", mTestTag));
         sb.append(mResultCountListener.getTotalTests());
         sb.append(" Tests [");
         sb.append(results[TestStatus.PASSED.ordinal()]);
-        sb.append(" Passed ");
+        sb.append(" Passed");
         if (results[TestStatus.FAILURE.ordinal()] > 0) {
+            sb.append(" ");
             sb.append(results[TestStatus.FAILURE.ordinal()]);
-            sb.append(" Failed ");
+            sb.append(" Failed");
         }
         if (results[TestStatus.IGNORED.ordinal()] > 0) {
+            sb.append(" ");
             sb.append(results[TestStatus.IGNORED.ordinal()]);
-            sb.append(" Ignored ");
+            sb.append(" Ignored");
         }
         if (results[TestStatus.ASSUMPTION_FAILURE.ordinal()] > 0) {
+            sb.append(" ");
             sb.append(results[TestStatus.ASSUMPTION_FAILURE.ordinal()]);
-            sb.append(" Assumption failures ");
+            sb.append(" Assumption failures");
         }
         if (results[TestStatus.INCOMPLETE.ordinal()] > 0) {
+            sb.append(" ");
             sb.append(results[TestStatus.INCOMPLETE.ordinal()]);
             sb.append(" Incomplete");
         }
-        sb.append("\r\n");
+        sb.append("] \r\n");
         print(sb.toString());
         if (mDisplayFailureSummary) {
             for (Entry<TestDescription, TestResult> entry : mFailures.entrySet()) {
diff --git a/src/com/android/tradefed/result/proto/ProtoResultParser.java b/src/com/android/tradefed/result/proto/ProtoResultParser.java
index bb07623..0f775f9 100644
--- a/src/com/android/tradefed/result/proto/ProtoResultParser.java
+++ b/src/com/android/tradefed/result/proto/ProtoResultParser.java
@@ -36,9 +36,12 @@
 import com.android.tradefed.result.proto.LogFileProto.LogFileInfo;
 import com.android.tradefed.result.proto.TestRecordProto.ChildReference;
 import com.android.tradefed.result.proto.TestRecordProto.DebugInfo;
+import com.android.tradefed.result.proto.TestRecordProto.DebugInfoContext;
+import com.android.tradefed.result.proto.TestRecordProto.FailureStatus;
 import com.android.tradefed.result.proto.TestRecordProto.TestRecord;
 import com.android.tradefed.testtype.suite.ModuleDefinition;
 import com.android.tradefed.util.MultiMap;
+import com.android.tradefed.util.SerializationUtil;
 import com.android.tradefed.util.proto.TestRecordProtoUtil;
 
 import com.google.common.base.Splitter;
@@ -75,6 +78,8 @@
     private boolean mInvocationStarted = false;
     private boolean mInvocationEnded = false;
     private boolean mFirstModule = true;
+    /** Track the name of the module in progress. */
+    private String mModuleInProgress = null;
 
     /** Ctor. */
     public ProtoResultParser(
@@ -187,6 +192,26 @@
         return mInvocationEnded;
     }
 
+    /** Returns the id of the module in progress. Returns null if none in progress. */
+    public String getModuleInProgress() {
+        return mModuleInProgress;
+    }
+
+    /** If needed to ensure consistent reporting, complete the events of the module. */
+    public void completeModuleEvents() {
+        if (getModuleInProgress() == null) {
+            return;
+        }
+        mListener.testRunStarted(getModuleInProgress(), 0);
+        FailureDescription failure =
+                FailureDescription.create(
+                        "Module was interrupted after starting, results are incomplete.",
+                        FailureStatus.INFRA_FAILURE);
+        mListener.testRunFailed(failure);
+        mListener.testRunEnded(0L, new HashMap<String, Metric>());
+        mListener.testModuleEnded();
+    }
+
     private void evalChildrenProto(List<ChildReference> children, boolean isInRun) {
         for (ChildReference child : children) {
             TestRecord childProto = child.getInlineTestRecord();
@@ -274,9 +299,24 @@
         }
 
         if (endInvocationProto.hasDebugInfo()) {
-            // TODO: Re-interpret the exception with proper type.
             String trace = endInvocationProto.getDebugInfo().getTrace();
-            mListener.invocationFailed(new Throwable(trace));
+            Throwable invocationError = new Throwable(trace);
+            if (endInvocationProto.getDebugInfo().hasDebugInfoContext()) {
+                DebugInfoContext failureContext =
+                        endInvocationProto.getDebugInfo().getDebugInfoContext();
+                if (!Strings.isNullOrEmpty(failureContext.getErrorType())) {
+                    try {
+                        invocationError =
+                                (Throwable)
+                                        SerializationUtil.deserialize(
+                                                failureContext.getErrorType());
+                    } catch (IOException e) {
+                        CLog.e("Failed to deserialize the invocation exception:");
+                        CLog.e(e);
+                    }
+                }
+            }
+            mListener.invocationFailed(invocationError);
         }
 
         log("Invocation ended proto");
@@ -311,12 +351,13 @@
                     InvocationContext.fromProto(anyDescription.unpack(Context.class));
             String message = "Test module started proto";
             if (moduleContext.getAttributes().containsKey(ModuleDefinition.MODULE_ID)) {
-                message +=
-                        (": "
-                                + moduleContext
-                                        .getAttributes()
-                                        .getUniqueMap()
-                                        .get(ModuleDefinition.MODULE_ID));
+                String moduleId =
+                        moduleContext
+                                .getAttributes()
+                                .getUniqueMap()
+                                .get(ModuleDefinition.MODULE_ID);
+                message += (": " + moduleId);
+                mModuleInProgress = moduleId;
             }
             log(message);
             mListener.testModuleStarted(moduleContext);
@@ -334,6 +375,7 @@
         handleLogs(moduleProto);
         log("Test module ended proto");
         mListener.testModuleEnded();
+        mModuleInProgress = null;
     }
 
     /** Handles the test run level of the invocation. */
diff --git a/src/com/android/tradefed/result/proto/ProtoResultReporter.java b/src/com/android/tradefed/result/proto/ProtoResultReporter.java
index 6ba026f..4e77622 100644
--- a/src/com/android/tradefed/result/proto/ProtoResultReporter.java
+++ b/src/com/android/tradefed/result/proto/ProtoResultReporter.java
@@ -33,12 +33,14 @@
 import com.android.tradefed.result.proto.TestRecordProto.TestStatus;
 import com.android.tradefed.result.retry.ISupportGranularResults;
 import com.android.tradefed.testtype.suite.ModuleDefinition;
+import com.android.tradefed.util.SerializationUtil;
 import com.android.tradefed.util.StreamUtil;
 
 import com.google.common.base.Strings;
 import com.google.protobuf.Any;
 import com.google.protobuf.Timestamp;
 
+import java.io.IOException;
 import java.util.HashMap;
 import java.util.Map;
 import java.util.Stack;
@@ -192,6 +194,14 @@
                 debugBuilder.setErrorMessage(mInvocationFailure.getMessage());
             }
             debugBuilder.setTrace(StreamUtil.getStackTrace(mInvocationFailure));
+            DebugInfoContext.Builder debugContext = DebugInfoContext.newBuilder();
+            try {
+                debugContext.setErrorType(SerializationUtil.serializeToString(mInvocationFailure));
+            } catch (IOException e) {
+                CLog.e("Failed to serialize the invocation failure:");
+                CLog.e(e);
+            }
+            debugBuilder.setDebugInfoContext(debugContext);
             mInvocationRecordBuilder.setDebugInfo(debugBuilder);
         }
 
diff --git a/src/com/android/tradefed/result/proto/StreamProtoReceiver.java b/src/com/android/tradefed/result/proto/StreamProtoReceiver.java
index adef18b..f674906 100644
--- a/src/com/android/tradefed/result/proto/StreamProtoReceiver.java
+++ b/src/com/android/tradefed/result/proto/StreamProtoReceiver.java
@@ -49,6 +49,12 @@
     private long mExtraWaitTimeForEvents = 0L;
 
     /**
+     * Stop parsing events when this is set. This allows to avoid a thread parsing the events when
+     * we don't expect them anymore.
+     */
+    private boolean mStopParsing = false;
+
+    /**
      * Ctor.
      *
      * @param listener the {@link ITestInvocationListener} where to report the results.
@@ -184,12 +190,25 @@
             } catch (InterruptedException e) {
                 CLog.e(e);
                 throw new RuntimeException(e);
+            } finally {
+                mStopParsing = true;
             }
         }
         return true;
     }
 
+    /** If needed to ensure consistent reporting, complete the events of the module. */
+    public void completeModuleEvents() {
+        mParser.completeModuleEvents();
+    }
+
     private void parse(TestRecord receivedRecord) {
+        if (mStopParsing) {
+            CLog.i(
+                    "Skip parsing of %s. It came after joinReceiver.",
+                    receivedRecord.getTestRecordId());
+            return;
+        }
         try {
             TestLevel level = mParser.processNewProto(receivedRecord);
             if (TestLevel.MODULE.equals(level)) {
diff --git a/src/com/android/tradefed/sandbox/SandboxConfigDump.java b/src/com/android/tradefed/sandbox/SandboxConfigDump.java
index 6b6b7e8..2395bbb 100644
--- a/src/com/android/tradefed/sandbox/SandboxConfigDump.java
+++ b/src/com/android/tradefed/sandbox/SandboxConfigDump.java
@@ -106,10 +106,6 @@
                     // Ensure we get the stdout logging in FileLogger case.
                     ((FileLogger) logger).setLogLevelDisplay(LogLevel.VERBOSE);
                 }
-                // Turn off some of the invocation level options that would be duplicated in the
-                // parent.
-                config.getCommandOptions().setBugreportOnInvocationEnded(false);
-                config.getCommandOptions().setBugreportzOnInvocationEnded(false);
 
                 // Ensure in special conditions (placeholder devices) we can still allocate.
                 secureDeviceAllocation(config);
diff --git a/src/com/android/tradefed/sandbox/TradefedSandbox.java b/src/com/android/tradefed/sandbox/TradefedSandbox.java
index 83572ca..9718642 100644
--- a/src/com/android/tradefed/sandbox/TradefedSandbox.java
+++ b/src/com/android/tradefed/sandbox/TradefedSandbox.java
@@ -169,6 +169,9 @@
             result.setStderr(
                     String.format("Event receiver thread did not complete.:\n%s", stderrText));
         }
+        if (mProtoReceiver != null) {
+            mProtoReceiver.completeModuleEvents();
+        }
         PrettyPrintDelimiter.printStageDelimiter(
                 String.format(
                         "Execution of the tests occurred in the sandbox, you can find its logs "
@@ -378,6 +381,10 @@
                 }
                 throw e;
             }
+            // Turn off some of the invocation level options that would be duplicated in the
+            // child sandbox subprocess.
+            config.getCommandOptions().setBugreportOnInvocationEnded(false);
+            config.getCommandOptions().setBugreportzOnInvocationEnded(false);
         } catch (IOException | ConfigurationException e) {
             StreamUtil.close(mEventParser);
             StreamUtil.close(mProtoReceiver);
diff --git a/src/com/android/tradefed/testtype/suite/ModuleDefinition.java b/src/com/android/tradefed/testtype/suite/ModuleDefinition.java
index 192aff6..d869243 100644
--- a/src/com/android/tradefed/testtype/suite/ModuleDefinition.java
+++ b/src/com/android/tradefed/testtype/suite/ModuleDefinition.java
@@ -188,7 +188,7 @@
                 configDescriptor
                         .getAllMetaData()
                         .getUniqueMap()
-                        .get(ConfigurationDescriptor.PARAMETER_KEY);
+                        .get(ConfigurationDescriptor.ACTIVE_PARAMETER_KEY);
         if (parameterization != null) {
             mModuleInvocationContext.addInvocationAttribute(
                     MODULE_PARAMETERIZATION, parameterization);
diff --git a/src/com/android/tradefed/testtype/suite/SuiteModuleLoader.java b/src/com/android/tradefed/testtype/suite/SuiteModuleLoader.java
index df958aa..f233b8d 100644
--- a/src/com/android/tradefed/testtype/suite/SuiteModuleLoader.java
+++ b/src/com/android/tradefed/testtype/suite/SuiteModuleLoader.java
@@ -328,7 +328,7 @@
                             paramConfig
                                     .getConfigurationDescription()
                                     .addMetadata(
-                                            ConfigurationDescriptor.PARAMETER_KEY,
+                                            ConfigurationDescriptor.ACTIVE_PARAMETER_KEY,
                                             param.getParameterIdentifier());
                             param.addParameterSpecificConfig(paramConfig);
                             setUpConfig(name, baseId, fullId, paramConfig, abi);
diff --git a/src/com/android/tradefed/testtype/suite/TestMappingSuiteRunner.java b/src/com/android/tradefed/testtype/suite/TestMappingSuiteRunner.java
index 037ceeb..1f3514a 100644
--- a/src/com/android/tradefed/testtype/suite/TestMappingSuiteRunner.java
+++ b/src/com/android/tradefed/testtype/suite/TestMappingSuiteRunner.java
@@ -177,7 +177,7 @@
                 moduleConfig.setTests(allTests);
                 // Set test sources to ConfigurationDescriptor.
                 List<String> testSources = getTestSources(testInfos);
-                configDescriptor.addMetaData(TestMapping.TEST_SOURCES, testSources);
+                configDescriptor.addMetadata(TestMapping.TEST_SOURCES, testSources);
             }
         }
         return testConfigs;
diff --git a/src/com/android/tradefed/util/LocalRunInstructionBuilder.java b/src/com/android/tradefed/util/LocalRunInstructionBuilder.java
index 63c72f0..3550df9 100644
--- a/src/com/android/tradefed/util/LocalRunInstructionBuilder.java
+++ b/src/com/android/tradefed/util/LocalRunInstructionBuilder.java
@@ -130,7 +130,7 @@
         }
         // Ensure repro is aligned with parameterized modules.
         List<String> paramMetadata =
-                configDescriptor.getMetaData(ConfigurationDescriptor.PARAMETER_KEY);
+                configDescriptor.getMetaData(ConfigurationDescriptor.ACTIVE_PARAMETER_KEY);
         if (paramMetadata != null
                 && paramMetadata.size() > 0
                 && "instant".equals(paramMetadata.get(0))) {
diff --git a/src/com/android/tradefed/util/SerializationUtil.java b/src/com/android/tradefed/util/SerializationUtil.java
index 0c991ff..0bd3fed 100644
--- a/src/com/android/tradefed/util/SerializationUtil.java
+++ b/src/com/android/tradefed/util/SerializationUtil.java
@@ -15,6 +15,8 @@
  */
 package com.android.tradefed.util;
 
+import java.io.ByteArrayInputStream;
+import java.io.ByteArrayOutputStream;
 import java.io.File;
 import java.io.FileInputStream;
 import java.io.FileOutputStream;
@@ -22,6 +24,7 @@
 import java.io.ObjectInputStream;
 import java.io.ObjectOutputStream;
 import java.io.Serializable;
+import java.util.Base64;
 
 /** Utility to serialize/deserialize an object that implements {@link Serializable}. */
 public class SerializationUtil {
@@ -53,6 +56,49 @@
     }
 
     /**
+     * Serialize and object into a base64 encoded string.
+     *
+     * @param o the object to serialize.
+     * @return the {@link String} where the object was serialized.
+     * @throws IOException if serialization fails.
+     */
+    public static String serializeToString(Serializable o) throws IOException {
+        ByteArrayOutputStream byteOut = null;
+        ObjectOutputStream out = null;
+        try {
+            byteOut = new ByteArrayOutputStream();
+            out = new ObjectOutputStream(byteOut);
+            out.writeObject(o);
+            return Base64.getEncoder().encodeToString(byteOut.toByteArray());
+        } finally {
+            StreamUtil.close(out);
+            StreamUtil.close(byteOut);
+        }
+    }
+
+    /**
+     * Deserialize an object that was serialized using {@link #serializeToString(Serializable)}.
+     *
+     * @param serialized the base64 string where the object was serialized.
+     * @return the Object deserialized.
+     * @throws IOException if the deserialization fails.
+     */
+    public static Object deserialize(String serialized) throws IOException {
+        ByteArrayInputStream bais = null;
+        ObjectInputStream in = null;
+        try {
+            bais = new ByteArrayInputStream(Base64.getDecoder().decode(serialized));
+            in = new ObjectInputStream(bais);
+            return in.readObject();
+        } catch (ClassNotFoundException cnfe) {
+            throw new RuntimeException(cnfe);
+        } finally {
+            StreamUtil.close(in);
+            StreamUtil.close(bais);
+        }
+    }
+
+    /**
      * Deserialize an object that was serialized using {@link #serialize(Serializable)}.
      *
      * @param serializedFile the file where the object was serialized.
diff --git a/test_framework/com/android/tradefed/postprocessor/PerfettoGenericPostProcessor.java b/test_framework/com/android/tradefed/postprocessor/PerfettoGenericPostProcessor.java
index 14ec4ef..618b6bc 100644
--- a/test_framework/com/android/tradefed/postprocessor/PerfettoGenericPostProcessor.java
+++ b/test_framework/com/android/tradefed/postprocessor/PerfettoGenericPostProcessor.java
@@ -135,6 +135,12 @@
             + "keys parsed and value is be the replacement string.")
     private Map<String, String> mReplacePrefixMap = new LinkedHashMap<String, String>();
 
+    @Option(
+            name = "perfetto-all-metric-prefix",
+            description = "Prefix to be used with the metrics collected from perfetto."
+                    + "This will be applied before any other prefixes to metrics.")
+    private String mAllMetricPrefix = "perfetto";
+
     // Matches 1.73, 1.73E+2
     private Pattern mNumberWithExponentPattern =
             Pattern.compile("[-+]?[0-9]*[\\.]?[0-9]+([eE][-+]?[0-9]+)?");
@@ -222,6 +228,9 @@
                         parsedMetrics.putAll(
                                 filterMetrics(convertPerfettoProtoMessage(builder.build())));
                         replacePrefix(parsedMetrics);
+                        // Generic prefix string is applied to all the metrics parsed from
+                        // perfetto trace file.
+                        replaceAllMetricPrefix(parsedMetrics);
                         break;
                     case binary:
                         TraceMetrics metricProto = null;
@@ -230,6 +239,9 @@
                         parsedMetrics
                                 .putAll(filterMetrics(convertPerfettoProtoMessage(metricProto)));
                         replacePrefix(parsedMetrics);
+                        // Generic prefix string is applied to all the metrics parsed from
+                        // perfetto trace file.
+                        replaceAllMetricPrefix(parsedMetrics);
                         break;
                     case json:
                         CLog.w("JSON perfetto metric file processing not supported.");
@@ -245,6 +257,7 @@
                 FileUtil.recursiveDelete(uncompressedDir);
             }
         }
+
         return parsedMetrics;
     }
 
@@ -279,6 +292,25 @@
     }
 
     /**
+     * Prefix all the metrics key with given string.
+     *
+     * @param processPerfettoMetrics metrics parsed from the perfetto proto file.
+     */
+    private void replaceAllMetricPrefix(Map<String, Metric.Builder> processPerfettoMetrics) {
+        if (mAllMetricPrefix == null || mAllMetricPrefix.isEmpty()) {
+            return;
+        }
+        Map<String, Metric.Builder> finalMetrics = new HashMap<String, Metric.Builder>();
+        for (Map.Entry<String, Metric.Builder> metric : processPerfettoMetrics.entrySet()) {
+            String newKey = String.format("%s_%s", mAllMetricPrefix, metric.getKey());
+            finalMetrics.put(newKey, metric.getValue());
+            CLog.d("Perfetto trace metric: key: %s value: %s", newKey, metric.getValue());
+        }
+        processPerfettoMetrics.clear();
+        processPerfettoMetrics.putAll(finalMetrics);
+    }
+
+    /**
      * Expands the metric proto file as tree structure and converts it into key, value pairs by
      * recursively constructing the key using the message name, proto fields with string values
      * until the numeric proto field is encountered.
diff --git a/test_framework/com/android/tradefed/testtype/InstrumentationFileTest.java b/test_framework/com/android/tradefed/testtype/InstrumentationFileTest.java
index 5fbbd76..7c0d7b6 100644
--- a/test_framework/com/android/tradefed/testtype/InstrumentationFileTest.java
+++ b/test_framework/com/android/tradefed/testtype/InstrumentationFileTest.java
@@ -203,7 +203,7 @@
         } finally {
             deleteTestFileFromDevice(mFilePathOnDevice);
             Collection<TestDescription> completedTests =
-                    testTracker.getCurrentRunResults().getCompletedTests();
+                    InstrumentationTest.excludeNonExecuted(testTracker.getCurrentRunResults());
             if (mTests.removeAll(completedTests) && !mTests.isEmpty()) {
                 // re-run remaining tests from file
                 writeTestsToFileAndRun(mTests, testInfo, listener);
diff --git a/test_framework/com/android/tradefed/testtype/InstrumentationListener.java b/test_framework/com/android/tradefed/testtype/InstrumentationListener.java
index 2a96294..baafb76 100644
--- a/test_framework/com/android/tradefed/testtype/InstrumentationListener.java
+++ b/test_framework/com/android/tradefed/testtype/InstrumentationListener.java
@@ -45,6 +45,7 @@
     private Set<TestDescription> mDuplicateTests = new HashSet<>();
     private final Collection<TestDescription> mExpectedTests;
     private boolean mDisableDuplicateCheck = false;
+    private boolean mReportUnexecutedTests = false;
     private ProcessInfo mSystemServerProcess = null;
 
     /**
@@ -68,6 +69,10 @@
         mSystemServerProcess = info;
     }
 
+    public void setReportUnexecutedTests(boolean enable) {
+        mReportUnexecutedTests = enable;
+    }
+
     @Override
     public void testRunStarted(String runName, int testCount) {
         // In case of crash, run will attempt to report with 0
@@ -123,6 +128,18 @@
                                     mDuplicateTests));
             error.setFailureStatus(FailureStatus.TEST_FAILURE);
             super.testRunFailed(error);
+        } else if (mReportUnexecutedTests && mExpectedTests.size() > mTests.size()) {
+            Set<TestDescription> missingTests = new LinkedHashSet<>(mExpectedTests);
+            missingTests.removeAll(mTests);
+            for (TestDescription miss : missingTests) {
+                super.testStarted(miss);
+                FailureDescription failure =
+                        FailureDescription.create(
+                                "test did not run due to instrumentation issue.",
+                                FailureStatus.NOT_EXECUTED);
+                super.testFailed(miss, failure);
+                super.testEnded(miss, new HashMap<String, Metric>());
+            }
         }
         super.testRunEnded(elapsedTime, runMetrics);
     }
diff --git a/test_framework/com/android/tradefed/testtype/InstrumentationTest.java b/test_framework/com/android/tradefed/testtype/InstrumentationTest.java
index 97ae0dd..108f920 100644
--- a/test_framework/com/android/tradefed/testtype/InstrumentationTest.java
+++ b/test_framework/com/android/tradefed/testtype/InstrumentationTest.java
@@ -45,8 +45,10 @@
 import com.android.tradefed.result.CollectingTestListener;
 import com.android.tradefed.result.ITestInvocationListener;
 import com.android.tradefed.result.TestDescription;
+import com.android.tradefed.result.TestResult;
 import com.android.tradefed.result.TestRunResult;
 import com.android.tradefed.result.ddmlib.DefaultRemoteAndroidTestRunner;
+import com.android.tradefed.result.proto.TestRecordProto.FailureStatus;
 import com.android.tradefed.retry.IRetryDecision;
 import com.android.tradefed.retry.RetryStrategy;
 import com.android.tradefed.testtype.coverage.CoverageOptions;
@@ -68,6 +70,7 @@
 import java.util.LinkedHashSet;
 import java.util.List;
 import java.util.Map;
+import java.util.Map.Entry;
 import java.util.Set;
 import java.util.concurrent.TimeUnit;
 
@@ -315,6 +318,12 @@
                             + "a system_server restart.")
     private boolean mEnableSoftRestartCheck = false;
 
+    @Option(
+            name = "report-unexecuted-tests",
+            description =
+                    "Whether or not to enable reporting all unexecuted tests from instrumentation.")
+    private boolean mReportUnexecuted = true;
+
     private IAbi mAbi = null;
 
     private Collection<String> mInstallArgs = new ArrayList<>();
@@ -1053,13 +1062,14 @@
             instrumentationListener.setOriginalSystemServer(
                     getDevice().getProcessByName("system_server"));
         }
+        instrumentationListener.setReportUnexecutedTests(mReportUnexecuted);
         mDevice.runInstrumentationTests(mRunner, instrumentationListener);
         TestRunResult testRun = testTracker.getCurrentRunResults();
         if (testRun.isRunFailure() || !testRun.getCompletedTests().containsAll(expectedTests)) {
             // Don't re-run any completed tests, unless this is a coverage run.
             if (mConfiguration != null
                     && !mConfiguration.getCoverageOptions().isCoverageEnabled()) {
-                expectedTests.removeAll(testTracker.getCurrentRunResults().getCompletedTests());
+                expectedTests.removeAll(excludeNonExecuted(testTracker.getCurrentRunResults()));
                 IRetryDecision decision = mConfiguration.getRetryDecision();
                 if (!RetryStrategy.NO_RETRY.equals(decision.getRetryStrategy())
                         && decision.getMaxRetryCount() > 1) {
@@ -1073,6 +1083,20 @@
         }
     }
 
+    /** Filter out "NOT_EXECUTED" for the purpose of tracking what needs to be rerun. */
+    protected static Set<TestDescription> excludeNonExecuted(TestRunResult results) {
+        Set<TestDescription> completedTest = results.getCompletedTests();
+        for (Entry<TestDescription, TestResult> entry : results.getTestResults().entrySet()) {
+            if (completedTest.contains(entry.getKey()) && entry.getValue().getFailure() != null) {
+                if (FailureStatus.NOT_EXECUTED.equals(
+                        entry.getValue().getFailure().getFailureStatus())) {
+                    completedTest.remove(entry.getKey());
+                }
+            }
+        }
+        return completedTest;
+    }
+
     /**
      * Rerun any <var>mRemainingTests</var>
      *
diff --git a/tests/src/com/android/tradefed/postprocessor/PerfettoGenericPostProcessorTest.java b/tests/src/com/android/tradefed/postprocessor/PerfettoGenericPostProcessorTest.java
index 71c02fd..d83c343 100644
--- a/tests/src/com/android/tradefed/postprocessor/PerfettoGenericPostProcessorTest.java
+++ b/tests/src/com/android/tradefed/postprocessor/PerfettoGenericPostProcessorTest.java
@@ -67,6 +67,7 @@
     private static final String KEY_PREFIX_OPTION = "perfetto-prefix-key-field";
     private static final String REGEX_OPTION_VALUE = "perfetto-metric-filter-regex";
     private static final String ALL_METRICS_OPTION = "perfetto-include-all-metrics";
+    private static final String ALL_METRICS_PREFIX_OPTION = "perfetto-all-metric-prefix";
     private static final String REPLACE_REGEX_OPTION = "perfetto-metric-replace-prefix";
     private static final String FILE_FORMAT_OPTION = "trace-processor-output-format";
 
@@ -120,10 +121,10 @@
         Map<String, Metric.Builder> parsedMetrics =
                 mProcessor.processRunMetricsAndLogs(new HashMap<>(), testLogs);
 
-        assertMetricsContain(parsedMetrics, "android_startup-startup-1-startup_id", 1);
+        assertMetricsContain(parsedMetrics, "perfetto_android_startup-startup-1-startup_id", 1);
         assertMetricsContain(
                 parsedMetrics,
-                "android_startup-startup-1-package_name-com.google."
+                "perfetto_android_startup-startup-1-package_name-com.google."
                         + "android.apps.nexuslauncher-to_first_frame-dur_ns",
                 36175473);
     }
@@ -152,10 +153,10 @@
 
         assertFalse("Metric key not expected but found",
                 parsedMetrics.containsKey("android_startup-startup-1-startup_id"));
-        assertMetricsContain(parsedMetrics, "newprefix-startup_id", 1);
+        assertMetricsContain(parsedMetrics, "perfetto_newprefix-startup_id", 1);
         assertMetricsContain(
                 parsedMetrics,
-                "newprefix-package_name-com.google."
+                "perfetto_newprefix-package_name-com.google."
                         + "android.apps.nexuslauncher-to_first_frame-dur_ns",
                 36175473);
     }
@@ -180,7 +181,7 @@
         // Test for non startup metrics exists.
         assertMetricsContain(
                 parsedMetrics,
-                "android_mem-process_metrics-process_name-"
+                "perfetto_android_mem-process_metrics-process_name-"
                         + ".dataservices-total_counters-anon_rss-min",
                 27938816);
     }
@@ -201,7 +202,29 @@
                         new TestDescription("class", "test"), new HashMap<>(), testLogs);
         assertMetricsContain(
                 parsedMetrics,
-                "android_mem-process_metrics-process_name-"
+                "perfetto_android_mem-process_metrics-process_name-"
+                        + ".dataservices-total_counters-anon_rss-min",
+                27938816);
+    }
+
+    /** Test custom all metric suffix is applied correctly. */
+    @Test
+    public void testParsingWithAllMetricsPrefix() throws ConfigurationException, IOException {
+        setupPerfettoMetricFile(METRIC_FILE_FORMAT.text, true);
+        mOptionSetter.setOptionValue(PREFIX_OPTION, PREFIX_OPTION_VALUE);
+        mOptionSetter.setOptionValue(ALL_METRICS_OPTION, "true");
+        mOptionSetter.setOptionValue(ALL_METRICS_PREFIX_OPTION, "custom_all_prefix");
+        Map<String, LogFile> testLogs = new HashMap<>();
+        testLogs.put(
+                PREFIX_OPTION_VALUE,
+                new LogFile(
+                        perfettoMetricProtoFile.getAbsolutePath(), "some.url", LogDataType.TEXTPB));
+        Map<String, Metric.Builder> parsedMetrics =
+                mProcessor.processTestMetricsAndLogs(
+                        new TestDescription("class", "test"), new HashMap<>(), testLogs);
+        assertMetricsContain(
+                parsedMetrics,
+                "custom_all_prefix_android_mem-process_metrics-process_name-"
                         + ".dataservices-total_counters-anon_rss-min",
                 27938816);
     }
@@ -221,7 +244,7 @@
                 mProcessor.processRunMetricsAndLogs(new HashMap<>(), testLogs);
         assertMetricsContain(
                 parsedMetrics,
-                "android_mem-process_metrics-process_name-"
+                "perfetto_android_mem-process_metrics-process_name-"
                         + ".dataservices-total_counters-anon_rss-min",
                 27938816);
     }
@@ -242,10 +265,10 @@
                         perfettoMetricProtoFile.getAbsolutePath(), "some.url", LogDataType.TEXTPB));
         Map<String, Metric.Builder> parsedMetrics =
                 mProcessor.processRunMetricsAndLogs(new HashMap<>(), testLogs);
-        assertMetricsContain(parsedMetrics, "android_startup-startup-startup_id", 2);
+        assertMetricsContain(parsedMetrics, "perfetto_android_startup-startup-startup_id", 2);
         assertMetricsContain(
                 parsedMetrics,
-                "android_startup-startup-package_name-com.google."
+                "perfetto_android_startup-startup-package_name-com.google."
                         + "android.apps.nexuslauncher-to_first_frame-dur_ns",
                 53102401);
     }
@@ -268,16 +291,16 @@
         Map<String, Metric.Builder> parsedMetrics =
                 mProcessor.processRunMetricsAndLogs(new HashMap<>(), testLogs);
 
-        assertMetricsContain(parsedMetrics, "android_startup-startup-1-startup_id", 1);
+        assertMetricsContain(parsedMetrics, "perfetto_android_startup-startup-1-startup_id", 1);
         assertMetricsContain(
                 parsedMetrics,
-                "android_startup-startup-1-package_name-com.google."
+                "perfetto_android_startup-startup-1-package_name-com.google."
                         + "android.apps.nexuslauncher-to_first_frame-dur_ns",
                 36175473);
-        assertMetricsContain(parsedMetrics, "android_startup-startup-2-startup_id", 2);
+        assertMetricsContain(parsedMetrics, "perfetto_android_startup-startup-2-startup_id", 2);
         assertMetricsContain(
                 parsedMetrics,
-                "android_startup-startup-2-package_name-com.google."
+                "perfetto_android_startup-startup-2-package_name-com.google."
                         + "android.apps.nexuslauncher-to_first_frame-dur_ns",
                 53102401);
     }
@@ -301,7 +324,7 @@
                 mProcessor.processRunMetricsAndLogs(new HashMap<>(), testLogs);
 
         assertMetricsContain(parsedMetrics,
-                "android_hwui_metric-process_info-process_name-com.android.systemui-all_mem_min",
+                "perfetto_android_hwui_metric-process_info-process_name-com.android.systemui-all_mem_min",
                 15120269);
 
     }
@@ -323,7 +346,7 @@
                 mProcessor.processRunMetricsAndLogs(new HashMap<>(), testLogs);
         assertMetricsContain(
                 parsedMetrics,
-                "android_mem-process_metrics-process_name-"
+                "perfetto_android_mem-process_metrics-process_name-"
                         + ".dataservices-total_counters-anon_rss-min",
                 27938816);
     }
@@ -366,7 +389,7 @@
                 mProcessor.processRunMetricsAndLogs(new HashMap<>(), testLogs);
         assertMetricsContain(
                 parsedMetrics,
-                "android_mem-process_metrics-process_name-"
+                "perfetto_android_mem-process_metrics-process_name-"
                         + ".dataservices-total_counters-anon_rss-min",
                 27938816);
     }
diff --git a/tests/src/com/android/tradefed/result/ConsoleResultReporterTest.java b/tests/src/com/android/tradefed/result/ConsoleResultReporterTest.java
index f1290f8..fd0a169 100644
--- a/tests/src/com/android/tradefed/result/ConsoleResultReporterTest.java
+++ b/tests/src/com/android/tradefed/result/ConsoleResultReporterTest.java
@@ -88,7 +88,7 @@
     public void testSummary() {
         mResultReporter.testResult(mTest, createTestResult(TestStatus.PASSED));
         mResultReporter.invocationEnded(0);
-        Truth.assertThat(mOutput.toString()).contains("1 Tests [1 Passed ");
+        Truth.assertThat(mOutput.toString()).contains("1 Tests [1 Passed]");
     }
 
     @Test
diff --git a/tests/src/com/android/tradefed/result/proto/ProtoResultParserTest.java b/tests/src/com/android/tradefed/result/proto/ProtoResultParserTest.java
index 517c0a0..eabcab2 100644
--- a/tests/src/com/android/tradefed/result/proto/ProtoResultParserTest.java
+++ b/tests/src/com/android/tradefed/result/proto/ProtoResultParserTest.java
@@ -17,6 +17,7 @@
 
 import static org.junit.Assert.assertEquals;
 import static org.junit.Assert.assertFalse;
+import static org.junit.Assert.assertTrue;
 
 import com.android.tradefed.build.BuildInfo;
 import com.android.tradefed.config.ConfigurationDef;
@@ -166,7 +167,8 @@
         mMockListener.logAssociation(
                 EasyMock.eq("subprocess-invocation_log1"), EasyMock.anyObject());
         // Invocation failure is replayed
-        mMockListener.invocationFailed(EasyMock.anyObject());
+        Capture<Throwable> captureInvocFailure = new Capture<>();
+        mMockListener.invocationFailed(EasyMock.capture(captureInvocFailure));
         mMockListener.invocationEnded(500L);
 
         EasyMock.replay(mMockListener);
@@ -225,6 +227,9 @@
         assertEquals(logFile.getType(), capturedFile.getType());
         assertEquals(logFile.getSize(), capturedFile.getSize());
 
+        Throwable invocFailureCaptured = captureInvocFailure.getValue();
+        assertTrue(invocFailureCaptured instanceof RuntimeException);
+
         // Check Context at the end
         assertEquals(
                 "build_value", context.getBuildInfos().get(0).getBuildAttributes().get(TEST_KEY));
diff --git a/tests/src/com/android/tradefed/result/proto/StreamProtoResultReporterTest.java b/tests/src/com/android/tradefed/result/proto/StreamProtoResultReporterTest.java
index 007df56..64f2eac 100644
--- a/tests/src/com/android/tradefed/result/proto/StreamProtoResultReporterTest.java
+++ b/tests/src/com/android/tradefed/result/proto/StreamProtoResultReporterTest.java
@@ -15,6 +15,7 @@
  */
 package com.android.tradefed.result.proto;
 
+import static org.junit.Assert.assertEquals;
 import static org.junit.Assert.assertNull;
 
 import com.android.tradefed.config.ConfigurationDescriptor;
@@ -30,6 +31,7 @@
 import com.android.tradefed.testtype.suite.ModuleDefinition;
 import com.android.tradefed.util.proto.TfMetricProtoUtil;
 
+import org.easymock.Capture;
 import org.easymock.EasyMock;
 import org.junit.Before;
 import org.junit.Test;
@@ -123,6 +125,29 @@
         assertNull(receiver.getError());
     }
 
+    /** Once the join receiver is done, we don't parse any more events. */
+    @Test
+    public void testStream_stopParsing() throws Exception {
+        StreamProtoReceiver receiver =
+                new StreamProtoReceiver(mMockListener, mMainInvocationContext, true);
+        OptionSetter setter = new OptionSetter(mReporter);
+        try {
+            setter.setOptionValue(
+                    "proto-report-port", Integer.toString(receiver.getSocketServerPort()));
+            // No calls on the mocks
+            EasyMock.replay(mMockListener);
+            // If we join, then we will stop parsing events
+            receiver.joinReceiver(100);
+            mReporter.invocationStarted(mInvocationContext);
+            // Invocation ends
+            mReporter.invocationEnded(500L);
+        } finally {
+            receiver.close();
+        }
+        EasyMock.verify(mMockListener);
+        assertNull(receiver.getError());
+    }
+
     @Test
     public void testStream_noInvocationReporting() throws Exception {
         StreamProtoReceiver receiver =
@@ -188,6 +213,42 @@
         assertNull(receiver.getError());
     }
 
+    @Test
+    public void testStream_incompleteModule() throws Exception {
+        StreamProtoReceiver receiver =
+                new StreamProtoReceiver(mMockListener, mMainInvocationContext, true);
+        OptionSetter setter = new OptionSetter(mReporter);
+        Capture<FailureDescription> capture = new Capture<>();
+        try {
+            setter.setOptionValue(
+                    "proto-report-port", Integer.toString(receiver.getSocketServerPort()));
+            // Verify mocks
+            mMockListener.invocationStarted(EasyMock.anyObject());
+
+            mMockListener.testModuleStarted(EasyMock.anyObject());
+            mMockListener.testRunStarted(EasyMock.eq("arm64 module1"), EasyMock.eq(0));
+            mMockListener.testRunFailed(EasyMock.capture(capture));
+            mMockListener.testRunEnded(
+                    EasyMock.anyLong(), EasyMock.<HashMap<String, Metric>>anyObject());
+            mMockListener.testModuleEnded();
+
+            EasyMock.replay(mMockListener);
+            mReporter.invocationStarted(mInvocationContext);
+            // Run modules
+            mReporter.testModuleStarted(createModuleContext("arm64 module1"));
+            // It stops unexpectedly
+        } finally {
+            receiver.joinReceiver(2000);
+            receiver.close();
+            receiver.completeModuleEvents();
+        }
+        EasyMock.verify(mMockListener);
+        assertNull(receiver.getError());
+        assertEquals(
+                "Module was interrupted after starting, results are incomplete.",
+                capture.getValue().getErrorMessage());
+    }
+
     /** Helper to create a module context. */
     private IInvocationContext createModuleContext(String moduleId) {
         IInvocationContext context = new InvocationContext();
diff --git a/tests/src/com/android/tradefed/testtype/InstrumentationFileTestTest.java b/tests/src/com/android/tradefed/testtype/InstrumentationFileTestTest.java
index 09c5ef4..46e4b7b 100644
--- a/tests/src/com/android/tradefed/testtype/InstrumentationFileTestTest.java
+++ b/tests/src/com/android/tradefed/testtype/InstrumentationFileTestTest.java
@@ -31,6 +31,7 @@
 import com.android.tradefed.invoker.InvocationContext;
 import com.android.tradefed.invoker.TestInformation;
 import com.android.tradefed.metrics.proto.MetricMeasurement.Metric;
+import com.android.tradefed.result.FailureDescription;
 import com.android.tradefed.result.ITestInvocationListener;
 import com.android.tradefed.result.ITestLifeCycleReceiver;
 import com.android.tradefed.result.TestDescription;
@@ -262,6 +263,7 @@
     @Test
     public void testRun_serialReRunOfTwoFailedToCompleteTests()
             throws DeviceNotAvailableException, ConfigurationException {
+        mMockListener = EasyMock.createStrictMock(ITestInvocationListener.class);
         final Collection<TestDescription> testsList = new ArrayList<>(1);
         final TestDescription test1 = new TestDescription("ClassFoo1", "methodBar1");
         final TestDescription test2 = new TestDescription("ClassFoo2", "methodBar2");
@@ -351,6 +353,10 @@
         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.testFailed(EasyMock.eq(test2), EasyMock.<FailureDescription>anyObject());
+        mMockListener.testEnded(
+                EasyMock.eq(test2), EasyMock.anyLong(), EasyMock.eq(new HashMap<String, Metric>()));
         mMockListener.testRunEnded(EasyMock.anyLong(), EasyMock.eq(new HashMap<String, Metric>()));
         // first serial re-run:
         mMockListener.testRunStarted(TEST_PACKAGE_VALUE, 0, 1);
diff --git a/tests/src/com/android/tradefed/testtype/InstrumentationTestTest.java b/tests/src/com/android/tradefed/testtype/InstrumentationTestTest.java
index c633824..1c5e61a 100644
--- a/tests/src/com/android/tradefed/testtype/InstrumentationTestTest.java
+++ b/tests/src/com/android/tradefed/testtype/InstrumentationTestTest.java
@@ -465,6 +465,9 @@
         inOrder.verify(mMockListener)
                 .testRunFailed(
                         FailureDescription.create(RUN_ERROR_MSG, FailureStatus.TEST_FAILURE));
+        inOrder.verify(mMockListener).testStarted(eq(TEST2), anyLong());
+        inOrder.verify(mMockListener).testFailed(eq(TEST2), (FailureDescription) any());
+        inOrder.verify(mMockListener).testEnded(eq(TEST2), anyLong(), eq(EMPTY_STRING_MAP));
         inOrder.verify(mMockListener).testRunEnded(1, EMPTY_STRING_MAP);
 
         inOrder.verify(mMockListener).testRunStarted(TEST_PACKAGE_VALUE, 0, 1);
diff --git a/tests/src/com/android/tradefed/testtype/suite/ModuleDefinitionTest.java b/tests/src/com/android/tradefed/testtype/suite/ModuleDefinitionTest.java
index 7430069..3a4ff57 100644
--- a/tests/src/com/android/tradefed/testtype/suite/ModuleDefinitionTest.java
+++ b/tests/src/com/android/tradefed/testtype/suite/ModuleDefinitionTest.java
@@ -16,6 +16,8 @@
 package com.android.tradefed.testtype.suite;
 
 import static org.junit.Assert.assertEquals;
+import static org.junit.Assert.assertNotNull;
+import static org.junit.Assert.assertNull;
 import static org.junit.Assert.assertTrue;
 import static org.junit.Assert.fail;
 
@@ -358,6 +360,51 @@
         }
     }
 
+    @Test
+    public void testCreateModule() {
+        IConfiguration config = new Configuration("", "");
+        ConfigurationDescriptor descriptor = config.getConfigurationDescription();
+        descriptor.setAbi(new Abi("armeabi-v7a", "32"));
+        descriptor.addMetadata(ITestSuite.PARAMETER_KEY, Arrays.asList("instant_app", "multi_abi"));
+        mModule =
+                new ModuleDefinition(
+                        MODULE_NAME,
+                        mTestList,
+                        mMapDeviceTargetPreparer,
+                        mMultiTargetPrepList,
+                        config);
+        assertNotNull(mModule.getModuleInvocationContext());
+        IInvocationContext moduleContext = mModule.getModuleInvocationContext();
+        assertNull(moduleContext.getAttributes().get(ModuleDefinition.MODULE_PARAMETERIZATION));
+    }
+
+    @Test
+    public void testCreateModule_withParams() {
+        IConfiguration config = new Configuration("", "");
+        ConfigurationDescriptor descriptor = config.getConfigurationDescription();
+        descriptor.setAbi(new Abi("armeabi-v7a", "32"));
+        descriptor.addMetadata(
+                ConfigurationDescriptor.ACTIVE_PARAMETER_KEY, Arrays.asList("instant"));
+        mModule =
+                new ModuleDefinition(
+                        MODULE_NAME,
+                        mTestList,
+                        mMapDeviceTargetPreparer,
+                        mMultiTargetPrepList,
+                        config);
+        assertNotNull(mModule.getModuleInvocationContext());
+        IInvocationContext moduleContext = mModule.getModuleInvocationContext();
+        assertEquals(
+                1,
+                moduleContext.getAttributes().get(ModuleDefinition.MODULE_PARAMETERIZATION).size());
+        assertEquals(
+                "instant",
+                moduleContext
+                        .getAttributes()
+                        .getUniqueMap()
+                        .get(ModuleDefinition.MODULE_PARAMETERIZATION));
+    }
+
     /**
      * Test that {@link ModuleDefinition#run(TestInformation, ITestInvocationListener)} is properly
      * going through the execution flow.
@@ -506,7 +553,7 @@
     public void testParseTokens() throws Exception {
         Configuration config = new Configuration("", "");
         ConfigurationDescriptor descriptor = config.getConfigurationDescription();
-        descriptor.addMetaData(ITestSuite.TOKEN_KEY, Arrays.asList("SIM_CARD"));
+        descriptor.addMetadata(ITestSuite.TOKEN_KEY, Arrays.asList("SIM_CARD"));
         mModule =
                 new ModuleDefinition(
                         MODULE_NAME,
diff --git a/tests/src/com/android/tradefed/testtype/suite/SuiteModuleLoaderTest.java b/tests/src/com/android/tradefed/testtype/suite/SuiteModuleLoaderTest.java
index 3df2d4b..921f675 100644
--- a/tests/src/com/android/tradefed/testtype/suite/SuiteModuleLoaderTest.java
+++ b/tests/src/com/android/tradefed/testtype/suite/SuiteModuleLoaderTest.java
@@ -311,11 +311,17 @@
         // Ensure that appropriate metadata are set on the module config descriptor
         ConfigurationDescriptor descriptor = instantModule.getConfigurationDescription();
         assertEquals(
-                "instant_app",
+                1,
+                descriptor
+                        .getAllMetaData()
+                        .get(ConfigurationDescriptor.ACTIVE_PARAMETER_KEY)
+                        .size());
+        assertEquals(
+                "instant",
                 descriptor
                         .getAllMetaData()
                         .getUniqueMap()
-                        .get(ConfigurationDescriptor.PARAMETER_KEY));
+                        .get(ConfigurationDescriptor.ACTIVE_PARAMETER_KEY));
         assertEquals("armeabi-v7a", descriptor.getAbi().getName());
     }
 
diff --git a/tests/src/com/android/tradefed/util/LocalRunInstructionBuilderTest.java b/tests/src/com/android/tradefed/util/LocalRunInstructionBuilderTest.java
index 4a9d110..e861154 100644
--- a/tests/src/com/android/tradefed/util/LocalRunInstructionBuilderTest.java
+++ b/tests/src/com/android/tradefed/util/LocalRunInstructionBuilderTest.java
@@ -110,7 +110,7 @@
         ConfigurationDescriptor configDescriptor = new ConfigurationDescriptor();
         configDescriptor.setAbi(new Abi(ABI_NAME, "32"));
         configDescriptor.setModuleName(OPTION_SOURCE);
-        configDescriptor.addMetadata(ConfigurationDescriptor.PARAMETER_KEY, "instant");
+        configDescriptor.addMetadata(ConfigurationDescriptor.ACTIVE_PARAMETER_KEY, "instant");
         String instruction =
                 LocalRunInstructionBuilder.getInstruction(
                         configDescriptor, LocalTestRunner.ATEST, null);
diff --git a/tests/src/com/android/tradefed/util/SerializationUtilTest.java b/tests/src/com/android/tradefed/util/SerializationUtilTest.java
index aab84cb..08a2f4f 100644
--- a/tests/src/com/android/tradefed/util/SerializationUtilTest.java
+++ b/tests/src/com/android/tradefed/util/SerializationUtilTest.java
@@ -15,7 +15,9 @@
  */
 package com.android.tradefed.util;
 
-import static org.junit.Assert.*;
+import static org.junit.Assert.assertEquals;
+import static org.junit.Assert.assertTrue;
+import static org.junit.Assert.fail;
 
 import com.android.tradefed.build.BuildInfo;
 import com.android.tradefed.build.BuildSerializedVersion;
@@ -48,6 +50,15 @@
         }
     }
 
+    @Test
+    public void testSerialize_DeserializeString() throws Exception {
+        RuntimeException e = new RuntimeException("test");
+        String serializedException = SerializationUtil.serializeToString(e);
+        Object o = SerializationUtil.deserialize(serializedException);
+        assertTrue(o instanceof RuntimeException);
+        assertEquals("test", ((RuntimeException) o).getMessage());
+    }
+
     /** Tests that serialization and deserialization creates a similar object from the original. */
     @Test
     public void testSerialize_Deserialize() throws Exception {