diff options
Diffstat (limited to 'eclipse/plugins/com.android.ide.eclipse.adt/src/com/android/ide/eclipse/adt/internal/launch/junit/runtime')
6 files changed, 1032 insertions, 0 deletions
diff --git a/eclipse/plugins/com.android.ide.eclipse.adt/src/com/android/ide/eclipse/adt/internal/launch/junit/runtime/AndroidJUnitLaunchInfo.java b/eclipse/plugins/com.android.ide.eclipse.adt/src/com/android/ide/eclipse/adt/internal/launch/junit/runtime/AndroidJUnitLaunchInfo.java new file mode 100644 index 000000000..08702f4e2 --- /dev/null +++ b/eclipse/plugins/com.android.ide.eclipse.adt/src/com/android/ide/eclipse/adt/internal/launch/junit/runtime/AndroidJUnitLaunchInfo.java @@ -0,0 +1,148 @@ +/* + * 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.internal.launch.junit.runtime; + +import com.android.ddmlib.IDevice; +import com.android.ddmlib.testrunner.IRemoteAndroidTestRunner.TestSize; + +import org.eclipse.core.resources.IProject; +import org.eclipse.debug.core.ILaunch; + +import java.util.Collection; +import java.util.Collections; + +/** + * Contains info about Android JUnit launch + */ +public class AndroidJUnitLaunchInfo { + private final IProject mProject; + private final String mAppPackage; + private final String mRunner; + + private boolean mDebugMode = false; + private Collection<IDevice> mDevices = Collections.EMPTY_LIST; + private String mTestPackage = null; + private String mTestClass = null; + private String mTestMethod = null; + private ILaunch mLaunch = null; + private TestSize mTestSize = null; + + public AndroidJUnitLaunchInfo(IProject project, String appPackage, String runner) { + mProject = project; + mAppPackage = appPackage; + mRunner = runner; + } + + public IProject getProject() { + return mProject; + } + + public String getAppPackage() { + return mAppPackage; + } + + public String getRunner() { + return mRunner; + } + + public boolean isDebugMode() { + return mDebugMode; + } + + public void setDebugMode(boolean debugMode) { + mDebugMode = debugMode; + } + + public TestSize getTestSize() { + return mTestSize; + } + + public void setTestSize(TestSize size) { + mTestSize = size; + } + + public Collection<IDevice> getDevices() { + return mDevices; + } + + public void setDevices(Collection<IDevice> devices) { + mDevices = devices; + } + + /** + * Specify to run all tests within given package. + * + * @param testPackage fully qualified java package + */ + public void setTestPackage(String testPackage) { + mTestPackage = testPackage; + } + + /** + * Return the package of tests to run. + * + * @return fully qualified java package. <code>null</code> if not specified. + */ + public String getTestPackage() { + return mTestPackage; + } + + /** + * Sets the test class to run. + * + * @param testClass fully qualfied test class to run + * Expected format: x.y.x.testclass + */ + public void setTestClass(String testClass) { + mTestClass = testClass; + } + + /** + * Returns the test class to run. + * + * @return fully qualfied test class to run. + * <code>null</code> if not specified. + */ + public String getTestClass() { + return mTestClass; + } + + /** + * Sets the test method to run. testClass must also be set. + * + * @param testMethod test method to run + */ + public void setTestMethod(String testMethod) { + mTestMethod = testMethod; + } + + /** + * Returns the test method to run. + * + * @return test method to run. <code>null</code> if not specified. + */ + public String getTestMethod() { + return mTestMethod; + } + + public ILaunch getLaunch() { + return mLaunch; + } + + public void setLaunch(ILaunch launch) { + mLaunch = launch; + } +} diff --git a/eclipse/plugins/com.android.ide.eclipse.adt/src/com/android/ide/eclipse/adt/internal/launch/junit/runtime/AndroidTestReference.java b/eclipse/plugins/com.android.ide.eclipse.adt/src/com/android/ide/eclipse/adt/internal/launch/junit/runtime/AndroidTestReference.java new file mode 100644 index 000000000..ec3104d91 --- /dev/null +++ b/eclipse/plugins/com.android.ide.eclipse.adt/src/com/android/ide/eclipse/adt/internal/launch/junit/runtime/AndroidTestReference.java @@ -0,0 +1,65 @@ +/* + * 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.internal.launch.junit.runtime; + +import org.eclipse.jdt.internal.junit.runner.ITestIdentifier; +import org.eclipse.jdt.internal.junit.runner.ITestReference; +import org.eclipse.jdt.internal.junit.runner.TestExecution; + +/** + * Base implementation of the Eclipse {@link ITestReference} and {@link ITestIdentifier} interfaces + * for Android tests. + * <p/> + * Provides generic equality/hashcode services + */ +@SuppressWarnings("restriction") +abstract class AndroidTestReference implements ITestReference, ITestIdentifier { + + /** + * Gets the {@link ITestIdentifier} for this test reference. + */ + @Override + public ITestIdentifier getIdentifier() { + // this class serves as its own test identifier + return this; + } + + /** + * Not supported. + */ + @Override + public void run(TestExecution execution) { + throw new UnsupportedOperationException(); + } + + /** + * Compares {@link ITestIdentifier} using names + */ + @Override + public boolean equals(Object obj) { + if (obj instanceof ITestIdentifier) { + ITestIdentifier testid = (ITestIdentifier) obj; + return getName().equals(testid.getName()); + } + return false; + } + + @Override + public int hashCode() { + return getName().hashCode(); + } +} diff --git a/eclipse/plugins/com.android.ide.eclipse.adt/src/com/android/ide/eclipse/adt/internal/launch/junit/runtime/RemoteAdtTestRunner.java b/eclipse/plugins/com.android.ide.eclipse.adt/src/com/android/ide/eclipse/adt/internal/launch/junit/runtime/RemoteAdtTestRunner.java new file mode 100755 index 000000000..a48acd324 --- /dev/null +++ b/eclipse/plugins/com.android.ide.eclipse.adt/src/com/android/ide/eclipse/adt/internal/launch/junit/runtime/RemoteAdtTestRunner.java @@ -0,0 +1,524 @@ +/* + * 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.internal.launch.junit.runtime; + +import com.android.ddmlib.AdbCommandRejectedException; +import com.android.ddmlib.IDevice; +import com.android.ddmlib.ShellCommandUnresponsiveException; +import com.android.ddmlib.TimeoutException; +import com.android.ddmlib.testrunner.IRemoteAndroidTestRunner.TestSize; +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 com.android.ide.eclipse.adt.internal.launch.LaunchMessages; + +import org.eclipse.core.runtime.IProgressMonitor; +import org.eclipse.core.runtime.IStatus; +import org.eclipse.core.runtime.Status; +import org.eclipse.core.runtime.jobs.Job; +import org.eclipse.jdt.internal.junit.runner.IListensToTestExecutions; +import org.eclipse.jdt.internal.junit.runner.ITestReference; +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; + +import java.io.IOException; +import java.util.ArrayList; +import java.util.List; +import java.util.Map; + +/** + * 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 static final String DELAY_MSEC_KEY = "delay_msec"; + /** the delay between each test execution when in collecting test info */ + private static final String COLLECT_TEST_DELAY_MS = "15"; + + 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. + * + * The tests are run concurrently on all devices. The overall structure is as follows: + * <ol> + * <li> First, a separate job per device is run to collect test tree data. A per device + * {@link TestCollector} records information regarding the tests run on the device. + * </li> + * <li> Once all the devices have finished collecting the test tree data, the tree info is + * collected from all of them and passed to the Junit UI </li> + * <li> A job per device is again launched to do the actual test run. A per device + * {@link TestRunListener} notifies the shared {@link TestResultsNotifier} of test + * status. </li> + * <li> As tests complete, the test run listener updates the Junit UI </li> + * </ol> + * + * @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; + + List<IDevice> devices = new ArrayList<IDevice>(mLaunchInfo.getDevices()); + List<RemoteAndroidTestRunner> runners = + new ArrayList<RemoteAndroidTestRunner>(devices.size()); + + for (IDevice device : devices) { + RemoteAndroidTestRunner runner = new RemoteAndroidTestRunner( + mLaunchInfo.getAppPackage(), mLaunchInfo.getRunner(), device); + + 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()); + } + + TestSize size = mLaunchInfo.getTestSize(); + if (size != null) { + runner.setTestSize(size); + } + + runners.add(runner); + } + + // Launch all test info collector jobs + List<TestTreeCollectorJob> collectorJobs = + new ArrayList<TestTreeCollectorJob>(devices.size()); + List<TestCollector> perDeviceCollectors = new ArrayList<TestCollector>(devices.size()); + for (int i = 0; i < devices.size(); i++) { + RemoteAndroidTestRunner runner = runners.get(i); + String deviceName = devices.get(i).getName(); + TestCollector collector = new TestCollector(deviceName); + perDeviceCollectors.add(collector); + + TestTreeCollectorJob job = new TestTreeCollectorJob( + "Test Tree Collector for " + deviceName, + runner, mLaunchInfo.isDebugMode(), collector); + job.setPriority(Job.INTERACTIVE); + job.schedule(); + + collectorJobs.add(job); + } + + // wait for all test info collector jobs to complete + int totalTests = 0; + for (TestTreeCollectorJob job : collectorJobs) { + try { + job.join(); + } catch (InterruptedException e) { + endTestRunWithError(e.getMessage()); + return; + } + + if (!job.getResult().isOK()) { + endTestRunWithError(job.getResult().getMessage()); + return; + } + + TestCollector collector = job.getCollector(); + String err = collector.getErrorMessage(); + if (err != null) { + endTestRunWithError(err); + return; + } + + totalTests += collector.getTestCaseCount(); + } + + AdtPlugin.printToConsole(mLaunchInfo.getProject(), "Sending test information to Eclipse"); + notifyTestRunStarted(totalTests); + sendTestTrees(perDeviceCollectors); + + List<TestRunnerJob> instrumentationRunnerJobs = + new ArrayList<TestRunnerJob>(devices.size()); + + TestResultsNotifier notifier = new TestResultsNotifier(mExecution.getListener(), + devices.size()); + + // Spawn all instrumentation runner jobs + for (int i = 0; i < devices.size(); i++) { + RemoteAndroidTestRunner runner = runners.get(i); + String deviceName = devices.get(i).getName(); + TestRunListener testRunListener = new TestRunListener(deviceName, notifier); + InstrumentationRunJob job = new InstrumentationRunJob( + "Test Tree Collector for " + deviceName, + runner, mLaunchInfo.isDebugMode(), testRunListener); + job.setPriority(Job.INTERACTIVE); + job.schedule(); + + instrumentationRunnerJobs.add(job); + } + + // Wait for all jobs to complete + for (TestRunnerJob job : instrumentationRunnerJobs) { + try { + job.join(); + } catch (InterruptedException e) { + endTestRunWithError(e.getMessage()); + return; + } + + if (!job.getResult().isOK()) { + endTestRunWithError(job.getResult().getMessage()); + return; + } + } + } + + /** Sends info about the test tree to be executed (ie the suites and their enclosed tests) */ + private void sendTestTrees(List<TestCollector> perDeviceCollectors) { + for (TestCollector c : perDeviceCollectors) { + ITestReference ref = c.getDeviceSuite(); + ref.sendTree(this); + } + } + + private static abstract class TestRunnerJob extends Job { + private ITestRunListener mListener; + private RemoteAndroidTestRunner mRunner; + private boolean mIsDebug; + + public TestRunnerJob(String name, RemoteAndroidTestRunner runner, + boolean isDebug, ITestRunListener listener) { + super(name); + + mRunner = runner; + mIsDebug = isDebug; + mListener = listener; + } + + @Override + protected IStatus run(IProgressMonitor monitor) { + try { + setupRunner(); + mRunner.run(mListener); + } catch (TimeoutException e) { + return new Status(Status.ERROR, AdtPlugin.PLUGIN_ID, + LaunchMessages.RemoteAdtTestRunner_RunTimeoutException, + e); + } catch (IOException e) { + return new Status(Status.ERROR, AdtPlugin.PLUGIN_ID, + String.format(LaunchMessages.RemoteAdtTestRunner_RunIOException_s, + e.getMessage()), + e); + } catch (AdbCommandRejectedException e) { + return new Status(Status.ERROR, AdtPlugin.PLUGIN_ID, + String.format( + LaunchMessages.RemoteAdtTestRunner_RunAdbCommandRejectedException_s, + e.getMessage()), + e); + } catch (ShellCommandUnresponsiveException e) { + return new Status(Status.ERROR, AdtPlugin.PLUGIN_ID, + LaunchMessages.RemoteAdtTestRunner_RunTimeoutException, + e); + } + + return Status.OK_STATUS; + } + + public RemoteAndroidTestRunner getRunner() { + return mRunner; + } + + public boolean isDebug() { + return mIsDebug; + } + + public ITestRunListener getListener() { + return mListener; + } + + protected abstract void setupRunner(); + } + + private static class TestTreeCollectorJob extends TestRunnerJob { + public TestTreeCollectorJob(String name, RemoteAndroidTestRunner runner, boolean isDebug, + TestCollector listener) { + super(name, runner, isDebug, listener); + } + + @Override + protected void setupRunner() { + RemoteAndroidTestRunner runner = getRunner(); + + // set log only to just collect test case info, + // so Eclipse has correct test case count/tree info + runner.setLogOnly(true); + + // add a small delay between each test. Otherwise for large test suites framework may + // report Binder transaction failures + runner.addInstrumentationArg(DELAY_MSEC_KEY, COLLECT_TEST_DELAY_MS); + } + + public TestCollector getCollector() { + return (TestCollector) getListener(); + } + } + + private static class InstrumentationRunJob extends TestRunnerJob { + public InstrumentationRunJob(String name, RemoteAndroidTestRunner runner, boolean isDebug, + ITestRunListener listener) { + super(name, runner, isDebug, listener); + } + + @Override + protected void setupRunner() { + RemoteAndroidTestRunner runner = getRunner(); + runner.setLogOnly(false); + runner.removeInstrumentationArg(DELAY_MSEC_KEY); + if (isDebug()) { + runner.setDebug(true); + } + } + } + + /** + * 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(LaunchMessages.RemoteAdtTestRunner_RunFailedMsg_s, errorMessage)); + // is this needed? + //notifyTestRunStopped(-1); + } + + private void endTestRunWithError(String message) { + reportError(message); + notifyTestRunEnded(0); + } + + /** + * This class provides the interface to notify the JDT UI regarding the status of tests. + * When running tests on multiple devices, there is a {@link TestRunListener} that listens + * to results from each device. Rather than all such listeners directly notifying JDT + * from different threads, they all notify this class which notifies JDT. In addition, + * the {@link #testRunEnded(String, long)} method make sure that JDT is notified that the + * test run has completed only when tests on all devices have completed. + * */ + private class TestResultsNotifier { + private final IListensToTestExecutions mListener; + private final int mDeviceCount; + + private int mCompletedRuns; + private long mMaxElapsedTime; + + public TestResultsNotifier(IListensToTestExecutions listener, int nDevices) { + mListener = listener; + mDeviceCount = nDevices; + } + + public synchronized void testEnded(TestCaseReference ref) { + mListener.notifyTestEnded(ref); + } + + public synchronized void testFailed(TestReferenceFailure ref) { + mListener.notifyTestFailed(ref); + } + + public synchronized void testRunEnded(String mDeviceName, long elapsedTime) { + mCompletedRuns++; + + if (elapsedTime > mMaxElapsedTime) { + mMaxElapsedTime = elapsedTime; + } + + if (mCompletedRuns == mDeviceCount) { + notifyTestRunEnded(mMaxElapsedTime); + } + } + + public synchronized void testStarted(TestCaseReference testId) { + mListener.notifyTestStarted(testId); + } + } + + /** + * TestRunListener that communicates results in real-time back to JDT JUnit via the + * {@link TestResultsNotifier}. + * */ + private class TestRunListener implements ITestRunListener { + private final String mDeviceName; + private TestResultsNotifier mNotifier; + + /** + * Constructs a {@link ITestRunListener} that listens for test results on given device. + * @param deviceName device on which the tests are being run + * @param notifier notifier to inform of test status + */ + public TestRunListener(String deviceName, TestResultsNotifier notifier) { + mDeviceName = deviceName; + mNotifier = notifier; + } + + @Override + public void testEnded(TestIdentifier test, Map<String, String> ignoredTestMetrics) { + mNotifier.testEnded(new TestCaseReference(mDeviceName, test)); + } + + @Override + public void testFailed(TestIdentifier test, String trace) { + TestReferenceFailure failure = + new TestReferenceFailure(new TestCaseReference(mDeviceName, test), + MessageIds.TEST_FAILED, trace, null); + mNotifier.testFailed(failure); + } + + @Override + public void testAssumptionFailure(TestIdentifier test, String trace) { + TestReferenceFailure failure = + new TestReferenceFailure(new TestCaseReference(mDeviceName, test), + MessageIds.TEST_FAILED, trace, null); + mNotifier.testFailed(failure); + } + + @Override + public void testIgnored(TestIdentifier test) { + // TODO: implement me? + } + + @Override + public synchronized void testRunEnded(long elapsedTime, Map<String, String> runMetrics) { + mNotifier.testRunEnded(mDeviceName, elapsedTime); + AdtPlugin.printToConsole(mLaunchInfo.getProject(), + LaunchMessages.RemoteAdtTestRunner_RunCompleteMsg); + } + + @Override + public synchronized void testRunFailed(String errorMessage) { + reportError(errorMessage); + } + + @Override + public synchronized void testRunStarted(String runName, int testCount) { + // ignore + } + + @Override + public synchronized void testRunStopped(long elapsedTime) { + notifyTestRunStopped(elapsedTime); + AdtPlugin.printToConsole(mLaunchInfo.getProject(), + LaunchMessages.RemoteAdtTestRunner_RunStoppedMsg); + } + + @Override + public synchronized void testStarted(TestIdentifier test) { + TestCaseReference testId = new TestCaseReference(mDeviceName, test); + mNotifier.testStarted(testId); + } + } + + /** Override parent to get extra logs. */ + @Override + protected boolean connect() { + boolean result = super.connect(); + if (!result) { + AdtPlugin.printErrorToConsole(mLaunchInfo.getProject(), + "Connect to Eclipse test result listener failed"); + } + return result; + } + + /** Override parent to dump error message to console. */ + @Override + public void runFailed(String message, Exception exception) { + if (exception != null) { + AdtPlugin.logAndPrintError(exception, mLaunchInfo.getProject().getName(), + "Test launch failed: %s", message); + } else { + AdtPlugin.printErrorToConsole(mLaunchInfo.getProject(), "Test launch failed: %s", + message); + } + } +} diff --git a/eclipse/plugins/com.android.ide.eclipse.adt/src/com/android/ide/eclipse/adt/internal/launch/junit/runtime/TestCaseReference.java b/eclipse/plugins/com.android.ide.eclipse.adt/src/com/android/ide/eclipse/adt/internal/launch/junit/runtime/TestCaseReference.java new file mode 100644 index 000000000..e05e9b857 --- /dev/null +++ b/eclipse/plugins/com.android.ide.eclipse.adt/src/com/android/ide/eclipse/adt/internal/launch/junit/runtime/TestCaseReference.java @@ -0,0 +1,67 @@ +/* + * 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.internal.launch.junit.runtime; + +import com.android.ddmlib.testrunner.TestIdentifier; + +import org.eclipse.jdt.internal.junit.runner.IVisitsTestTrees; + +/** + * Reference for a single Android test method. + */ +@SuppressWarnings("restriction") +class TestCaseReference extends AndroidTestReference { + private final String mClassName; + private final String mTestName; + private final String mDeviceName; + + /** + * Creates a TestCaseReference from a {@link TestIdentifier} + * @param test + */ + TestCaseReference(String deviceName, TestIdentifier test) { + mDeviceName = deviceName; + mClassName = test.getClassName(); + mTestName = test.getTestName(); + } + + /** + * Returns a count of the number of test cases referenced. Is always one for this class. + */ + @Override + public int countTestCases() { + return 1; + } + + /** + * Sends test identifier and test count information for this test + * + * @param notified the {@link IVisitsTestTrees} to send test info to + */ + @Override + public void sendTree(IVisitsTestTrees notified) { + notified.visitTreeEntry(getIdentifier(), false, countTestCases()); + } + + /** + * Returns the identifier of this test, in a format expected by JDT JUnit + */ + @Override + public String getName() { + return String.format("%s (%s) [%s]", mTestName, mClassName, mDeviceName); + } +} diff --git a/eclipse/plugins/com.android.ide.eclipse.adt/src/com/android/ide/eclipse/adt/internal/launch/junit/runtime/TestCollector.java b/eclipse/plugins/com.android.ide.eclipse.adt/src/com/android/ide/eclipse/adt/internal/launch/junit/runtime/TestCollector.java new file mode 100644 index 000000000..32c5ef81e --- /dev/null +++ b/eclipse/plugins/com.android.ide.eclipse.adt/src/com/android/ide/eclipse/adt/internal/launch/junit/runtime/TestCollector.java @@ -0,0 +1,136 @@ +/* + * 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.internal.launch.junit.runtime; + +import com.android.ddmlib.testrunner.ITestRunListener; +import com.android.ddmlib.testrunner.TestIdentifier; + +import java.util.Map; + +/** + * Collects info about tests to be executed by listening to the results of an Android test run. + */ +class TestCollector implements ITestRunListener { + private final String mDeviceName; + private final TestSuiteReference mDeviceSuiteRef; + + private int mTotalTestCount; + /** test name to test suite reference map. */ + + private String mErrorMessage = null; + + TestCollector(String deviceName) { + mDeviceName = deviceName; + mDeviceSuiteRef = new TestSuiteReference(deviceName); + + mTotalTestCount = 0; + } + + @Override + public synchronized void testEnded(TestIdentifier test, Map<String, String> testMetrics) { + // ignore + } + + /* (non-Javadoc) + * @see com.android.ddmlib.testrunner.ITestRunListener#testFailed(com.android.ddmlib.testrunner.TestIdentifier, java.lang.String) + */ + @Override + public synchronized void testFailed(TestIdentifier test, String trace) { + // ignore - should be impossible since this is only collecting test information + } + + /* (non-Javadoc) + * @see com.android.ddmlib.testrunner.ITestRunListener#testIgnored(com.android.ddmlib.testrunner.TestIdentifier) + */ + @Override + public synchronized void testIgnored(TestIdentifier test) { + // ignore - should be impossible since this is only collecting test information + } + + /* (non-Javadoc) + * @see com.android.ddmlib.testrunner.ITestRunListener#testAssumptionFailure(com.android.ddmlib.testrunner.TestIdentifier, java.lang.String) + */ + @Override + public synchronized void testAssumptionFailure(TestIdentifier test, String trace) { + // ignore - should be impossible since this is only collecting test information + } + + /* (non-Javadoc) + * @see com.android.ddmlib.testrunner.ITestRunListener#testRunEnded(long, Map<String, String>) + */ + @Override + public synchronized void testRunEnded(long elapsedTime, Map<String, String> runMetrics) { + // ignore + } + + /* (non-Javadoc) + * @see com.android.ddmlib.testrunner.ITestRunListener#testRunFailed(java.lang.String) + */ + @Override + public synchronized void testRunFailed(String errorMessage) { + mErrorMessage = errorMessage; + } + + /* (non-Javadoc) + * @see com.android.ddmlib.testrunner.ITestRunListener#testRunStarted(int) + */ + @Override + public synchronized void testRunStarted(String ignoredRunName, int testCount) { + mTotalTestCount = testCount; + } + + /* (non-Javadoc) + * @see com.android.ddmlib.testrunner.ITestRunListener#testRunStopped(long) + */ + @Override + public synchronized void testRunStopped(long elapsedTime) { + // ignore + } + + /* (non-Javadoc) + * @see com.android.ddmlib.testrunner.ITestRunListener#testStarted(com.android.ddmlib.testrunner.TestIdentifier) + */ + @Override + public synchronized void testStarted(TestIdentifier test) { + TestSuiteReference suiteRef = mDeviceSuiteRef.getTestSuite(test.getClassName()); + if (suiteRef == null) { + suiteRef = new TestSuiteReference(test.getClassName()); + mDeviceSuiteRef.addTest(suiteRef); + } + + suiteRef.addTest(new TestCaseReference(mDeviceName, test)); + } + + /** + * Returns the total test count in the test run. + */ + public synchronized int getTestCaseCount() { + return mTotalTestCount; + } + + /** + * Returns the error message that was reported when collecting test info. + * Returns <code>null</code> if no error occurred. + */ + public synchronized String getErrorMessage() { + return mErrorMessage; + } + + public TestSuiteReference getDeviceSuite() { + return mDeviceSuiteRef; + } +} diff --git a/eclipse/plugins/com.android.ide.eclipse.adt/src/com/android/ide/eclipse/adt/internal/launch/junit/runtime/TestSuiteReference.java b/eclipse/plugins/com.android.ide.eclipse.adt/src/com/android/ide/eclipse/adt/internal/launch/junit/runtime/TestSuiteReference.java new file mode 100644 index 000000000..dcc9f10ec --- /dev/null +++ b/eclipse/plugins/com.android.ide.eclipse.adt/src/com/android/ide/eclipse/adt/internal/launch/junit/runtime/TestSuiteReference.java @@ -0,0 +1,92 @@ +/* + * 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.internal.launch.junit.runtime; + +import org.eclipse.jdt.internal.junit.runner.IVisitsTestTrees; + +import java.util.ArrayList; +import java.util.List; + +/** + * Reference for an Android test suite aka class. + */ +@SuppressWarnings("restriction") +class TestSuiteReference extends AndroidTestReference { + + private final String mClassName; + private List<AndroidTestReference> mTests; + + /** + * Creates a TestSuiteReference + * + * @param className the fully qualified name of the test class + */ + TestSuiteReference(String className) { + mClassName = className; + mTests = new ArrayList<AndroidTestReference>(); + } + + /** + * Returns a count of the number of test cases included in this suite. + */ + @Override + public int countTestCases() { + return mTests.size(); + } + + /** + * Sends test identifier and test count information for this test class, and all its included + * test methods. + * + * @param notified the {@link IVisitsTestTrees} to send test info too + */ + @Override + public void sendTree(IVisitsTestTrees notified) { + notified.visitTreeEntry(getIdentifier(), true, countTestCases()); + for (AndroidTestReference ref: mTests) { + ref.sendTree(notified); + } + } + + /** + * Return the name of this test class. + */ + @Override + public String getName() { + return mClassName; + } + + /** + * Adds a test method to this suite. + * + * @param testRef the {@link TestCaseReference} to add + */ + void addTest(AndroidTestReference testRef) { + mTests.add(testRef); + } + + /** Returns the test suite of given name, null if no such test suite exists */ + public TestSuiteReference getTestSuite(String name) { + for (AndroidTestReference ref: mTests) { + if (ref instanceof TestSuiteReference && ref.getName().equals(name)) { + return (TestSuiteReference) ref; + } + } + + return null; + } +} |