Merge "Add invocation timeout error"
diff --git a/src/com/android/tradefed/cluster/ClusterCommandScheduler.java b/src/com/android/tradefed/cluster/ClusterCommandScheduler.java
index 427ed39..2e09168 100644
--- a/src/com/android/tradefed/cluster/ClusterCommandScheduler.java
+++ b/src/com/android/tradefed/cluster/ClusterCommandScheduler.java
@@ -380,22 +380,22 @@
             @Override
             public void run() {
                 try {
-                    // check cluster command's status
+                    // Check cluster command's status.
                     if (getClusterOptions().checkCommandState()) {
-                        ClusterCommand.State status =
+                        ClusterCommandStatus commandStatus =
                                 getClusterClient()
-                                        .getCommandState(
+                                        .getCommandStatus(
                                                 mCommandTask.getRequestId(),
                                                 mCommandTask.getCommandId());
-                        if (ClusterCommand.State.CANCELED.equals(status)) {
-                            // TODO: retrieve cancel reason from TFC.
+                        if (ClusterCommand.State.CANCELED.equals(commandStatus.getState())) {
                             String cause =
                                     String.format(
                                             "The cluster client %s has marked command "
-                                                    + "(requestId=%s, commandId=%s) canceled",
+                                                    + "(requestId=%s, commandId=%s) canceled with reason: %s",
                                             getClusterClient().getClass().getSimpleName(),
                                             mCommandTask.getRequestId(),
-                                            mCommandTask.getCommandId());
+                                            mCommandTask.getCommandId(),
+                                            commandStatus.getCancelReason());
                             CLog.w("Stop invocation due to: %s", cause);
                             Optional.ofNullable(getInvocationContext())
                                     .map(IInvocationContext::getInvocationId)
diff --git a/src/com/android/tradefed/cluster/SubprocessConfigBuilder.java b/src/com/android/tradefed/cluster/SubprocessConfigBuilder.java
index a69dc86..4f76fc2 100644
--- a/src/com/android/tradefed/cluster/SubprocessConfigBuilder.java
+++ b/src/com/android/tradefed/cluster/SubprocessConfigBuilder.java
@@ -119,8 +119,12 @@
                 in = loader.getResourceAsStream(String.format("config/%s", mOriginalConfig));
             }
             if (in == null) {
+                File f = new File(mOriginalConfig);
+                if (!f.isAbsolute()) {
+                    f = new File(mWorkDir, mOriginalConfig);
+                }
                 try {
-                    in = new FileInputStream(mOriginalConfig);
+                    in = new FileInputStream(f);
                 } catch (FileNotFoundException e) {
                     throw new RuntimeException(
                             String.format("Could not find configuration '%s'", mOriginalConfig));
@@ -143,7 +147,7 @@
             root.appendChild(reporter);
         }
 
-        File f = new File(mWorkDir, createConfigName(mOriginalConfig));
+        File f = new File(mWorkDir, mOriginalConfig);
         TransformerFactory transformerFactory = TransformerFactory.newInstance();
         try {
             Transformer transformer = transformerFactory.newTransformer();
diff --git a/src/com/android/tradefed/cluster/SubprocessReportingHelper.java b/src/com/android/tradefed/cluster/SubprocessReportingHelper.java
index e657226..4944859 100644
--- a/src/com/android/tradefed/cluster/SubprocessReportingHelper.java
+++ b/src/com/android/tradefed/cluster/SubprocessReportingHelper.java
@@ -42,7 +42,8 @@
     private static final String REPORTER_JAR_NAME = "subprocess-results-reporter.jar";
     private static final String CLASS_FILTER =
             String.format(
-                    "(^%s|^%s|^%s|^%s|^%s).*class$",
+                    "(^%s|^%s|^%s|^%s|^%s|^%s).*class$",
+                    "ErrorIdentifier",
                     "LegacySubprocessResultsReporter",
                     "SubprocessTestResultsParser",
                     "SubprocessEventHelper",
diff --git a/src/com/android/tradefed/device/metric/FilePullerLogCollector.java b/src/com/android/tradefed/device/metric/FilePullerLogCollector.java
index 75cb203..5474433 100644
--- a/src/com/android/tradefed/device/metric/FilePullerLogCollector.java
+++ b/src/com/android/tradefed/device/metric/FilePullerLogCollector.java
@@ -42,9 +42,10 @@
                 String ext = FileUtil.getExtension(metricFile.getName()).toLowerCase();
                 if (".png".equals(ext)) {
                     type = LogDataType.PNG;
-                }
-                if (".pb".equals(ext)) {
+                } else if (".pb".equals(ext)) {
                     type = LogDataType.PB;
+                } else if (".mp4".equals(ext)) {
+                    type = LogDataType.MP4;
                 }
                 testLog(metricFile.getName(), type, source);
             }
diff --git a/src/com/android/tradefed/result/LogcatCrashResultForwarder.java b/src/com/android/tradefed/result/LogcatCrashResultForwarder.java
index 25d4e7b..8891069 100644
--- a/src/com/android/tradefed/result/LogcatCrashResultForwarder.java
+++ b/src/com/android/tradefed/result/LogcatCrashResultForwarder.java
@@ -107,7 +107,7 @@
         }
         error.setErrorMessage(errorMessage);
         if (isCrash(errorMessage)) {
-            error.setErrorIdentifier(DeviceErrorIdentifier.INSTRUMENATION_CRASH);
+            error.setErrorIdentifier(DeviceErrorIdentifier.INSTRUMENTATION_CRASH);
         }
         super.testRunFailed(error);
     }
diff --git a/src/com/android/tradefed/targetprep/InstallApexModuleTargetPreparer.java b/src/com/android/tradefed/targetprep/InstallApexModuleTargetPreparer.java
index ce4fabd..3c81407 100644
--- a/src/com/android/tradefed/targetprep/InstallApexModuleTargetPreparer.java
+++ b/src/com/android/tradefed/targetprep/InstallApexModuleTargetPreparer.java
@@ -158,7 +158,8 @@
                     String.format(
                             "Failed to activate %s on device %s.",
                             listApexInfo(failToActivateApex).toString(), device.getSerialNumber()),
-                    device.getDeviceDescriptor());
+                    device.getDeviceDescriptor(),
+                    DeviceErrorIdentifier.FAIL_ACTIVATE_APEX);
         }
         CLog.i("Train activation succeed.");
     }
diff --git a/test_result_interfaces/com/android/tradefed/result/error/DeviceErrorIdentifier.java b/test_result_interfaces/com/android/tradefed/result/error/DeviceErrorIdentifier.java
index 150169c..72efb6a 100644
--- a/test_result_interfaces/com/android/tradefed/result/error/DeviceErrorIdentifier.java
+++ b/test_result_interfaces/com/android/tradefed/result/error/DeviceErrorIdentifier.java
@@ -24,13 +24,14 @@
     // Device Errors: 520_001 ~ 530_000
     // ********************************************************************************************
     APK_INSTALLATION_FAILED(520_001, FailureStatus.DEPENDENCY_ISSUE),
+    FAIL_ACTIVATE_APEX(520_002, FailureStatus.DEPENDENCY_ISSUE),
 
     AAPT_PARSER_FAILED(520_050, FailureStatus.DEPENDENCY_ISSUE),
 
     SHELL_COMMAND_ERROR(520_100, FailureStatus.DEPENDENCY_ISSUE),
     DEVICE_UNEXPECTED_RESPONSE(30_101, FailureStatus.DEPENDENCY_ISSUE),
 
-    INSTRUMENATION_CRASH(520_200, FailureStatus.SYSTEM_UNDER_TEST_CRASHED),
+    INSTRUMENTATION_CRASH(520_200, FailureStatus.SYSTEM_UNDER_TEST_CRASHED),
 
     FAILED_TO_LAUNCH_GCE(520_500, FailureStatus.LOST_SYSTEM_UNDER_TEST),
     FAILED_TO_CONNECT_TO_GCE(520_501, FailureStatus.LOST_SYSTEM_UNDER_TEST),
diff --git a/tests/src/com/android/tradefed/cluster/ClusterCommandLauncherFuncTest.java b/tests/src/com/android/tradefed/cluster/ClusterCommandLauncherFuncTest.java
index 3bf18e0..0c7de73 100644
--- a/tests/src/com/android/tradefed/cluster/ClusterCommandLauncherFuncTest.java
+++ b/tests/src/com/android/tradefed/cluster/ClusterCommandLauncherFuncTest.java
@@ -15,6 +15,7 @@
  */
 package com.android.tradefed.cluster;
 
+import static org.junit.Assert.fail;
 import static org.mockito.ArgumentMatchers.any;
 import static org.mockito.ArgumentMatchers.anyInt;
 import static org.mockito.ArgumentMatchers.anyLong;
@@ -39,7 +40,9 @@
 import org.junit.Before;
 import org.junit.Test;
 import org.junit.runner.RunWith;
+import org.mockito.InOrder;
 import org.mockito.Mock;
+import org.mockito.Mockito;
 import org.mockito.Spy;
 import org.mockito.junit.MockitoJUnitRunner;
 
@@ -52,8 +55,9 @@
 public class ClusterCommandLauncherFuncTest {
 
     private static final String LEGACY_TRADEFED_JAR = "/testdata/tradefed-prebuilt-cts-8.0_r21.jar";
-    private static final String LEGACY_TRADEFED_COMMAND =
-            "host --null-device --class com.android.tradefed.device.DeviceDiagTest";
+    private static final String LEGACY_TRADEFED_COMMAND = "fake.xml --null-device --run testRun PF";
+    private static final String LEGACY_TRADEFED_COMMAND_FOR_INVOCATION_FAILURE =
+            "fake.xml --null-device --fail-invocation-with-cause cause";
 
     private File mRootDir;
     private IConfiguration mConfiguration;
@@ -82,22 +86,50 @@
         FileUtil.recursiveDelete(mRootDir);
     }
 
-    // @Ignore
     @Test
     public void testRun_withLegacyTradefed()
             throws IOException, ConfigurationException, DeviceNotAvailableException {
         File tfJar = new File(mRootDir, "tradefed.jar");
         FileUtil.writeToFile(getClass().getResourceAsStream(LEGACY_TRADEFED_JAR), tfJar);
+        FileUtil.writeToFile(
+                getClass().getResourceAsStream("/config/tf/fake.xml"),
+                new File(mRootDir, "fake.xml"));
         mOptionSetter.setOptionValue("cluster:env-var", "TF_PATH", mRootDir.getAbsolutePath());
         mOptionSetter.setOptionValue("cluster:use-subprocess-reporting", "true");
         mOptionSetter.setOptionValue("cluster:command-line", LEGACY_TRADEFED_COMMAND);
 
         mLauncher.run(mTestInformation, mListener);
 
+        InOrder inOrder = Mockito.inOrder(mListener);
         HashMap<String, MetricMeasurement.Metric> emptyMap = new HashMap<>();
-        verify(mListener).testRunStarted(anyString(), anyInt(), anyInt(), anyLong());
-        verify(mListener).testStarted(any(TestDescription.class), anyLong());
-        verify(mListener).testEnded(any(TestDescription.class), anyLong(), eq(emptyMap));
-        verify(mListener).testRunEnded(anyLong(), eq(emptyMap));
+        inOrder.verify(mListener).testRunStarted(eq("testRun"), anyInt(), anyInt(), anyLong());
+        inOrder.verify(mListener).testStarted(any(TestDescription.class), anyLong());
+        inOrder.verify(mListener).testEnded(any(TestDescription.class), anyLong(), eq(emptyMap));
+        inOrder.verify(mListener).testStarted(any(TestDescription.class), anyLong());
+        inOrder.verify(mListener).testFailed(any(TestDescription.class), anyString());
+        inOrder.verify(mListener).testEnded(any(TestDescription.class), anyLong(), eq(emptyMap));
+        inOrder.verify(mListener).testRunEnded(anyLong(), eq(emptyMap));
+    }
+
+    @Test
+    public void testRun_withLegacyTradefed_invocationFailed()
+            throws IOException, ConfigurationException, DeviceNotAvailableException {
+        File tfJar = new File(mRootDir, "tradefed.jar");
+        FileUtil.writeToFile(getClass().getResourceAsStream(LEGACY_TRADEFED_JAR), tfJar);
+        FileUtil.writeToFile(
+                getClass().getResourceAsStream("/config/tf/fake.xml"),
+                new File(mRootDir, "fake.xml"));
+        mOptionSetter.setOptionValue("cluster:env-var", "TF_PATH", mRootDir.getAbsolutePath());
+        mOptionSetter.setOptionValue("cluster:use-subprocess-reporting", "true");
+        mOptionSetter.setOptionValue(
+                "cluster:command-line", LEGACY_TRADEFED_COMMAND_FOR_INVOCATION_FAILURE);
+
+        try {
+            mLauncher.run(mTestInformation, mListener);
+            fail("RuntimeException should be thrown");
+        } catch (RuntimeException e) {
+        }
+
+        verify(mListener).invocationFailed(any(Throwable.class));
     }
 }
diff --git a/tests/src/com/android/tradefed/cluster/ClusterCommandSchedulerTest.java b/tests/src/com/android/tradefed/cluster/ClusterCommandSchedulerTest.java
index 0a20930..d4befb9 100644
--- a/tests/src/com/android/tradefed/cluster/ClusterCommandSchedulerTest.java
+++ b/tests/src/com/android/tradefed/cluster/ClusterCommandSchedulerTest.java
@@ -1421,8 +1421,8 @@
 
         // command status is CANCELED
         mMockClusterClient = Mockito.mock(IClusterClient.class, RETURNS_DEEP_STUBS);
-        Mockito.when(mMockClusterClient.getCommandState(any(), any()))
-                .thenReturn(ClusterCommand.State.CANCELED);
+        Mockito.when(mMockClusterClient.getCommandStatus(any(), any()))
+                .thenReturn(new ClusterCommandStatus(ClusterCommand.State.CANCELED, "Reason"));
 
         // not stopped if check is disabled
         mMockClusterOptions.setCheckCommandState(false);
@@ -1434,7 +1434,7 @@
         heartbeat.run();
         assertTrue(scheduler.wasStopInvocationCalled());
 
-        Mockito.verify(mMockClusterClient, Mockito.times(1)).getCommandState(any(), any());
+        Mockito.verify(mMockClusterClient, Mockito.times(1)).getCommandStatus(any(), any());
     }
 
     /** Tests whether the heartbeat can determine the invocationId to stop. */
@@ -1450,8 +1450,8 @@
 
         // command status is CANCELED
         mMockClusterClient = Mockito.mock(IClusterClient.class, RETURNS_DEEP_STUBS);
-        Mockito.when(mMockClusterClient.getCommandState(any(), any()))
-                .thenReturn(ClusterCommand.State.CANCELED);
+        Mockito.when(mMockClusterClient.getCommandStatus(any(), any()))
+                .thenReturn(new ClusterCommandStatus(ClusterCommand.State.CANCELED, "Reason"));
 
         // not stopped without invocation context
         heartbeat.run();
@@ -1473,7 +1473,7 @@
         heartbeat.run();
         assertTrue(scheduler.wasStopInvocationCalled());
 
-        Mockito.verify(mMockClusterClient, Mockito.times(4)).getCommandState(any(), any());
+        Mockito.verify(mMockClusterClient, Mockito.times(4)).getCommandStatus(any(), any());
     }
 
     /** Tests whether the heartbeat can determine the cluster command state. */