diff --git a/tools/eclipse/plugins/com.android.ide.eclipse.adt/src/com/android/ide/eclipse/adt/launch/junit/AndroidJUnitLaunchConfigDelegate.java b/tools/eclipse/plugins/com.android.ide.eclipse.adt/src/com/android/ide/eclipse/adt/launch/junit/AndroidJUnitLaunchConfigDelegate.java index 05cc6ae73..a624b0001 100755 --- a/tools/eclipse/plugins/com.android.ide.eclipse.adt/src/com/android/ide/eclipse/adt/launch/junit/AndroidJUnitLaunchConfigDelegate.java +++ b/tools/eclipse/plugins/com.android.ide.eclipse.adt/src/com/android/ide/eclipse/adt/launch/junit/AndroidJUnitLaunchConfigDelegate.java @@ -18,10 +18,11 @@ package com.android.ide.eclipse.adt.launch.junit; 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; @@ -31,6 +32,7 @@ import org.eclipse.core.runtime.CoreException; 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; @@ -46,6 +48,7 @@ public class AndroidJUnitLaunchConfigDelegate extends LaunchConfigDelegate { /** 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 @@ -87,7 +90,8 @@ public class AndroidJUnitLaunchConfigDelegate extends LaunchConfigDelegate { * 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) { @@ -117,4 +121,56 @@ public class AndroidJUnitLaunchConfigDelegate extends LaunchConfigDelegate { 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 INSTRUMENTATION_OK 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 AndroidConstants.LIBRARY_TEST_RUNNER + * 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; + } } diff --git a/tools/eclipse/plugins/com.android.ide.eclipse.adt/src/com/android/ide/eclipse/adt/launch/junit/AndroidJUnitLaunchConfigurationTab.java b/tools/eclipse/plugins/com.android.ide.eclipse.adt/src/com/android/ide/eclipse/adt/launch/junit/AndroidJUnitLaunchConfigurationTab.java index 5fbda983d..aa59a5157 100644 --- a/tools/eclipse/plugins/com.android.ide.eclipse.adt/src/com/android/ide/eclipse/adt/launch/junit/AndroidJUnitLaunchConfigurationTab.java +++ b/tools/eclipse/plugins/com.android.ide.eclipse.adt/src/com/android/ide/eclipse/adt/launch/junit/AndroidJUnitLaunchConfigurationTab.java @@ -691,10 +691,18 @@ public class AndroidJUnitLaunchConfigurationTab extends AbstractLaunchConfigurat 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; } } diff --git a/tools/eclipse/plugins/com.android.ide.eclipse.adt/src/com/android/ide/eclipse/adt/launch/junit/AndroidJUnitLaunchShortcut.java b/tools/eclipse/plugins/com.android.ide.eclipse.adt/src/com/android/ide/eclipse/adt/launch/junit/AndroidJUnitLaunchShortcut.java index e03f2822b..30649e2e8 100755 --- a/tools/eclipse/plugins/com.android.ide.eclipse.adt/src/com/android/ide/eclipse/adt/launch/junit/AndroidJUnitLaunchShortcut.java +++ b/tools/eclipse/plugins/com.android.ide.eclipse.adt/src/com/android/ide/eclipse/adt/launch/junit/AndroidJUnitLaunchShortcut.java @@ -16,6 +16,9 @@ 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; @@ -34,7 +37,7 @@ public class AndroidJUnitLaunchShortcut extends JUnitLaunchShortcut { /** * 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) @@ -43,10 +46,27 @@ public class AndroidJUnitLaunchShortcut extends JUnitLaunchShortcut { 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); diff --git a/tools/eclipse/plugins/com.android.ide.eclipse.adt/src/com/android/ide/eclipse/common/AndroidConstants.java b/tools/eclipse/plugins/com.android.ide.eclipse.adt/src/com/android/ide/eclipse/common/AndroidConstants.java index 5abfd811d..1da753c7b 100644 --- a/tools/eclipse/plugins/com.android.ide.eclipse.adt/src/com/android/ide/eclipse/common/AndroidConstants.java +++ b/tools/eclipse/plugins/com.android.ide.eclipse.adt/src/com/android/ide/eclipse/common/AndroidConstants.java @@ -180,6 +180,8 @@ public class AndroidConstants { 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$ @@ -215,4 +217,5 @@ public class AndroidConstants { /** 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$ } diff --git a/tools/eclipse/plugins/com.android.ide.eclipse.adt/src/com/android/ide/eclipse/common/project/AndroidManifestParser.java b/tools/eclipse/plugins/com.android.ide.eclipse.adt/src/com/android/ide/eclipse/common/project/AndroidManifestParser.java index 42c881b15..fe11e694c 100644 --- a/tools/eclipse/plugins/com.android.ide.eclipse.adt/src/com/android/ide/eclipse/common/project/AndroidManifestParser.java +++ b/tools/eclipse/plugins/com.android.ide.eclipse.adt/src/com/android/ide/eclipse/common/project/AndroidManifestParser.java @@ -255,12 +255,8 @@ public class AndroidManifestParser { } 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: @@ -449,6 +445,25 @@ public class AndroidManifestParser { 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. @@ -484,8 +499,7 @@ public class AndroidManifestParser { } catch (CoreException e) { } } - } - + } } /** @@ -583,7 +597,7 @@ public class AndroidManifestParser { return null; } - + /** * Parses the Android Manifest, and returns an object containing the result of the parsing. *