import com.android.ide.eclipse.adt.AdtPlugin;
import com.android.ide.eclipse.adt.launch.AndroidLaunch;
+import com.android.ide.eclipse.adt.launch.AndroidLaunchConfiguration;
import com.android.ide.eclipse.adt.launch.AndroidLaunchController;
import com.android.ide.eclipse.adt.launch.IAndroidLaunchAction;
import com.android.ide.eclipse.adt.launch.LaunchConfigDelegate;
-import com.android.ide.eclipse.adt.launch.AndroidLaunchConfiguration;
+import com.android.ide.eclipse.common.AndroidConstants;
import com.android.ide.eclipse.common.project.AndroidManifestParser;
import com.android.ide.eclipse.common.project.BaseProjectHelper;
import org.eclipse.core.runtime.IProgressMonitor;
import org.eclipse.debug.core.ILaunchConfiguration;
import org.eclipse.debug.core.ILaunchConfigurationWorkingCopy;
+import org.eclipse.jdt.core.IJavaProject;
import org.eclipse.jdt.internal.junit.launcher.JUnitLaunchConfigurationConstants;
import org.eclipse.jdt.internal.junit.launcher.TestKindRegistry;
/** Launch config attribute that stores instrumentation runner */
static final String ATTR_INSTR_NAME = AdtPlugin.PLUGIN_ID + ".instrumentation"; //$NON-NLS-1$
+ static final String INSTRUMENTATION_OK = null;
private static final String EMPTY_STRING = ""; //$NON-NLS-1$
@Override
* Helper method to return the set of instrumentations for the Android project
*
* @param project the {@link IProject} to get instrumentations for
- * @return null if no error occurred parsing instrumentations
+ * @return null if error occurred parsing instrumentations, otherwise returns array of
+ * instrumentation class names
*/
static String[] getInstrumentationsForProject(IProject project) {
if (project != null) {
config.setAttribute(JUnitLaunchConfigurationConstants.ATTR_TEST_RUNNER_KIND,
TestKindRegistry.JUNIT3_TEST_KIND_ID);
}
+
+ /**
+ * Helper method to determine if specified instrumentation can be used as a test runner
+ *
+ * @param project the {@link IJavaProject} to validate
+ * @param instrumentation the instrumentation class name to validate
+ * @return <code>INSTRUMENTATION_OK</code> if valid, otherwise returns error message
+ */
+ static String validateInstrumentationRunner(IJavaProject project, String instrumentation) {
+ AndroidManifestParser manifestParser;
+ try {
+ manifestParser = AndroidManifestParser.parse(
+ project, null /* errorListener */,
+ true /* gatherData */, false /* markErrors */);
+ // check if this instrumentation is the standard test runner
+ if (!instrumentation.equals(AndroidConstants.CLASS_INSTRUMENTATION_RUNNER)) {
+ // check if it extends the standard test runner
+ String result = BaseProjectHelper.testClassForManifest(project,
+ instrumentation, AndroidConstants.CLASS_INSTRUMENTATION_RUNNER, true);
+ if (result != BaseProjectHelper.TEST_CLASS_OK) {
+ return String.format("The instrumentation runner must be of type %s",
+ AndroidConstants.CLASS_INSTRUMENTATION_RUNNER);
+ }
+ }
+ if (!hasTestRunnerLibrary(manifestParser)) {
+ return String.format("%s does not not use the %s library",
+ project.getProject().getName(), AndroidConstants.LIBRARY_TEST_RUNNER);
+ }
+ } catch (CoreException e) {
+ String err = String.format("Error parsing AndroidManifest for %s",
+ project.getProject().getName());
+ AdtPlugin.log(e, err);
+ return err;
+ }
+ return INSTRUMENTATION_OK;
+ }
+
+ /**
+ * Helper method to determine if given manifest has a <code>AndroidConstants.LIBRARY_TEST_RUNNER
+ * </code> library reference
+ *
+ * @param manifestParser the {@link AndroidManifestParser} to search
+ * @return true if test runner library found, false otherwise
+ */
+ static boolean hasTestRunnerLibrary(AndroidManifestParser manifestParser) {
+ for (String lib : manifestParser.getUsesLibraries()) {
+ if (lib.equals(AndroidConstants.LIBRARY_TEST_RUNNER)) {
+ return true;
+ }
+ }
+ return false;
+ }
}
private void validateInstrumentation(IJavaProject javaProject) {
if (mInstrumentations == null || mInstrumentations.length < 1) {
setErrorMessage("Specified project has no defined instrumentations");
+ return;
}
String instrumentation = getSelectedInstrumentation();
if (instrumentation == null) {
setErrorMessage("Instrumentation not specified");
+ return;
+ }
+ String result = AndroidJUnitLaunchConfigDelegate.validateInstrumentationRunner(
+ javaProject, instrumentation);
+ if (result != AndroidJUnitLaunchConfigDelegate.INSTRUMENTATION_OK) {
+ setErrorMessage(result);
+ return;
}
}
package com.android.ide.eclipse.adt.launch.junit;
+import com.android.ide.eclipse.adt.AdtPlugin;
+import com.android.ide.eclipse.common.AndroidConstants;
+
import org.eclipse.core.resources.IProject;
import org.eclipse.core.runtime.CoreException;
import org.eclipse.debug.core.ILaunchConfigurationWorkingCopy;
/**
* Creates a default Android JUnit launch configuration. Sets the instrumentation runner to the
- * first instrumentation found in the AndroidManifest.
+ * first instrumentation found in the AndroidManifest.
*/
@Override
protected ILaunchConfigurationWorkingCopy createLaunchConfiguration(IJavaElement element)
IProject project = element.getResource().getProject();
String[] instrumentations =
AndroidJUnitLaunchConfigDelegate.getInstrumentationsForProject(project);
- if (instrumentations != null && instrumentations.length > 0) {
- // just pick the first runner
- config.setAttribute(AndroidJUnitLaunchConfigDelegate.ATTR_INSTR_NAME,
- instrumentations[0]);
+ boolean runnerFound = false;
+ if (instrumentations != null) {
+ // just pick the first valid runner
+ for (String instr : instrumentations) {
+ if (AndroidJUnitLaunchConfigDelegate.validateInstrumentationRunner(
+ element.getJavaProject(), instr) ==
+ AndroidJUnitLaunchConfigDelegate.INSTRUMENTATION_OK) {
+
+ config.setAttribute(AndroidJUnitLaunchConfigDelegate.ATTR_INSTR_NAME,
+ instr);
+ runnerFound = true;
+ break;
+ }
+ }
+ }
+ if (!runnerFound) {
+ // TODO: put this in a string properties
+ String msg = String.format("ERROR: Application does not specify a %s instrumentation or does not declare uses-library %s",
+ AndroidConstants.CLASS_INSTRUMENTATION_RUNNER,
+ AndroidConstants.LIBRARY_TEST_RUNNER);
+ AdtPlugin.printErrorToConsole(project, msg);
}
AndroidJUnitLaunchConfigDelegate.setJUnitDefaults(config);
public final static String CLASS_BROADCASTRECEIVER = "android.content.BroadcastReceiver"; //$NON-NLS-1$
public final static String CLASS_CONTENTPROVIDER = "android.content.ContentProvider"; //$NON-NLS-1$
public final static String CLASS_INSTRUMENTATION = "android.app.Instrumentation"; //$NON-NLS-1$
+ public final static String CLASS_INSTRUMENTATION_RUNNER =
+ "android.test.InstrumentationTestRunner"; //$NON-NLS-1$
public final static String CLASS_BUNDLE = "android.os.Bundle"; //$NON-NLS-1$
public final static String CLASS_R = "android.R"; //$NON-NLS-1$
public final static String CLASS_MANIFEST_PERMISSION = "android.Manifest$permission"; //$NON-NLS-1$
/** The base URL where to find the Android class & manifest documentation */
public static final String CODESITE_BASE_URL = "http://code.google.com/android"; //$NON-NLS-1$
+ public static final String LIBRARY_TEST_RUNNER = "android.test.runner"; // $NON-NLS-1$
}
} catch (NumberFormatException e) {
handleError(e, -1 /* lineNumber */);
}
- } else if (NODE_INSTRUMENTATION.equals(localName)) {
- value = getAttributeValue(attributes, ATTRIBUTE_NAME,
- true /* hasNamespace */);
- if (value != null) {
- mInstrumentations.add(value);
- }
+ } else if (NODE_INSTRUMENTATION.equals(localName)) {
+ processInstrumentationNode(attributes);
}
break;
case LEVEL_ACTIVITY:
addProcessName(processName);
}
}
+
+ /**
+ * Processes the instrumentation nodes.
+ * @param attributes the attributes for the activity node.
+ * node is representing
+ */
+ private void processInstrumentationNode(Attributes attributes) {
+ // lets get the class name, and check it if required.
+ String instrumentationName = getAttributeValue(attributes, ATTRIBUTE_NAME,
+ true /* hasNamespace */);
+ if (instrumentationName != null) {
+ String instrClassName = combinePackageAndClassName(mPackage, instrumentationName);
+ mInstrumentations.add(instrClassName);
+ if (mMarkErrors) {
+ checkClass(instrClassName, AndroidConstants.CLASS_INSTRUMENTATION,
+ true /* testVisibility */);
+ }
+ }
+ }
/**
* Checks that a class is valid and can be used in the Android Manifest.
} catch (CoreException e) {
}
}
- }
-
+ }
}
/**
return null;
}
-
+
/**
* Parses the Android Manifest, and returns an object containing the result of the parsing.
* <p/>