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 */
16package com.android.ide.eclipse.adt.internal.launch.junit;
17
18import com.android.ddmlib.IDevice;
19import com.android.ide.eclipse.adt.AdtPlugin;
20import com.android.ide.eclipse.adt.internal.launch.DelayedLaunchInfo;
21import com.android.ide.eclipse.adt.internal.launch.IAndroidLaunchAction;
22import com.android.ide.eclipse.adt.internal.launch.LaunchMessages;
23import com.android.ide.eclipse.adt.internal.launch.junit.runtime.AndroidJUnitLaunchInfo;
24import com.android.ide.eclipse.adt.internal.launch.junit.runtime.RemoteAdtTestRunner;
25import com.google.common.base.Joiner;
26
27import org.eclipse.core.runtime.CoreException;
28import org.eclipse.core.runtime.IProgressMonitor;
29import org.eclipse.core.runtime.IStatus;
30import org.eclipse.debug.core.ILaunch;
31import org.eclipse.debug.core.ILaunchConfiguration;
32import org.eclipse.debug.core.ILaunchManager;
33import org.eclipse.debug.core.model.IProcess;
34import org.eclipse.debug.core.model.IStreamsProxy;
35import org.eclipse.jdt.junit.launcher.JUnitLaunchConfigurationDelegate;
36import org.eclipse.jdt.launching.IVMRunner;
37import org.eclipse.jdt.launching.VMRunnerConfiguration;
38import org.eclipse.swt.widgets.Display;
39
40import java.util.Collection;
41
42/**
43 * A launch action that executes a instrumentation test run on an Android device.
44 */
45class 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     */
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
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
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
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
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
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
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
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
155        VMTestRunner(AndroidJUnitLaunchInfo info) {
156            mJUnitInfo = info;
157        }
158
159        /**
160         * {@inheritDoc}
161         * @throws CoreException
162         */
163        @Override
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
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
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
202        public int getExitValue() {
203            return 0;
204        }
205
206        /* (non-Javadoc)
207         * @see org.eclipse.debug.core.model.IProcess#getLabel()
208         */
209        @Override
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
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
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
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
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
251        public boolean canTerminate() {
252            return true;
253        }
254
255        /* (non-Javadoc)
256         * @see org.eclipse.debug.core.model.ITerminate#isTerminated()
257         */
258        @Override
259        public boolean isTerminated() {
260            return mIsTerminated;
261        }
262
263        /**
264         * {@inheritDoc}
265         * @see org.eclipse.debug.core.model.ITerminate#terminate()
266         */
267        @Override
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         */
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