/*
* Copyright (C) 2009 The Android Open Source Project
*
* Licensed under the Eclipse Public License, Version 1.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.eclipse.org/org/documents/epl-v10.php
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
package com.android.ide.eclipse.adt.launch.junit.runtime;
import com.android.ddmlib.testrunner.ITestRunListener;
import com.android.ddmlib.testrunner.RemoteAndroidTestRunner;
import com.android.ddmlib.testrunner.TestIdentifier;
import com.android.ide.eclipse.adt.AdtPlugin;
import org.eclipse.jdt.internal.junit.runner.MessageIds;
import org.eclipse.jdt.internal.junit.runner.RemoteTestRunner;
import org.eclipse.jdt.internal.junit.runner.TestExecution;
import org.eclipse.jdt.internal.junit.runner.TestReferenceFailure;
/**
* Supports Eclipse JUnit execution of Android tests.
* <p/>
* Communicates back to a Eclipse JDT JUnit client via a socket connection.
*
* @see org.eclipse.jdt.internal.junit.runner.RemoteTestRunner for more details on the protocol
*/
@SuppressWarnings("restriction")
public class RemoteAdtTestRunner extends RemoteTestRunner {
private AndroidJUnitLaunchInfo mLaunchInfo;
private TestExecution mExecution;
/**
* Initialize the JDT JUnit test runner parameters from the {@code args}.
*
* @param args name-value pair of arguments to pass to parent JUnit runner.
* @param launchInfo the Android specific test launch info
*/
protected void init(String[] args, AndroidJUnitLaunchInfo launchInfo) {
defaultInit(args);
mLaunchInfo = launchInfo;
}
/**
* Runs a set of tests, and reports back results using parent class.
* <p/>
* JDT Unit expects to be sent data in the following sequence:
* <ol>
* <li>The total number of tests to be executed.</li>
* <li>The test 'tree' data about the tests to be executed, which is composed of the set of
* test class names, the number of tests in each class, and the names of each test in the
* class.</li>
* <li>The test execution result for each test method. Expects individual notifications of
* the test execution start, any failures, and the end of the test execution.</li>
* <li>The end of the test run, with its elapsed time.</li>
* </ol>
* <p/>
* In order to satisfy this, this method performs two actual Android instrumentation runs.
* The first is a 'log only' run that will collect the test tree data, without actually
* executing the tests, and send it back to JDT JUnit. The second is the actual test execution,
* whose results will be communicated back in real-time to JDT JUnit.
*
* @param testClassNames ignored - the AndroidJUnitLaunchInfo will be used to determine which
* tests to run.
* @param testName ignored
* @param execution used to report test progress
*/
@Override
public void runTests(String[] testClassNames, String testName, TestExecution execution) {
// hold onto this execution reference so it can be used to report test progress
mExecution = execution;
RemoteAndroidTestRunner runner = new RemoteAndroidTestRunner(mLaunchInfo.getAppPackage(),
mLaunchInfo.getRunner(), mLaunchInfo.getDevice());
if (mLaunchInfo.getTestClass() != null) {
if (mLaunchInfo.getTestMethod() != null) {
runner.setMethodName(mLaunchInfo.getTestClass(), mLaunchInfo.getTestMethod());
} else {
runner.setClassName(mLaunchInfo.getTestClass());
}
}
if (mLaunchInfo.getTestPackage() != null) {
runner.setTestPackageName(mLaunchInfo.getTestPackage());
}
// set log only to first collect test case info, so Eclipse has correct test case count/
// tree info
runner.setLogOnly(true);
TestCollector collector = new TestCollector();
runner.run(collector);
if (collector.getErrorMessage() != null) {
// error occurred during test collection.
reportError(collector.getErrorMessage());
// abort here
notifyTestRunEnded(0);
return;
}
notifyTestRunStarted(collector.getTestCaseCount());
collector.sendTrees(this);
// now do real execution
runner.setLogOnly(false);
if (mLaunchInfo.isDebugMode()) {
runner.setDebug(true);
}
runner.run(new TestRunListener());
}
/**
* Main entry method to run tests
*
* @param programArgs JDT JUnit program arguments to be processed by parent
* @param junitInfo the {@link AndroidJUnitLaunchInfo} containing info about this test ru
*/
public void runTests(String[] programArgs, AndroidJUnitLaunchInfo junitInfo) {
init(programArgs, junitInfo);
run();
}
/**
* Stop the current test run.
*/
public void terminate() {
stop();
}
@Override
protected void stop() {
if (mExecution != null) {
mExecution.stop();
}
}
private void notifyTestRunEnded(long elapsedTime) {
// copy from parent - not ideal, but method is private
sendMessage(MessageIds.TEST_RUN_END + elapsedTime);
flush();
//shutDown();
}
/**
* @param errorMessage
*/
private void reportError(String errorMessage) {
AdtPlugin.printErrorToConsole(mLaunchInfo.getProject(),
String.format("Test run failed: %s", errorMessage));
// is this needed?
//notifyTestRunStopped(-1);
}
/**
* TestRunListener that communicates results in real-time back to JDT JUnit
*/
private class TestRunListener implements ITestRunListener {
/* (non-Javadoc)
* @see com.android.ddmlib.testrunner.ITestRunListener#testEnded(com.android.ddmlib.testrunner.TestIdentifier)
*/
public void testEnded(TestIdentifier test) {
mExecution.getListener().notifyTestEnded(new TestCaseReference(test));
}
/* (non-Javadoc)
* @see com.android.ddmlib.testrunner.ITestRunListener#testFailed(com.android.ddmlib.testrunner.ITestRunListener.TestFailure, com.android.ddmlib.testrunner.TestIdentifier, java.lang.String)
*/
public void testFailed(TestFailure status, TestIdentifier test, String trace) {
String statusString;
if (status == TestFailure.ERROR) {
statusString = MessageIds.TEST_ERROR;
} else {
statusString = MessageIds.TEST_FAILED;
}
TestReferenceFailure failure =
new TestReferenceFailure(new TestCaseReference(test),
statusString, trace, null);
mExecution.getListener().notifyTestFailed(failure);
}
/* (non-Javadoc)
* @see com.android.ddmlib.testrunner.ITestRunListener#testRunEnded(long)
*/
public void testRunEnded(long elapsedTime) {
notifyTestRunEnded(elapsedTime);
AdtPlugin.printToConsole(mLaunchInfo.getProject(), "Test run complete");
}
/* (non-Javadoc)
* @see com.android.ddmlib.testrunner.ITestRunListener#testRunFailed(java.lang.String)
*/
public void testRunFailed(String errorMessage) {
reportError(errorMessage);
}
/* (non-Javadoc)
* @see com.android.ddmlib.testrunner.ITestRunListener#testRunStarted(int)
*/
public void testRunStarted(int testCount) {
// ignore
}
/* (non-Javadoc)
* @see com.android.ddmlib.testrunner.ITestRunListener#testRunStopped(long)
*/
public void testRunStopped(long elapsedTime) {
notifyTestRunStopped(elapsedTime);
AdtPlugin.printToConsole(mLaunchInfo.getProject(), "Test run stopped");
}
/* (non-Javadoc)
* @see com.android.ddmlib.testrunner.ITestRunListener#testStarted(com.android.ddmlib.testrunner.TestIdentifier)
*/
public void testStarted(TestIdentifier test) {
TestCaseReference testId = new TestCaseReference(test);
mExecution.getListener().notifyTestStarted(testId);
}
}
} |