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