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