Listen and handle external interruption requests in deqp runner.
- Check for interruption in deqp runner between batches.
- Forward RunInterruptedException generated by the supplied
ITestRunListener.
- Add unit tests.
Bug: 21071283
Change-Id: I2d107a016c4ef73249e137b23395a414985f4b3b
diff --git a/tools/tradefed-host/src/com/android/cts/tradefed/testtype/DeqpTestRunner.java b/tools/tradefed-host/src/com/android/cts/tradefed/testtype/DeqpTestRunner.java
index 1b7a916..43aaf98 100644
--- a/tools/tradefed-host/src/com/android/cts/tradefed/testtype/DeqpTestRunner.java
+++ b/tools/tradefed-host/src/com/android/cts/tradefed/testtype/DeqpTestRunner.java
@@ -19,6 +19,9 @@
import com.android.tradefed.testtype.IBuildReceiver;
import com.android.tradefed.testtype.IDeviceTest;
import com.android.tradefed.testtype.IRemoteTest;
+import com.android.tradefed.util.IRunUtil;
+import com.android.tradefed.util.RunInterruptedException;
+import com.android.tradefed.util.RunUtil;
import java.io.File;
import java.io.FileNotFoundException;
@@ -74,6 +77,7 @@
private ITestDevice mDevice;
private Set<String> mDeviceFeatures;
private Map<String, Boolean> mConfigQuerySupportCache = new HashMap<>();
+ private IRunUtil mRunUtil = RunUtil.getDefault();
private IRecovery mDeviceRecovery = new Recovery();
{
@@ -147,6 +151,15 @@
mDeviceRecovery = deviceRecovery;
}
+ /**
+ * Set IRunUtil.
+ *
+ * Exposed for unit testing.
+ */
+ public void setRunUtil(IRunUtil runUtil) {
+ mRunUtil = runUtil;
+ }
+
private static final class CapabilityQueryFailureException extends Exception {
};
@@ -1395,6 +1408,8 @@
throw new AssertionError("executeTestRunBatchRun precondition failed");
}
+ checkInterrupted(); // throws if interrupted
+
final String testCases = generateTestCaseTrie(batch.tests);
mDevice.executeShellCommand("rm " + CASE_LIST_FILE_NAME);
@@ -1448,6 +1463,9 @@
mDeviceRecovery.recoverConnectionRefused();
} else if (interruptingError instanceof AdbComLinkKilledError) {
mDeviceRecovery.recoverComLinkKilled();
+ } else if (interruptingError instanceof RunInterruptedException) {
+ // external run interruption request. Terminate immediately.
+ throw (RunInterruptedException)interruptingError;
} else {
CLog.e(interruptingError);
throw new RuntimeException(interruptingError);
@@ -1555,6 +1573,15 @@
}
/**
+ * Checks if this execution has been marked as interrupted and throws if it has.
+ */
+ private void checkInterrupted() throws RunInterruptedException {
+ // Work around the API. RunUtil::checkInterrupted is private but we can call it indirectly
+ // by sleeping a value <= 0.
+ mRunUtil.sleep(0);
+ }
+
+ /**
* Pass given batch tests without running it
*/
private void fakePassTestRunBatch(TestBatch batch) {
diff --git a/tools/tradefed-host/tests/src/com/android/cts/tradefed/testtype/DeqpTestRunnerTest.java b/tools/tradefed-host/tests/src/com/android/cts/tradefed/testtype/DeqpTestRunnerTest.java
index 570d2b5..7ec09c9 100644
--- a/tools/tradefed-host/tests/src/com/android/cts/tradefed/testtype/DeqpTestRunnerTest.java
+++ b/tools/tradefed-host/tests/src/com/android/cts/tradefed/testtype/DeqpTestRunnerTest.java
@@ -27,6 +27,8 @@
import com.android.tradefed.device.ITestDevice;
import com.android.tradefed.result.ITestInvocationListener;
import com.android.tradefed.testtype.IAbi;
+import com.android.tradefed.util.IRunUtil;
+import com.android.tradefed.util.RunInterruptedException;
import junit.framework.TestCase;
@@ -2143,6 +2145,173 @@
orderedControl.verify();
}
+ /**
+ * Test external interruption before batch run.
+ */
+ public void testInterrupt_killBeforeBatch() throws Exception {
+ final TestIdentifier testId = new TestIdentifier("dEQP-GLES3.interrupt", "test");
+
+ Collection<TestIdentifier> tests = new ArrayList<TestIdentifier>();
+ tests.add(testId);
+
+ Map<TestIdentifier, List<Map<String, String>>> instance = new HashMap<>();
+ instance.put(testId, DEFAULT_INSTANCE_ARGS);
+
+ ITestInvocationListener mockListener
+ = EasyMock.createStrictMock(ITestInvocationListener.class);
+ ITestDevice mockDevice = EasyMock.createMock(ITestDevice.class);
+ IDevice mockIDevice = EasyMock.createMock(IDevice.class);
+ IRunUtil mockRunUtil = EasyMock.createMock(IRunUtil.class);
+
+ DeqpTestRunner deqpTest = new DeqpTestRunner(NAME, NAME, tests, instance);
+ deqpTest.setAbi(UnitTests.ABI);
+ deqpTest.setDevice(mockDevice);
+ deqpTest.setBuildHelper(new StubCtsBuildHelper());
+ deqpTest.setRunUtil(mockRunUtil);
+
+ int version = 3 << 16;
+ EasyMock.expect(mockDevice.getProperty("ro.opengles.version"))
+ .andReturn(Integer.toString(version)).atLeastOnce();
+
+ EasyMock.expect(mockDevice.uninstallPackage(EasyMock.eq(DEQP_ONDEVICE_PKG))).
+ andReturn("").once();
+
+ EasyMock.expect(mockDevice.installPackage(EasyMock.<File>anyObject(),
+ EasyMock.eq(true),
+ EasyMock.eq(AbiUtils.createAbiFlag(UnitTests.ABI.getName())))).andReturn(null)
+ .once();
+
+ expectRenderConfigQuery(mockDevice,
+ "--deqp-gl-config-name=rgba8888d24s8 --deqp-screen-rotation=unspecified "
+ + "--deqp-surface-type=window --deqp-gl-major-version=3 "
+ + "--deqp-gl-minor-version=0");
+
+ mockRunUtil.sleep(0);
+ EasyMock.expectLastCall().andThrow(new RunInterruptedException());
+
+ mockListener.testRunStarted(ID, 1);
+ EasyMock.expectLastCall().once();
+
+ EasyMock.replay(mockDevice, mockIDevice);
+ EasyMock.replay(mockListener);
+ EasyMock.replay(mockRunUtil);
+ try {
+ deqpTest.run(mockListener);
+ fail("expected RunInterruptedException");
+ } catch (RunInterruptedException ex) {
+ // expected
+ }
+ EasyMock.verify(mockRunUtil);
+ EasyMock.verify(mockListener);
+ EasyMock.verify(mockDevice, mockIDevice);
+ }
+
+ /**
+ * Test external interruption in testFailed().
+ */
+ public void testInterrupt_killReportTestFailed() throws Exception {
+ final TestIdentifier testId = new TestIdentifier("dEQP-GLES3.interrupt", "test");
+ final String testPath = "dEQP-GLES3.interrupt.test";
+ final String testTrie = "{dEQP-GLES3{interrupt{test}}}";
+ final String output = "INSTRUMENTATION_STATUS: dEQP-SessionInfo-Name=releaseName\r\n"
+ + "INSTRUMENTATION_STATUS: dEQP-EventType=SessionInfo\r\n"
+ + "INSTRUMENTATION_STATUS: dEQP-SessionInfo-Value=2014.x\r\n"
+ + "INSTRUMENTATION_STATUS_CODE: 0\r\n"
+ + "INSTRUMENTATION_STATUS: dEQP-SessionInfo-Name=releaseId\r\n"
+ + "INSTRUMENTATION_STATUS: dEQP-EventType=SessionInfo\r\n"
+ + "INSTRUMENTATION_STATUS: dEQP-SessionInfo-Value=0xcafebabe\r\n"
+ + "INSTRUMENTATION_STATUS_CODE: 0\r\n"
+ + "INSTRUMENTATION_STATUS: dEQP-SessionInfo-Name=targetName\r\n"
+ + "INSTRUMENTATION_STATUS: dEQP-EventType=SessionInfo\r\n"
+ + "INSTRUMENTATION_STATUS: dEQP-SessionInfo-Value=android\r\n"
+ + "INSTRUMENTATION_STATUS_CODE: 0\r\n"
+ + "INSTRUMENTATION_STATUS: dEQP-EventType=BeginSession\r\n"
+ + "INSTRUMENTATION_STATUS_CODE: 0\r\n"
+ + "INSTRUMENTATION_STATUS: dEQP-EventType=BeginTestCase\r\n"
+ + "INSTRUMENTATION_STATUS: dEQP-BeginTestCase-TestCasePath=" + testPath + "\r\n"
+ + "INSTRUMENTATION_STATUS_CODE: 0\r\n"
+ + "INSTRUMENTATION_STATUS: dEQP-TestCaseResult-Code=Fail\r\n"
+ + "INSTRUMENTATION_STATUS: dEQP-TestCaseResult-Details=Fail\r\n"
+ + "INSTRUMENTATION_STATUS: dEQP-EventType=TestCaseResult\r\n"
+ + "INSTRUMENTATION_STATUS_CODE: 0\r\n"
+ + "INSTRUMENTATION_STATUS: dEQP-EventType=EndTestCase\r\n"
+ + "INSTRUMENTATION_STATUS_CODE: 0\r\n"
+ + "INSTRUMENTATION_STATUS: dEQP-EventType=EndSession\r\n"
+ + "INSTRUMENTATION_STATUS_CODE: 0\r\n"
+ + "INSTRUMENTATION_CODE: 0\r\n";
+
+ Collection<TestIdentifier> tests = new ArrayList<TestIdentifier>();
+ tests.add(testId);
+
+ Map<TestIdentifier, List<Map<String, String>>> instance = new HashMap<>();
+ instance.put(testId, DEFAULT_INSTANCE_ARGS);
+
+ ITestInvocationListener mockListener
+ = EasyMock.createStrictMock(ITestInvocationListener.class);
+ ITestDevice mockDevice = EasyMock.createMock(ITestDevice.class);
+ IDevice mockIDevice = EasyMock.createMock(IDevice.class);
+ IRunUtil mockRunUtil = EasyMock.createMock(IRunUtil.class);
+
+ DeqpTestRunner deqpTest = new DeqpTestRunner(NAME, NAME, tests, instance);
+ deqpTest.setAbi(UnitTests.ABI);
+ deqpTest.setDevice(mockDevice);
+ deqpTest.setBuildHelper(new StubCtsBuildHelper());
+ deqpTest.setRunUtil(mockRunUtil);
+
+ int version = 3 << 16;
+ EasyMock.expect(mockDevice.getProperty("ro.opengles.version"))
+ .andReturn(Integer.toString(version)).atLeastOnce();
+
+ EasyMock.expect(mockDevice.uninstallPackage(EasyMock.eq(DEQP_ONDEVICE_PKG))).
+ andReturn("").once();
+
+ EasyMock.expect(mockDevice.installPackage(EasyMock.<File>anyObject(),
+ EasyMock.eq(true),
+ EasyMock.eq(AbiUtils.createAbiFlag(UnitTests.ABI.getName())))).andReturn(null)
+ .once();
+
+ expectRenderConfigQuery(mockDevice,
+ "--deqp-gl-config-name=rgba8888d24s8 --deqp-screen-rotation=unspecified "
+ + "--deqp-surface-type=window --deqp-gl-major-version=3 "
+ + "--deqp-gl-minor-version=0");
+
+ mockRunUtil.sleep(0);
+ EasyMock.expectLastCall().once();
+
+ String commandLine = String.format(
+ "--deqp-caselist-file=%s --deqp-gl-config-name=rgba8888d24s8 "
+ + "--deqp-screen-rotation=unspecified "
+ + "--deqp-surface-type=window "
+ + "--deqp-log-images=disable "
+ + "--deqp-watchdog=enable",
+ CASE_LIST_FILE_NAME);
+
+ runInstrumentationLineAndAnswer(mockDevice, mockIDevice, testTrie, commandLine,
+ output);
+
+ mockListener.testRunStarted(ID, 1);
+ EasyMock.expectLastCall().once();
+
+ mockListener.testStarted(EasyMock.eq(testId));
+ EasyMock.expectLastCall().once();
+
+ mockListener.testFailed(EasyMock.eq(testId), EasyMock.<String>notNull());
+ EasyMock.expectLastCall().andThrow(new RunInterruptedException());
+
+ EasyMock.replay(mockDevice, mockIDevice);
+ EasyMock.replay(mockListener);
+ EasyMock.replay(mockRunUtil);
+ try {
+ deqpTest.run(mockListener);
+ fail("expected RunInterruptedException");
+ } catch (RunInterruptedException ex) {
+ // expected
+ }
+ EasyMock.verify(mockRunUtil);
+ EasyMock.verify(mockListener);
+ EasyMock.verify(mockDevice, mockIDevice);
+ }
+
private void runInstrumentationLineAndAnswer(ITestDevice mockDevice, IDevice mockIDevice,
final String testTrie, final String cmd, final String output) throws Exception {
EasyMock.expect(mockDevice.executeShellCommand(EasyMock.eq("rm " + CASE_LIST_FILE_NAME)))