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