1 /* 2 * Copyright (C) 2009 The Android Open Source Project 3 * 4 * Licensed under the Eclipse Public License, Version 1.0 (the "License"); 5 * you may not use this file except in compliance with the License. 6 * You may obtain a copy of the License at 7 * 8 * http://www.eclipse.org/org/documents/epl-v10.php 9 * 10 * Unless required by applicable law or agreed to in writing, software 11 * distributed under the License is distributed on an "AS IS" BASIS, 12 * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 13 * See the License for the specific language governing permissions and 14 * limitations under the License. 15 */ 16 17 package com.android.ide.eclipse.adt.internal.launch.junit.runtime; 18 19 import com.android.ddmlib.AdbCommandRejectedException; 20 import com.android.ddmlib.ShellCommandUnresponsiveException; 21 import com.android.ddmlib.TimeoutException; 22 import com.android.ddmlib.testrunner.ITestRunListener; 23 import com.android.ddmlib.testrunner.RemoteAndroidTestRunner; 24 import com.android.ddmlib.testrunner.TestIdentifier; 25 import com.android.ide.eclipse.adt.AdtPlugin; 26 import com.android.ide.eclipse.adt.internal.launch.LaunchMessages; 27 28 import org.eclipse.jdt.internal.junit.runner.MessageIds; 29 import org.eclipse.jdt.internal.junit.runner.RemoteTestRunner; 30 import org.eclipse.jdt.internal.junit.runner.TestExecution; 31 import org.eclipse.jdt.internal.junit.runner.TestReferenceFailure; 32 33 import java.io.IOException; 34 import java.util.Map; 35 36 /** 37 * Supports Eclipse JUnit execution of Android tests. 38 * <p/> 39 * Communicates back to a Eclipse JDT JUnit client via a socket connection. 40 * 41 * @see org.eclipse.jdt.internal.junit.runner.RemoteTestRunner for more details on the protocol 42 */ 43 @SuppressWarnings("restriction") 44 public class RemoteAdtTestRunner extends RemoteTestRunner { 45 46 private static final String DELAY_MSEC_KEY = "delay_msec"; 47 /** the delay between each test execution when in collecting test info */ 48 private static final String COLLECT_TEST_DELAY_MS = "15"; 49 50 private AndroidJUnitLaunchInfo mLaunchInfo; 51 private TestExecution mExecution; 52 53 /** 54 * Initialize the JDT JUnit test runner parameters from the {@code args}. 55 * 56 * @param args name-value pair of arguments to pass to parent JUnit runner. 57 * @param launchInfo the Android specific test launch info 58 */ init(String[] args, AndroidJUnitLaunchInfo launchInfo)59 protected void init(String[] args, AndroidJUnitLaunchInfo launchInfo) { 60 defaultInit(args); 61 mLaunchInfo = launchInfo; 62 } 63 64 /** 65 * Runs a set of tests, and reports back results using parent class. 66 * <p/> 67 * JDT Unit expects to be sent data in the following sequence: 68 * <ol> 69 * <li>The total number of tests to be executed.</li> 70 * <li>The test 'tree' data about the tests to be executed, which is composed of the set of 71 * test class names, the number of tests in each class, and the names of each test in the 72 * class.</li> 73 * <li>The test execution result for each test method. Expects individual notifications of 74 * the test execution start, any failures, and the end of the test execution.</li> 75 * <li>The end of the test run, with its elapsed time.</li> 76 * </ol> 77 * <p/> 78 * In order to satisfy this, this method performs two actual Android instrumentation runs. 79 * The first is a 'log only' run that will collect the test tree data, without actually 80 * executing the tests, and send it back to JDT JUnit. The second is the actual test execution, 81 * whose results will be communicated back in real-time to JDT JUnit. 82 * 83 * @param testClassNames ignored - the AndroidJUnitLaunchInfo will be used to determine which 84 * tests to run. 85 * @param testName ignored 86 * @param execution used to report test progress 87 */ 88 @Override runTests(String[] testClassNames, String testName, TestExecution execution)89 public void runTests(String[] testClassNames, String testName, TestExecution execution) { 90 // hold onto this execution reference so it can be used to report test progress 91 mExecution = execution; 92 93 RemoteAndroidTestRunner runner = new RemoteAndroidTestRunner(mLaunchInfo.getAppPackage(), 94 mLaunchInfo.getRunner(), mLaunchInfo.getDevice()); 95 96 if (mLaunchInfo.getTestClass() != null) { 97 if (mLaunchInfo.getTestMethod() != null) { 98 runner.setMethodName(mLaunchInfo.getTestClass(), mLaunchInfo.getTestMethod()); 99 } else { 100 runner.setClassName(mLaunchInfo.getTestClass()); 101 } 102 } 103 104 if (mLaunchInfo.getTestPackage() != null) { 105 runner.setTestPackageName(mLaunchInfo.getTestPackage()); 106 } 107 108 // set log only to first collect test case info, so Eclipse has correct test case count/ 109 // tree info 110 runner.setLogOnly(true); 111 // add a small delay between each test. Otherwise for large test suites framework may 112 // report Binder transaction failures 113 runner.addInstrumentationArg(DELAY_MSEC_KEY, COLLECT_TEST_DELAY_MS); 114 TestCollector collector = new TestCollector(); 115 try { 116 AdtPlugin.printToConsole(mLaunchInfo.getProject(), "Collecting test information"); 117 118 runner.run(collector); 119 if (collector.getErrorMessage() != null) { 120 // error occurred during test collection. 121 reportError(collector.getErrorMessage()); 122 // abort here 123 notifyTestRunEnded(0); 124 return; 125 } 126 notifyTestRunStarted(collector.getTestCaseCount()); 127 AdtPlugin.printToConsole(mLaunchInfo.getProject(), 128 "Sending test information to Eclipse"); 129 130 collector.sendTrees(this); 131 132 // now do real execution 133 runner.setLogOnly(false); 134 runner.removeInstrumentationArg(DELAY_MSEC_KEY); 135 if (mLaunchInfo.isDebugMode()) { 136 runner.setDebug(true); 137 } 138 AdtPlugin.printToConsole(mLaunchInfo.getProject(), "Running tests..."); 139 runner.run(new TestRunListener()); 140 } catch (TimeoutException e) { 141 reportError(LaunchMessages.RemoteAdtTestRunner_RunTimeoutException); 142 } catch (IOException e) { 143 reportError(String.format(LaunchMessages.RemoteAdtTestRunner_RunIOException_s, 144 e.getMessage())); 145 } catch (AdbCommandRejectedException e) { 146 reportError(String.format( 147 LaunchMessages.RemoteAdtTestRunner_RunAdbCommandRejectedException_s, 148 e.getMessage())); 149 } catch (ShellCommandUnresponsiveException e) { 150 reportError(LaunchMessages.RemoteAdtTestRunner_RunTimeoutException); 151 } 152 } 153 154 /** 155 * Main entry method to run tests 156 * 157 * @param programArgs JDT JUnit program arguments to be processed by parent 158 * @param junitInfo the {@link AndroidJUnitLaunchInfo} containing info about this test ru 159 */ runTests(String[] programArgs, AndroidJUnitLaunchInfo junitInfo)160 public void runTests(String[] programArgs, AndroidJUnitLaunchInfo junitInfo) { 161 init(programArgs, junitInfo); 162 run(); 163 } 164 165 /** 166 * Stop the current test run. 167 */ terminate()168 public void terminate() { 169 stop(); 170 } 171 172 @Override stop()173 protected void stop() { 174 if (mExecution != null) { 175 mExecution.stop(); 176 } 177 } 178 notifyTestRunEnded(long elapsedTime)179 private void notifyTestRunEnded(long elapsedTime) { 180 // copy from parent - not ideal, but method is private 181 sendMessage(MessageIds.TEST_RUN_END + elapsedTime); 182 flush(); 183 //shutDown(); 184 } 185 186 /** 187 * @param errorMessage 188 */ reportError(String errorMessage)189 private void reportError(String errorMessage) { 190 AdtPlugin.printErrorToConsole(mLaunchInfo.getProject(), 191 String.format(LaunchMessages.RemoteAdtTestRunner_RunFailedMsg_s, errorMessage)); 192 // is this needed? 193 //notifyTestRunStopped(-1); 194 } 195 196 /** 197 * TestRunListener that communicates results in real-time back to JDT JUnit 198 */ 199 private class TestRunListener implements ITestRunListener { 200 testEnded(TestIdentifier test, Map<String, String> ignoredTestMetrics)201 public void testEnded(TestIdentifier test, Map<String, String> ignoredTestMetrics) { 202 mExecution.getListener().notifyTestEnded(new TestCaseReference(test)); 203 } 204 205 /* (non-Javadoc) 206 * @see com.android.ddmlib.testrunner.ITestRunListener#testFailed(com.android.ddmlib.testrunner.ITestRunListener.TestFailure, com.android.ddmlib.testrunner.TestIdentifier, java.lang.String) 207 */ testFailed(TestFailure status, TestIdentifier test, String trace)208 public void testFailed(TestFailure status, TestIdentifier test, String trace) { 209 String statusString; 210 if (status == TestFailure.ERROR) { 211 statusString = MessageIds.TEST_ERROR; 212 } else { 213 statusString = MessageIds.TEST_FAILED; 214 } 215 TestReferenceFailure failure = 216 new TestReferenceFailure(new TestCaseReference(test), 217 statusString, trace, null); 218 mExecution.getListener().notifyTestFailed(failure); 219 } 220 221 /* (non-Javadoc) 222 * @see com.android.ddmlib.testrunner.ITestRunListener#testRunEnded(long, Map<String, String>) 223 */ testRunEnded(long elapsedTime, Map<String, String> runMetrics)224 public void testRunEnded(long elapsedTime, Map<String, String> runMetrics) { 225 notifyTestRunEnded(elapsedTime); 226 AdtPlugin.printToConsole(mLaunchInfo.getProject(), 227 LaunchMessages.RemoteAdtTestRunner_RunCompleteMsg); 228 } 229 230 /* (non-Javadoc) 231 * @see com.android.ddmlib.testrunner.ITestRunListener#testRunFailed(java.lang.String) 232 */ testRunFailed(String errorMessage)233 public void testRunFailed(String errorMessage) { 234 reportError(errorMessage); 235 } 236 237 /* (non-Javadoc) 238 * @see com.android.ddmlib.testrunner.ITestRunListener#testRunStarted(int) 239 */ testRunStarted(String runName, int testCount)240 public void testRunStarted(String runName, int testCount) { 241 // ignore 242 } 243 244 /* (non-Javadoc) 245 * @see com.android.ddmlib.testrunner.ITestRunListener#testRunStopped(long) 246 */ testRunStopped(long elapsedTime)247 public void testRunStopped(long elapsedTime) { 248 notifyTestRunStopped(elapsedTime); 249 AdtPlugin.printToConsole(mLaunchInfo.getProject(), 250 LaunchMessages.RemoteAdtTestRunner_RunStoppedMsg); 251 } 252 253 /* (non-Javadoc) 254 * @see com.android.ddmlib.testrunner.ITestRunListener#testStarted(com.android.ddmlib.testrunner.TestIdentifier) 255 */ testStarted(TestIdentifier test)256 public void testStarted(TestIdentifier test) { 257 TestCaseReference testId = new TestCaseReference(test); 258 mExecution.getListener().notifyTestStarted(testId); 259 } 260 261 } 262 263 /** 264 * Override parent to get extra logs. 265 */ 266 @Override connect()267 protected boolean connect() { 268 boolean result = super.connect(); 269 if (!result) { 270 AdtPlugin.printErrorToConsole(mLaunchInfo.getProject(), 271 "Connect to Eclipse test result listener failed"); 272 } 273 return result; 274 } 275 276 /** 277 * Override parent to dump error message to console. 278 */ 279 @Override runFailed(String message, Exception exception)280 public void runFailed(String message, Exception exception) { 281 if (exception != null) { 282 AdtPlugin.logAndPrintError(exception, mLaunchInfo.getProject().getName(), 283 "Test launch failed: %s", message); 284 } else { 285 AdtPlugin.printErrorToConsole(mLaunchInfo.getProject(), "Test launch failed: %s", 286 message); 287 } 288 } 289 } 290