Work around deqp log file creation issues.
Work around log file creation spuriously failing by creating and making sure
the log file exists in the host-side test runner.
Bug: 22328970
Change-Id: I92d167ae169da617acd515b666c5336e8c43acd5
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 43aaf98..12adb9f 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
@@ -64,6 +64,7 @@
new BatchRunConfiguration("rgba8888d24s8", "unspecified", "window");
private static final int UNRESPOSIVE_CMD_TIMEOUT_MS = 60000; // one minute
+ private static final int FILE_REMOVE_RETRY_INTERVAL = 2000; // 2 seconds
private final String mPackageName;
private final String mName;
@@ -78,6 +79,7 @@
private Set<String> mDeviceFeatures;
private Map<String, Boolean> mConfigQuerySupportCache = new HashMap<>();
private IRunUtil mRunUtil = RunUtil.getDefault();
+ private ISleepProvider mSleepProvider = new SleepProvider();
private IRecovery mDeviceRecovery = new Recovery();
{
@@ -152,6 +154,15 @@
}
/**
+ * Sets the sleep provider DeqpTestRunner works on.
+ *
+ * Exposed for unit testing.
+ */
+ public void setSleepProvider(ISleepProvider sleepProvider) {
+ mSleepProvider = sleepProvider;
+ }
+
+ /**
* Set IRunUtil.
*
* Exposed for unit testing.
@@ -766,12 +777,12 @@
public void recoverConnectionRefused() throws DeviceNotAvailableException;
/**
- * Tries to recover device after abnormal execution termination or link failure.
+ * Tries to recover device after abnormal termination of a command or a test run execution
+ * or link failure.
*
- * @param progressedSinceLastCall true if test execution has progressed since last call
* @throws DeviceNotAvailableException if recovery did not succeed
*/
- public void recoverComLinkKilled() throws DeviceNotAvailableException;
+ public void recoverCommandNotCompleted() throws DeviceNotAvailableException;
};
/**
@@ -864,7 +875,7 @@
* {@inheritDoc}
*/
@Override
- public void recoverComLinkKilled() throws DeviceNotAvailableException {
+ public void recoverCommandNotCompleted() throws DeviceNotAvailableException {
switch (mState) {
case WAIT:
// First failure, just try to wait and try again
@@ -879,10 +890,10 @@
killDeqpProcess();
} catch (DeviceNotAvailableException ex) {
// chain forward
- recoverComLinkKilled();
+ recoverCommandNotCompleted();
} catch (ProcessKillFailureException ex) {
// chain forward
- recoverComLinkKilled();
+ recoverCommandNotCompleted();
}
break;
@@ -896,10 +907,10 @@
killDeqpProcess();
} catch (DeviceNotAvailableException ex) {
// chain forward
- recoverComLinkKilled();
+ recoverCommandNotCompleted();
} catch (ProcessKillFailureException ex) {
// chain forward
- recoverComLinkKilled();
+ recoverCommandNotCompleted();
}
break;
@@ -912,7 +923,7 @@
rebootDevice();
} catch (DeviceNotAvailableException ex) {
// chain forward
- recoverComLinkKilled();
+ recoverCommandNotCompleted();
}
break;
@@ -1397,6 +1408,51 @@
}
}
+ private static final class FileRecreateFailedException extends Exception {
+ };
+
+ /**
+ * Creates a log file suitable for writing.
+ *
+ * Creates an empty file that is suitable for writing. If target file already exists, it will
+ * first be removed and then recreated. This delete-recreate cycle should guarantee that the
+ * target file is always writable and is not affected by file-access flags of the pre-existing
+ * file.
+ *
+ * Precreating a file in the runner makes detecting file-access issues faster and more
+ * reliable. It also avoids issues caused by sdcard FUSE spuriously preventing file creation.
+ */
+ private void precreateLogFile() throws DeviceNotAvailableException,
+ FileRecreateFailedException {
+ final int NUM_ATTEMPTS = 4;
+ int attemptNum = 0;
+ for (;;) {
+ mDevice.executeShellCommand("rm " + LOG_FILE_NAME);
+ ++attemptNum;
+
+ if (!mDevice.doesFileExist(LOG_FILE_NAME)) {
+ // yay, remove works like it should
+ break;
+ } else if (attemptNum < NUM_ATTEMPTS) {
+ // wait if we failed
+ CLog.w("Remote file removal failed, retrying...");
+ mSleepProvider.sleep(FILE_REMOVE_RETRY_INTERVAL);
+ } else {
+ // Bail
+ CLog.e("Could not delete a remote file.");
+ throw new FileRecreateFailedException();
+ }
+ }
+
+ // create & truncate and make world read-writable
+ mDevice.pushString("", LOG_FILE_NAME);
+
+ if (!mDevice.doesFileExist(LOG_FILE_NAME)) {
+ CLog.e("Could not precreate log file.");
+ throw new FileRecreateFailedException();
+ }
+ }
+
/**
* Runs one execution pass over the given batch.
*
@@ -1412,8 +1468,8 @@
final String testCases = generateTestCaseTrie(batch.tests);
+ // Caselist file cannot linger.
mDevice.executeShellCommand("rm " + CASE_LIST_FILE_NAME);
- mDevice.executeShellCommand("rm " + LOG_FILE_NAME);
mDevice.pushString(testCases + "\n", CASE_LIST_FILE_NAME);
final String instrumentationName =
@@ -1443,6 +1499,9 @@
Throwable interruptingError = null;
try {
+ // we might have lingering log file from a previous dirty run and it might be somehow
+ // locked (details unknown). Make sure it's writable when test is run.
+ precreateLogFile();
executeShellCommandAndReadOutput(command, parser);
} catch (Throwable ex) {
interruptingError = ex;
@@ -1462,7 +1521,9 @@
if (interruptingError instanceof AdbComLinkOpenError) {
mDeviceRecovery.recoverConnectionRefused();
} else if (interruptingError instanceof AdbComLinkKilledError) {
- mDeviceRecovery.recoverComLinkKilled();
+ mDeviceRecovery.recoverCommandNotCompleted();
+ } else if (interruptingError instanceof FileRecreateFailedException) {
+ mDeviceRecovery.recoverCommandNotCompleted();
} else if (interruptingError instanceof RunInterruptedException) {
// external run interruption request. Terminate immediately.
throw (RunInterruptedException)interruptingError;
@@ -1473,7 +1534,7 @@
// recoverXXX did not throw => recovery succeeded
} else if (!parser.wasSuccessful()) {
- mDeviceRecovery.recoverComLinkKilled();
+ mDeviceRecovery.recoverCommandNotCompleted();
// recoverXXX did not throw => recovery succeeded
}
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 7ec09c9..4c879b4 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
@@ -106,7 +106,7 @@
* {@inheritDoc}
*/
@Override
- public void recoverComLinkKilled() throws DeviceNotAvailableException {
+ public void recoverCommandNotCompleted() throws DeviceNotAvailableException {
}
};
@@ -1717,12 +1717,15 @@
EasyMock.expect(mockDevice.executeShellCommand(EasyMock.eq("rm " + CASE_LIST_FILE_NAME)))
.andReturn("").once();
- EasyMock.expect(mockDevice.executeShellCommand(EasyMock.eq("rm " + LOG_FILE_NAME)))
- .andReturn("").once();
-
EasyMock.expect(mockDevice.pushString("{dEQP-GLES3{loss{instance}}}\n", CASE_LIST_FILE_NAME))
.andReturn(true).once();
+ EasyMock.expect(mockDevice.executeShellCommand(EasyMock.eq("rm " + LOG_FILE_NAME)))
+ .andReturn("").once();
+ EasyMock.expect(mockDevice.doesFileExist(LOG_FILE_NAME)).andReturn(false).once();
+ EasyMock.expect(mockDevice.pushString("", LOG_FILE_NAME)).andReturn(true).once();
+ EasyMock.expect(mockDevice.doesFileExist(LOG_FILE_NAME)).andReturn(true).once();
+
String command = String.format(
"am instrument %s -w -e deqpLogFileName \"%s\" -e deqpCmdLine \""
+ "--deqp-caselist-file=%s"
@@ -1753,10 +1756,10 @@
});
if (!recoverySuccessful) {
- mockRecovery.recoverComLinkKilled();
+ mockRecovery.recoverCommandNotCompleted();
EasyMock.expectLastCall().andThrow(new DeviceNotAvailableException()).once();
} else {
- mockRecovery.recoverComLinkKilled();
+ mockRecovery.recoverCommandNotCompleted();
EasyMock.expectLastCall().once();
// retry running config B
@@ -1860,7 +1863,7 @@
recovery.recoverConnectionRefused();
break;
case FAIL_LINK_KILLED:
- recovery.recoverComLinkKilled();
+ recovery.recoverCommandNotCompleted();
break;
}
}
@@ -2141,7 +2144,7 @@
orderedControl.replay();
recovery.setDevice(mockDevice);
recovery.setSleepProvider(mockSleepProvider);
- recovery.recoverComLinkKilled();
+ recovery.recoverCommandNotCompleted();
orderedControl.verify();
}
@@ -2312,17 +2315,217 @@
EasyMock.verify(mockDevice, mockIDevice);
}
+ /**
+ * Test log file precreate persistent failure
+ */
+ public void testPrecreate_persistentFailure() throws Exception {
+ final TestIdentifier testId = new TestIdentifier("dEQP-GLES3.precreate", "test");
+ final String testTrie = "{dEQP-GLES3{precreate{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);
+ DeqpTestRunner.ISleepProvider mockSleepProvider =
+ EasyMock.createMock(DeqpTestRunner.ISleepProvider.class);
+ DeqpTestRunner.IRecovery mockRecovery =
+ EasyMock.createMock(DeqpTestRunner.IRecovery.class);
+
+ DeqpTestRunner deqpTest = new DeqpTestRunner(NAME, NAME, tests, instance);
+ deqpTest.setAbi(UnitTests.ABI);
+ deqpTest.setDevice(mockDevice);
+ deqpTest.setBuildHelper(new StubCtsBuildHelper());
+ deqpTest.setSleepProvider(mockSleepProvider);
+ deqpTest.setRecovery(mockRecovery);
+
+ mockRecovery.setDevice(mockDevice);
+ EasyMock.expectLastCall().atLeastOnce();
+
+ 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");
+
+ EasyMock.expect(mockDevice.executeShellCommand(EasyMock.eq("rm " + CASE_LIST_FILE_NAME)))
+ .andReturn("").once();
+
+ EasyMock.expect(mockDevice.pushString(testTrie + "\n", CASE_LIST_FILE_NAME))
+ .andReturn(true).once();
+
+ // fail persistently...
+ EasyMock.expect(mockDevice.executeShellCommand(EasyMock.eq("rm " + LOG_FILE_NAME)))
+ .andReturn("").anyTimes();
+ EasyMock.expect(mockDevice.doesFileExist(LOG_FILE_NAME)).andReturn(true).anyTimes();
+ mockSleepProvider.sleep(EasyMock.gt(0));
+ EasyMock.expectLastCall().anyTimes();
+
+ // ..and when retries won't help, trigger recovery tactics
+ mockRecovery.recoverCommandNotCompleted();
+ EasyMock.expectLastCall().andThrow(new DeviceNotAvailableException()).once();
+
+ mockListener.testRunStarted(ID, 1);
+ EasyMock.expectLastCall().once();
+
+ EasyMock.replay(mockDevice, mockIDevice);
+ EasyMock.replay(mockListener);
+ EasyMock.replay(mockSleepProvider);
+ EasyMock.replay(mockRecovery);
+ try {
+ deqpTest.run(mockListener);
+ fail("expected DeviceNotAvailableException");
+ } catch (DeviceNotAvailableException ex) {
+ // expected
+ }
+ EasyMock.verify(mockRecovery);
+ EasyMock.verify(mockSleepProvider);
+ EasyMock.verify(mockListener);
+ EasyMock.verify(mockDevice, mockIDevice);
+ }
+
+ /**
+ * Test log file precreate transient failure
+ */
+ public void testPrecreate_transientFailure() throws Exception {
+ final TestIdentifier testId = new TestIdentifier("dEQP-GLES3.precreate", "test");
+ final String testPath = "dEQP-GLES3.precreate.test";
+ final String testTrie = "{dEQP-GLES3{precreate{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=Pass\r\n"
+ + "INSTRUMENTATION_STATUS: dEQP-TestCaseResult-Details=DetailPass\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);
+ DeqpTestRunner.ISleepProvider mockSleepProvider =
+ EasyMock.createMock(DeqpTestRunner.ISleepProvider.class);
+
+ DeqpTestRunner deqpTest = new DeqpTestRunner(NAME, NAME, tests, instance);
+ deqpTest.setAbi(UnitTests.ABI);
+ deqpTest.setDevice(mockDevice);
+ deqpTest.setBuildHelper(new StubCtsBuildHelper());
+ deqpTest.setSleepProvider(mockSleepProvider);
+
+ 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");
+
+ // fail once
+ EasyMock.expect(mockDevice.executeShellCommand(EasyMock.eq("rm " + LOG_FILE_NAME)))
+ .andReturn("").once();
+ EasyMock.expect(mockDevice.doesFileExist(LOG_FILE_NAME)).andReturn(true).once();
+ mockSleepProvider.sleep(EasyMock.gt(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);
+
+ EasyMock.expect(mockDevice.uninstallPackage(EasyMock.eq(DEQP_ONDEVICE_PKG))).
+ andReturn("").once();
+
+ mockListener.testRunStarted(ID, 1);
+ EasyMock.expectLastCall().once();
+
+ mockListener.testStarted(EasyMock.eq(testId));
+ EasyMock.expectLastCall().once();
+
+ mockListener.testEnded(EasyMock.eq(testId), EasyMock.<Map<String, String>>notNull());
+ EasyMock.expectLastCall().once();
+
+ mockListener.testRunEnded(EasyMock.anyLong(), EasyMock.<Map<String, String>>notNull());
+ EasyMock.expectLastCall().once();
+
+ EasyMock.replay(mockDevice, mockIDevice);
+ EasyMock.replay(mockListener);
+ EasyMock.replay(mockSleepProvider);
+ deqpTest.run(mockListener);
+ EasyMock.verify(mockSleepProvider);
+ 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)))
.andReturn("").once();
- EasyMock.expect(mockDevice.executeShellCommand(EasyMock.eq("rm " + LOG_FILE_NAME)))
- .andReturn("").once();
-
EasyMock.expect(mockDevice.pushString(testTrie + "\n", CASE_LIST_FILE_NAME))
.andReturn(true).once();
+ EasyMock.expect(mockDevice.executeShellCommand(EasyMock.eq("rm " + LOG_FILE_NAME)))
+ .andReturn("").once();
+ EasyMock.expect(mockDevice.doesFileExist(LOG_FILE_NAME)).andReturn(false).once();
+ EasyMock.expect(mockDevice.pushString("", LOG_FILE_NAME)).andReturn(true).once();
+ EasyMock.expect(mockDevice.doesFileExist(LOG_FILE_NAME)).andReturn(true).once();
+
String command = String.format(
"am instrument %s -w -e deqpLogFileName \"%s\" -e deqpCmdLine \"%s\" "
+ "-e deqpLogData \"%s\" %s",