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 package com.android.ide.eclipse.adt.internal.launch.junit;
17 
18 import com.android.ddmlib.IDevice;
19 import com.android.ide.eclipse.adt.AdtPlugin;
20 import com.android.ide.eclipse.adt.internal.launch.DelayedLaunchInfo;
21 import com.android.ide.eclipse.adt.internal.launch.IAndroidLaunchAction;
22 import com.android.ide.eclipse.adt.internal.launch.LaunchMessages;
23 import com.android.ide.eclipse.adt.internal.launch.junit.runtime.AndroidJUnitLaunchInfo;
24 import com.android.ide.eclipse.adt.internal.launch.junit.runtime.RemoteAdtTestRunner;
25 import com.google.common.base.Joiner;
26 
27 import org.eclipse.core.runtime.CoreException;
28 import org.eclipse.core.runtime.IProgressMonitor;
29 import org.eclipse.core.runtime.IStatus;
30 import org.eclipse.debug.core.ILaunch;
31 import org.eclipse.debug.core.ILaunchConfiguration;
32 import org.eclipse.debug.core.ILaunchManager;
33 import org.eclipse.debug.core.model.IProcess;
34 import org.eclipse.debug.core.model.IStreamsProxy;
35 import org.eclipse.jdt.junit.launcher.JUnitLaunchConfigurationDelegate;
36 import org.eclipse.jdt.launching.IVMRunner;
37 import org.eclipse.jdt.launching.VMRunnerConfiguration;
38 import org.eclipse.swt.widgets.Display;
39 
40 import java.util.Collection;
41 
42 /**
43  * A launch action that executes a instrumentation test run on an Android device.
44  */
45 class AndroidJUnitLaunchAction implements IAndroidLaunchAction {
46     private static final Joiner JOINER = Joiner.on(',').skipNulls();
47     private final AndroidJUnitLaunchInfo mLaunchInfo;
48 
49     /**
50      * Creates a AndroidJUnitLaunchAction.
51      *
52      * @param launchInfo the {@link AndroidJUnitLaunchInfo} for the JUnit run
53      */
AndroidJUnitLaunchAction(AndroidJUnitLaunchInfo launchInfo)54     public AndroidJUnitLaunchAction(AndroidJUnitLaunchInfo launchInfo) {
55         mLaunchInfo = launchInfo;
56     }
57 
58     /**
59      * Launch a instrumentation test run on given Android devices.
60      * Reuses JDT JUnit launch delegate so results can be communicated back to JDT JUnit UI.
61      * <p/>
62      * Note: Must be executed on non-UI thread.
63      *
64      * @see IAndroidLaunchAction#doLaunchActions(DelayedLaunchInfo, IDevice)
65      */
66     @Override
doLaunchAction(DelayedLaunchInfo info, Collection<IDevice> devices)67     public boolean doLaunchAction(DelayedLaunchInfo info, Collection<IDevice> devices) {
68         String msg = String.format(LaunchMessages.AndroidJUnitLaunchAction_LaunchInstr_2s,
69                 mLaunchInfo.getRunner(), JOINER.join(devices));
70         AdtPlugin.printToConsole(info.getProject(), msg);
71 
72         try {
73            mLaunchInfo.setDebugMode(info.isDebugMode());
74            mLaunchInfo.setDevices(devices);
75            JUnitLaunchDelegate junitDelegate = new JUnitLaunchDelegate(mLaunchInfo);
76            final String mode = info.isDebugMode() ? ILaunchManager.DEBUG_MODE :
77                ILaunchManager.RUN_MODE;
78 
79            junitDelegate.launch(info.getLaunch().getLaunchConfiguration(), mode, info.getLaunch(),
80                    info.getMonitor());
81 
82            // TODO: need to add AMReceiver-type functionality somewhere
83         } catch (CoreException e) {
84             AdtPlugin.printErrorToConsole(info.getProject(),
85                     LaunchMessages.AndroidJUnitLaunchAction_LaunchFail);
86         }
87         return true;
88     }
89 
90     /**
91      * {@inheritDoc}
92      */
93     @Override
getLaunchDescription()94     public String getLaunchDescription() {
95         return String.format(LaunchMessages.AndroidJUnitLaunchAction_LaunchDesc_s,
96                 mLaunchInfo.getRunner());
97     }
98 
99     /**
100      * Extends the JDT JUnit launch delegate to allow for JUnit UI reuse.
101      */
102     private static class JUnitLaunchDelegate extends JUnitLaunchConfigurationDelegate {
103 
104         private AndroidJUnitLaunchInfo mLaunchInfo;
105 
JUnitLaunchDelegate(AndroidJUnitLaunchInfo launchInfo)106         public JUnitLaunchDelegate(AndroidJUnitLaunchInfo launchInfo) {
107             mLaunchInfo = launchInfo;
108         }
109 
110         /* (non-Javadoc)
111          * @see org.eclipse.jdt.junit.launcher.JUnitLaunchConfigurationDelegate#launch(org.eclipse.debug.core.ILaunchConfiguration, java.lang.String, org.eclipse.debug.core.ILaunch, org.eclipse.core.runtime.IProgressMonitor)
112          */
113         @Override
launch(ILaunchConfiguration configuration, String mode, ILaunch launch, IProgressMonitor monitor)114         public synchronized void launch(ILaunchConfiguration configuration, String mode,
115                 ILaunch launch, IProgressMonitor monitor) throws CoreException {
116             // TODO: is progress monitor adjustment needed here?
117             super.launch(configuration, mode, launch, monitor);
118         }
119 
120         /**
121          * {@inheritDoc}
122          * @see org.eclipse.jdt.junit.launcher.JUnitLaunchConfigurationDelegate#verifyMainTypeName(org.eclipse.debug.core.ILaunchConfiguration)
123          */
124         @Override
verifyMainTypeName(ILaunchConfiguration configuration)125         public String verifyMainTypeName(ILaunchConfiguration configuration) {
126             return "com.android.ide.eclipse.adt.junit.internal.runner.RemoteAndroidTestRunner"; //$NON-NLS-1$
127         }
128 
129         /**
130          * Overrides parent to return a VM Runner implementation which launches a thread, rather
131          * than a separate VM process
132          */
133         @Override
getVMRunner(ILaunchConfiguration configuration, String mode)134         public IVMRunner getVMRunner(ILaunchConfiguration configuration, String mode) {
135             return new VMTestRunner(mLaunchInfo);
136         }
137 
138         /**
139          * {@inheritDoc}
140          * @see org.eclipse.debug.core.model.LaunchConfigurationDelegate#getLaunch(org.eclipse.debug.core.ILaunchConfiguration, java.lang.String)
141          */
142         @Override
getLaunch(ILaunchConfiguration configuration, String mode)143         public ILaunch getLaunch(ILaunchConfiguration configuration, String mode) {
144             return mLaunchInfo.getLaunch();
145         }
146     }
147 
148     /**
149      * Provides a VM runner implementation which starts a inline implementation of a launch process
150      */
151     private static class VMTestRunner implements IVMRunner {
152 
153         private final AndroidJUnitLaunchInfo mJUnitInfo;
154 
VMTestRunner(AndroidJUnitLaunchInfo info)155         VMTestRunner(AndroidJUnitLaunchInfo info) {
156             mJUnitInfo = info;
157         }
158 
159         /**
160          * {@inheritDoc}
161          * @throws CoreException
162          */
163         @Override
run(final VMRunnerConfiguration config, ILaunch launch, IProgressMonitor monitor)164         public void run(final VMRunnerConfiguration config, ILaunch launch,
165                 IProgressMonitor monitor) throws CoreException {
166 
167             TestRunnerProcess runnerProcess =
168                 new TestRunnerProcess(config, mJUnitInfo);
169             launch.addProcess(runnerProcess);
170             runnerProcess.run();
171         }
172     }
173 
174     /**
175      * Launch process that executes the tests.
176      */
177     private static class TestRunnerProcess implements IProcess  {
178 
179         private final VMRunnerConfiguration mRunConfig;
180         private final AndroidJUnitLaunchInfo mJUnitInfo;
181         private RemoteAdtTestRunner mTestRunner = null;
182         private boolean mIsTerminated = false;
183 
TestRunnerProcess(VMRunnerConfiguration runConfig, AndroidJUnitLaunchInfo info)184         TestRunnerProcess(VMRunnerConfiguration runConfig, AndroidJUnitLaunchInfo info) {
185             mRunConfig = runConfig;
186             mJUnitInfo = info;
187         }
188 
189         /* (non-Javadoc)
190          * @see org.eclipse.debug.core.model.IProcess#getAttribute(java.lang.String)
191          */
192         @Override
getAttribute(String key)193         public String getAttribute(String key) {
194             return null;
195         }
196 
197         /**
198          * {@inheritDoc}
199          * @see org.eclipse.debug.core.model.IProcess#getExitValue()
200          */
201         @Override
getExitValue()202         public int getExitValue() {
203             return 0;
204         }
205 
206         /* (non-Javadoc)
207          * @see org.eclipse.debug.core.model.IProcess#getLabel()
208          */
209         @Override
getLabel()210         public String getLabel() {
211             return mJUnitInfo.getLaunch().getLaunchMode();
212         }
213 
214         /* (non-Javadoc)
215          * @see org.eclipse.debug.core.model.IProcess#getLaunch()
216          */
217         @Override
getLaunch()218         public ILaunch getLaunch() {
219             return mJUnitInfo.getLaunch();
220         }
221 
222         /* (non-Javadoc)
223          * @see org.eclipse.debug.core.model.IProcess#getStreamsProxy()
224          */
225         @Override
getStreamsProxy()226         public IStreamsProxy getStreamsProxy() {
227             return null;
228         }
229 
230         /* (non-Javadoc)
231          * @see org.eclipse.debug.core.model.IProcess#setAttribute(java.lang.String,
232          * java.lang.String)
233          */
234         @Override
setAttribute(String key, String value)235         public void setAttribute(String key, String value) {
236             // ignore
237         }
238 
239         /* (non-Javadoc)
240          * @see org.eclipse.core.runtime.IAdaptable#getAdapter(java.lang.Class)
241          */
242         @Override
getAdapter(Class adapter)243         public Object getAdapter(Class adapter) {
244             return null;
245         }
246 
247         /* (non-Javadoc)
248          * @see org.eclipse.debug.core.model.ITerminate#canTerminate()
249          */
250         @Override
canTerminate()251         public boolean canTerminate() {
252             return true;
253         }
254 
255         /* (non-Javadoc)
256          * @see org.eclipse.debug.core.model.ITerminate#isTerminated()
257          */
258         @Override
isTerminated()259         public boolean isTerminated() {
260             return mIsTerminated;
261         }
262 
263         /**
264          * {@inheritDoc}
265          * @see org.eclipse.debug.core.model.ITerminate#terminate()
266          */
267         @Override
terminate()268         public void terminate() {
269             if (mTestRunner != null) {
270                 mTestRunner.terminate();
271             }
272             mIsTerminated = true;
273         }
274 
275         /**
276          * Launches a test runner that will communicate results back to JDT JUnit UI.
277          * <p/>
278          * Must be executed on a non-UI thread.
279          */
run()280         public void run() {
281             if (Display.getCurrent() != null) {
282                 AdtPlugin.log(IStatus.ERROR, "Adt test runner executed on UI thread");
283                 AdtPlugin.printErrorToConsole(mJUnitInfo.getProject(),
284                         "Test launch failed due to internal error: Running tests on UI thread");
285                 terminate();
286                 return;
287             }
288             mTestRunner = new RemoteAdtTestRunner();
289             mTestRunner.runTests(mRunConfig.getProgramArguments(), mJUnitInfo);
290         }
291     }
292 }
293 
294