Adding Interrupted Exception + Adding 'timeout-ms' parameter as commandline argument

Test: manually run 'atest -c CtsDeqpTestCases -- --module-arg 'CtsDeqpTestCases:include-filter:dEQP-VK.api.*' --module-arg 'CtsDeqpTestCases:timeout:8000' and observe test failures on long running tests.
Test: manually added artificial sleep (this_thread::sleep_for(9s)) to a subset of tests and re-run 'atest -c CtsDeqpTestCases -- --module-arg 'CtsDeqpTestCases:include-filter:dEQP-VK.api.<your_test_name>' --module-arg 'CtsDeqpTestCases:timeout:8000' - observe failure message "Timeout: Test timeout".

Change-Id: Iac4b84fea771e239f6eeedc02afacc6daf7c724c
(cherry picked from commit 465635cb2a9ffa1dfadaf850213d998230f4f592)
Bug: b/253083777
diff --git a/android/cts/runner/src/com/drawelements/deqp/runner/DeqpTestRunner.java b/android/cts/runner/src/com/drawelements/deqp/runner/DeqpTestRunner.java
index e042078..246785a 100644
--- a/android/cts/runner/src/com/drawelements/deqp/runner/DeqpTestRunner.java
+++ b/android/cts/runner/src/com/drawelements/deqp/runner/DeqpTestRunner.java
@@ -86,6 +86,7 @@
     private static final String DEQP_ONDEVICE_APK = "com.drawelements.deqp.apk";
     private static final String DEQP_ONDEVICE_PKG = "com.drawelements.deqp";
     private static final String INCOMPLETE_LOG_MESSAGE = "Crash: Incomplete test log";
+    private static final String TIMEOUT_LOG_MESSAGE = "Timeout: Test timeout";
     private static final String SKIPPED_INSTANCE_LOG_MESSAGE = "Configuration skipped";
     private static final String NOT_EXECUTABLE_LOG_MESSAGE = "Abort: Test cannot be executed";
     private static final String APP_DIR = "/sdcard/";
@@ -99,7 +100,7 @@
 
     private static final int TESTCASE_BATCH_LIMIT = 1000;
     private static final int TESTCASE_BATCH_LIMIT_LARGE = 10000;
-    private static final int UNRESPONSIVE_CMD_TIMEOUT_MS = 10 * 60 * 1000; // 10min
+    private static final int UNRESPONSIVE_CMD_TIMEOUT_MS_DEFAULT = 10 * 60 * 1000; // 10min
     private static final int R_API_LEVEL = 30;
     private static final int DEQP_LEVEL_R_2020 = 132383489;
 
@@ -110,6 +111,10 @@
     // !NOTE: There's a static method copyOptions() for copying options during split.
     // If you add state update copyOptions() as appropriate!
 
+    @Option(name="timeout",
+        description="Timeout for unresponsive tests in milliseconds. Default: " + UNRESPONSIVE_CMD_TIMEOUT_MS_DEFAULT,
+        importance=Option.Importance.NEVER)
+    private long mUnresponsiveCmdTimeoutMs = UNRESPONSIVE_CMD_TIMEOUT_MS_DEFAULT;
     @Option(name="deqp-package",
             description="Name of the deqp module used. Determines GLES version.",
             importance=Option.Importance.ALWAYS)
@@ -1390,6 +1395,12 @@
         }
     }
 
+    private static final class AdbComLinkUnresponsiveError extends Exception {
+        public AdbComLinkUnresponsiveError(String description, Throwable inner) {
+            super(description, inner);
+        }
+    }
+
     /**
      * Executes a given command in adb shell
      *
@@ -1398,10 +1409,10 @@
      */
     private void executeShellCommandAndReadOutput(final String command,
             final IShellOutputReceiver receiver)
-            throws AdbComLinkOpenError, AdbComLinkKilledError {
+            throws AdbComLinkOpenError, AdbComLinkKilledError, AdbComLinkUnresponsiveError {
         try {
             mDevice.getIDevice().executeShellCommand(command, receiver,
-                    UNRESPONSIVE_CMD_TIMEOUT_MS, TimeUnit.MILLISECONDS);
+                mUnresponsiveCmdTimeoutMs, TimeUnit.MILLISECONDS);
         } catch (TimeoutException ex) {
             // Opening connection timed out
             throw new AdbComLinkOpenError("opening connection timed out", ex);
@@ -1413,7 +1424,7 @@
             throw new AdbComLinkKilledError("command link killed", ex);
         } catch (ShellCommandUnresponsiveException ex) {
             // shell command halted
-            throw new AdbComLinkKilledError("command link hung", ex);
+            throw new AdbComLinkUnresponsiveError("command link was unresponsive for longer than requested timeout", ex);
         }
     }
 
@@ -1537,9 +1548,14 @@
             mDeviceRecovery.onExecutionProgressed();
         }
 
-        // interrupted, try to recover
+        // interrupted either because of ADB or test timeout
         if (interruptingError != null) {
-            if (interruptingError instanceof AdbComLinkOpenError) {
+
+            // AdbComLinkUnresponsiveError means the test has timeout during execution.
+            // Device is likely fine, so we won't attempt to recover the device.
+            if (interruptingError instanceof AdbComLinkUnresponsiveError) {
+                mInstanceListerner.abortTest(mInstanceListerner.getCurrentTestId(), TIMEOUT_LOG_MESSAGE);
+            } else if (interruptingError instanceof AdbComLinkOpenError) {
                 mDeviceRecovery.recoverConnectionRefused();
             } else if (interruptingError instanceof AdbComLinkKilledError) {
                 mDeviceRecovery.recoverComLinkKilled();
@@ -2429,6 +2445,7 @@
     }
 
     private static void copyOptions(DeqpTestRunner destination, DeqpTestRunner source) {
+        destination.mUnresponsiveCmdTimeoutMs = source.mUnresponsiveCmdTimeoutMs;
         destination.mDeqpPackage = source.mDeqpPackage;
         destination.mConfigName = source.mConfigName;
         destination.mCaselistFile = source.mCaselistFile;