• Home
  • Line#
  • Scopes#
  • Navigate#
  • Raw
  • Download
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