Make XmlDefsTest and InstrumentationTest resumable.
Bug 3143680
Change-Id: I98510f2ce19fa65c7aac905206c46b0da01590bd
diff --git a/src/com/android/tradefed/testtype/InstrumentationTest.java b/src/com/android/tradefed/testtype/InstrumentationTest.java
index 342c19a..a86eab7 100644
--- a/src/com/android/tradefed/testtype/InstrumentationTest.java
+++ b/src/com/android/tradefed/testtype/InstrumentationTest.java
@@ -43,7 +43,7 @@
/**
* A Test that runs an instrumentation test package on given device.
*/
-public class InstrumentationTest extends AbstractRemoteTest implements IDeviceTest, IRemoteTest,
+public class InstrumentationTest extends AbstractRemoteTest implements IDeviceTest, IResumableTest,
ITimeoutCallback {
private static final String LOG_TAG = "InstrumentationTest";
@@ -102,6 +102,8 @@
private IRemoteAndroidTestRunner mRunner;
private Collection<ITestRunListener> mListeners;
+ private Collection<TestIdentifier> mRemainingTests = new ArrayList<TestIdentifier>();
+
/**
* {@inheritDoc}
*/
@@ -313,20 +315,82 @@
Collection<TestIdentifier> expectedTests) throws DeviceNotAvailableException {
CollectingTestListener testTracker = new CollectingTestListener();
mListeners.add(testTracker);
- mDevice.runInstrumentationTests(mRunner, mListeners);
- TestRunResult runResult = testTracker.getCurrentRunResults();
- if (runResult.isRunFailure() || !runResult.isRunComplete()) {
- // get the delta incomplete tests
- expectedTests.removeAll(runResult.getTests());
+ mRemainingTests = expectedTests;
+ try {
+ mDevice.runInstrumentationTests(mRunner, mListeners);
+ } finally {
+ calculateRemainingTests(mRemainingTests, testTracker);
+ }
+ rerunTests(listeners);
+ }
+
+ /**
+ * Rerun any <var>mRemainingTests</var> one by one
+ *
+ * @param listeners
+ * @throws DeviceNotAvailableException
+ */
+ private void rerunTests(final List<ITestInvocationListener> listeners)
+ throws DeviceNotAvailableException {
+ if (mRemainingTests.size() > 0) {
InstrumentationListTest testRerunner = new InstrumentationListTest(mPackageName,
- mRunnerName, expectedTests);
+ mRunnerName, mRemainingTests);
testRerunner.setDevice(getDevice());
testRerunner.setTestTimeout(getTestTimeout());
- testRerunner.run(listeners);
+ CollectingTestListener testTracker = new CollectingTestListener();
+ List<ITestInvocationListener> listenersCopy = new ArrayList<ITestInvocationListener>(
+ listeners);
+ listenersCopy.add(testTracker);
+ try {
+ testRerunner.run(listenersCopy);
+ } finally {
+ calculateRemainingTests(mRemainingTests, testTracker);
+ }
}
}
/**
+ * Remove the set of tests collected by testTracker from the set of expectedTests
+ *
+ * @param expectedTests
+ * @param testTracker
+ */
+ private void calculateRemainingTests(Collection<TestIdentifier> expectedTests,
+ CollectingTestListener testTracker) {
+ expectedTests.removeAll(testTracker.getCurrentRunResults().getTests());
+ }
+
+ /**
+ * {@inheritDoc}
+ */
+ public void testTimeout(TestIdentifier test) {
+ mRunner.cancel();
+ final String msg = String.format(TIMED_OUT_MSG, mTestTimeout);
+ for (ITestRunListener listener : mListeners) {
+ listener.testFailed(TestFailure.ERROR, test, msg);
+ listener.testRunFailed(msg);
+ }
+ }
+
+ /**
+ * {@inheritDoc}
+ */
+ @Override
+ public void resume(List<ITestInvocationListener> listeners) throws DeviceNotAvailableException {
+ rerunTests(listeners);
+ }
+
+ /**
+ * {@inheritDoc}
+ */
+ @Override
+ public void resume(ITestInvocationListener listener) throws DeviceNotAvailableException {
+ List<ITestInvocationListener> list = new ArrayList<ITestInvocationListener>(1);
+ list.add(listener);
+ resume(list);
+ }
+
+ /**
* Collect the list of tests that should be executed by this test run.
* <p/>
* This will be done by executing the test run in 'logOnly' mode, and recording the list of
@@ -420,16 +484,4 @@
mRunner.cancel();
}
}
-
- /**
- * {@inheritDoc}
- */
- public void testTimeout(TestIdentifier test) {
- mRunner.cancel();
- final String msg = String.format(TIMED_OUT_MSG, mTestTimeout);
- for (ITestRunListener listener : mListeners) {
- listener.testFailed(TestFailure.ERROR, test, msg);
- listener.testRunFailed(msg);
- }
- }
}
diff --git a/src/com/android/tradefed/testtype/testdefs/XmlDefsTest.java b/src/com/android/tradefed/testtype/testdefs/XmlDefsTest.java
index ebe8ed2..64590a2 100644
--- a/src/com/android/tradefed/testtype/testdefs/XmlDefsTest.java
+++ b/src/com/android/tradefed/testtype/testdefs/XmlDefsTest.java
@@ -22,7 +22,7 @@
import com.android.tradefed.result.ITestInvocationListener;
import com.android.tradefed.testtype.AbstractRemoteTest;
import com.android.tradefed.testtype.IDeviceTest;
-import com.android.tradefed.testtype.IRemoteTest;
+import com.android.tradefed.testtype.IResumableTest;
import com.android.tradefed.testtype.InstrumentationTest;
import com.android.tradefed.util.FileUtil;
import com.android.tradefed.util.xml.AbstractXmlParser.ParseException;
@@ -34,6 +34,7 @@
import java.util.ArrayList;
import java.util.Collection;
import java.util.HashMap;
+import java.util.LinkedList;
import java.util.List;
import java.util.Map;
@@ -43,7 +44,7 @@
* The test definition files can either be one or more files on local file system, and/or one or
* more files stored on the device under test.
*/
-public class XmlDefsTest extends AbstractRemoteTest implements IDeviceTest, IRemoteTest {
+public class XmlDefsTest extends AbstractRemoteTest implements IDeviceTest, IResumableTest {
private static final String LOG_TAG = "XmlDefsTest";
@@ -78,6 +79,9 @@
description = "Send coverage target info to test listeners. Default true.")
private boolean mSendCoverage = true;
+ private List<InstrumentationTestDef> mTestDefs = null;
+ private InstrumentationTest mCurrentTest = null;
+
public XmlDefsTest() {
}
@@ -130,12 +134,23 @@
testDefFile.delete();
}
}
- for (InstrumentationTestDef def : parser.getTestDefs()) {
+ mTestDefs = new LinkedList<InstrumentationTestDef>(parser.getTestDefs());
+ doRun(listeners);
+ }
+
+ /**
+ * @param listeners
+ * @throws DeviceNotAvailableException
+ */
+ private void doRun(List<ITestInvocationListener> listeners) throws DeviceNotAvailableException {
+ while (!mTestDefs.isEmpty()) {
+ InstrumentationTestDef def = mTestDefs.remove(0);
// only run continuous for now. Consider making this configurable
if (def.isContinuous()) {
Log.d(LOG_TAG, String.format("Running test def %s on %s", def.getName(),
getDevice().getSerialNumber()));
InstrumentationTest test = createInstrumentationTest();
+ mCurrentTest = test;
test.setDevice(getDevice());
test.setPackageName(def.getPackage());
if (def.getRunner() != null) {
@@ -151,6 +166,7 @@
if (mSendCoverage && def.getCoverageTarget() != null) {
sendCoverage(def.getPackage(), def.getCoverageTarget(), listeners);
}
+ mCurrentTest = null;
}
}
}
@@ -218,4 +234,25 @@
InstrumentationTest createInstrumentationTest() {
return new InstrumentationTest();
}
+
+ /**
+ * {@inheritDoc}
+ */
+ @Override
+ public void resume(List<ITestInvocationListener> listeners) throws DeviceNotAvailableException {
+ if (mCurrentTest != null) {
+ mCurrentTest.resume(listeners);
+ }
+ doRun(listeners);
+ }
+
+ /**
+ * {@inheritDoc}
+ */
+ @Override
+ public void resume(ITestInvocationListener listener) throws DeviceNotAvailableException {
+ List<ITestInvocationListener> list = new ArrayList<ITestInvocationListener>(1);
+ list.add(listener);
+ resume(list);
+ }
}
diff --git a/tests/src/com/android/tradefed/testtype/InstrumentationTestTest.java b/tests/src/com/android/tradefed/testtype/InstrumentationTestTest.java
index 738acbf..9c2965e 100644
--- a/tests/src/com/android/tradefed/testtype/InstrumentationTestTest.java
+++ b/tests/src/com/android/tradefed/testtype/InstrumentationTestTest.java
@@ -268,8 +268,9 @@
}
});
// now expect third run to run remaining test
- mMockRemoteRunner.setMethodName(test2.getClassName(), test2.getTestName());
- mMockTestDevice.runInstrumentationTests(EasyMock.eq(mMockRemoteRunner),
+ // InstrumentationListTest will create its own runner, won't use mock runner
+ //mMockRemoteRunner.setMethodName(test2.getClassName(), test2.getTestName());
+ mMockTestDevice.runInstrumentationTests((IRemoteAndroidTestRunner)EasyMock.anyObject(),
(Collection<ITestRunListener>)EasyMock.anyObject());
EasyMock.expectLastCall().andDelegateTo(new StubTestDevice() {
@Override
@@ -294,10 +295,101 @@
mMockListener.testEnded(test2, emptyMap);
mMockListener.testRunEnded(1, EMPTY_STRING_MAP);
- EasyMock.replay(mMockRemoteRunner);
- EasyMock.replay(mMockTestDevice);
- EasyMock.replay(mMockListener);
+ EasyMock.replay(mMockRemoteRunner, mMockTestDevice, mMockListener);
mInstrumentationTest.run(mMockListener);
+ EasyMock.verify(mMockListener);
+ }
+
+ /**
+ * Test resuming a test run when first run is aborted due to
+ * {@link DeviceNotAvailableException}
+ */
+ @SuppressWarnings("unchecked")
+ public void testRun_resume() throws Exception {
+ final TestIdentifier test1 = new TestIdentifier("Test", "test1");
+ final TestIdentifier test2 = new TestIdentifier("Test", "test2");
+ final Map<String, String> emptyMap = Collections.emptyMap();
+ final String runErrorMsg = "error";
+
+ mInstrumentationTest.setRerunMode(true);
+ // expect log only mode run first to collect tests
+ mMockRemoteRunner.setLogOnly(true);
+ mMockRemoteRunner.addInstrumentationArg(InstrumentationTest.DELAY_MSEC_ARG,
+ Long.toString(mInstrumentationTest.getTestDelay()));
+ mMockTestDevice.runInstrumentationTests(EasyMock.eq(mMockRemoteRunner),
+ (Collection<ITestRunListener>)EasyMock.anyObject());
+ EasyMock.expectLastCall().andDelegateTo(new StubTestDevice() {
+ @Override
+ public void runInstrumentationTests(IRemoteAndroidTestRunner runner,
+ Collection<ITestRunListener> listeners) throws DeviceNotAvailableException {
+ // perform call back on listeners to show run of two tests
+ for (ITestRunListener listener : listeners) {
+ listener.testRunStarted(TEST_PACKAGE_VALUE, 2);
+ listener.testStarted(test1);
+ listener.testEnded(test1, emptyMap);
+ listener.testStarted(test2);
+ listener.testEnded(test2, emptyMap);
+ listener.testRunEnded(1, EMPTY_STRING_MAP);
+ }
+ }
+ });
+ // now expect second run with log only mode off
+ mMockRemoteRunner.setLogOnly(false);
+ mMockRemoteRunner.removeInstrumentationArg(InstrumentationTest.DELAY_MSEC_ARG);
+ mMockTestDevice.runInstrumentationTests(EasyMock.eq(mMockRemoteRunner),
+ (Collection<ITestRunListener>)EasyMock.anyObject());
+ EasyMock.expectLastCall().andDelegateTo(new StubTestDevice() {
+ @Override
+ public void runInstrumentationTests(IRemoteAndroidTestRunner runner,
+ Collection<ITestRunListener> listeners) throws DeviceNotAvailableException {
+ // perform call back on listeners to show run failed - only one test
+ for (ITestRunListener listener : listeners) {
+ listener.testRunStarted(TEST_PACKAGE_VALUE, 2);
+ listener.testStarted(test1);
+ listener.testEnded(test1, emptyMap);
+ listener.testRunFailed(runErrorMsg);
+ }
+ throw new DeviceNotAvailableException();
+ }
+ });
+
+ // now expect third run to run remaining test
+ // InstrumentationListTest will create its own runner, won't use mock runner
+ //mMockRemoteRunner.setMethodName(test2.getClassName(), test2.getTestName());
+ mMockTestDevice.runInstrumentationTests((IRemoteAndroidTestRunner)EasyMock.anyObject(),
+ (Collection<ITestRunListener>)EasyMock.anyObject());
+ EasyMock.expectLastCall().andDelegateTo(new StubTestDevice() {
+ @Override
+ public void runInstrumentationTests(IRemoteAndroidTestRunner runner,
+ Collection<ITestRunListener> listeners) throws DeviceNotAvailableException {
+ // perform call back on listeners to show run failed - only one test
+ for (ITestRunListener listener : listeners) {
+ listener.testRunStarted(TEST_PACKAGE_VALUE, 1);
+ listener.testStarted(test2);
+ listener.testEnded(test2, emptyMap);
+ listener.testRunEnded(1, EMPTY_STRING_MAP);
+ }
+ }
+ });
+
+ mMockListener.testRunStarted(TEST_PACKAGE_VALUE, 2);
+ mMockListener.testStarted(test1);
+ mMockListener.testEnded(test1, emptyMap);
+ mMockListener.testRunFailed(runErrorMsg);
+ mMockListener.testRunStarted(TEST_PACKAGE_VALUE, 1);
+ mMockListener.testStarted(test2);
+ mMockListener.testEnded(test2, emptyMap);
+ mMockListener.testRunEnded(1, EMPTY_STRING_MAP);
+
+ EasyMock.replay(mMockRemoteRunner, mMockTestDevice, mMockListener);
+ try {
+ mInstrumentationTest.run(mMockListener);
+ fail("DeviceNotAvailableException not thrown");
+ } catch (DeviceNotAvailableException e) {
+ // expected
+ }
+ mInstrumentationTest.resume(mMockListener);
+ EasyMock.verify(mMockListener);
}
/**
diff --git a/tests/src/com/android/tradefed/testtype/MockInstrumentationTest.java b/tests/src/com/android/tradefed/testtype/MockInstrumentationTest.java
index 5b15cee..18f8f16 100644
--- a/tests/src/com/android/tradefed/testtype/MockInstrumentationTest.java
+++ b/tests/src/com/android/tradefed/testtype/MockInstrumentationTest.java
@@ -15,6 +15,7 @@
*/
package com.android.tradefed.testtype;
+import com.android.tradefed.device.DeviceNotAvailableException;
import com.android.tradefed.result.ITestInvocationListener;
import java.util.List;
@@ -25,18 +26,52 @@
public class MockInstrumentationTest extends InstrumentationTest {
private ITestInvocationListener mListener = null;
+ private DeviceNotAvailableException mException = null;
@Override
- public void run(final List<ITestInvocationListener> listeners) {
+ public void run(final List<ITestInvocationListener> listeners)
+ throws DeviceNotAvailableException {
mListener = listeners.get(0);
+ if (mException != null) {
+ throw mException;
+ }
}
@Override
- public void run(final ITestInvocationListener listener) {
+ public void run(final ITestInvocationListener listener) throws DeviceNotAvailableException {
mListener = listener;
+ if (mException != null) {
+ throw mException;
+ }
}
public ITestInvocationListener getListener() {
return mListener;
}
+
+ @Override
+ public void resume(final List<ITestInvocationListener> listeners)
+ throws DeviceNotAvailableException {
+ mListener = listeners.get(0);
+ if (mException != null) {
+ throw mException;
+ }
+ }
+
+ @Override
+ public void resume(ITestInvocationListener listener) throws DeviceNotAvailableException {
+ mListener = listener;
+ if (mException != null) {
+ throw mException;
+ }
+
+ }
+
+ public void clearListener() {
+ mListener = null;
+ }
+
+ public void setException(DeviceNotAvailableException e) {
+ mException = e;
+ }
}
diff --git a/tests/src/com/android/tradefed/testtype/testdefs/XmlDefsTestTest.java b/tests/src/com/android/tradefed/testtype/testdefs/XmlDefsTestTest.java
index a86a2a7..4274365 100644
--- a/tests/src/com/android/tradefed/testtype/testdefs/XmlDefsTestTest.java
+++ b/tests/src/com/android/tradefed/testtype/testdefs/XmlDefsTestTest.java
@@ -73,6 +73,19 @@
public void testRun() throws DeviceNotAvailableException {
mXmlTest.addRemoteFilePath(TEST_PATH);
+ injectMockXmlData();
+ mMockListener.testRunStarted(TEST_PKG, 0);
+ Capture<Map<String, String>> captureMetrics = new Capture<Map<String, String>>();
+ mMockListener.testRunEnded(EasyMock.anyLong(), EasyMock.capture(captureMetrics));
+ EasyMock.replay(mMockTestDevice, mMockListener);
+ mXmlTest.run(mMockListener);
+ assertEquals(mMockListener, mMockInstrumentationTest.getListener());
+ assertEquals(TEST_PKG, mMockInstrumentationTest.getPackageName());
+ assertEquals(TEST_COVERAGE_TARGET, captureMetrics.getValue().get(
+ XmlDefsTest.COVERAGE_TARGET_KEY));
+ }
+
+ private void injectMockXmlData() throws DeviceNotAvailableException {
// TODO: it would be nice to mock out the file objects, so this test wouldn't need to do
// IO
mMockTestDevice.pullFile(EasyMock.eq(TEST_PATH), (File)EasyMock.anyObject());
@@ -93,15 +106,31 @@
return false;
}
});
- mMockListener.testRunStarted(TEST_PKG, 0);
- Capture<Map<String, String>> captureMetrics = new Capture<Map<String, String>>();
- mMockListener.testRunEnded(EasyMock.anyLong(), EasyMock.capture(captureMetrics));
+ }
+
+ /**
+ * Test a run that was aborted then resumed
+ */
+ @SuppressWarnings("unchecked")
+ public void testRun_resume() throws DeviceNotAvailableException {
+ mXmlTest.addRemoteFilePath(TEST_PATH);
+
+ injectMockXmlData();
+ mMockInstrumentationTest.setException(new DeviceNotAvailableException());
EasyMock.replay(mMockTestDevice, mMockListener);
- mXmlTest.run(mMockListener);
+ try {
+ mXmlTest.run(mMockListener);
+ fail("DeviceNotAvailableException not thrown");
+ } catch (DeviceNotAvailableException e) {
+ // expected
+ }
+ // verify InstrumentationTest.run was called
assertEquals(mMockListener, mMockInstrumentationTest.getListener());
- assertEquals(TEST_PKG, mMockInstrumentationTest.getPackageName());
- assertEquals(TEST_COVERAGE_TARGET, captureMetrics.getValue().get(
- XmlDefsTest.COVERAGE_TARGET_KEY));
+ mMockInstrumentationTest.setException(null);
+ mMockInstrumentationTest.clearListener();
+ mXmlTest.resume(mMockListener);
+ // verify InstrumentationTest.resume was called
+ assertEquals(mMockListener, mMockInstrumentationTest.getListener());
}
/**