Call tearDown before reportLogs + invocationEnded.
Bug 8300094
Change-Id: Idd671820f77468b45e97518d4d3a6a84352a18be
diff --git a/src/com/android/tradefed/invoker/ITestInvocation.java b/src/com/android/tradefed/invoker/ITestInvocation.java
index 210f6bc..68c3098 100644
--- a/src/com/android/tradefed/invoker/ITestInvocation.java
+++ b/src/com/android/tradefed/invoker/ITestInvocation.java
@@ -34,8 +34,9 @@
* @param rescheduler the {@link IRescheduler}, for rescheduling portions of the invocation for
* execution on another resource(s)
* @throws DeviceNotAvailableException if communication with device was lost
+ * @throws Throwable
*/
public void invoke(ITestDevice device, IConfiguration config, IRescheduler rescheduler)
- throws DeviceNotAvailableException;
+ throws DeviceNotAvailableException, Throwable;
}
diff --git a/src/com/android/tradefed/invoker/TestInvocation.java b/src/com/android/tradefed/invoker/TestInvocation.java
index 7f1ddfd..cbed078 100644
--- a/src/com/android/tradefed/invoker/TestInvocation.java
+++ b/src/com/android/tradefed/invoker/TestInvocation.java
@@ -103,7 +103,7 @@
*/
@Override
public void invoke(ITestDevice device, IConfiguration config, IRescheduler rescheduler)
- throws DeviceNotAvailableException {
+ throws DeviceNotAvailableException, Throwable {
try {
mStatus = "fetching build";
config.getLogOutput().init();
@@ -266,37 +266,29 @@
* @param config the {@link IConfiguration}
* @param device the {@link ITestDevice} to use. May be <code>null</code>
* @param info the {@link IBuildInfo}
- *
- * @throws DeviceNotAvailableException
- * @throws IOException if log could not be created
*/
private void performInvocation(IConfiguration config, ITestDevice device, IBuildInfo info,
- IRescheduler rescheduler) throws DeviceNotAvailableException, IOException {
+ IRescheduler rescheduler) throws Throwable {
boolean resumed = false;
long startTime = System.currentTimeMillis();
long elapsedTime = -1;
- Throwable exception = null;
info.setDeviceSerial(device.getSerialNumber());
startInvocation(config, device, info);
try {
device.setOptions(config.getDeviceOptions());
- for (ITargetPreparer preparer : config.getTargetPreparers()) {
- preparer.setUp(device, info);
- }
- runTests(device, info, config, rescheduler);
+
+ prepareAndRun(config, device, info, rescheduler);
} catch (BuildError e) {
CLog.w("Build %s failed on device %s. Reason: %s", info.getBuildId(),
device.getSerialNumber(), e.toString());
takeBugreport(device, config.getTestInvocationListeners());
reportFailure(e, config.getTestInvocationListeners(), config, info, rescheduler);
- exception = e;
} catch (TargetSetupError e) {
CLog.e("Caught exception while running invocation");
CLog.e(e);
reportFailure(e, config.getTestInvocationListeners(), config, info, rescheduler);
- exception = e;
} catch (DeviceNotAvailableException e) {
// log a warning here so its captured before reportLogs is called
CLog.w("Invocation did not complete due to device %s becoming not available. " +
@@ -307,18 +299,15 @@
} else {
CLog.i("Rescheduled failed invocation for resume");
}
- exception = e;
throw e;
} catch (RuntimeException e) {
// log a warning here so its captured before reportLogs is called
CLog.w("Unexpected exception when running invocation: %s", e.toString());
reportFailure(e, config.getTestInvocationListeners(), config, info, rescheduler);
- exception = e;
throw e;
} catch (AssertionError e) {
CLog.w("Caught AssertionError while running invocation: ", e.toString());
reportFailure(e, config.getTestInvocationListeners(), config, info, rescheduler);
- exception = e;
} finally {
mStatus = "done running tests";
try {
@@ -329,16 +318,6 @@
config.getTestInvocationListeners(), elapsedTime);
}
- for (ITargetPreparer preparer : config.getTargetPreparers()) {
- // Note: adjusted indentation below for legibility. If preparer is an
- // ITargetCleaner and we didn't hit DeviceNotAvailableException, then...
- if (preparer instanceof ITargetCleaner &&
- !(exception != null &&
- exception instanceof DeviceNotAvailableException)) {
- ITargetCleaner cleaner = (ITargetCleaner) preparer;
- cleaner.tearDown(device, info, exception);
- }
- }
} finally {
config.getBuildProvider().cleanUp(info);
}
@@ -346,6 +325,54 @@
}
/**
+ * Do setup, run the tests, then call tearDown
+ */
+ private void prepareAndRun(IConfiguration config, ITestDevice device, IBuildInfo info,
+ IRescheduler rescheduler) throws Throwable {
+ // use the JUnit3 logic for handling exceptions when running tests
+ Throwable exception = null;
+
+ try {
+ doSetup(config, device, info);
+ runTests(device, info, config, rescheduler);
+ } catch (Throwable running) {
+ exception = running;
+ } finally {
+ try {
+ doTeardown(config, device, info, exception);
+ } catch (Throwable tearingDown) {
+ if (exception == null) {
+ exception = tearingDown;
+ }
+ }
+ }
+ if (exception != null) {
+ throw exception;
+ }
+ }
+
+ private void doSetup(IConfiguration config, ITestDevice device, IBuildInfo info)
+ throws TargetSetupError, BuildError, DeviceNotAvailableException {
+ for (ITargetPreparer preparer : config.getTargetPreparers()) {
+ preparer.setUp(device, info);
+ }
+ }
+
+ private void doTeardown(IConfiguration config, ITestDevice device, IBuildInfo info,
+ Throwable exception) throws DeviceNotAvailableException {
+ for (ITargetPreparer preparer : config.getTargetPreparers()) {
+ // Note: adjusted indentation below for legibility. If preparer is an
+ // ITargetCleaner and we didn't hit DeviceNotAvailableException, then...
+ if (preparer instanceof ITargetCleaner &&
+ !(exception != null &&
+ exception instanceof DeviceNotAvailableException)) {
+ ITargetCleaner cleaner = (ITargetCleaner) preparer;
+ cleaner.tearDown(device, info, exception);
+ }
+ }
+ }
+
+ /**
* Starts the invocation.
* <p/>
* Starts logging, and informs listeners that invocation has been started.
diff --git a/tests/src/com/android/tradefed/command/CommandSchedulerTest.java b/tests/src/com/android/tradefed/command/CommandSchedulerTest.java
index 4a47d19..d58f722 100644
--- a/tests/src/com/android/tradefed/command/CommandSchedulerTest.java
+++ b/tests/src/com/android/tradefed/command/CommandSchedulerTest.java
@@ -146,7 +146,7 @@
/**
* Test {@link CommandScheduler#run()} when one config has been added
*/
- public void testRun_oneConfig() throws Exception {
+ public void testRun_oneConfig() throws Throwable {
String[] args = new String[] {};
mMockManager.setNumDevices(2);
setCreateConfigExpectations(args, 1);
@@ -163,7 +163,7 @@
/**
* Test {@link CommandScheduler#run()} when one config has been added in dry-run mode
*/
- public void testRun_dryRun() throws Exception {
+ public void testRun_dryRun() throws Throwable {
String[] dryRunArgs = new String[] {"--dry-run"};
mCommandOptions.setDryRunMode(true);
mMockManager.setNumDevices(2);
@@ -192,7 +192,7 @@
*
* @param times
*/
- private void setExpectedInvokeCalls(int times) throws DeviceNotAvailableException {
+ private void setExpectedInvokeCalls(int times) throws Throwable {
mMockInvocation.invoke((ITestDevice)EasyMock.anyObject(),
(IConfiguration)EasyMock.anyObject(), (IRescheduler)EasyMock.anyObject());
EasyMock.expectLastCall().times(times);
@@ -204,7 +204,7 @@
*
* @param times
*/
- private Object waitForExpectedInvokeCalls(final int times) throws DeviceNotAvailableException {
+ private Object waitForExpectedInvokeCalls(final int times) throws Throwable {
IAnswer<Object> blockResult = new IAnswer<Object>() {
private int mCalls = 0;
@Override
@@ -228,7 +228,7 @@
/**
* Test {@link CommandScheduler#run()} when one config has been added in a loop
*/
- public void testRun_oneConfigLoop() throws Exception {
+ public void testRun_oneConfigLoop() throws Throwable {
String[] args = new String[] {};
// track if exception occurs on scheduler thread
UncaughtExceptionHandler defaultHandler = Thread.getDefaultUncaughtExceptionHandler();
@@ -274,7 +274,7 @@
/**
* Verify that scheduler goes into shutdown mode when a {@link FatalHostError} is thrown.
*/
- public void testRun_fatalError() throws Exception {
+ public void testRun_fatalError() throws Throwable {
mMockInvocation.invoke((ITestDevice)EasyMock.anyObject(),
(IConfiguration)EasyMock.anyObject(), (IRescheduler)EasyMock.anyObject());
EasyMock.expectLastCall().andThrow(new FatalHostError("error"));
@@ -295,7 +295,7 @@
* <p/>
* Adds two configs to run, and verify they both run on one device
*/
- public void testRun_configSerial() throws Exception {
+ public void testRun_configSerial() throws Throwable {
String[] args = new String[] {};
mMockManager.setNumDevices(2);
setCreateConfigExpectations(args, 2);
@@ -322,7 +322,7 @@
* <p/>
* Adds two configs to run, and verify they both run on the other device
*/
- public void testRun_configExcludeSerial() throws Exception {
+ public void testRun_configExcludeSerial() throws Throwable {
String[] args = new String[] {};
mMockManager.setNumDevices(2);
setCreateConfigExpectations(args, 2);
@@ -348,7 +348,7 @@
* Test {@link CommandScheduler#run()} when one config has been rescheduled
*/
@SuppressWarnings("unchecked")
- public void testRun_rescheduled() throws Exception {
+ public void testRun_rescheduled() throws Throwable {
String[] args = new String[] {};
mMockManager.setNumDevices(2);
setCreateConfigExpectations(args, 1);
diff --git a/tests/src/com/android/tradefed/invoker/TestInvocationTest.java b/tests/src/com/android/tradefed/invoker/TestInvocationTest.java
index aebcaec..d47d426 100644
--- a/tests/src/com/android/tradefed/invoker/TestInvocationTest.java
+++ b/tests/src/com/android/tradefed/invoker/TestInvocationTest.java
@@ -21,7 +21,6 @@
import com.android.tradefed.build.IBuildProvider;
import com.android.tradefed.command.FatalHostError;
import com.android.tradefed.config.Configuration;
-import com.android.tradefed.config.ConfigurationException;
import com.android.tradefed.config.IConfiguration;
import com.android.tradefed.device.DeviceNotAvailableException;
import com.android.tradefed.device.IDeviceRecovery;
@@ -37,12 +36,14 @@
import com.android.tradefed.result.LogDataType;
import com.android.tradefed.result.TestSummary;
import com.android.tradefed.targetprep.BuildError;
+import com.android.tradefed.targetprep.ITargetCleaner;
import com.android.tradefed.targetprep.ITargetPreparer;
import com.android.tradefed.testtype.IDeviceTest;
import com.android.tradefed.testtype.IRemoteTest;
import com.android.tradefed.testtype.IResumableTest;
import com.android.tradefed.testtype.IRetriableTest;
+import junit.framework.AssertionFailedError;
import junit.framework.Test;
import junit.framework.TestCase;
@@ -141,7 +142,7 @@
* <p/>
* Verifies that all external interfaces get notified as expected.
*/
- public void testInvoke_RemoteTest() throws Exception {
+ public void testInvoke_RemoteTest() throws Throwable {
IRemoteTest test = EasyMock.createMock(IRemoteTest.class);
setupMockSuccessListeners();
@@ -160,7 +161,7 @@
* TODO: For results_reporters to work as both ITestInvocationListener and ITestSummaryListener,
* TODO: this test _must_ pass. Currently, it does not, so that mode of usage is not supported.
*/
- public void DISABLED_testInvoke_twoSummary() throws Exception {
+ public void DISABLED_testInvoke_twoSummary() throws Throwable {
IRemoteTest test = EasyMock.createMock(IRemoteTest.class);
setupMockSuccessListeners();
@@ -177,8 +178,7 @@
* <p/>
* An invocation will be started in this scenario.
*/
- public void testInvoke_buildFailed() throws BuildRetrievalError, ConfigurationException,
- DeviceNotAvailableException {
+ public void testInvoke_buildFailed() throws Throwable {
BuildRetrievalError exception = new BuildRetrievalError("error", null, mMockBuildInfo);
EasyMock.expect(mMockBuildProvider.getBuild()).andThrow(exception);
setupMockFailureListeners(exception);
@@ -196,8 +196,7 @@
/**
* Test the invoke scenario where there is no build to test.
*/
- public void testInvoke_noBuild() throws BuildRetrievalError, ConfigurationException,
- DeviceNotAvailableException {
+ public void testInvoke_noBuild() throws Throwable {
EasyMock.expect(mMockBuildProvider.getBuild()).andReturn(null);
IRemoteTest test = EasyMock.createMock(IRemoteTest.class);
mStubConfiguration.setTest(test);
@@ -210,8 +209,7 @@
/**
* Test the invoke scenario where there is no build to test for a {@link IRetriableTest}.
*/
- public void testInvoke_noBuildRetry() throws BuildRetrievalError, ConfigurationException,
- DeviceNotAvailableException {
+ public void testInvoke_noBuildRetry() throws Throwable {
EasyMock.expect(mMockBuildProvider.getBuild()).andReturn(null);
IRetriableTest test = EasyMock.createMock(IRetriableTest.class);
@@ -234,7 +232,7 @@
* Test the {@link TestInvocation#invoke(IDevice, IConfiguration)} scenario where the
* test is a {@link IDeviceTest}
*/
- public void testInvoke_deviceTest() throws Exception {
+ public void testInvoke_deviceTest() throws Throwable {
DeviceConfigTest mockDeviceTest = EasyMock.createMock(DeviceConfigTest.class);
mStubConfiguration.setTest(mockDeviceTest);
mockDeviceTest.setDevice(mMockDevice);
@@ -251,7 +249,7 @@
*
* @throws Exception if unexpected error occurs
*/
- public void testInvoke_testFail() throws Exception {
+ public void testInvoke_testFail() throws Throwable {
IllegalArgumentException exception = new IllegalArgumentException();
IRemoteTest test = EasyMock.createMock(IRemoteTest.class);
test.run((ITestInvocationListener)EasyMock.anyObject());
@@ -274,7 +272,7 @@
*
* @throws Exception if unexpected error occurs
*/
- public void testInvoke_fatalError() throws Exception {
+ public void testInvoke_fatalError() throws Throwable {
FatalHostError exception = new FatalHostError("error");
IRemoteTest test = EasyMock.createMock(IRemoteTest.class);
test.run((ITestInvocationListener)EasyMock.anyObject());
@@ -297,7 +295,7 @@
*
* @throws Exception if unexpected error occurs
*/
- public void testInvoke_deviceNotAvail() throws Exception {
+ public void testInvoke_deviceNotAvail() throws Throwable {
DeviceNotAvailableException exception = new DeviceNotAvailableException();
IRemoteTest test = EasyMock.createMock(IRemoteTest.class);
test.run((ITestInvocationListener)EasyMock.anyObject());
@@ -320,7 +318,7 @@
*
* @throws Exception if unexpected error occurs
*/
- public void testInvoke_buildError() throws Exception {
+ public void testInvoke_buildError() throws Throwable {
BuildError exception = new BuildError("error");
IRemoteTest test = EasyMock.createMock(IRemoteTest.class);
mStubConfiguration.setTest(test);
@@ -351,7 +349,7 @@
*
* @throws Exception if unexpected error occurs
*/
- public void testInvoke_resume() throws Exception {
+ public void testInvoke_resume() throws Throwable {
IResumableTest resumableTest = EasyMock.createMock(IResumableTest.class);
mStubConfiguration.setTest(resumableTest);
ITestInvocationListener resumeListener = EasyMock.createStrictMock(
@@ -427,7 +425,7 @@
*
* @throws Exception if unexpected error occurs
*/
- public void testInvoke_retry() throws Exception {
+ public void testInvoke_retry() throws Throwable {
AssertionError exception = new AssertionError();
IRetriableTest test = EasyMock.createMock(IRetriableTest.class);
test.run((ITestInvocationListener)EasyMock.anyObject());
@@ -448,11 +446,87 @@
}
/**
+ * Test the {@link TestInvocation#invoke(IDevice, IConfiguration)} scenario when a
+ * {@link ITargetCleaner} is part of the config
+ */
+ public void testInvoke_tearDown() throws Throwable {
+ IRemoteTest test = EasyMock.createNiceMock(IRemoteTest.class);
+ ITargetCleaner mockCleaner = EasyMock.createMock(ITargetCleaner.class);
+ mockCleaner.setUp(mMockDevice, mMockBuildInfo);
+ mockCleaner.tearDown(mMockDevice, mMockBuildInfo, null);
+ mStubConfiguration.getTargetPreparers().add(mockCleaner);
+ setupMockSuccessListeners();
+ setupNormalInvoke(test);
+ EasyMock.replay(mockCleaner);
+ mTestInvocation.invoke(mMockDevice, mStubConfiguration, new StubRescheduler());
+ verifyMocks(mockCleaner);
+ verifySummaryListener();
+ }
+
+ /**
+ * Test the {@link TestInvocation#invoke(IDevice, IConfiguration)} scenario when a
+ * {@link ITargetCleaner} is part of the config, and the test throws a
+ * {@link DeviceNotAvailableException}.
+ */
+ public void testInvoke_tearDown_deviceNotAvail() throws Throwable {
+ DeviceNotAvailableException exception = new DeviceNotAvailableException();
+ IRemoteTest test = EasyMock.createMock(IRemoteTest.class);
+ test.run((ITestInvocationListener)EasyMock.anyObject());
+ EasyMock.expectLastCall().andThrow(exception);
+ ITargetCleaner mockCleaner = EasyMock.createMock(ITargetCleaner.class);
+ mockCleaner.setUp(mMockDevice, mMockBuildInfo);
+ // tearDown should NOT be called
+ // mockCleaner.tearDown(mMockDevice, mMockBuildInfo, null);
+ mStubConfiguration.getTargetPreparers().add(mockCleaner);
+ setupMockFailureListeners(exception);
+ mMockBuildProvider.buildNotTested(mMockBuildInfo);
+ setupNormalInvoke(test);
+ EasyMock.replay(mockCleaner);
+ try {
+ mTestInvocation.invoke(mMockDevice, mStubConfiguration, new StubRescheduler());
+ fail("DeviceNotAvailableException not thrown");
+ } catch (DeviceNotAvailableException e) {
+ // expected
+ }
+ verifyMocks(mockCleaner);
+ verifySummaryListener();
+ }
+
+ /**
+ * Test the {@link TestInvocation#invoke(IDevice, IConfiguration)} scenario when a
+ * {@link ITargetCleaner} is part of the config, and the test throws a
+ * {@link RuntimeException}.
+ */
+ public void testInvoke_tearDown_runtime() throws Throwable {
+ RuntimeException exception = new RuntimeException();
+ IRemoteTest test = EasyMock.createMock(IRemoteTest.class);
+ test.run((ITestInvocationListener)EasyMock.anyObject());
+ EasyMock.expectLastCall().andThrow(exception);
+ ITargetCleaner mockCleaner = EasyMock.createMock(ITargetCleaner.class);
+ mockCleaner.setUp(mMockDevice, mMockBuildInfo);
+ // tearDown should be called
+ mockCleaner.tearDown(mMockDevice, mMockBuildInfo, exception);
+ mStubConfiguration.getTargetPreparers().add(mockCleaner);
+ setupMockFailureListeners(exception);
+ mMockBuildProvider.buildNotTested(mMockBuildInfo);
+ setupNormalInvoke(test);
+ EasyMock.replay(mockCleaner);
+ try {
+ mTestInvocation.invoke(mMockDevice, mStubConfiguration, new StubRescheduler());
+ fail("RuntimeException not thrown");
+ } catch (RuntimeException e) {
+ // expected
+ }
+ verifyMocks(mockCleaner);
+ verifySummaryListener();
+ }
+
+ /**
* Set up expected conditions for normal run up to the part where tests are run.
*
* @param test the {@link Test} to use.
*/
- private void setupNormalInvoke(IRemoteTest test) throws Exception {
+ private void setupNormalInvoke(IRemoteTest test) throws Throwable {
mStubConfiguration.setTest(test);
EasyMock.expect(mMockBuildProvider.getBuild()).andReturn(mMockBuildInfo);
mMockDevice.setOptions((TestDeviceOptions)EasyMock.anyObject());
@@ -504,15 +578,16 @@
mMockSummaryListener.invocationFailed(EasyMock.eq(throwable));
}
+
// testLog (mMockTestListener)
mMockTestListener.testLog(EasyMock.eq(TestInvocation.DEVICE_LOG_NAME),
EasyMock.eq(LogDataType.TEXT), (InputStreamSource)EasyMock.anyObject());
+ // testLog (mMockSummaryListener)
+ mMockSummaryListener.testLog(EasyMock.eq(TestInvocation.DEVICE_LOG_NAME),
+ EasyMock.eq(LogDataType.TEXT), (InputStreamSource)EasyMock.anyObject());
mMockTestListener.testLog(EasyMock.eq(TestInvocation.TRADEFED_LOG_NAME),
EasyMock.eq(LogDataType.TEXT), (InputStreamSource)EasyMock.anyObject());
- // testLog (mMockSummaryListener)
- mMockSummaryListener.testLog(EasyMock.eq(TestInvocation.DEVICE_LOG_NAME),
- EasyMock.eq(LogDataType.TEXT), (InputStreamSource)EasyMock.anyObject());
mMockSummaryListener.testLog(EasyMock.eq(TestInvocation.TRADEFED_LOG_NAME),
EasyMock.eq(LogDataType.TEXT), (InputStreamSource)EasyMock.anyObject());
@@ -543,20 +618,26 @@
/**
* Verify all mock objects received expected calls
*/
- private void verifyMocks(IRemoteTest mockTest) {
+ private void verifyMocks(Object... mocks) {
// note: intentionally exclude configuration from verification - don't care
// what methods are called
- EasyMock.verify(mockTest, mMockTestListener, mMockSummaryListener, mMockPreparer,
+ EasyMock.verify(mMockTestListener, mMockSummaryListener, mMockPreparer,
mMockBuildProvider, mMockBuildInfo, mMockLogRegistry, mMockLogger);
+ if (mocks.length > 0) {
+ EasyMock.verify(mocks);
+ }
}
/**
* Switch all mock objects into replay mode.
*/
- private void replayMocks(IRemoteTest mockTest) {
- EasyMock.replay(mockTest, mMockTestListener, mMockSummaryListener,
+ private void replayMocks(Object... mocks) {
+ EasyMock.replay(mMockTestListener, mMockSummaryListener,
mMockPreparer, mMockBuildProvider, mMockLogger, mMockDevice, mMockBuildInfo,
mMockLogRegistry);
+ if (mocks.length > 0) {
+ EasyMock.replay(mocks);
+ }
}
/**