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; 18 19 import com.android.SdkConstants; 20 import com.android.ddmlib.testrunner.IRemoteAndroidTestRunner.TestSize; 21 import com.android.ide.common.xml.ManifestData; 22 import com.android.ide.common.xml.ManifestData.Instrumentation; 23 import com.android.ide.eclipse.adt.AdtConstants; 24 import com.android.ide.eclipse.adt.AdtPlugin; 25 import com.android.ide.eclipse.adt.internal.launch.AndroidLaunch; 26 import com.android.ide.eclipse.adt.internal.launch.AndroidLaunchConfiguration; 27 import com.android.ide.eclipse.adt.internal.launch.AndroidLaunchController; 28 import com.android.ide.eclipse.adt.internal.launch.IAndroidLaunchAction; 29 import com.android.ide.eclipse.adt.internal.launch.LaunchConfigDelegate; 30 import com.android.ide.eclipse.adt.internal.launch.LaunchMessages; 31 import com.android.ide.eclipse.adt.internal.launch.junit.runtime.AndroidJUnitLaunchInfo; 32 import com.android.ide.eclipse.adt.internal.project.BaseProjectHelper; 33 34 import org.eclipse.core.resources.IFile; 35 import org.eclipse.core.resources.IProject; 36 import org.eclipse.core.runtime.CoreException; 37 import org.eclipse.core.runtime.IProgressMonitor; 38 import org.eclipse.core.runtime.IStatus; 39 import org.eclipse.core.runtime.Status; 40 import org.eclipse.core.runtime.jobs.Job; 41 import org.eclipse.debug.core.ILaunchConfiguration; 42 import org.eclipse.debug.core.ILaunchConfigurationWorkingCopy; 43 import org.eclipse.jdt.core.IJavaElement; 44 import org.eclipse.jdt.core.JavaCore; 45 import org.eclipse.jdt.internal.junit.launcher.JUnitLaunchConfigurationConstants; 46 import org.eclipse.jdt.internal.junit.launcher.TestKindRegistry; 47 import org.eclipse.jdt.launching.IJavaLaunchConfigurationConstants; 48 import org.eclipse.swt.widgets.Display; 49 50 /** 51 * Run configuration that can execute JUnit tests on an Android platform. 52 * <p/> 53 * Will deploy apps on target Android platform by reusing functionality from ADT 54 * LaunchConfigDelegate, and then run JUnits tests by reusing functionality from JDT 55 * JUnitLaunchConfigDelegate. 56 */ 57 @SuppressWarnings("restriction") 58 public class AndroidJUnitLaunchConfigDelegate extends LaunchConfigDelegate { 59 60 /** Launch config attribute that stores instrumentation runner. */ 61 static final String ATTR_INSTR_NAME = AdtPlugin.PLUGIN_ID + ".instrumentation"; //$NON-NLS-1$ 62 63 /** Launch config attribute that stores the test size annotation to run. */ 64 static final String ATTR_TEST_SIZE = AdtPlugin.PLUGIN_ID + ".testSize"; //$NON-NLS-1$ 65 66 private static final String EMPTY_STRING = ""; //$NON-NLS-1$ 67 68 @Override 69 protected void doLaunch(final ILaunchConfiguration configuration, final String mode, 70 final IProgressMonitor monitor, final IProject project, 71 final AndroidLaunch androidLaunch, final AndroidLaunchConfiguration config, 72 final AndroidLaunchController controller, final IFile applicationPackage, 73 final ManifestData manifestData) { 74 75 String runner = getRunner(project, configuration, manifestData); 76 if (runner == null) { 77 AdtPlugin.displayError(LaunchMessages.LaunchDialogTitle, 78 String.format(LaunchMessages.AndroidJUnitDelegate_NoRunnerMsg_s, 79 project.getName())); 80 androidLaunch.stopLaunch(); 81 return; 82 } 83 // get the target app's package 84 final String targetAppPackage = getTargetPackage(manifestData, runner); 85 if (targetAppPackage == null) { 86 AdtPlugin.displayError(LaunchMessages.LaunchDialogTitle, 87 String.format(LaunchMessages.AndroidJUnitDelegate_NoTargetMsg_3s, 88 project.getName(), runner, SdkConstants.FN_ANDROID_MANIFEST_XML)); 89 androidLaunch.stopLaunch(); 90 return; 91 } 92 final String testAppPackage = manifestData.getPackage(); 93 AndroidJUnitLaunchInfo junitLaunchInfo = new AndroidJUnitLaunchInfo(project, 94 testAppPackage, runner); 95 junitLaunchInfo.setTestClass(getTestClass(configuration)); 96 junitLaunchInfo.setTestPackage(getTestPackage(configuration)); 97 junitLaunchInfo.setTestMethod(getTestMethod(configuration)); 98 junitLaunchInfo.setLaunch(androidLaunch); 99 junitLaunchInfo.setTestSize(getTestSize(configuration)); 100 final IAndroidLaunchAction junitLaunch = new AndroidJUnitLaunchAction(junitLaunchInfo); 101 102 // launch on a separate thread if currently on the display thread 103 if (Display.getCurrent() != null) { 104 Job job = new Job("Junit Launch") { //$NON-NLS-1$ 105 @Override 106 protected IStatus run(IProgressMonitor m) { 107 controller.launch(project, mode, applicationPackage, testAppPackage, 108 targetAppPackage, manifestData.getDebuggable(), 109 manifestData.getMinSdkVersionString(), 110 junitLaunch, config, androidLaunch, monitor); 111 return Status.OK_STATUS; 112 } 113 }; 114 job.setPriority(Job.INTERACTIVE); 115 job.schedule(); 116 } else { 117 controller.launch(project, mode, applicationPackage, testAppPackage, targetAppPackage, 118 manifestData.getDebuggable(), manifestData.getMinSdkVersionString(), 119 junitLaunch, config, androidLaunch, monitor); 120 } 121 } 122 123 /** 124 * Get the target Android application's package for the given instrumentation runner, or 125 * <code>null</code> if it could not be found. 126 * 127 * @param manifestParser the {@link ManifestData} for the test project 128 * @param runner the instrumentation runner class name 129 * @return the target package or <code>null</code> 130 */ 131 private String getTargetPackage(ManifestData manifestParser, String runner) { 132 for (Instrumentation instr : manifestParser.getInstrumentations()) { 133 if (instr.getName().equals(runner)) { 134 return instr.getTargetPackage(); 135 } 136 } 137 return null; 138 } 139 140 /** 141 * Returns the test package stored in the launch configuration, or <code>null</code> if not 142 * specified. 143 * 144 * @param configuration the {@link ILaunchConfiguration} to retrieve the test package info from 145 * @return the test package or <code>null</code>. 146 */ 147 private String getTestPackage(ILaunchConfiguration configuration) { 148 // try to retrieve a package name from the JUnit container attribute 149 String containerHandle = getStringLaunchAttribute( 150 JUnitLaunchConfigurationConstants.ATTR_TEST_CONTAINER, configuration); 151 if (containerHandle != null && containerHandle.length() > 0) { 152 IJavaElement element = JavaCore.create(containerHandle); 153 // containerHandle could be a IProject, check if its a java package 154 if (element.getElementType() == IJavaElement.PACKAGE_FRAGMENT) { 155 return element.getElementName(); 156 } 157 } 158 return null; 159 } 160 161 /** 162 * Returns the test class stored in the launch configuration. 163 * 164 * @param configuration the {@link ILaunchConfiguration} to retrieve the test class info from 165 * @return the test class. <code>null</code> if not specified. 166 */ 167 private String getTestClass(ILaunchConfiguration configuration) { 168 return getStringLaunchAttribute(IJavaLaunchConfigurationConstants.ATTR_MAIN_TYPE_NAME, 169 configuration); 170 } 171 172 /** 173 * Returns the test method stored in the launch configuration. 174 * 175 * @param configuration the {@link ILaunchConfiguration} to retrieve the test method info from 176 * @return the test method. <code>null</code> if not specified. 177 */ 178 private String getTestMethod(ILaunchConfiguration configuration) { 179 return getStringLaunchAttribute(JUnitLaunchConfigurationConstants.ATTR_TEST_METHOD_NAME, 180 configuration); 181 } 182 183 /** 184 * Returns the test sizes to run as saved in the launch configuration. 185 * @return {@link TestSize} if only tests of specific sizes should be run, 186 * null if all tests should be run 187 */ 188 private TestSize getTestSize(ILaunchConfiguration configuration) { 189 String testSizeAnnotation = getStringLaunchAttribute( 190 AndroidJUnitLaunchConfigDelegate.ATTR_TEST_SIZE, 191 configuration); 192 if (AndroidJUnitLaunchConfigurationTab.SMALL_TEST_ANNOTATION.equals( 193 testSizeAnnotation)){ 194 return TestSize.SMALL; 195 } else if (AndroidJUnitLaunchConfigurationTab.MEDIUM_TEST_ANNOTATION.equals( 196 testSizeAnnotation)) { 197 return TestSize.MEDIUM; 198 } else if (AndroidJUnitLaunchConfigurationTab.LARGE_TEST_ANNOTATION.equals( 199 testSizeAnnotation)) { 200 return TestSize.LARGE; 201 } else { 202 return null; 203 } 204 } 205 206 /** 207 * Gets a instrumentation runner for the launch. 208 * <p/> 209 * If a runner is stored in the given <code>configuration</code>, will return that. 210 * Otherwise, will try to find the first valid runner for the project. 211 * If a runner can still not be found, will return <code>null</code>, and will log an error 212 * to the console. 213 * 214 * @param project the {@link IProject} for the app 215 * @param configuration the {@link ILaunchConfiguration} for the launch 216 * @param manifestData the {@link ManifestData} for the project 217 * 218 * @return <code>null</code> if no instrumentation runner can be found, otherwise return 219 * the fully qualified runner name. 220 */ 221 private String getRunner(IProject project, ILaunchConfiguration configuration, 222 ManifestData manifestData) { 223 try { 224 String runner = getRunnerFromConfig(configuration); 225 if (runner != null) { 226 return runner; 227 } 228 final InstrumentationRunnerValidator instrFinder = new InstrumentationRunnerValidator( 229 BaseProjectHelper.getJavaProject(project), manifestData); 230 runner = instrFinder.getValidInstrumentationTestRunner(); 231 if (runner != null) { 232 AdtPlugin.printErrorToConsole(project, String.format( 233 LaunchMessages.AndroidJUnitDelegate_NoRunnerConfigMsg_s, runner)); 234 return runner; 235 } 236 AdtPlugin.printErrorToConsole(project, String.format( 237 LaunchMessages.AndroidJUnitDelegate_NoRunnerConsoleMsg_4s, 238 project.getName(), 239 SdkConstants.CLASS_INSTRUMENTATION_RUNNER, 240 AdtConstants.LIBRARY_TEST_RUNNER, 241 SdkConstants.FN_ANDROID_MANIFEST_XML)); 242 return null; 243 } catch (CoreException e) { 244 AdtPlugin.log(e, "Error when retrieving instrumentation info"); //$NON-NLS-1$ 245 } 246 247 return null; 248 } 249 250 private String getRunnerFromConfig(ILaunchConfiguration configuration) { 251 return getStringLaunchAttribute(ATTR_INSTR_NAME, configuration); 252 } 253 254 /** 255 * Helper method to retrieve a string attribute from the launch configuration 256 * 257 * @param attributeName name of the launch attribute 258 * @param configuration the {@link ILaunchConfiguration} to retrieve the attribute from 259 * @return the attribute's value. <code>null</code> if not found. 260 */ 261 private String getStringLaunchAttribute(String attributeName, 262 ILaunchConfiguration configuration) { 263 try { 264 String attrValue = configuration.getAttribute(attributeName, EMPTY_STRING); 265 if (attrValue.length() < 1) { 266 return null; 267 } 268 return attrValue; 269 } catch (CoreException e) { 270 AdtPlugin.log(e, String.format("Error when retrieving launch info %1$s", //$NON-NLS-1$ 271 attributeName)); 272 } 273 return null; 274 } 275 276 /** 277 * Helper method to set JUnit-related attributes expected by JDT JUnit runner 278 * 279 * @param config the launch configuration to modify 280 */ 281 static void setJUnitDefaults(ILaunchConfigurationWorkingCopy config) { 282 // set the test runner to JUnit3 to placate JDT JUnit runner logic 283 config.setAttribute(JUnitLaunchConfigurationConstants.ATTR_TEST_RUNNER_KIND, 284 TestKindRegistry.JUNIT3_TEST_KIND_ID); 285 } 286 } 287