auto import from //branches/cupcake_rel/...@140373
diff --git a/tools/ddms/libs/ddmlib/src/com/android/ddmlib/testrunner/InstrumentationResultParser.java b/tools/ddms/libs/ddmlib/src/com/android/ddmlib/testrunner/InstrumentationResultParser.java
index bc1834f..1a0b21f 100755
--- a/tools/ddms/libs/ddmlib/src/com/android/ddmlib/testrunner/InstrumentationResultParser.java
+++ b/tools/ddms/libs/ddmlib/src/com/android/ddmlib/testrunner/InstrumentationResultParser.java
@@ -27,7 +27,14 @@
* <p>Expects the following output:
*
* <p>If fatal error occurred when attempted to run the tests:
- * <pre> INSTRUMENTATION_FAILED: </pre>
+ * <pre>
+ * INSTRUMENTATION_STATUS: Error=error Message
+ * INSTRUMENTATION_FAILED:
+ * </pre>
+ * <p>or
+ * <pre>
+ * INSTRUMENTATION_RESULT: shortMsg=error Message
+ * </pre>
*
* <p>Otherwise, expect a series of test results, each one containing a set of status key/value
* pairs, delimited by a start(1)/pass(0)/fail(-2)/error(-1) status code result. At end of test
@@ -56,6 +63,8 @@
private static final String CLASS = "class";
private static final String STACK = "stack";
private static final String NUMTESTS = "numtests";
+ private static final String ERROR = "Error";
+ private static final String SHORTMSG = "shortMsg";
}
/** Test result status codes. */
@@ -71,6 +80,8 @@
private static final String STATUS = "INSTRUMENTATION_STATUS: ";
private static final String STATUS_CODE = "INSTRUMENTATION_STATUS_CODE: ";
private static final String STATUS_FAILED = "INSTRUMENTATION_FAILED: ";
+ private static final String CODE = "INSTRUMENTATION_CODE: ";
+ private static final String RESULT = "INSTRUMENTATION_RESULT: ";
private static final String TIME_REPORT = "Time: ";
}
@@ -90,6 +101,23 @@
boolean isComplete() {
return mCode != null && mTestName != null && mTestClass != null;
}
+
+ /** Provides a more user readable string for TestResult, if possible */
+ @Override
+ public String toString() {
+ StringBuilder output = new StringBuilder();
+ if (mTestClass != null ) {
+ output.append(mTestClass);
+ output.append('#');
+ }
+ if (mTestName != null) {
+ output.append(mTestName);
+ }
+ if (output.length() > 0) {
+ return output.toString();
+ }
+ return "unknown result";
+ }
}
/** Stores the status values for the test result currently being parsed */
@@ -130,6 +158,8 @@
public void processNewLines(String[] lines) {
for (String line : lines) {
parse(line);
+ // in verbose mode, dump all adb output to log
+ Log.v(LOG_TAG, line);
}
}
@@ -160,9 +190,15 @@
// Previous status key-value has been collected. Store it.
submitCurrentKeyValue();
parseKey(line, Prefixes.STATUS.length());
- } else if (line.startsWith(Prefixes.STATUS_FAILED)) {
- Log.e(LOG_TAG, "test run failed " + line);
- mTestListener.testRunFailed(line);
+ } else if (line.startsWith(Prefixes.RESULT)) {
+ // Previous status key-value has been collected. Store it.
+ submitCurrentKeyValue();
+ parseKey(line, Prefixes.RESULT.length());
+ } else if (line.startsWith(Prefixes.STATUS_FAILED) ||
+ line.startsWith(Prefixes.CODE)) {
+ // Previous status key-value has been collected. Store it.
+ submitCurrentKeyValue();
+ // just ignore the remaining data on this line
} else if (line.startsWith(Prefixes.TIME_REPORT)) {
parseTime(line, Prefixes.TIME_REPORT.length());
} else {
@@ -186,19 +222,19 @@
if (mCurrentKey.equals(StatusKeys.CLASS)) {
testInfo.mTestClass = statusValue.trim();
- }
- else if (mCurrentKey.equals(StatusKeys.TEST)) {
+ } else if (mCurrentKey.equals(StatusKeys.TEST)) {
testInfo.mTestName = statusValue.trim();
- }
- else if (mCurrentKey.equals(StatusKeys.NUMTESTS)) {
+ } else if (mCurrentKey.equals(StatusKeys.NUMTESTS)) {
try {
testInfo.mNumTests = Integer.parseInt(statusValue);
- }
- catch (NumberFormatException e) {
+ } catch (NumberFormatException e) {
Log.e(LOG_TAG, "Unexpected integer number of tests, received " + statusValue);
}
- }
- else if (mCurrentKey.equals(StatusKeys.STACK)) {
+ } else if (mCurrentKey.equals(StatusKeys.ERROR) ||
+ mCurrentKey.equals(StatusKeys.SHORTMSG)) {
+ // test run must have failed
+ handleTestRunFailed(statusValue);
+ } else if (mCurrentKey.equals(StatusKeys.STACK)) {
testInfo.mStackTrace = statusValue;
}
@@ -229,7 +265,7 @@
int endKeyPos = line.indexOf('=', keyStartPos);
if (endKeyPos != -1) {
mCurrentKey = line.substring(keyStartPos, endKeyPos).trim();
- parseValue(line, endKeyPos+1);
+ parseValue(line, endKeyPos + 1);
}
}
@@ -252,8 +288,7 @@
TestResult testInfo = getCurrentTestInfo();
try {
testInfo.mCode = Integer.parseInt(value);
- }
- catch (NumberFormatException e) {
+ } catch (NumberFormatException e) {
Log.e(LOG_TAG, "Expected integer status code, received: " + value);
}
@@ -286,7 +321,7 @@
*/
private void reportResult(TestResult testInfo) {
if (!testInfo.isComplete()) {
- Log.e(LOG_TAG, "invalid instrumentation status bundle " + testInfo.toString());
+ Log.w(LOG_TAG, "invalid instrumentation status bundle " + testInfo.toString());
return;
}
reportTestRunStarted(testInfo);
@@ -337,8 +372,7 @@
private String getTrace(TestResult testInfo) {
if (testInfo.mStackTrace != null) {
return testInfo.mStackTrace;
- }
- else {
+ } else {
Log.e(LOG_TAG, "Could not find stack trace for failed test ");
return new Throwable("Unknown failure").toString();
}
@@ -351,14 +385,20 @@
String timeString = line.substring(startPos);
try {
float timeSeconds = Float.parseFloat(timeString);
- mTestTime = (long)(timeSeconds * 1000);
- }
- catch (NumberFormatException e) {
+ mTestTime = (long) (timeSeconds * 1000);
+ } catch (NumberFormatException e) {
Log.e(LOG_TAG, "Unexpected time format " + timeString);
}
}
/**
+ * Process a instrumentation run failure
+ */
+ private void handleTestRunFailed(String errorMsg) {
+ mTestListener.testRunFailed(errorMsg == null ? "Unknown error" : errorMsg);
+ }
+
+ /**
* Called by parent when adb session is complete.
*/
@Override
diff --git a/tools/ddms/libs/ddmlib/src/com/android/ddmlib/testrunner/RemoteAndroidTestRunner.java b/tools/ddms/libs/ddmlib/src/com/android/ddmlib/testrunner/RemoteAndroidTestRunner.java
index 4edbbbb..9995426 100644
--- a/tools/ddms/libs/ddmlib/src/com/android/ddmlib/testrunner/RemoteAndroidTestRunner.java
+++ b/tools/ddms/libs/ddmlib/src/com/android/ddmlib/testrunner/RemoteAndroidTestRunner.java
@@ -21,27 +21,35 @@
import com.android.ddmlib.Log;
import java.io.IOException;
+import java.util.Hashtable;
+import java.util.Map;
+import java.util.Map.Entry;
/**
* Runs a Android test command remotely and reports results.
*/
public class RemoteAndroidTestRunner {
- private static final char CLASS_SEPARATOR = ',';
- private static final char METHOD_SEPARATOR = '#';
- private static final char RUNNER_SEPARATOR = '/';
- private String mClassArg;
private final String mPackageName;
private final String mRunnerName;
- private String mExtraArgs;
- private boolean mLogOnlyMode;
private IDevice mRemoteDevice;
+ /** map of name-value instrumentation argument pairs */
+ private Map<String, String> mArgMap;
private InstrumentationResultParser mParser;
private static final String LOG_TAG = "RemoteAndroidTest";
- private static final String DEFAULT_RUNNER_NAME =
- "android.test.InstrumentationTestRunner";
-
+ private static final String DEFAULT_RUNNER_NAME = "android.test.InstrumentationTestRunner";
+
+ private static final char CLASS_SEPARATOR = ',';
+ private static final char METHOD_SEPARATOR = '#';
+ private static final char RUNNER_SEPARATOR = '/';
+
+ // defined instrumentation argument names
+ private static final String CLASS_ARG_NAME = "class";
+ private static final String LOG_ARG_NAME = "log";
+ private static final String DEBUG_ARG_NAME = "debug";
+ private static final String COVERAGE_ARG_NAME = "coverage";
+
/**
* Creates a remote Android test runner.
*
@@ -56,12 +64,10 @@
mPackageName = packageName;
mRunnerName = runnerName;
- mRemoteDevice = remoteDevice;
- mClassArg = null;
- mExtraArgs = "";
- mLogOnlyMode = false;
+ mRemoteDevice = remoteDevice;
+ mArgMap = new Hashtable<String, String>();
}
-
+
/**
* Alternate constructor. Uses default instrumentation runner.
*
@@ -72,7 +78,7 @@
IDevice remoteDevice) {
this(packageName, null, remoteDevice);
}
-
+
/**
* Returns the application package name.
*/
@@ -89,14 +95,14 @@
}
return mRunnerName;
}
-
+
/**
* Returns the complete instrumentation component path.
*/
private String getRunnerPath() {
return getPackageName() + RUNNER_SEPARATOR + getRunnerName();
}
-
+
/**
* Sets to run only tests in this class
* Must be called before 'run'.
@@ -104,7 +110,7 @@
* @param className fully qualified class name (eg x.y.z)
*/
public void setClassName(String className) {
- mClassArg = className;
+ addInstrumentationArg(CLASS_ARG_NAME, className);
}
/**
@@ -119,15 +125,15 @@
public void setClassNames(String[] classNames) {
StringBuilder classArgBuilder = new StringBuilder();
- for (int i=0; i < classNames.length; i++) {
+ for (int i = 0; i < classNames.length; i++) {
if (i != 0) {
classArgBuilder.append(CLASS_SEPARATOR);
}
classArgBuilder.append(classNames[i]);
}
- mClassArg = classArgBuilder.toString();
+ setClassName(classArgBuilder.toString());
}
-
+
/**
* Sets to run only specified test method
* Must be called before 'run'.
@@ -136,47 +142,70 @@
* @param testName method name
*/
public void setMethodName(String className, String testName) {
- mClassArg = className + METHOD_SEPARATOR + testName;
+ setClassName(className + METHOD_SEPARATOR + testName);
}
-
+
/**
- * Sets extra arguments to include in instrumentation command.
- * Must be called before 'run'.
+ * Adds a argument to include in instrumentation command.
+ * <p/>
+ * Must be called before 'run'. If an argument with given name has already been provided, it's
+ * value will be overridden.
*
- * @param instrumentationArgs must not be null
+ * @param name the name of the instrumentation bundle argument
+ * @param value the value of the argument
*/
- public void setExtraArgs(String instrumentationArgs) {
- if (instrumentationArgs == null) {
- throw new IllegalArgumentException("instrumentationArgs cannot be null");
+ public void addInstrumentationArg(String name, String value) {
+ if (name == null || value == null) {
+ throw new IllegalArgumentException("name or value arguments cannot be null");
}
- mExtraArgs = instrumentationArgs;
+ mArgMap.put(name, value);
}
-
+
/**
- * Returns the extra instrumentation arguments.
+ * Adds a boolean argument to include in instrumentation command.
+ * <p/>
+ * @see RemoteAndroidTestRunner#addInstrumentationArg
+ *
+ * @param name the name of the instrumentation bundle argument
+ * @param value the value of the argument
*/
- public String getExtraArgs() {
- return mExtraArgs;
+ public void addBooleanArg(String name, boolean value) {
+ addInstrumentationArg(name, Boolean.toString(value));
}
-
+
/**
* Sets this test run to log only mode - skips test execution.
*/
public void setLogOnly(boolean logOnly) {
- mLogOnlyMode = logOnly;
+ addBooleanArg(LOG_ARG_NAME, logOnly);
}
-
+
+ /**
+ * Sets this debug mode of this test run. If true, the Android test runner will wait for a
+ * debugger to attach before proceeding with test execution.
+ */
+ public void setDebug(boolean debug) {
+ addBooleanArg(DEBUG_ARG_NAME, debug);
+ }
+
+ /**
+ * Sets this code coverage mode of this test run.
+ */
+ public void setCoverage(boolean coverage) {
+ addBooleanArg(COVERAGE_ARG_NAME, coverage);
+ }
+
/**
* Execute this test run.
*
* @param listener listens for test results
*/
public void run(ITestRunListener listener) {
- final String runCaseCommandStr = "am instrument -w -r "
- + getClassCmd() + " " + getLogCmd() + " " + getExtraArgs() + " " + getRunnerPath();
+ final String runCaseCommandStr = String.format("am instrument -w -r %s %s",
+ getArgsCommand(), getRunnerPath());
Log.d(LOG_TAG, runCaseCommandStr);
mParser = new InstrumentationResultParser(listener);
-
+
try {
mRemoteDevice.executeShellCommand(runCaseCommandStr, mParser);
} catch (IOException e) {
@@ -184,7 +213,7 @@
listener.testRunFailed(e.toString());
}
}
-
+
/**
* Requests cancellation of this test run.
*/
@@ -193,36 +222,19 @@
mParser.cancel();
}
}
-
- /**
- * Returns the test class argument.
- */
- private String getClassArg() {
- return mClassArg;
- }
-
- /**
- * Returns the full instrumentation command which specifies the test classes to execute.
- * Returns an empty string if no classes were specified.
- */
- private String getClassCmd() {
- String classArg = getClassArg();
- if (classArg != null) {
- return "-e class " + classArg;
- }
- return "";
- }
/**
- * Returns the full command to enable log only mode - if specified. Otherwise returns an
- * empty string.
+ * Returns the full instrumentation command line syntax for the provided instrumentation
+ * arguments.
+ * Returns an empty string if no arguments were specified.
*/
- private String getLogCmd() {
- if (mLogOnlyMode) {
- return "-e log true";
+ private String getArgsCommand() {
+ StringBuilder commandBuilder = new StringBuilder();
+ for (Entry<String, String> argPair : mArgMap.entrySet()) {
+ final String argCmd = String.format(" -e %s %s", argPair.getKey(),
+ argPair.getValue());
+ commandBuilder.append(argCmd);
}
- else {
- return "";
- }
+ return commandBuilder.toString();
}
}
diff --git a/tools/ddms/libs/ddmlib/tests/src/com/android/ddmlib/testrunner/InstrumentationResultParserTest.java b/tools/ddms/libs/ddmlib/tests/src/com/android/ddmlib/testrunner/InstrumentationResultParserTest.java
index 77d10c1..7742dd6 100644
--- a/tools/ddms/libs/ddmlib/tests/src/com/android/ddmlib/testrunner/InstrumentationResultParserTest.java
+++ b/tools/ddms/libs/ddmlib/tests/src/com/android/ddmlib/testrunner/InstrumentationResultParserTest.java
@@ -103,9 +103,43 @@
injectTestString(timeString);
assertEquals(4900, mTestResult.mTestTime);
}
+
+ /**
+ * Test basic parsing of a test run failure.
+ */
+ public void testRunFailed() {
+ StringBuilder output = new StringBuilder();
+ final String errorMessage = "Unable to find instrumentation info";
+ addStatusKey(output, "Error", errorMessage);
+ addStatusCode(output, "-1");
+ output.append("INSTRUMENTATION_FAILED: com.dummy/android.test.InstrumentationTestRunner");
+ addLineBreak(output);
+
+ injectTestString(output.toString());
+
+ assertEquals(errorMessage, mTestResult.mRunFailedMessage);
+ }
+
+ /**
+ * Test parsing of a test run failure, where an instrumentation component failed to load
+ * Parsing input takes the from of INSTRUMENTATION_RESULT: fff
+ */
+ public void testRunFailedResult() {
+ StringBuilder output = new StringBuilder();
+ final String errorMessage = "Unable to instantiate instrumentation";
+ output.append("INSTRUMENTATION_RESULT: shortMsg=");
+ output.append(errorMessage);
+ addLineBreak(output);
+ output.append("INSTRUMENTATION_CODE: 0");
+ addLineBreak(output);
+
+ injectTestString(output.toString());
+
+ assertEquals(errorMessage, mTestResult.mRunFailedMessage);
+ }
/**
- * builds a common test result using TEST_NAME and TEST_CLASS.
+ * Builds a common test result using TEST_NAME and TEST_CLASS.
*/
private StringBuilder buildCommonResult() {
StringBuilder output = new StringBuilder();
@@ -146,6 +180,13 @@
outputBuilder.append(key);
outputBuilder.append('=');
outputBuilder.append(value);
+ addLineBreak(outputBuilder);
+ }
+
+ /**
+ * Append line break characters to output
+ */
+ private void addLineBreak(StringBuilder outputBuilder) {
outputBuilder.append("\r\n");
}
@@ -164,7 +205,7 @@
private void addStatusCode(StringBuilder outputBuilder, String value) {
outputBuilder.append("INSTRUMENTATION_STATUS_CODE: ");
outputBuilder.append(value);
- outputBuilder.append("\r\n");
+ addLineBreak(outputBuilder);
}
/**
@@ -197,11 +238,14 @@
TestFailure mTestStatus;
String mTrace;
boolean mStopped;
+ /** stores the error message provided to testRunFailed */
+ String mRunFailedMessage;
VerifyingTestResult() {
mNumTestsRun = 0;
mTestStatus = null;
mStopped = false;
+ mRunFailedMessage = null;
}
public void testEnded(TestIdentifier test) {
@@ -238,8 +282,7 @@
}
public void testRunFailed(String errorMessage) {
- // ignored
+ mRunFailedMessage = errorMessage;
}
}
-
}
diff --git a/tools/ddms/libs/ddmlib/tests/src/com/android/ddmlib/testrunner/RemoteAndroidTestRunnerTest.java b/tools/ddms/libs/ddmlib/tests/src/com/android/ddmlib/testrunner/RemoteAndroidTestRunnerTest.java
index 9acaaf9..6a653ad 100644
--- a/tools/ddms/libs/ddmlib/tests/src/com/android/ddmlib/testrunner/RemoteAndroidTestRunnerTest.java
+++ b/tools/ddms/libs/ddmlib/tests/src/com/android/ddmlib/testrunner/RemoteAndroidTestRunnerTest.java
@@ -17,18 +17,17 @@
package com.android.ddmlib.testrunner;
import com.android.ddmlib.Client;
+import com.android.ddmlib.Device.DeviceState;
import com.android.ddmlib.FileListingService;
import com.android.ddmlib.IDevice;
import com.android.ddmlib.IShellOutputReceiver;
+import com.android.ddmlib.log.LogReceiver;
import com.android.ddmlib.RawImage;
import com.android.ddmlib.SyncService;
-import com.android.ddmlib.Device.DeviceState;
-import com.android.ddmlib.log.LogReceiver;
-
-import junit.framework.TestCase;
import java.io.IOException;
import java.util.Map;
+import junit.framework.TestCase;
/**
* Tests RemoteAndroidTestRunner.
@@ -82,14 +81,15 @@
}
/**
- * Test the building of the instrumentation runner command with extra args set.
+ * Test the building of the instrumentation runner command with extra argument added.
*/
- public void testRunWithExtraArgs() {
- final String extraArgs = "blah";
- mRunner.setExtraArgs(extraArgs);
+ public void testRunWithAddInstrumentationArg() {
+ final String extraArgName = "blah";
+ final String extraArgValue = "blahValue";
+ mRunner.addInstrumentationArg(extraArgName, extraArgValue);
mRunner.run(new EmptyListener());
- assertStringsEquals(String.format("am instrument -w -r %s %s/%s", extraArgs,
- TEST_PACKAGE, TEST_RUNNER), mMockDevice.getLastShellCommand());
+ assertStringsEquals(String.format("am instrument -w -r -e %s %s %s/%s", extraArgName,
+ extraArgValue, TEST_PACKAGE, TEST_RUNNER), mMockDevice.getLastShellCommand());
}
@@ -243,6 +243,5 @@
public void testStarted(TestIdentifier test) {
// ignore
}
-
}
}
diff --git a/tools/eclipse/plugins/com.android.ide.eclipse.adt/META-INF/MANIFEST.MF b/tools/eclipse/plugins/com.android.ide.eclipse.adt/META-INF/MANIFEST.MF
index 3750f66..55c18bb 100644
--- a/tools/eclipse/plugins/com.android.ide.eclipse.adt/META-INF/MANIFEST.MF
+++ b/tools/eclipse/plugins/com.android.ide.eclipse.adt/META-INF/MANIFEST.MF
@@ -40,7 +40,8 @@
org.eclipse.wst.sse.ui,
org.eclipse.wst.xml.core,
org.eclipse.wst.xml.ui,
- org.eclipse.jdt.junit
+ org.eclipse.jdt.junit,
+ org.eclipse.jdt.junit.runtime
Eclipse-LazyStart: true
Export-Package: com.android.ide.eclipse.adt,
com.android.ide.eclipse.adt.build;x-friends:="com.android.ide.eclipse.tests",
diff --git a/tools/eclipse/plugins/com.android.ide.eclipse.adt/plugin.xml b/tools/eclipse/plugins/com.android.ide.eclipse.adt/plugin.xml
index 39e6dd5..c18c72f 100644
--- a/tools/eclipse/plugins/com.android.ide.eclipse.adt/plugin.xml
+++ b/tools/eclipse/plugins/com.android.ide.eclipse.adt/plugin.xml
@@ -510,4 +510,59 @@
type="org.eclipse.jdt.junit.launchconfig">
</launchDelegate>
</extension>
+ <extension
+ point="org.eclipse.debug.core.launchConfigurationTypes">
+ <launchConfigurationType
+ delegate="com.android.ide.eclipse.adt.launch.junit.AndroidJUnitLaunchConfigDelegate"
+ id="com.android.ide.eclipse.adt.junit.launchConfigurationType"
+ modes="run,debug"
+ name="Android Instrumentation"
+ public="true"
+ sourceLocatorId="org.eclipse.jdt.launching.sourceLocator.JavaSourceLookupDirector"
+ sourcePathComputerId="org.eclipse.jdt.launching.sourceLookup.javaSourcePathComputer">
+ </launchConfigurationType>
+ </extension>
+ <extension
+ point="org.eclipse.debug.ui.launchConfigurationTypeImages">
+ <launchConfigurationTypeImage
+ configTypeID="com.android.ide.eclipse.adt.junit.launchConfigurationType"
+ icon="icons/android.png"
+ id="com.android.ide.eclipse.adt.junit.launchConfigurationTypeImage">
+ </launchConfigurationTypeImage>
+ </extension>
+ <extension
+ point="org.eclipse.debug.ui.launchConfigurationTabGroups">
+ <launchConfigurationTabGroup
+ class="com.android.ide.eclipse.adt.launch.junit.AndroidJUnitTabGroup"
+ description="Android Instrumentation"
+ id="com.android.ide.eclipse.adt.junit.AndroidJUnitLaunchConfigTabGroup"
+ type="com.android.ide.eclipse.adt.junit.launchConfigurationType"/>
+ </extension>
+ <extension
+ point="org.eclipse.debug.ui.launchShortcuts">
+ <shortcut
+ class="com.android.ide.eclipse.adt.launch.junit.AndroidJUnitLaunchShortcut"
+ icon="icons/android.png"
+ id="com.android.ide.eclipse.adt.junit.launchShortcut"
+ label="Android Instrumentation"
+ modes="run,debug">
+ <contextualLaunch>
+ <enablement>
+ <with variable="selection">
+ <count value="1"/>
+ <iterate>
+ <adapt type="org.eclipse.jdt.core.IJavaElement">
+ <test property="org.eclipse.jdt.core.isInJavaProjectWithNature" value="com.android.ide.eclipse.adt.AndroidNature"/>
+ <test property="org.eclipse.jdt.core.hasTypeOnClasspath" value="junit.framework.Test"/>
+ <test property="org.eclipse.jdt.junit.canLaunchAsJUnit" forcePluginActivation="true"/>
+ </adapt>
+ </iterate>
+ </with>
+ </enablement>
+ </contextualLaunch>
+ <configurationType
+ id="com.android.ide.eclipse.adt.junit.launchConfigurationType">
+ </configurationType>
+ </shortcut>
+ </extension>
</plugin>
diff --git a/tools/eclipse/plugins/com.android.ide.eclipse.adt/src/com/android/ide/eclipse/adt/AdtPlugin.java b/tools/eclipse/plugins/com.android.ide.eclipse.adt/src/com/android/ide/eclipse/adt/AdtPlugin.java
index 48a21d1..e0708f3 100644
--- a/tools/eclipse/plugins/com.android.ide.eclipse.adt/src/com/android/ide/eclipse/adt/AdtPlugin.java
+++ b/tools/eclipse/plugins/com.android.ide.eclipse.adt/src/com/android/ide/eclipse/adt/AdtPlugin.java
@@ -1040,6 +1040,16 @@
mSdkIsLoaded = LoadStatus.LOADED;
progress.setTaskName("Check Projects");
+
+ ArrayList<IJavaProject> list = new ArrayList<IJavaProject>();
+ for (IJavaProject javaProject : mPostLoadProjectsToResolve) {
+ if (javaProject.getProject().isOpen()) {
+ list.add(javaProject);
+ }
+ }
+
+ // done with this list.
+ mPostLoadProjectsToResolve.clear();
// check the projects that need checking.
// The method modifies the list (it removes the project that
@@ -1047,14 +1057,13 @@
AndroidClasspathContainerInitializer.checkProjectsCache(
mPostLoadProjectsToCheck);
- mPostLoadProjectsToResolve.addAll(mPostLoadProjectsToCheck);
+ list.addAll(mPostLoadProjectsToCheck);
// update the project that needs recompiling.
- if (mPostLoadProjectsToResolve.size() > 0) {
- IJavaProject[] array = mPostLoadProjectsToResolve.toArray(
- new IJavaProject[mPostLoadProjectsToResolve.size()]);
+ if (list.size() > 0) {
+ IJavaProject[] array = list.toArray(
+ new IJavaProject[list.size()]);
AndroidClasspathContainerInitializer.updateProjects(array);
- mPostLoadProjectsToResolve.clear();
}
progress.worked(10);
diff --git a/tools/eclipse/plugins/com.android.ide.eclipse.adt/src/com/android/ide/eclipse/adt/build/PreCompilerBuilder.java b/tools/eclipse/plugins/com.android.ide.eclipse.adt/src/com/android/ide/eclipse/adt/build/PreCompilerBuilder.java
index c508283..8aa1aba 100644
--- a/tools/eclipse/plugins/com.android.ide.eclipse.adt/src/com/android/ide/eclipse/adt/build/PreCompilerBuilder.java
+++ b/tools/eclipse/plugins/com.android.ide.eclipse.adt/src/com/android/ide/eclipse/adt/build/PreCompilerBuilder.java
@@ -222,6 +222,7 @@
PreCompilerDeltaVisitor dv = null;
String javaPackage = null;
+ int minSdkVersion = AndroidManifestParser.INVALID_MIN_SDK;
if (kind == FULL_BUILD) {
AdtPlugin.printBuildToConsole(AdtConstants.BUILD_VERBOSE, project,
@@ -253,6 +254,7 @@
// get the java package from the visitor
javaPackage = dv.getManifestPackage();
+ minSdkVersion = dv.getMinSdkVersion();
}
}
@@ -276,7 +278,7 @@
if (manifest == null) {
String msg = String.format(Messages.s_File_Missing,
AndroidConstants.FN_ANDROID_MANIFEST);
- AdtPlugin.printBuildToConsole(AdtConstants.BUILD_VERBOSE, project, msg);
+ AdtPlugin.printErrorToConsole(project, msg);
markProject(AdtConstants.MARKER_ADT, msg, IMarker.SEVERITY_ERROR);
// This interrupts the build. The next builders will not run.
@@ -304,19 +306,34 @@
// get the java package from the parser
javaPackage = parser.getPackage();
+ minSdkVersion = parser.getApiLevelRequirement();
}
-
- if (javaPackage == null || javaPackage.length() == 0) {
- // looks like the AndroidManifest file isn't valid.
- String msg = String.format(Messages.s_Doesnt_Declare_Package_Error,
- AndroidConstants.FN_ANDROID_MANIFEST);
- AdtPlugin.printBuildToConsole(AdtConstants.BUILD_VERBOSE, project,
- msg);
+
+ if (minSdkVersion != AndroidManifestParser.INVALID_MIN_SDK &&
+ minSdkVersion < projectTarget.getApiVersionNumber()) {
+ // check it against the target api level
+ String msg = String.format(
+ "Manifest min SDK version (%1$d) is lower than project target API level (%2$d)",
+ minSdkVersion, projectTarget.getApiVersionNumber());
+ AdtPlugin.printErrorToConsole(project, msg);
+ BaseProjectHelper.addMarker(manifest, AdtConstants.MARKER_ADT, msg,
+ IMarker.SEVERITY_ERROR);
// This interrupts the build. The next builders will not run.
stopBuild(msg);
}
+
+ if (javaPackage == null || javaPackage.length() == 0) {
+ // looks like the AndroidManifest file isn't valid.
+ String msg = String.format(Messages.s_Doesnt_Declare_Package_Error,
+ AndroidConstants.FN_ANDROID_MANIFEST);
+ AdtPlugin.printErrorToConsole(project, msg);
+ markProject(AdtConstants.MARKER_ADT, msg, IMarker.SEVERITY_ERROR);
+ // This interrupts the build. The next builders will not run.
+ stopBuild(msg);
+ }
+
// at this point we have the java package. We need to make sure it's not a different
// package than the previous one that were built.
if (javaPackage.equals(mManifestPackage) == false) {
diff --git a/tools/eclipse/plugins/com.android.ide.eclipse.adt/src/com/android/ide/eclipse/adt/build/PreCompilerDeltaVisitor.java b/tools/eclipse/plugins/com.android.ide.eclipse.adt/src/com/android/ide/eclipse/adt/build/PreCompilerDeltaVisitor.java
index 6841830..29942e8 100644
--- a/tools/eclipse/plugins/com.android.ide.eclipse.adt/src/com/android/ide/eclipse/adt/build/PreCompilerDeltaVisitor.java
+++ b/tools/eclipse/plugins/com.android.ide.eclipse.adt/src/com/android/ide/eclipse/adt/build/PreCompilerDeltaVisitor.java
@@ -72,8 +72,10 @@
/** Manifest check/parsing flag. */
private boolean mCheckedManifestXml = false;
- /** Application Pacakge, gathered from the parsing of the manifest */
+ /** Application Package, gathered from the parsing of the manifest */
private String mJavaPackage = null;
+ /** minSDKVersion attribute value, gathered from the parsing of the manifest */
+ private int mMinSdkVersion = AndroidManifestParser.INVALID_MIN_SDK;
// Internal usage fields.
/**
@@ -137,6 +139,22 @@
return mJavaPackage;
}
+ /**
+ * Returns the minSDkVersion attribute from the manifest if it was checked/parsed.
+ * <p/>
+ * This can return {@link AndroidManifestParser#INVALID_MIN_SDK} in two cases:
+ * <ul>
+ * <li>The manifest was not part of the resource change delta, and the manifest was
+ * not checked/parsed ({@link #getCheckedManifestXml()} returns <code>false</code>)</li>
+ * <li>The manifest was parsed ({@link #getCheckedManifestXml()} returns <code>true</code>),
+ * but the package declaration is missing</li>
+ * </ul>
+ * @return the minSdkVersion or {@link AndroidManifestParser#INVALID_MIN_SDK}.
+ */
+ public int getMinSdkVersion() {
+ return mMinSdkVersion;
+ }
+
/*
* (non-Javadoc)
*
@@ -184,6 +202,7 @@
if (parser != null) {
mJavaPackage = parser.getPackage();
+ mMinSdkVersion = parser.getApiLevelRequirement();
}
mCheckedManifestXml = true;
diff --git a/tools/eclipse/plugins/com.android.ide.eclipse.adt/src/com/android/ide/eclipse/adt/launch/AndroidLaunchController.java b/tools/eclipse/plugins/com.android.ide.eclipse.adt/src/com/android/ide/eclipse/adt/launch/AndroidLaunchController.java
index 88ee8b6..499cca7 100644
--- a/tools/eclipse/plugins/com.android.ide.eclipse.adt/src/com/android/ide/eclipse/adt/launch/AndroidLaunchController.java
+++ b/tools/eclipse/plugins/com.android.ide.eclipse.adt/src/com/android/ide/eclipse/adt/launch/AndroidLaunchController.java
@@ -307,7 +307,8 @@
* <code>DEBUG_MODE</code>.
* @param apk the resource to the apk to launch.
* @param debuggable the debuggable value of the app, or null if not set.
- * @param requiredApiVersionNumber the api version required by the app, or -1 if none.
+ * @param requiredApiVersionNumber the api version required by the app, or
+ * {@link AndroidManifestParser#INVALID_MIN_SDK} if none.
* @param launchAction the action to perform after app sync
* @param config the launch configuration
* @param launch the launch object
@@ -638,20 +639,21 @@
String deviceApiVersionName = device.getProperty(IDevice.PROP_BUILD_VERSION);
String value = device.getProperty(IDevice.PROP_BUILD_VERSION_NUMBER);
- int deviceApiVersionNumber = 0;
+ int deviceApiVersionNumber = AndroidManifestParser.INVALID_MIN_SDK;
try {
deviceApiVersionNumber = Integer.parseInt(value);
} catch (NumberFormatException e) {
// pass, we'll keep the deviceVersionNumber value at 0.
}
- if (launchInfo.getRequiredApiVersionNumber() == 0) {
+ if (launchInfo.getRequiredApiVersionNumber() == AndroidManifestParser.INVALID_MIN_SDK) {
// warn the API level requirement is not set.
AdtPlugin.printErrorToConsole(launchInfo.getProject(),
"WARNING: Application does not specify an API level requirement!");
// and display the target device API level (if known)
- if (deviceApiVersionName == null || deviceApiVersionNumber == 0) {
+ if (deviceApiVersionName == null ||
+ deviceApiVersionNumber == AndroidManifestParser.INVALID_MIN_SDK) {
AdtPlugin.printErrorToConsole(launchInfo.getProject(),
"WARNING: Unknown device API version!");
} else {
@@ -660,7 +662,8 @@
deviceApiVersionName));
}
} else { // app requires a specific API level
- if (deviceApiVersionName == null || deviceApiVersionNumber == 0) {
+ if (deviceApiVersionName == null ||
+ deviceApiVersionNumber == AndroidManifestParser.INVALID_MIN_SDK) {
AdtPlugin.printToConsole(launchInfo.getProject(),
"WARNING: Unknown device API version!");
} else if (deviceApiVersionNumber < launchInfo.getRequiredApiVersionNumber()) {
diff --git a/tools/eclipse/plugins/com.android.ide.eclipse.adt/src/com/android/ide/eclipse/adt/launch/DelayedLaunchInfo.java b/tools/eclipse/plugins/com.android.ide.eclipse.adt/src/com/android/ide/eclipse/adt/launch/DelayedLaunchInfo.java
index a59518c..7dae56d 100644
--- a/tools/eclipse/plugins/com.android.ide.eclipse.adt/src/com/android/ide/eclipse/adt/launch/DelayedLaunchInfo.java
+++ b/tools/eclipse/plugins/com.android.ide.eclipse.adt/src/com/android/ide/eclipse/adt/launch/DelayedLaunchInfo.java
@@ -16,12 +16,13 @@
package com.android.ide.eclipse.adt.launch;
+import com.android.ddmlib.IDevice;
+import com.android.ide.eclipse.common.project.AndroidManifestParser;
+
import org.eclipse.core.resources.IFile;
import org.eclipse.core.resources.IProject;
import org.eclipse.core.runtime.IProgressMonitor;
-import com.android.ddmlib.IDevice;
-
/**
* A delayed launch waiting for a device to be present or ready before the
* application is launched.
@@ -50,7 +51,8 @@
/** debuggable attribute of the manifest file. */
private final Boolean mDebuggable;
- /** Required ApiVersionNumber by the app. 0 means no requirements */
+ /** Required ApiVersionNumber by the app. {@link AndroidManifestParser#INVALID_MIN_SDK} means
+ * no requirements */
private final int mRequiredApiVersionNumber;
private InstallRetryMode mRetryMode = InstallRetryMode.NEVER;
@@ -81,7 +83,8 @@
* @param launchAction action to perform after app install
* @param pack IFile to the package (.apk) file
* @param debuggable debuggable attribute of the app's manifest file.
- * @param requiredApiVersionNumber required SDK version by the app. 0 means no requirements.
+ * @param requiredApiVersionNumber required SDK version by the app.
+ * {@link AndroidManifestParser#INVALID_MIN_SDK} means no requirements.
* @param launch the launch object
* @param monitor progress monitor for launch
*/
diff --git a/tools/eclipse/plugins/com.android.ide.eclipse.adt/src/com/android/ide/eclipse/adt/launch/MainLaunchConfigTab.java b/tools/eclipse/plugins/com.android.ide.eclipse.adt/src/com/android/ide/eclipse/adt/launch/MainLaunchConfigTab.java
index 599da5f..30b0723 100644
--- a/tools/eclipse/plugins/com.android.ide.eclipse.adt/src/com/android/ide/eclipse/adt/launch/MainLaunchConfigTab.java
+++ b/tools/eclipse/plugins/com.android.ide.eclipse.adt/src/com/android/ide/eclipse/adt/launch/MainLaunchConfigTab.java
@@ -55,6 +55,11 @@
*/
public class MainLaunchConfigTab extends AbstractLaunchConfigurationTab {
+ /**
+ *
+ */
+ public static final String LAUNCH_TAB_IMAGE = "mainLaunchTab.png";
+
protected static final String EMPTY_STRING = ""; //$NON-NLS-1$
protected Text mProjText;
@@ -194,7 +199,7 @@
@Override
public Image getImage() {
- return AdtPlugin.getImageLoader().loadImage("mainLaunchTab.png", null);
+ return AdtPlugin.getImageLoader().loadImage(LAUNCH_TAB_IMAGE, null);
}
@@ -310,21 +315,8 @@
}
mProjText.setText(projectName);
- // get the list of projects
- IJavaProject[] projects = mProjectChooserHelper.getAndroidProjects(null);
-
- if (projects != null) {
- // look for the currently selected project
- IProject proj = null;
- for (IJavaProject p : projects) {
- if (p.getElementName().equals(projectName)) {
- proj = p.getProject();
- break;
- }
- }
-
- loadActivities(proj);
- }
+ IProject proj = mProjectChooserHelper.getAndroidProject(projectName);
+ loadActivities(proj);
// load the launch action.
mLaunchAction = LaunchConfigDelegate.DEFAULT_LAUNCH_ACTION;
diff --git a/tools/eclipse/plugins/com.android.ide.eclipse.adt/src/com/android/ide/eclipse/adt/launch/junit/AndroidJUnitLaunchAction.java b/tools/eclipse/plugins/com.android.ide.eclipse.adt/src/com/android/ide/eclipse/adt/launch/junit/AndroidJUnitLaunchAction.java
new file mode 100644
index 0000000..4dfe37d
--- /dev/null
+++ b/tools/eclipse/plugins/com.android.ide.eclipse.adt/src/com/android/ide/eclipse/adt/launch/junit/AndroidJUnitLaunchAction.java
@@ -0,0 +1,263 @@
+/*
+ * Copyright (C) 2009 The Android Open Source Project
+ *
+ * Licensed under the Eclipse Public License, Version 1.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.eclipse.org/org/documents/epl-v10.php
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+package com.android.ide.eclipse.adt.launch.junit;
+
+import com.android.ddmlib.IDevice;
+import com.android.ide.eclipse.adt.AdtPlugin;
+import com.android.ide.eclipse.adt.launch.DelayedLaunchInfo;
+import com.android.ide.eclipse.adt.launch.IAndroidLaunchAction;
+import com.android.ide.eclipse.adt.launch.junit.runtime.AndroidJUnitLaunchInfo;
+import com.android.ide.eclipse.adt.launch.junit.runtime.RemoteADTTestRunner;
+
+import org.eclipse.core.runtime.CoreException;
+import org.eclipse.core.runtime.IProgressMonitor;
+import org.eclipse.debug.core.DebugException;
+import org.eclipse.debug.core.ILaunch;
+import org.eclipse.debug.core.ILaunchConfiguration;
+import org.eclipse.debug.core.ILaunchManager;
+import org.eclipse.debug.core.model.IProcess;
+import org.eclipse.debug.core.model.IStreamsProxy;
+import org.eclipse.jdt.junit.launcher.JUnitLaunchConfigurationDelegate;
+import org.eclipse.jdt.launching.IVMRunner;
+import org.eclipse.jdt.launching.VMRunnerConfiguration;
+
+/**
+ * A launch action that executes a instrumentation test run on an Android device.
+ */
+class AndroidJUnitLaunchAction implements IAndroidLaunchAction {
+
+ private String mTestPackage;
+ private String mRunner;
+
+ /**
+ * Creates a AndroidJUnitLaunchAction.
+ *
+ * @param testPackage the Android application package that contains the tests to run
+ * @param runner the InstrumentationTestRunner that will execute the tests
+ */
+ public AndroidJUnitLaunchAction(String testPackage, String runner) {
+ mTestPackage = testPackage;
+ mRunner = runner;
+ }
+
+ /**
+ * Launch a instrumentation test run on given Android device.
+ * Reuses JDT JUnit launch delegate so results can be communicated back to JDT JUnit UI.
+ *
+ * @see com.android.ide.eclipse.adt.launch.IAndroidLaunchAction#doLaunchAction(com.android.ide.eclipse.adt.launch.AndroidLaunchController.DelayedLaunchInfo, com.android.ddmlib.Device)
+ */
+ public boolean doLaunchAction(DelayedLaunchInfo info, IDevice device) {
+ String msg = String.format("Launching instrumentation %s on device %s", mRunner,
+ device.getSerialNumber());
+ AdtPlugin.printToConsole(info.getProject(), msg);
+
+ try {
+ JUnitLaunchDelegate junitDelegate = new JUnitLaunchDelegate(info, device);
+ final String mode = info.isDebugMode() ? ILaunchManager.DEBUG_MODE :
+ ILaunchManager.RUN_MODE;
+ junitDelegate.launch(info.getLaunch().getLaunchConfiguration(), mode, info.getLaunch(),
+ info.getMonitor());
+
+ // TODO: need to add AMReceiver-type functionality somewhere
+ } catch (CoreException e) {
+ AdtPlugin.printErrorToConsole(info.getProject(), "Failed to launch test");
+ }
+ return true;
+ }
+
+ /**
+ * {@inheritDoc}
+ */
+ public String getLaunchDescription() {
+ return String.format("%s JUnit launch", mRunner);
+ }
+
+ /**
+ * Extends the JDT JUnit launch delegate to allow for JUnit UI reuse.
+ */
+ private class JUnitLaunchDelegate extends JUnitLaunchConfigurationDelegate {
+
+ private IDevice mDevice;
+ private DelayedLaunchInfo mLaunchInfo;
+
+ public JUnitLaunchDelegate(DelayedLaunchInfo info, IDevice device) {
+ mLaunchInfo = info;
+ mDevice = device;
+ }
+
+ /* (non-Javadoc)
+ * @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)
+ */
+ @Override
+ public synchronized void launch(ILaunchConfiguration configuration, String mode,
+ ILaunch launch, IProgressMonitor monitor) throws CoreException {
+ // TODO: is progress monitor adjustment needed here?
+ super.launch(configuration, mode, launch, monitor);
+ }
+
+ /* (non-Javadoc)
+ * @see org.eclipse.jdt.junit.launcher.JUnitLaunchConfigurationDelegate#verifyMainTypeName(org.eclipse.debug.core.ILaunchConfiguration)
+ */
+ @Override
+ public String verifyMainTypeName(ILaunchConfiguration configuration) throws CoreException {
+ return "com.android.ide.eclipse.adt.junit.internal.runner.RemoteAndroidTestRunner"; //$NON-NLS-1$
+ }
+
+ /**
+ * Overrides parent to return a VM Runner implementation which launches a thread, rather
+ * than a separate VM process
+ */
+ @Override
+ public IVMRunner getVMRunner(ILaunchConfiguration configuration, String mode)
+ throws CoreException {
+ return new VMTestRunner(new AndroidJUnitLaunchInfo(mLaunchInfo.getProject(),
+ mTestPackage, mRunner, mLaunchInfo.isDebugMode(), mDevice));
+ }
+
+ /* (non-Javadoc)
+ * @see org.eclipse.debug.core.model.LaunchConfigurationDelegate#getLaunch(org.eclipse.debug.core.ILaunchConfiguration, java.lang.String)
+ */
+ @Override
+ public ILaunch getLaunch(ILaunchConfiguration configuration, String mode)
+ throws CoreException {
+ return mLaunchInfo.getLaunch();
+ }
+ }
+
+ /**
+ * Provides a VM runner implementation which starts a thread implementation of a launch process
+ */
+ private static class VMTestRunner implements IVMRunner {
+
+ private final AndroidJUnitLaunchInfo mJUnitInfo;
+
+ VMTestRunner(AndroidJUnitLaunchInfo info) {
+ mJUnitInfo = info;
+ }
+
+ public void run(final VMRunnerConfiguration config, ILaunch launch,
+ IProgressMonitor monitor) throws CoreException {
+
+ TestRunnerProcess runnerProcess =
+ new TestRunnerProcess(config, launch, mJUnitInfo);
+ runnerProcess.start();
+ launch.addProcess(runnerProcess);
+ }
+ }
+
+ /**
+ * Launch process that executes the tests.
+ */
+ private static class TestRunnerProcess extends Thread implements IProcess {
+
+ private final VMRunnerConfiguration mRunConfig;
+ private final ILaunch mLaunch;
+ private final AndroidJUnitLaunchInfo mJUnitInfo;
+ private RemoteADTTestRunner mTestRunner = null;
+ private boolean mIsTerminated = false;
+
+ TestRunnerProcess(VMRunnerConfiguration runConfig, ILaunch launch,
+ AndroidJUnitLaunchInfo info) {
+ mRunConfig = runConfig;
+ mLaunch = launch;
+ mJUnitInfo = info;
+ }
+
+ /* (non-Javadoc)
+ * @see org.eclipse.debug.core.model.IProcess#getAttribute(java.lang.String)
+ */
+ public String getAttribute(String key) {
+ return null;
+ }
+
+ /* (non-Javadoc)
+ * @see org.eclipse.debug.core.model.IProcess#getExitValue()
+ */
+ public int getExitValue() throws DebugException {
+ return 0;
+ }
+
+ /* (non-Javadoc)
+ * @see org.eclipse.debug.core.model.IProcess#getLabel()
+ */
+ public String getLabel() {
+ return mLaunch.getLaunchMode();
+ }
+
+ /* (non-Javadoc)
+ * @see org.eclipse.debug.core.model.IProcess#getLaunch()
+ */
+ public ILaunch getLaunch() {
+ return mLaunch;
+ }
+
+ /* (non-Javadoc)
+ * @see org.eclipse.debug.core.model.IProcess#getStreamsProxy()
+ */
+ public IStreamsProxy getStreamsProxy() {
+ return null;
+ }
+
+ /* (non-Javadoc)
+ * @see org.eclipse.debug.core.model.IProcess#setAttribute(java.lang.String,
+ * java.lang.String)
+ */
+ public void setAttribute(String key, String value) {
+ // ignore
+ }
+
+ /* (non-Javadoc)
+ * @see org.eclipse.core.runtime.IAdaptable#getAdapter(java.lang.Class)
+ */
+ @SuppressWarnings("unchecked")
+ public Object getAdapter(Class adapter) {
+ return null;
+ }
+
+ /* (non-Javadoc)
+ * @see org.eclipse.debug.core.model.ITerminate#canTerminate()
+ */
+ public boolean canTerminate() {
+ return true;
+ }
+
+ /* (non-Javadoc)
+ * @see org.eclipse.debug.core.model.ITerminate#isTerminated()
+ */
+ public boolean isTerminated() {
+ return mIsTerminated;
+ }
+
+ /* (non-Javadoc)
+ * @see org.eclipse.debug.core.model.ITerminate#terminate()
+ */
+ public void terminate() throws DebugException {
+ if (mTestRunner != null) {
+ mTestRunner.terminate();
+ }
+ mIsTerminated = true;
+ }
+
+ /**
+ * Launches a test runner that will communicate results back to JDT JUnit UI
+ */
+ @Override
+ public void run() {
+ mTestRunner = new RemoteADTTestRunner();
+ mTestRunner.runTests(mRunConfig.getProgramArguments(), mJUnitInfo);
+ }
+ }
+}
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
new file mode 100755
index 0000000..05cc6ae
--- /dev/null
+++ b/tools/eclipse/plugins/com.android.ide.eclipse.adt/src/com/android/ide/eclipse/adt/launch/junit/AndroidJUnitLaunchConfigDelegate.java
@@ -0,0 +1,120 @@
+/*
+ * Copyright (C) 2009 The Android Open Source Project
+ *
+ * Licensed under the Eclipse Public License, Version 1.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.eclipse.org/org/documents/epl-v10.php
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+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.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.project.AndroidManifestParser;
+import com.android.ide.eclipse.common.project.BaseProjectHelper;
+
+import org.eclipse.core.resources.IFile;
+import org.eclipse.core.resources.IProject;
+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.internal.junit.launcher.JUnitLaunchConfigurationConstants;
+import org.eclipse.jdt.internal.junit.launcher.TestKindRegistry;
+
+/**
+ * Run configuration that can execute JUnit tests on an Android platform
+ * <p/>
+ * Will deploy apps on target Android platform by reusing functionality from ADT
+ * LaunchConfigDelegate, and then run JUnits tests by reusing functionality from JDT
+ * JUnitLaunchConfigDelegate.
+ */
+@SuppressWarnings("restriction") //$NON-NLS-1$
+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$
+ private static final String EMPTY_STRING = ""; //$NON-NLS-1$
+
+ @Override
+ protected void doLaunch(final ILaunchConfiguration configuration, final String mode,
+ IProgressMonitor monitor, IProject project, final AndroidLaunch androidLaunch,
+ AndroidLaunchConfiguration config, AndroidLaunchController controller,
+ IFile applicationPackage, AndroidManifestParser manifestParser) {
+
+ String testPackage = manifestParser.getPackage();
+ String runner = getRunnerFromConfig(configuration);
+ if (runner == null) {
+ AdtPlugin.displayError("Android Launch",
+ "An instrumention test runner is not specified!");
+ androidLaunch.stopLaunch();
+ return;
+ }
+
+ IAndroidLaunchAction junitLaunch = new AndroidJUnitLaunchAction(testPackage, runner);
+
+ controller.launch(project, mode, applicationPackage, manifestParser.getPackage(),
+ manifestParser.getDebuggable(), manifestParser.getApiLevelRequirement(),
+ junitLaunch, config, androidLaunch, monitor);
+ }
+
+ private String getRunnerFromConfig(ILaunchConfiguration configuration) {
+ String runner = EMPTY_STRING;
+ try {
+ runner = configuration.getAttribute(ATTR_INSTR_NAME, EMPTY_STRING);
+ } catch (CoreException e) {
+ AdtPlugin.log(e, "Error when retrieving instrumentation info from launch config"); //$NON-NLS-1$
+ }
+ if (runner.length() < 1) {
+ return null;
+ }
+ return runner;
+ }
+
+ /**
+ * 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
+ */
+ static String[] getInstrumentationsForProject(IProject project) {
+ if (project != null) {
+ try {
+ // parse the manifest for the list of instrumentations
+ AndroidManifestParser manifestParser = AndroidManifestParser.parse(
+ BaseProjectHelper.getJavaProject(project), null /* errorListener */,
+ true /* gatherData */, false /* markErrors */);
+ if (manifestParser != null) {
+ return manifestParser.getInstrumentations();
+ }
+ } catch (CoreException e) {
+ AdtPlugin.log(e, "%s: Error parsing AndroidManifest.xml", //$NON-NLS-1$
+ project.getName());
+ }
+ }
+ return null;
+ }
+
+ /**
+ * Helper method to set JUnit-related attributes expected by JDT JUnit runner
+ *
+ * @param config the launch configuration to modify
+ */
+ static void setJUnitDefaults(ILaunchConfigurationWorkingCopy config) {
+ // set the test runner to JUnit3 to placate JDT JUnit runner logic
+ config.setAttribute(JUnitLaunchConfigurationConstants.ATTR_TEST_RUNNER_KIND,
+ TestKindRegistry.JUNIT3_TEST_KIND_ID);
+ }
+}
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
new file mode 100644
index 0000000..5fbda98
--- /dev/null
+++ b/tools/eclipse/plugins/com.android.ide.eclipse.adt/src/com/android/ide/eclipse/adt/launch/junit/AndroidJUnitLaunchConfigurationTab.java
@@ -0,0 +1,967 @@
+/*
+ * Copyright (C) 2009 The Android Open Source Project
+ *
+ * Licensed under the Eclipse Public License, Version 1.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.eclipse.org/org/documents/epl-v10.php
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+package com.android.ide.eclipse.adt.launch.junit;
+
+import com.android.ide.eclipse.adt.AdtPlugin;
+import com.android.ide.eclipse.adt.launch.MainLaunchConfigTab;
+import com.android.ide.eclipse.common.AndroidConstants;
+import com.android.ide.eclipse.common.project.ProjectChooserHelper;
+
+import org.eclipse.core.resources.IProject;
+import org.eclipse.core.resources.IResource;
+import org.eclipse.core.resources.IWorkspaceRoot;
+import org.eclipse.core.resources.ResourcesPlugin;
+import org.eclipse.core.runtime.CoreException;
+import org.eclipse.core.runtime.IPath;
+import org.eclipse.core.runtime.IStatus;
+import org.eclipse.core.runtime.Path;
+import org.eclipse.debug.core.ILaunchConfiguration;
+import org.eclipse.debug.core.ILaunchConfigurationWorkingCopy;
+import org.eclipse.debug.ui.AbstractLaunchConfigurationTab;
+import org.eclipse.jdt.core.IJavaElement;
+import org.eclipse.jdt.core.IJavaModel;
+import org.eclipse.jdt.core.IJavaProject;
+import org.eclipse.jdt.core.IPackageFragment;
+import org.eclipse.jdt.core.IPackageFragmentRoot;
+import org.eclipse.jdt.core.ISourceReference;
+import org.eclipse.jdt.core.IType;
+import org.eclipse.jdt.core.JavaCore;
+import org.eclipse.jdt.core.JavaModelException;
+import org.eclipse.jdt.internal.junit.Messages;
+import org.eclipse.jdt.internal.junit.launcher.ITestKind;
+import org.eclipse.jdt.internal.junit.launcher.JUnitLaunchConfigurationConstants;
+import org.eclipse.jdt.internal.junit.launcher.JUnitMigrationDelegate;
+import org.eclipse.jdt.internal.junit.launcher.TestKindRegistry;
+import org.eclipse.jdt.internal.junit.launcher.TestSelectionDialog;
+import org.eclipse.jdt.internal.junit.ui.JUnitMessages;
+import org.eclipse.jdt.internal.junit.util.LayoutUtil;
+import org.eclipse.jdt.internal.junit.util.TestSearchEngine;
+import org.eclipse.jdt.internal.ui.wizards.TypedElementSelectionValidator;
+import org.eclipse.jdt.internal.ui.wizards.TypedViewerFilter;
+import org.eclipse.jdt.launching.IJavaLaunchConfigurationConstants;
+import org.eclipse.jdt.ui.JavaElementComparator;
+import org.eclipse.jdt.ui.JavaElementLabelProvider;
+import org.eclipse.jdt.ui.StandardJavaElementContentProvider;
+import org.eclipse.jface.dialogs.Dialog;
+import org.eclipse.jface.viewers.ILabelProvider;
+import org.eclipse.jface.viewers.ISelection;
+import org.eclipse.jface.viewers.IStructuredSelection;
+import org.eclipse.jface.viewers.Viewer;
+import org.eclipse.jface.viewers.ViewerFilter;
+import org.eclipse.jface.window.Window;
+import org.eclipse.swt.SWT;
+import org.eclipse.swt.events.ModifyEvent;
+import org.eclipse.swt.events.ModifyListener;
+import org.eclipse.swt.events.SelectionAdapter;
+import org.eclipse.swt.events.SelectionEvent;
+import org.eclipse.swt.events.SelectionListener;
+import org.eclipse.swt.graphics.Image;
+import org.eclipse.swt.layout.GridData;
+import org.eclipse.swt.layout.GridLayout;
+import org.eclipse.swt.widgets.Button;
+import org.eclipse.swt.widgets.Combo;
+import org.eclipse.swt.widgets.Composite;
+import org.eclipse.swt.widgets.Label;
+import org.eclipse.swt.widgets.Shell;
+import org.eclipse.swt.widgets.Text;
+import org.eclipse.ui.IEditorInput;
+import org.eclipse.ui.IEditorPart;
+import org.eclipse.ui.IWorkbenchPage;
+import org.eclipse.ui.IWorkbenchWindow;
+import org.eclipse.ui.PlatformUI;
+import org.eclipse.ui.dialogs.ElementTreeSelectionDialog;
+import org.eclipse.ui.dialogs.SelectionDialog;
+
+import java.lang.reflect.InvocationTargetException;
+
+/**
+ * The launch config UI tab for Android JUnit
+ * <p/>
+ * Based on org.eclipse.jdt.junit.launcher.JUnitLaunchConfigurationTab
+ */
+@SuppressWarnings("restriction")
+public class AndroidJUnitLaunchConfigurationTab extends AbstractLaunchConfigurationTab {
+
+ // Project UI widgets
+ private Label mProjLabel;
+ private Text mProjText;
+ private Button mProjButton;
+
+ // Test class UI widgets
+ private Text mTestText;
+ private Button mSearchButton;
+ private String mOriginalTestMethodName;
+ private Label mTestMethodLabel;
+ private Text mContainerText;
+ private IJavaElement mContainerElement;
+ private final ILabelProvider mJavaElementLabelProvider = new JavaElementLabelProvider();
+
+ private Button mContainerSearchButton;
+ private Button mTestContainerRadioButton;
+ private Button mTestRadioButton;
+ private Label mTestLabel;
+
+ // Android specific members
+ private Image mTabIcon = null;
+ private Combo mInstrumentationCombo;
+ private static final String EMPTY_STRING = ""; //$NON-NLS-1$
+ private String[] mInstrumentations = null;
+ private ProjectChooserHelper mProjectChooserHelper;
+
+ /* (non-Javadoc)
+ * @see org.eclipse.debug.ui.ILaunchConfigurationTab#createControl(org.eclipse.swt.widgets.Composite)
+ */
+ public void createControl(Composite parent) {
+ mProjectChooserHelper = new ProjectChooserHelper(parent.getShell());
+
+ Composite comp = new Composite(parent, SWT.NONE);
+ setControl(comp);
+
+ GridLayout topLayout = new GridLayout();
+ topLayout.numColumns = 3;
+ comp.setLayout(topLayout);
+
+ createSingleTestSection(comp);
+ createTestContainerSelectionGroup(comp);
+
+ createSpacer(comp);
+
+ createInstrumentationGroup(comp);
+
+ createSpacer(comp);
+
+ Dialog.applyDialogFont(comp);
+ // TODO: add help link here when available
+ //PlatformUI.getWorkbench().getHelpSystem().setHelp(getControl(),
+ // IJUnitHelpContextIds.LAUNCH_CONFIGURATION_DIALOG_JUNIT_MAIN_TAB);
+ validatePage();
+ }
+
+
+ private void createSpacer(Composite comp) {
+ Label label = new Label(comp, SWT.NONE);
+ GridData gd = new GridData();
+ gd.horizontalSpan = 3;
+ label.setLayoutData(gd);
+ }
+
+ private void createSingleTestSection(Composite comp) {
+ mTestRadioButton = new Button(comp, SWT.RADIO);
+ mTestRadioButton.setText(JUnitMessages.JUnitLaunchConfigurationTab_label_oneTest);
+ GridData gd = new GridData();
+ gd.horizontalSpan = 3;
+ mTestRadioButton.setLayoutData(gd);
+ mTestRadioButton.addSelectionListener(new SelectionAdapter() {
+ @Override
+ public void widgetSelected(SelectionEvent e) {
+ if (mTestRadioButton.getSelection()) {
+ testModeChanged();
+ }
+ }
+ });
+
+ mProjLabel = new Label(comp, SWT.NONE);
+ mProjLabel.setText(JUnitMessages.JUnitLaunchConfigurationTab_label_project);
+ gd = new GridData();
+ gd.horizontalIndent = 25;
+ mProjLabel.setLayoutData(gd);
+
+ mProjText = new Text(comp, SWT.SINGLE | SWT.BORDER);
+ mProjText.setLayoutData(new GridData(GridData.FILL_HORIZONTAL));
+ mProjText.addModifyListener(new ModifyListener() {
+ public void modifyText(ModifyEvent evt) {
+ validatePage();
+ updateLaunchConfigurationDialog();
+ mSearchButton.setEnabled(mTestRadioButton.getSelection() &&
+ mProjText.getText().length() > 0);
+ }
+ });
+
+ mProjButton = new Button(comp, SWT.PUSH);
+ mProjButton.setText(JUnitMessages.JUnitLaunchConfigurationTab_label_browse);
+ mProjButton.addSelectionListener(new SelectionAdapter() {
+ @Override
+ public void widgetSelected(SelectionEvent evt) {
+ handleProjectButtonSelected();
+ }
+ });
+ setButtonGridData(mProjButton);
+
+ mTestLabel = new Label(comp, SWT.NONE);
+ gd = new GridData();
+ gd.horizontalIndent = 25;
+ mTestLabel.setLayoutData(gd);
+ mTestLabel.setText(JUnitMessages.JUnitLaunchConfigurationTab_label_test);
+
+
+ mTestText = new Text(comp, SWT.SINGLE | SWT.BORDER);
+ mTestText.setLayoutData(new GridData(GridData.FILL_HORIZONTAL));
+ mTestText.addModifyListener(new ModifyListener() {
+ public void modifyText(ModifyEvent evt) {
+ validatePage();
+ updateLaunchConfigurationDialog();
+ }
+ });
+
+ mSearchButton = new Button(comp, SWT.PUSH);
+ mSearchButton.setEnabled(mProjText.getText().length() > 0);
+ mSearchButton.setText(JUnitMessages.JUnitLaunchConfigurationTab_label_search);
+ mSearchButton.addSelectionListener(new SelectionAdapter() {
+ @Override
+ public void widgetSelected(SelectionEvent evt) {
+ handleSearchButtonSelected();
+ }
+ });
+ setButtonGridData(mSearchButton);
+
+ new Label(comp, SWT.NONE);
+
+ mTestMethodLabel = new Label(comp, SWT.NONE);
+ mTestMethodLabel.setText(""); //$NON-NLS-1$
+ gd = new GridData();
+ gd.horizontalSpan = 2;
+ mTestMethodLabel.setLayoutData(gd);
+ }
+
+ private void createTestContainerSelectionGroup(Composite comp) {
+ mTestContainerRadioButton = new Button(comp, SWT.RADIO);
+ mTestContainerRadioButton.setText(
+ JUnitMessages.JUnitLaunchConfigurationTab_label_containerTest);
+ GridData gd = new GridData();
+ gd.horizontalSpan = 3;
+ mTestContainerRadioButton.setLayoutData(gd);
+ mTestContainerRadioButton.addSelectionListener(new SelectionListener() {
+ public void widgetSelected(SelectionEvent e) {
+ if (mTestContainerRadioButton.getSelection()) {
+ testModeChanged();
+ }
+ }
+ public void widgetDefaultSelected(SelectionEvent e) {
+ }
+ });
+
+ mContainerText = new Text(comp, SWT.SINGLE | SWT.BORDER | SWT.READ_ONLY);
+ gd = new GridData(GridData.FILL_HORIZONTAL);
+ gd.horizontalIndent = 25;
+ gd.horizontalSpan = 2;
+ mContainerText.setLayoutData(gd);
+ mContainerText.addModifyListener(new ModifyListener() {
+ public void modifyText(ModifyEvent evt) {
+ updateLaunchConfigurationDialog();
+ }
+ });
+
+ mContainerSearchButton = new Button(comp, SWT.PUSH);
+ mContainerSearchButton.setText(JUnitMessages.JUnitLaunchConfigurationTab_label_search);
+ mContainerSearchButton.addSelectionListener(new SelectionAdapter() {
+ @Override
+ public void widgetSelected(SelectionEvent evt) {
+ handleContainerSearchButtonSelected();
+ }
+ });
+ setButtonGridData(mContainerSearchButton);
+ }
+
+ private void createInstrumentationGroup(Composite comp) {
+ Label loaderLabel = new Label(comp, SWT.NONE);
+ loaderLabel.setText("Instrumentation runner:");
+ GridData gd = new GridData();
+ gd.horizontalIndent = 0;
+ loaderLabel.setLayoutData(gd);
+
+ mInstrumentationCombo = new Combo(comp, SWT.DROP_DOWN | SWT.READ_ONLY);
+ gd = new GridData(GridData.FILL_HORIZONTAL);
+ mInstrumentationCombo.setLayoutData(new GridData(GridData.FILL_HORIZONTAL));
+ mInstrumentationCombo.clearSelection();
+ mInstrumentationCombo.addSelectionListener(new SelectionAdapter() {
+ @Override
+ public void widgetSelected(SelectionEvent e) {
+ validatePage();
+ updateLaunchConfigurationDialog();
+ }
+ });
+ }
+
+ private void handleContainerSearchButtonSelected() {
+ IJavaElement javaElement = chooseContainer(mContainerElement);
+ if (javaElement != null) {
+ setContainerElement(javaElement);
+ }
+ }
+
+ private void setContainerElement(IJavaElement javaElement) {
+ mContainerElement = javaElement;
+ mContainerText.setText(getPresentationName(javaElement));
+ validatePage();
+ updateLaunchConfigurationDialog();
+ }
+
+ /* (non-Javadoc)
+ * @see org.eclipse.debug.ui.ILaunchConfigurationTab#initializeFrom(org.eclipse.debug.core.ILaunchConfiguration)
+ */
+ public void initializeFrom(ILaunchConfiguration config) {
+ String projectName = updateProjectFromConfig(config);
+ String containerHandle = EMPTY_STRING;
+ try {
+ containerHandle = config.getAttribute(
+ JUnitLaunchConfigurationConstants.ATTR_TEST_CONTAINER, EMPTY_STRING);
+ } catch (CoreException ce) {
+ // ignore
+ }
+
+ if (containerHandle.length() > 0) {
+ updateTestContainerFromConfig(config);
+ } else {
+ updateTestTypeFromConfig(config);
+ }
+
+ IProject proj = mProjectChooserHelper.getAndroidProject(projectName);
+ loadInstrumentations(proj);
+ updateInstrumentationFromConfig(config);
+
+ validatePage();
+ }
+
+ private void updateInstrumentationFromConfig(ILaunchConfiguration config) {
+ boolean found = false;
+ try {
+ String currentInstrumentation = config.getAttribute(
+ AndroidJUnitLaunchConfigDelegate.ATTR_INSTR_NAME, EMPTY_STRING);
+ if (mInstrumentations != null) {
+ // look for the name of the instrumentation in the combo.
+ for (int i = 0; i < mInstrumentations.length; i++) {
+ if (currentInstrumentation.equals(mInstrumentations[i])) {
+ found = true;
+ mInstrumentationCombo.select(i);
+ break;
+ }
+ }
+ }
+ } catch (CoreException ce) {
+ // ignore
+ }
+ if (!found) {
+ mInstrumentationCombo.clearSelection();
+ }
+ }
+
+ private String updateProjectFromConfig(ILaunchConfiguration config) {
+ String projectName = EMPTY_STRING;
+ try {
+ projectName = config.getAttribute(IJavaLaunchConfigurationConstants.ATTR_PROJECT_NAME,
+ EMPTY_STRING);
+ } catch (CoreException ce) {
+ // ignore
+ }
+ mProjText.setText(projectName);
+ return projectName;
+ }
+
+ private void updateTestTypeFromConfig(ILaunchConfiguration config) {
+ String testTypeName = EMPTY_STRING;
+ mOriginalTestMethodName = EMPTY_STRING;
+ try {
+ testTypeName = config.getAttribute(
+ IJavaLaunchConfigurationConstants.ATTR_MAIN_TYPE_NAME, ""); //$NON-NLS-1$
+ mOriginalTestMethodName = config.getAttribute(
+ JUnitLaunchConfigurationConstants.ATTR_TEST_METHOD_NAME, ""); //$NON-NLS-1$
+ } catch (CoreException ce) {
+ // ignore
+ }
+ mTestRadioButton.setSelection(true);
+ setEnableSingleTestGroup(true);
+ setEnableContainerTestGroup(false);
+ mTestContainerRadioButton.setSelection(false);
+ mTestText.setText(testTypeName);
+ mContainerText.setText(EMPTY_STRING);
+ setTestMethodLabel(mOriginalTestMethodName);
+ }
+
+ private void setTestMethodLabel(String testMethodName) {
+ if (!EMPTY_STRING.equals(testMethodName)) {
+ mTestMethodLabel.setText(
+ JUnitMessages.JUnitLaunchConfigurationTab_label_method +
+ mOriginalTestMethodName);
+ } else {
+ mTestMethodLabel.setText(EMPTY_STRING);
+ }
+ }
+
+ private void updateTestContainerFromConfig(ILaunchConfiguration config) {
+ String containerHandle = EMPTY_STRING;
+ IJavaElement containerElement = null;
+ try {
+ containerHandle = config.getAttribute(
+ JUnitLaunchConfigurationConstants.ATTR_TEST_CONTAINER, EMPTY_STRING);
+ if (containerHandle.length() > 0) {
+ containerElement = JavaCore.create(containerHandle);
+ }
+ } catch (CoreException ce) {
+ // ignore
+ }
+ if (containerElement != null) {
+ mContainerElement = containerElement;
+ }
+ mTestContainerRadioButton.setSelection(true);
+ setEnableSingleTestGroup(false);
+ setEnableContainerTestGroup(true);
+ mTestRadioButton.setSelection(false);
+ if (mContainerElement != null) {
+ mContainerText.setText(getPresentationName(mContainerElement));
+ }
+ mTestText.setText(EMPTY_STRING);
+ }
+
+ /*
+ * (non-Javadoc)
+ * @see org.eclipse.debug.ui.ILaunchConfigurationTab#performApply(org.eclipse.debug.core.ILaunchConfigurationWorkingCopy)
+ */
+ public void performApply(ILaunchConfigurationWorkingCopy config) {
+ if (mTestContainerRadioButton.getSelection() && mContainerElement != null) {
+ config.setAttribute(IJavaLaunchConfigurationConstants.ATTR_PROJECT_NAME,
+ mContainerElement.getJavaProject().getElementName());
+ config.setAttribute(JUnitLaunchConfigurationConstants.ATTR_TEST_CONTAINER,
+ mContainerElement.getHandleIdentifier());
+ config.setAttribute(IJavaLaunchConfigurationConstants.ATTR_MAIN_TYPE_NAME,
+ EMPTY_STRING);
+ //workaround for Eclipse bug 65399
+ config.setAttribute(JUnitLaunchConfigurationConstants.ATTR_TEST_METHOD_NAME,
+ EMPTY_STRING);
+ } else {
+ config.setAttribute(IJavaLaunchConfigurationConstants.ATTR_PROJECT_NAME,
+ mProjText.getText());
+ config.setAttribute(IJavaLaunchConfigurationConstants.ATTR_MAIN_TYPE_NAME,
+ mTestText.getText());
+ config.setAttribute(JUnitLaunchConfigurationConstants.ATTR_TEST_CONTAINER,
+ EMPTY_STRING);
+ config.setAttribute(JUnitLaunchConfigurationConstants.ATTR_TEST_METHOD_NAME,
+ mOriginalTestMethodName);
+ }
+ try {
+ mapResources(config);
+ } catch (CoreException e) {
+ // TODO: does the real error need to be extracted out of CoreException
+ AdtPlugin.log(e, "Error occurred saving configuration");
+ }
+ AndroidJUnitLaunchConfigDelegate.setJUnitDefaults(config);
+
+ config.setAttribute(AndroidJUnitLaunchConfigDelegate.ATTR_INSTR_NAME,
+ getSelectedInstrumentation());
+ }
+
+ private void mapResources(ILaunchConfigurationWorkingCopy config) throws CoreException {
+ JUnitMigrationDelegate.mapResources(config);
+ }
+
+ /* (non-Javadoc)
+ * @see org.eclipse.debug.ui.AbstractLaunchConfigurationTab#dispose()
+ */
+ @Override
+ public void dispose() {
+ super.dispose();
+ if (mTabIcon != null) {
+ mTabIcon.dispose();
+ mTabIcon = null;
+ }
+ mJavaElementLabelProvider.dispose();
+ }
+
+ /* (non-Javadoc)
+ * @see org.eclipse.debug.ui.AbstractLaunchConfigurationTab#getImage()
+ */
+ @Override
+ public Image getImage() {
+ // reuse icon from the Android App Launch config tab
+ if (mTabIcon == null) {
+ mTabIcon= AdtPlugin.getImageLoader().loadImage(MainLaunchConfigTab.LAUNCH_TAB_IMAGE,
+ null);
+ }
+ return mTabIcon;
+ }
+
+ /**
+ * Show a dialog that lists all main types
+ */
+ private void handleSearchButtonSelected() {
+ Shell shell = getShell();
+
+ IJavaProject javaProject = getJavaProject();
+
+ IType[] types = new IType[0];
+ boolean[] radioSetting = new boolean[2];
+ try {
+ // fix for Eclipse bug 66922 Wrong radio behaviour when switching
+ // remember the selected radio button
+ radioSetting[0] = mTestRadioButton.getSelection();
+ radioSetting[1] = mTestContainerRadioButton.getSelection();
+
+ types = TestSearchEngine.findTests(getLaunchConfigurationDialog(), javaProject,
+ getTestKind());
+ } catch (InterruptedException e) {
+ setErrorMessage(e.getMessage());
+ return;
+ } catch (InvocationTargetException e) {
+ AdtPlugin.log(e.getTargetException(), "Error finding test types");
+ return;
+ } finally {
+ mTestRadioButton.setSelection(radioSetting[0]);
+ mTestContainerRadioButton.setSelection(radioSetting[1]);
+ }
+
+ SelectionDialog dialog = new TestSelectionDialog(shell, types);
+ dialog.setTitle(JUnitMessages.JUnitLaunchConfigurationTab_testdialog_title);
+ dialog.setMessage(JUnitMessages.JUnitLaunchConfigurationTab_testdialog_message);
+ if (dialog.open() == Window.CANCEL) {
+ return;
+ }
+
+ Object[] results = dialog.getResult();
+ if ((results == null) || (results.length < 1)) {
+ return;
+ }
+ IType type = (IType) results[0];
+
+ if (type != null) {
+ mTestText.setText(type.getFullyQualifiedName('.'));
+ javaProject = type.getJavaProject();
+ mProjText.setText(javaProject.getElementName());
+ }
+ }
+
+ private ITestKind getTestKind() {
+ // harddcode this to JUnit 3
+ return TestKindRegistry.getDefault().getKind(TestKindRegistry.JUNIT3_TEST_KIND_ID);
+ }
+
+ /**
+ * Show a dialog that lets the user select a Android project. This in turn provides
+ * context for the main type, allowing the user to key a main type name, or
+ * constraining the search for main types to the specified project.
+ */
+ private void handleProjectButtonSelected() {
+ IJavaProject project = mProjectChooserHelper.chooseJavaProject(getProjectName());
+ if (project == null) {
+ return;
+ }
+
+ String projectName = project.getElementName();
+ mProjText.setText(projectName);
+ loadInstrumentations(project.getProject());
+ }
+
+ /**
+ * Return the IJavaProject corresponding to the project name in the project name
+ * text field, or null if the text does not match a Android project name.
+ */
+ private IJavaProject getJavaProject() {
+ String projectName = getProjectName();
+ return getJavaModel().getJavaProject(projectName);
+ }
+
+ /**
+ * Returns the name of the currently specified project. Null if no project is selected.
+ */
+ private String getProjectName() {
+ String projectName = mProjText.getText().trim();
+ if (projectName.length() < 1) {
+ return null;
+ }
+ return projectName;
+ }
+
+ /**
+ * Convenience method to get the workspace root.
+ */
+ private IWorkspaceRoot getWorkspaceRoot() {
+ return ResourcesPlugin.getWorkspace().getRoot();
+ }
+
+ /**
+ * Convenience method to get access to the java model.
+ */
+ private IJavaModel getJavaModel() {
+ return JavaCore.create(getWorkspaceRoot());
+ }
+
+ /* (non-Javadoc)
+ * @see org.eclipse.debug.ui.AbstractLaunchConfigurationTab#isValid(org.eclipse.debug.core.ILaunchConfiguration)
+ */
+ @Override
+ public boolean isValid(ILaunchConfiguration config) {
+ validatePage();
+ return getErrorMessage() == null;
+ }
+
+ private void testModeChanged() {
+ boolean isSingleTestMode = mTestRadioButton.getSelection();
+ setEnableSingleTestGroup(isSingleTestMode);
+ setEnableContainerTestGroup(!isSingleTestMode);
+ if (!isSingleTestMode && mContainerText.getText().length() == 0) {
+ String projText = mProjText.getText();
+ if (Path.EMPTY.isValidSegment(projText)) {
+ IJavaProject javaProject = getJavaModel().getJavaProject(projText);
+ if (javaProject != null && javaProject.exists()) {
+ setContainerElement(javaProject);
+ }
+ }
+ }
+ validatePage();
+ updateLaunchConfigurationDialog();
+ }
+
+ private void validatePage() {
+ setErrorMessage(null);
+ setMessage(null);
+
+ if (mTestContainerRadioButton.getSelection()) {
+ if (mContainerElement == null) {
+ setErrorMessage(JUnitMessages.JUnitLaunchConfigurationTab_error_noContainer);
+ return;
+ }
+ validateJavaProject(mContainerElement.getJavaProject());
+ return;
+ }
+
+ String projectName = mProjText.getText().trim();
+ if (projectName.length() == 0) {
+ setErrorMessage(JUnitMessages.JUnitLaunchConfigurationTab_error_projectnotdefined);
+ return;
+ }
+
+ IStatus status = ResourcesPlugin.getWorkspace().validatePath(IPath.SEPARATOR + projectName,
+ IResource.PROJECT);
+ if (!status.isOK() || !Path.ROOT.isValidSegment(projectName)) {
+ setErrorMessage(Messages.format(
+ JUnitMessages.JUnitLaunchConfigurationTab_error_invalidProjectName,
+ projectName));
+ return;
+ }
+
+ IProject project = getWorkspaceRoot().getProject(projectName);
+ if (!project.exists()) {
+ setErrorMessage(JUnitMessages.JUnitLaunchConfigurationTab_error_projectnotexists);
+ return;
+ }
+ IJavaProject javaProject = JavaCore.create(project);
+ validateJavaProject(javaProject);
+
+ try {
+ if (!project.hasNature(AndroidConstants.NATURE)) {
+ setErrorMessage("Specified project is not an Android project");
+ return;
+ }
+ String className = mTestText.getText().trim();
+ if (className.length() == 0) {
+ setErrorMessage(JUnitMessages.JUnitLaunchConfigurationTab_error_testnotdefined);
+ return;
+ }
+ if (javaProject.findType(className) == null) {
+ setErrorMessage(Messages.format(
+ JUnitMessages.JUnitLaunchConfigurationTab_error_test_class_not_found,
+ new String[] { className, projectName }));
+ return;
+ }
+ } catch (CoreException e) {
+ AdtPlugin.log(e, "validatePage failed");
+ }
+
+ validateInstrumentation(javaProject);
+ }
+
+ private void validateJavaProject(IJavaProject javaProject) {
+ if (!TestSearchEngine.hasTestCaseType(javaProject)) {
+ setErrorMessage(JUnitMessages.JUnitLaunchConfigurationTab_error_testcasenotonpath);
+ return;
+ }
+ }
+
+ private void validateInstrumentation(IJavaProject javaProject) {
+ if (mInstrumentations == null || mInstrumentations.length < 1) {
+ setErrorMessage("Specified project has no defined instrumentations");
+ }
+ String instrumentation = getSelectedInstrumentation();
+ if (instrumentation == null) {
+ setErrorMessage("Instrumentation not specified");
+ }
+ }
+
+ private String getSelectedInstrumentation() {
+ int selectionIndex = mInstrumentationCombo.getSelectionIndex();
+ if (mInstrumentations != null && selectionIndex >= 0 &&
+ selectionIndex < mInstrumentations.length) {
+ return mInstrumentations[selectionIndex];
+ }
+ return null;
+ }
+
+ private void setEnableContainerTestGroup(boolean enabled) {
+ mContainerSearchButton.setEnabled(enabled);
+ mContainerText.setEnabled(enabled);
+ }
+
+ private void setEnableSingleTestGroup(boolean enabled) {
+ mProjLabel.setEnabled(enabled);
+ mProjText.setEnabled(enabled);
+ mProjButton.setEnabled(enabled);
+ mTestLabel.setEnabled(enabled);
+ mTestText.setEnabled(enabled);
+ mSearchButton.setEnabled(enabled && mProjText.getText().length() > 0);
+ mTestMethodLabel.setEnabled(enabled);
+ }
+
+ /* (non-Javadoc)
+ * @see org.eclipse.debug.ui.ILaunchConfigurationTab#setDefaults(org.eclipse.debug.core.ILaunchConfigurationWorkingCopy)
+ */
+ public void setDefaults(ILaunchConfigurationWorkingCopy config) {
+ IJavaElement javaElement = getContext();
+ if (javaElement != null) {
+ initializeJavaProject(javaElement, config);
+ } else {
+ // We set empty attributes for project & main type so that when one config is
+ // compared to another, the existence of empty attributes doesn't cause an
+ // incorrect result (the performApply() method can result in empty values
+ // for these attributes being set on a config if there is nothing in the
+ // corresponding text boxes)
+ config.setAttribute(IJavaLaunchConfigurationConstants.ATTR_PROJECT_NAME, EMPTY_STRING);
+ config.setAttribute(JUnitLaunchConfigurationConstants.ATTR_TEST_CONTAINER,
+ EMPTY_STRING);
+ }
+ initializeTestAttributes(javaElement, config);
+ }
+
+ private void initializeTestAttributes(IJavaElement javaElement,
+ ILaunchConfigurationWorkingCopy config) {
+ if (javaElement != null && javaElement.getElementType() < IJavaElement.COMPILATION_UNIT) {
+ initializeTestContainer(javaElement, config);
+ } else {
+ initializeTestType(javaElement, config);
+ }
+ }
+
+ private void initializeTestContainer(IJavaElement javaElement,
+ ILaunchConfigurationWorkingCopy config) {
+ config.setAttribute(JUnitLaunchConfigurationConstants.ATTR_TEST_CONTAINER,
+ javaElement.getHandleIdentifier());
+ initializeName(config, javaElement.getElementName());
+ }
+
+ private void initializeName(ILaunchConfigurationWorkingCopy config, String name) {
+ if (name == null) {
+ name = EMPTY_STRING;
+ }
+ if (name.length() > 0) {
+ int index = name.lastIndexOf('.');
+ if (index > 0) {
+ name = name.substring(index + 1);
+ }
+ name = getLaunchConfigurationDialog().generateName(name);
+ config.rename(name);
+ }
+ }
+
+ /**
+ * Sets the main type & name attributes on the working copy based on the IJavaElement
+ */
+ private void initializeTestType(IJavaElement javaElement,
+ ILaunchConfigurationWorkingCopy config) {
+ String name = EMPTY_STRING;
+ String testKindId = null;
+ try {
+ // only do a search for compilation units or class files or source references
+ if (javaElement instanceof ISourceReference) {
+ ITestKind testKind = TestKindRegistry.getContainerTestKind(javaElement);
+ testKindId = testKind.getId();
+
+ IType[] types = TestSearchEngine.findTests(getLaunchConfigurationDialog(),
+ javaElement, testKind);
+ if ((types == null) || (types.length < 1)) {
+ return;
+ }
+ // Simply grab the first main type found in the searched element
+ name = types[0].getFullyQualifiedName('.');
+
+ }
+ } catch (InterruptedException ie) {
+ // ignore
+ } catch (InvocationTargetException ite) {
+ // ignore
+ }
+ config.setAttribute(IJavaLaunchConfigurationConstants.ATTR_MAIN_TYPE_NAME, name);
+ if (testKindId != null) {
+ config.setAttribute(JUnitLaunchConfigurationConstants.ATTR_TEST_RUNNER_KIND,
+ testKindId);
+ }
+ initializeName(config, name);
+ }
+
+ /* (non-Javadoc)
+ * @see org.eclipse.debug.ui.ILaunchConfigurationTab#getName()
+ */
+ public String getName() {
+ return JUnitMessages.JUnitLaunchConfigurationTab_tab_label;
+ }
+
+ @SuppressWarnings("unchecked")
+ private IJavaElement chooseContainer(IJavaElement initElement) {
+ Class[] acceptedClasses = new Class[] { IPackageFragmentRoot.class, IJavaProject.class,
+ IPackageFragment.class };
+ TypedElementSelectionValidator validator = new TypedElementSelectionValidator(
+ acceptedClasses, false) {
+ @Override
+ public boolean isSelectedValid(Object element) {
+ return true;
+ }
+ };
+
+ acceptedClasses = new Class[] { IJavaModel.class, IPackageFragmentRoot.class,
+ IJavaProject.class, IPackageFragment.class };
+ ViewerFilter filter = new TypedViewerFilter(acceptedClasses) {
+ @Override
+ public boolean select(Viewer viewer, Object parent, Object element) {
+ if (element instanceof IPackageFragmentRoot &&
+ ((IPackageFragmentRoot) element).isArchive()) {
+ return false;
+ }
+ try {
+ if (element instanceof IPackageFragment &&
+ !((IPackageFragment) element).hasChildren()) {
+ return false;
+ }
+ } catch (JavaModelException e) {
+ return false;
+ }
+ return super.select(viewer, parent, element);
+ }
+ };
+
+ StandardJavaElementContentProvider provider = new StandardJavaElementContentProvider();
+ ILabelProvider labelProvider = new JavaElementLabelProvider(
+ JavaElementLabelProvider.SHOW_DEFAULT);
+ ElementTreeSelectionDialog dialog = new ElementTreeSelectionDialog(getShell(),
+ labelProvider, provider);
+ dialog.setValidator(validator);
+ dialog.setComparator(new JavaElementComparator());
+ dialog.setTitle(JUnitMessages.JUnitLaunchConfigurationTab_folderdialog_title);
+ dialog.setMessage(JUnitMessages.JUnitLaunchConfigurationTab_folderdialog_message);
+ dialog.addFilter(filter);
+ dialog.setInput(JavaCore.create(getWorkspaceRoot()));
+ dialog.setInitialSelection(initElement);
+ dialog.setAllowMultiple(false);
+
+ if (dialog.open() == Window.OK) {
+ Object element = dialog.getFirstResult();
+ return (IJavaElement) element;
+ }
+ return null;
+ }
+
+ private String getPresentationName(IJavaElement element) {
+ return mJavaElementLabelProvider.getText(element);
+ }
+
+ /**
+ * Returns the current Java element context from which to initialize
+ * default settings, or <code>null</code> if none.
+ *
+ * @return Java element context.
+ */
+ private IJavaElement getContext() {
+ IWorkbenchWindow activeWorkbenchWindow =
+ PlatformUI.getWorkbench().getActiveWorkbenchWindow();
+ if (activeWorkbenchWindow == null) {
+ return null;
+ }
+ IWorkbenchPage page = activeWorkbenchWindow.getActivePage();
+ if (page != null) {
+ ISelection selection = page.getSelection();
+ if (selection instanceof IStructuredSelection) {
+ IStructuredSelection ss = (IStructuredSelection) selection;
+ if (!ss.isEmpty()) {
+ Object obj = ss.getFirstElement();
+ if (obj instanceof IJavaElement) {
+ return (IJavaElement) obj;
+ }
+ if (obj instanceof IResource) {
+ IJavaElement je = JavaCore.create((IResource) obj);
+ if (je == null) {
+ IProject pro = ((IResource) obj).getProject();
+ je = JavaCore.create(pro);
+ }
+ if (je != null) {
+ return je;
+ }
+ }
+ }
+ }
+ IEditorPart part = page.getActiveEditor();
+ if (part != null) {
+ IEditorInput input = part.getEditorInput();
+ return (IJavaElement) input.getAdapter(IJavaElement.class);
+ }
+ }
+ return null;
+ }
+
+ private void initializeJavaProject(IJavaElement javaElement,
+ ILaunchConfigurationWorkingCopy config) {
+ IJavaProject javaProject = javaElement.getJavaProject();
+ String name = null;
+ if (javaProject != null && javaProject.exists()) {
+ name = javaProject.getElementName();
+ }
+ config.setAttribute(IJavaLaunchConfigurationConstants.ATTR_PROJECT_NAME, name);
+ }
+
+ private void setButtonGridData(Button button) {
+ GridData gridData = new GridData();
+ button.setLayoutData(gridData);
+ LayoutUtil.setButtonDimensionHint(button);
+ }
+
+ /* (non-Javadoc)
+ * @see org.eclipse.debug.ui.AbstractLaunchConfigurationTab#getId()
+ */
+ @Override
+ public String getId() {
+ return "com.android.ide.eclipse.adt.launch.AndroidJUnitLaunchConfigurationTab"; //$NON-NLS-1$
+ }
+
+ /**
+ * Loads the UI with the instrumentations of the specified project, and stores the
+ * activities in <code>mActivities</code>.
+ * <p/>
+ * First activity is selected by default if present.
+ *
+ * @param project the {@link IProject} to load the instrumentations from.
+ */
+ private void loadInstrumentations(IProject project) {
+ mInstrumentations = AndroidJUnitLaunchConfigDelegate.getInstrumentationsForProject(project);
+ if (mInstrumentations != null) {
+ mInstrumentationCombo.removeAll();
+ for (String instrumentation : mInstrumentations) {
+ mInstrumentationCombo.add(instrumentation);
+ }
+ // the selection will be set when we update the ui from the current
+ // config object.
+ return;
+ }
+
+ // if we reach this point, either project is null, or we got an exception during
+ // the parsing. In either case, we empty the instrumentation list.
+ mInstrumentations = null;
+ mInstrumentationCombo.removeAll();
+ }
+}
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
new file mode 100755
index 0000000..e03f282
--- /dev/null
+++ b/tools/eclipse/plugins/com.android.ide.eclipse.adt/src/com/android/ide/eclipse/adt/launch/junit/AndroidJUnitLaunchShortcut.java
@@ -0,0 +1,55 @@
+/*
+ * Copyright (C) 2009 The Android Open Source Project
+ *
+ * Licensed under the Eclipse Public License, Version 1.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.eclipse.org/org/documents/epl-v10.php
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package com.android.ide.eclipse.adt.launch.junit;
+
+import org.eclipse.core.resources.IProject;
+import org.eclipse.core.runtime.CoreException;
+import org.eclipse.debug.core.ILaunchConfigurationWorkingCopy;
+import org.eclipse.jdt.core.IJavaElement;
+import org.eclipse.jdt.junit.launcher.JUnitLaunchShortcut;
+
+/**
+ * Launch shortcut to launch debug/run Android JUnit configuration directly.
+ */
+public class AndroidJUnitLaunchShortcut extends JUnitLaunchShortcut {
+
+ @Override
+ protected String getLaunchConfigurationTypeId() {
+ return "com.android.ide.eclipse.adt.junit.launchConfigurationType"; //$NON-NLS-1$
+ }
+
+ /**
+ * Creates a default Android JUnit launch configuration. Sets the instrumentation runner to the
+ * first instrumentation found in the AndroidManifest.
+ */
+ @Override
+ protected ILaunchConfigurationWorkingCopy createLaunchConfiguration(IJavaElement element)
+ throws CoreException {
+ ILaunchConfigurationWorkingCopy config = super.createLaunchConfiguration(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]);
+ }
+ AndroidJUnitLaunchConfigDelegate.setJUnitDefaults(config);
+
+ return config;
+ }
+}
diff --git a/tools/eclipse/plugins/com.android.ide.eclipse.adt/src/com/android/ide/eclipse/adt/launch/junit/AndroidJUnitTabGroup.java b/tools/eclipse/plugins/com.android.ide.eclipse.adt/src/com/android/ide/eclipse/adt/launch/junit/AndroidJUnitTabGroup.java
new file mode 100644
index 0000000..3c82f57
--- /dev/null
+++ b/tools/eclipse/plugins/com.android.ide.eclipse.adt/src/com/android/ide/eclipse/adt/launch/junit/AndroidJUnitTabGroup.java
@@ -0,0 +1,42 @@
+/*
+ * Copyright (C) 2009 The Android Open Source Project
+ *
+ * Licensed under the Eclipse Public License, Version 1.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.eclipse.org/org/documents/epl-v10.php
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package com.android.ide.eclipse.adt.launch.junit;
+
+import org.eclipse.debug.ui.AbstractLaunchConfigurationTabGroup;
+import org.eclipse.debug.ui.CommonTab;
+import org.eclipse.debug.ui.ILaunchConfigurationDialog;
+import org.eclipse.debug.ui.ILaunchConfigurationTab;
+
+import com.android.ide.eclipse.adt.launch.EmulatorConfigTab;
+
+/**
+ * Tab group object for Android JUnit launch configuration type.
+ */
+public class AndroidJUnitTabGroup extends AbstractLaunchConfigurationTabGroup {
+
+ /**
+ * Creates the UI tabs for the Android JUnit configuration
+ */
+ public void createTabs(ILaunchConfigurationDialog dialog, String mode) {
+ ILaunchConfigurationTab[] tabs = new ILaunchConfigurationTab[] {
+ new AndroidJUnitLaunchConfigurationTab(),
+ new EmulatorConfigTab(),
+ new CommonTab()
+ };
+ setTabs(tabs);
+ }
+}
diff --git a/tools/eclipse/plugins/com.android.ide.eclipse.adt/src/com/android/ide/eclipse/adt/launch/junit/runtime/AndroidJUnitLaunchInfo.java b/tools/eclipse/plugins/com.android.ide.eclipse.adt/src/com/android/ide/eclipse/adt/launch/junit/runtime/AndroidJUnitLaunchInfo.java
new file mode 100644
index 0000000..89cad97
--- /dev/null
+++ b/tools/eclipse/plugins/com.android.ide.eclipse.adt/src/com/android/ide/eclipse/adt/launch/junit/runtime/AndroidJUnitLaunchInfo.java
@@ -0,0 +1,60 @@
+/*
+ * Copyright (C) 2009 The Android Open Source Project
+ *
+ * Licensed under the Eclipse Public License, Version 1.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.eclipse.org/org/documents/epl-v10.php
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+package com.android.ide.eclipse.adt.launch.junit.runtime;
+
+import org.eclipse.core.resources.IProject;
+
+import com.android.ddmlib.IDevice;
+
+/**
+ * Contains info about Android JUnit launch
+ */
+public class AndroidJUnitLaunchInfo {
+ private final IProject mProject;
+ private final String mTestPackage;
+ private final String mRunner;
+ private final boolean mDebugMode;
+ private final IDevice mDevice;
+
+ public AndroidJUnitLaunchInfo(IProject project, String testPackage, String runner,
+ boolean debugMode, IDevice device) {
+ mProject = project;
+ mTestPackage = testPackage;
+ mRunner = runner;
+ mDebugMode = debugMode;
+ mDevice = device;
+ }
+
+ public IProject getProject() {
+ return mProject;
+ }
+
+ public String getTestPackage() {
+ return mTestPackage;
+ }
+
+ public String getRunner() {
+ return mRunner;
+ }
+
+ public boolean isDebugMode() {
+ return mDebugMode;
+ }
+
+ public IDevice getDevice() {
+ return mDevice;
+ }
+}
diff --git a/tools/eclipse/plugins/com.android.ide.eclipse.adt/src/com/android/ide/eclipse/adt/launch/junit/runtime/AndroidTestReference.java b/tools/eclipse/plugins/com.android.ide.eclipse.adt/src/com/android/ide/eclipse/adt/launch/junit/runtime/AndroidTestReference.java
new file mode 100644
index 0000000..9db3ef0
--- /dev/null
+++ b/tools/eclipse/plugins/com.android.ide.eclipse.adt/src/com/android/ide/eclipse/adt/launch/junit/runtime/AndroidTestReference.java
@@ -0,0 +1,63 @@
+/*
+ * Copyright (C) 2009 The Android Open Source Project
+ *
+ * Licensed under the Eclipse Public License, Version 1.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.eclipse.org/org/documents/epl-v10.php
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package com.android.ide.eclipse.adt.launch.junit.runtime;
+
+import org.eclipse.jdt.internal.junit.runner.ITestIdentifier;
+import org.eclipse.jdt.internal.junit.runner.ITestReference;
+import org.eclipse.jdt.internal.junit.runner.TestExecution;
+
+/**
+ * Base implementation of the Eclipse {@link ITestReference} and {@link ITestIdentifier} interfaces
+ * for Android tests.
+ * <p/>
+ * Provides generic equality/hashcode services
+ */
+@SuppressWarnings("restriction") //$NON-NLS-1$
+abstract class AndroidTestReference implements ITestReference, ITestIdentifier {
+
+ /**
+ * Gets the {@link ITestIdentifier} for this test reference.
+ */
+ public ITestIdentifier getIdentifier() {
+ // this class serves as its own test identifier
+ return this;
+ }
+
+ /**
+ * Not supported.
+ */
+ public void run(TestExecution execution) {
+ throw new UnsupportedOperationException();
+ }
+
+ /**
+ * Compares {@link ITestIdentifier} using names
+ */
+ @Override
+ public boolean equals(Object obj) {
+ if (obj instanceof ITestIdentifier) {
+ ITestIdentifier testid = (ITestIdentifier) obj;
+ return getName().equals(testid.getName());
+ }
+ return false;
+ }
+
+ @Override
+ public int hashCode() {
+ return getName().hashCode();
+ }
+}
diff --git a/tools/eclipse/plugins/com.android.ide.eclipse.adt/src/com/android/ide/eclipse/adt/launch/junit/runtime/RemoteADTTestRunner.java b/tools/eclipse/plugins/com.android.ide.eclipse.adt/src/com/android/ide/eclipse/adt/launch/junit/runtime/RemoteADTTestRunner.java
new file mode 100755
index 0000000..6834c08
--- /dev/null
+++ b/tools/eclipse/plugins/com.android.ide.eclipse.adt/src/com/android/ide/eclipse/adt/launch/junit/runtime/RemoteADTTestRunner.java
@@ -0,0 +1,220 @@
+/*
+ * Copyright (C) 2009 The Android Open Source Project
+ *
+ * Licensed under the Eclipse Public License, Version 1.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.eclipse.org/org/documents/epl-v10.php
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package com.android.ide.eclipse.adt.launch.junit.runtime;
+
+import com.android.ddmlib.testrunner.ITestRunListener;
+import com.android.ddmlib.testrunner.RemoteAndroidTestRunner;
+import com.android.ddmlib.testrunner.TestIdentifier;
+import com.android.ide.eclipse.adt.AdtPlugin;
+
+import org.eclipse.jdt.internal.junit.runner.MessageIds;
+import org.eclipse.jdt.internal.junit.runner.RemoteTestRunner;
+import org.eclipse.jdt.internal.junit.runner.TestExecution;
+import org.eclipse.jdt.internal.junit.runner.TestReferenceFailure;
+
+/**
+ * Supports Eclipse JUnit execution of Android tests.
+ * <p/>
+ * Communicates back to a Eclipse JDT JUnit client via a socket connection.
+ *
+ * @see org.eclipse.jdt.internal.junit.runner.RemoteTestRunner for more details on the protocol
+ */
+@SuppressWarnings("restriction")
+public class RemoteADTTestRunner extends RemoteTestRunner {
+
+ private AndroidJUnitLaunchInfo mLaunchInfo;
+ private TestExecution mExecution;
+
+ /**
+ * Initialize the JDT JUnit test runner parameters from the {@code args}.
+ *
+ * @param args name-value pair of arguments to pass to parent JUnit runner.
+ * @param launchInfo the Android specific test launch info
+ */
+ protected void init(String[] args, AndroidJUnitLaunchInfo launchInfo) {
+ defaultInit(args);
+ mLaunchInfo = launchInfo;
+ }
+
+ /**
+ * Runs a set of tests, and reports back results using parent class.
+ * <p/>
+ * JDT Unit expects to be sent data in the following sequence:
+ * <ol>
+ * <li>The total number of tests to be executed.</li>
+ * <li>The test 'tree' data about the tests to be executed, which is composed of the set of
+ * test class names, the number of tests in each class, and the names of each test in the
+ * class.</li>
+ * <li>The test execution result for each test method. Expects individual notifications of
+ * the test execution start, any failures, and the end of the test execution.</li>
+ * <li>The end of the test run, with its elapsed time.</li>
+ * </ol>
+ * <p/>
+ * In order to satisfy this, this method performs two actual Android instrumentation runs.
+ * The first is a 'log only' run that will collect the test tree data, without actually
+ * executing the tests, and send it back to JDT JUnit. The second is the actual test execution,
+ * whose results will be communicated back in real-time to JDT JUnit.
+ *
+ * @param testClassNames array of fully qualified test class names to execute. Cannot be empty.
+ * @param testName test to execute. If null, will be ignored.
+ * @param execution used to report test progress
+ */
+ @Override
+ public void runTests(String[] testClassNames, String testName, TestExecution execution) {
+ // hold onto this execution reference so it can be used to report test progress
+ mExecution = execution;
+
+ RemoteAndroidTestRunner runner = new RemoteAndroidTestRunner(mLaunchInfo.getTestPackage(),
+ mLaunchInfo.getRunner(), mLaunchInfo.getDevice());
+
+ if (testClassNames != null && testClassNames.length > 0) {
+ if (testName != null) {
+ runner.setMethodName(testClassNames[0], testName);
+ } else {
+ runner.setClassNames(testClassNames);
+ }
+ }
+ // set log only to first collect test case info, so Eclipse has correct test case count/
+ // tree info
+ runner.setLogOnly(true);
+ TestCollector collector = new TestCollector();
+ runner.run(collector);
+ if (collector.getErrorMessage() != null) {
+ // error occurred during test collection.
+ reportError(collector.getErrorMessage());
+ // abort here
+ }
+ notifyTestRunStarted(collector.getTestCaseCount());
+ collector.sendTrees(this);
+
+ // now do real execution
+ runner.setLogOnly(false);
+ if (mLaunchInfo.isDebugMode()) {
+ runner.setDebug(true);
+ }
+ runner.run(new TestRunListener());
+ }
+
+ /**
+ * Main entry method to run tests
+ *
+ * @param programArgs JDT JUnit program arguments to be processed by parent
+ * @param junitInfo the {@link AndroidJUnitLaunchInfo} containing info about this test ru
+ */
+ public void runTests(String[] programArgs, AndroidJUnitLaunchInfo junitInfo) {
+ init(programArgs, junitInfo);
+ run();
+ }
+
+ /**
+ * Stop the current test run.
+ */
+ public void terminate() {
+ stop();
+ }
+
+ @Override
+ protected void stop() {
+ if (mExecution != null) {
+ mExecution.stop();
+ }
+ }
+
+ private void notifyTestRunEnded(long elapsedTime) {
+ // copy from parent - not ideal, but method is private
+ sendMessage(MessageIds.TEST_RUN_END + elapsedTime);
+ flush();
+ //shutDown();
+ }
+
+ /**
+ * @param errorMessage
+ */
+ private void reportError(String errorMessage) {
+ AdtPlugin.printErrorToConsole(mLaunchInfo.getProject(),
+ String.format("Test run failed: %s", errorMessage));
+ // is this needed?
+ //notifyTestRunStopped(-1);
+ }
+
+ /**
+ * TestRunListener that communicates results in real-time back to JDT JUnit
+ */
+ private class TestRunListener implements ITestRunListener {
+
+ /* (non-Javadoc)
+ * @see com.android.ddmlib.testrunner.ITestRunListener#testEnded(com.android.ddmlib.testrunner.TestIdentifier)
+ */
+ public void testEnded(TestIdentifier test) {
+ mExecution.getListener().notifyTestEnded(new TestCaseReference(test));
+ }
+
+ /* (non-Javadoc)
+ * @see com.android.ddmlib.testrunner.ITestRunListener#testFailed(com.android.ddmlib.testrunner.ITestRunListener.TestFailure, com.android.ddmlib.testrunner.TestIdentifier, java.lang.String)
+ */
+ public void testFailed(TestFailure status, TestIdentifier test, String trace) {
+ String statusString;
+ if (status == TestFailure.ERROR) {
+ statusString = MessageIds.TEST_ERROR;
+ } else {
+ statusString = MessageIds.TEST_FAILED;
+ }
+ TestReferenceFailure failure =
+ new TestReferenceFailure(new TestCaseReference(test),
+ statusString, trace, null);
+ mExecution.getListener().notifyTestFailed(failure);
+ }
+
+ /* (non-Javadoc)
+ * @see com.android.ddmlib.testrunner.ITestRunListener#testRunEnded(long)
+ */
+ public void testRunEnded(long elapsedTime) {
+ notifyTestRunEnded(elapsedTime);
+ AdtPlugin.printToConsole(mLaunchInfo.getProject(), "Test run complete");
+ }
+
+ /* (non-Javadoc)
+ * @see com.android.ddmlib.testrunner.ITestRunListener#testRunFailed(java.lang.String)
+ */
+ public void testRunFailed(String errorMessage) {
+ reportError(errorMessage);
+ }
+
+ /* (non-Javadoc)
+ * @see com.android.ddmlib.testrunner.ITestRunListener#testRunStarted(int)
+ */
+ public void testRunStarted(int testCount) {
+ // ignore
+ }
+
+ /* (non-Javadoc)
+ * @see com.android.ddmlib.testrunner.ITestRunListener#testRunStopped(long)
+ */
+ public void testRunStopped(long elapsedTime) {
+ notifyTestRunStopped(elapsedTime);
+ AdtPlugin.printToConsole(mLaunchInfo.getProject(), "Test run stopped");
+ }
+
+ /* (non-Javadoc)
+ * @see com.android.ddmlib.testrunner.ITestRunListener#testStarted(com.android.ddmlib.testrunner.TestIdentifier)
+ */
+ public void testStarted(TestIdentifier test) {
+ TestCaseReference testId = new TestCaseReference(test);
+ mExecution.getListener().notifyTestStarted(testId);
+ }
+ }
+}
\ No newline at end of file
diff --git a/tools/eclipse/plugins/com.android.ide.eclipse.adt/src/com/android/ide/eclipse/adt/launch/junit/runtime/TestCaseReference.java b/tools/eclipse/plugins/com.android.ide.eclipse.adt/src/com/android/ide/eclipse/adt/launch/junit/runtime/TestCaseReference.java
new file mode 100644
index 0000000..1a0ee9a
--- /dev/null
+++ b/tools/eclipse/plugins/com.android.ide.eclipse.adt/src/com/android/ide/eclipse/adt/launch/junit/runtime/TestCaseReference.java
@@ -0,0 +1,75 @@
+/*
+ * Copyright (C) 2009 The Android Open Source Project
+ *
+ * Licensed under the Eclipse Public License, Version 1.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.eclipse.org/org/documents/epl-v10.php
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package com.android.ide.eclipse.adt.launch.junit.runtime;
+
+import com.android.ddmlib.testrunner.TestIdentifier;
+
+import org.eclipse.jdt.internal.junit.runner.IVisitsTestTrees;
+import org.eclipse.jdt.internal.junit.runner.MessageIds;
+
+import java.text.MessageFormat;
+
+/**
+ * Reference for a single Android test method.
+ */
+@SuppressWarnings("restriction")
+class TestCaseReference extends AndroidTestReference {
+
+ private final String mClassName;
+ private final String mTestName;
+
+ /**
+ * Creates a TestCaseReference from a class and method name
+ */
+ TestCaseReference(String className, String testName) {
+ mClassName = className;
+ mTestName = testName;
+ }
+
+ /**
+ * Creates a TestCaseReference from a {@link TestIdentifier}
+ * @param test
+ */
+ TestCaseReference(TestIdentifier test) {
+ mClassName = test.getClassName();
+ mTestName = test.getTestName();
+ }
+
+ /**
+ * Returns a count of the number of test cases referenced. Is always one for this class.
+ */
+ public int countTestCases() {
+ return 1;
+ }
+
+ /**
+ * Sends test identifier and test count information for this test
+ *
+ * @param notified the {@link IVisitsTestTrees} to send test info to
+ */
+ public void sendTree(IVisitsTestTrees notified) {
+ notified.visitTreeEntry(getIdentifier(), false, countTestCases());
+ }
+
+ /**
+ * Returns the identifier of this test, in a format expected by JDT JUnit
+ */
+ public String getName() {
+ return MessageFormat.format(MessageIds.TEST_IDENTIFIER_MESSAGE_FORMAT,
+ new Object[] { mTestName, mClassName});
+ }
+}
diff --git a/tools/eclipse/plugins/com.android.ide.eclipse.adt/src/com/android/ide/eclipse/adt/launch/junit/runtime/TestCollector.java b/tools/eclipse/plugins/com.android.ide.eclipse.adt/src/com/android/ide/eclipse/adt/launch/junit/runtime/TestCollector.java
new file mode 100644
index 0000000..2dc13a7
--- /dev/null
+++ b/tools/eclipse/plugins/com.android.ide.eclipse.adt/src/com/android/ide/eclipse/adt/launch/junit/runtime/TestCollector.java
@@ -0,0 +1,124 @@
+/*
+ * Copyright (C) 2009 The Android Open Source Project
+ *
+ * Licensed under the Eclipse Public License, Version 1.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.eclipse.org/org/documents/epl-v10.php
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package com.android.ide.eclipse.adt.launch.junit.runtime;
+
+import com.android.ddmlib.testrunner.ITestRunListener;
+import com.android.ddmlib.testrunner.TestIdentifier;
+
+import org.eclipse.jdt.internal.junit.runner.ITestReference;
+import org.eclipse.jdt.internal.junit.runner.IVisitsTestTrees;
+
+import java.util.HashMap;
+import java.util.Map;
+
+/**
+ * Collects info about tests to be executed by listening to the results of an Android test run.
+ */
+@SuppressWarnings("restriction")
+class TestCollector implements ITestRunListener {
+
+ private int mTotalTestCount;
+ /** test name to test suite reference map. */
+ private Map<String, TestSuiteReference> mTestTree;
+ private String mErrorMessage = null;
+
+ TestCollector() {
+ mTotalTestCount = 0;
+ mTestTree = new HashMap<String, TestSuiteReference>();
+ }
+
+ /* (non-Javadoc)
+ * @see com.android.ddmlib.testrunner.ITestRunListener#testEnded(com.android.ddmlib.testrunner.TestIdentifier)
+ */
+ public void testEnded(TestIdentifier test) {
+ // ignore
+ }
+
+ /* (non-Javadoc)
+ * @see com.android.ddmlib.testrunner.ITestRunListener#testFailed(com.android.ddmlib.testrunner.ITestRunListener.TestFailure, com.android.ddmlib.testrunner.TestIdentifier, java.lang.String)
+ */
+ public void testFailed(TestFailure status, TestIdentifier test, String trace) {
+ // ignore - should be impossible since this is only collecting test information
+ }
+
+ /* (non-Javadoc)
+ * @see com.android.ddmlib.testrunner.ITestRunListener#testRunEnded(long)
+ */
+ public void testRunEnded(long elapsedTime) {
+ // ignore
+ }
+
+ /* (non-Javadoc)
+ * @see com.android.ddmlib.testrunner.ITestRunListener#testRunFailed(java.lang.String)
+ */
+ public void testRunFailed(String errorMessage) {
+ mErrorMessage = errorMessage;
+ }
+
+ /* (non-Javadoc)
+ * @see com.android.ddmlib.testrunner.ITestRunListener#testRunStarted(int)
+ */
+ public void testRunStarted(int testCount) {
+ mTotalTestCount = testCount;
+ }
+
+ /* (non-Javadoc)
+ * @see com.android.ddmlib.testrunner.ITestRunListener#testRunStopped(long)
+ */
+ public void testRunStopped(long elapsedTime) {
+ // ignore
+ }
+
+ /* (non-Javadoc)
+ * @see com.android.ddmlib.testrunner.ITestRunListener#testStarted(com.android.ddmlib.testrunner.TestIdentifier)
+ */
+ public void testStarted(TestIdentifier test) {
+ TestSuiteReference suiteRef = mTestTree.get(test.getClassName());
+ if (suiteRef == null) {
+ // this test suite has not been seen before, create it
+ suiteRef = new TestSuiteReference(test.getClassName());
+ mTestTree.put(test.getClassName(), suiteRef);
+ }
+ suiteRef.addTest(new TestCaseReference(test));
+ }
+
+ /**
+ * Returns the total test count in the test run.
+ */
+ public int getTestCaseCount() {
+ return mTotalTestCount;
+ }
+
+ /**
+ * Sends info about the test tree to be executed (ie the suites and their enclosed tests)
+ *
+ * @param notified the {@link IVisitsTestTrees} to send test data to
+ */
+ public void sendTrees(IVisitsTestTrees notified) {
+ for (ITestReference ref : mTestTree.values()) {
+ ref.sendTree(notified);
+ }
+ }
+
+ /**
+ * Returns the error message that was reported when collecting test info.
+ * Returns <code>null</code> if no error occurred.
+ */
+ public String getErrorMessage() {
+ return mErrorMessage;
+ }
+}
diff --git a/tools/eclipse/plugins/com.android.ide.eclipse.adt/src/com/android/ide/eclipse/adt/launch/junit/runtime/TestSuiteReference.java b/tools/eclipse/plugins/com.android.ide.eclipse.adt/src/com/android/ide/eclipse/adt/launch/junit/runtime/TestSuiteReference.java
new file mode 100644
index 0000000..797f27b
--- /dev/null
+++ b/tools/eclipse/plugins/com.android.ide.eclipse.adt/src/com/android/ide/eclipse/adt/launch/junit/runtime/TestSuiteReference.java
@@ -0,0 +1,78 @@
+/*
+ * Copyright (C) 2009 The Android Open Source Project
+ *
+ * Licensed under the Eclipse Public License, Version 1.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.eclipse.org/org/documents/epl-v10.php
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package com.android.ide.eclipse.adt.launch.junit.runtime;
+
+import org.eclipse.jdt.internal.junit.runner.IVisitsTestTrees;
+
+import java.util.List;
+import java.util.ArrayList;
+
+/**
+ * Reference for an Android test suite aka class.
+ */
+@SuppressWarnings("restriction")
+class TestSuiteReference extends AndroidTestReference {
+
+ private final String mClassName;
+ private List<TestCaseReference> mTests;
+
+ /**
+ * Creates a TestSuiteReference
+ *
+ * @param className the fully qualified name of the test class
+ */
+ TestSuiteReference(String className) {
+ mClassName = className;
+ mTests = new ArrayList<TestCaseReference>();
+ }
+
+ /**
+ * Returns a count of the number of test cases included in this suite.
+ */
+ public int countTestCases() {
+ return mTests.size();
+ }
+
+ /**
+ * Sends test identifier and test count information for this test class, and all its included
+ * test methods.
+ *
+ * @param notified the {@link IVisitsTestTrees} to send test info too
+ */
+ public void sendTree(IVisitsTestTrees notified) {
+ notified.visitTreeEntry(getIdentifier(), true, countTestCases());
+ for (TestCaseReference ref : mTests) {
+ ref.sendTree(notified);
+ }
+ }
+
+ /**
+ * Return the name of this test class.
+ */
+ public String getName() {
+ return mClassName;
+ }
+
+ /**
+ * Adds a test method to this suite.
+ *
+ * @param testRef the {@link TestCaseReference} to add
+ */
+ void addTest(TestCaseReference testRef) {
+ mTests.add(testRef);
+ }
+}
diff --git a/tools/eclipse/plugins/com.android.ide.eclipse.adt/src/com/android/ide/eclipse/adt/project/internal/AndroidClasspathContainerInitializer.java b/tools/eclipse/plugins/com.android.ide.eclipse.adt/src/com/android/ide/eclipse/adt/project/internal/AndroidClasspathContainerInitializer.java
index 5aeb335..e9df77f 100644
--- a/tools/eclipse/plugins/com.android.ide.eclipse.adt/src/com/android/ide/eclipse/adt/project/internal/AndroidClasspathContainerInitializer.java
+++ b/tools/eclipse/plugins/com.android.ide.eclipse.adt/src/com/android/ide/eclipse/adt/project/internal/AndroidClasspathContainerInitializer.java
@@ -487,6 +487,15 @@
IJavaProject javaProject = projects.get(i);
IProject iProject = javaProject.getProject();
+ // check if the project is opened
+ if (iProject.isOpen() == false) {
+ // remove from the list
+ // we do not increment i in this case.
+ projects.remove(i);
+
+ continue;
+ }
+
// get the target from the project and its paths
IAndroidTarget target = Sdk.getCurrent().getTarget(javaProject.getProject());
if (target == null) {
diff --git a/tools/eclipse/plugins/com.android.ide.eclipse.adt/src/com/android/ide/eclipse/adt/wizards/newproject/NewProjectCreationPage.java b/tools/eclipse/plugins/com.android.ide.eclipse.adt/src/com/android/ide/eclipse/adt/wizards/newproject/NewProjectCreationPage.java
index 0dd88c0..7d3cad3 100644
--- a/tools/eclipse/plugins/com.android.ide.eclipse.adt/src/com/android/ide/eclipse/adt/wizards/newproject/NewProjectCreationPage.java
+++ b/tools/eclipse/plugins/com.android.ide.eclipse.adt/src/com/android/ide/eclipse/adt/wizards/newproject/NewProjectCreationPage.java
@@ -829,7 +829,7 @@
String packageName = null;
String activityName = null;
- int minSdkVersion = 0; // 0 means no minSdkVersion provided in the manifest
+ int minSdkVersion = AndroidManifestParser.INVALID_MIN_SDK;
try {
packageName = manifestData.getPackage();
minSdkVersion = manifestData.getApiLevelRequirement();
@@ -927,7 +927,7 @@
}
}
- if (!foundTarget && minSdkVersion > 0) {
+ if (!foundTarget && minSdkVersion != AndroidManifestParser.INVALID_MIN_SDK) {
try {
for (IAndroidTarget target : mSdkTargetSelector.getTargets()) {
if (target.getApiVersionNumber() == minSdkVersion) {
@@ -954,7 +954,8 @@
if (!foundTarget) {
mInternalMinSdkVersionUpdate = true;
mMinSdkVersionField.setText(
- minSdkVersion <= 0 ? "" : Integer.toString(minSdkVersion)); //$NON-NLS-1$
+ minSdkVersion == AndroidManifestParser.INVALID_MIN_SDK ? "" :
+ Integer.toString(minSdkVersion)); //$NON-NLS-1$
mInternalMinSdkVersionUpdate = false;
}
}
@@ -1148,7 +1149,7 @@
return MSG_NONE;
}
- int version = -1;
+ int version = AndroidManifestParser.INVALID_MIN_SDK;
try {
// If not empty, it must be a valid integer > 0
version = Integer.parseInt(getMinSdkVersion());
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 0a45196..42c881b 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
@@ -72,6 +72,8 @@
private final static String ACTION_MAIN = "android.intent.action.MAIN"; //$NON-NLS-1$
private final static String CATEGORY_LAUNCHER = "android.intent.category.LAUNCHER"; //$NON-NLS-1$
+ public final static int INVALID_MIN_SDK = -1;
+
/**
* XML error & data handler used when parsing the AndroidManifest.xml file.
* <p/>
@@ -92,8 +94,9 @@
private Set<String> mProcesses = null;
/** debuggable attribute value. If null, the attribute is not present. */
private Boolean mDebuggable = null;
- /** API level requirement. if 0 the attribute was not present. */
- private int mApiLevelRequirement = 0;
+ /** API level requirement. if {@link AndroidManifestParser#INVALID_MIN_SDK}
+ * the attribute was not present. */
+ private int mApiLevelRequirement = INVALID_MIN_SDK;
/** List of all instrumentations declared by the manifest */
private final ArrayList<String> mInstrumentations = new ArrayList<String>();
/** List of all libraries in use declared by the manifest */
@@ -171,7 +174,8 @@
}
/**
- * Returns the <code>minSdkVersion</code> attribute, or 0 if it's not set.
+ * Returns the <code>minSdkVersion</code> attribute, or
+ * {@link AndroidManifestParser#INVALID_MIN_SDK} if it's not set.
*/
int getApiLevelRequirement() {
return mApiLevelRequirement;
@@ -750,7 +754,8 @@
}
/**
- * Returns the <code>minSdkVersion</code> attribute, or 0 if it's not set.
+ * Returns the <code>minSdkVersion</code> attribute, or {@link #INVALID_MIN_SDK}
+ * if it's not set.
*/
public int getApiLevelRequirement() {
return mApiLevelRequirement;
diff --git a/tools/eclipse/plugins/com.android.ide.eclipse.adt/src/com/android/ide/eclipse/common/project/ProjectChooserHelper.java b/tools/eclipse/plugins/com.android.ide.eclipse.adt/src/com/android/ide/eclipse/common/project/ProjectChooserHelper.java
index 0c43499..b6d4c9a 100644
--- a/tools/eclipse/plugins/com.android.ide.eclipse.adt/src/com/android/ide/eclipse/common/project/ProjectChooserHelper.java
+++ b/tools/eclipse/plugins/com.android.ide.eclipse.adt/src/com/android/ide/eclipse/common/project/ProjectChooserHelper.java
@@ -16,8 +16,7 @@
package com.android.ide.eclipse.common.project;
-import com.android.ide.eclipse.common.project.BaseProjectHelper;
-
+import org.eclipse.core.resources.IProject;
import org.eclipse.core.resources.IWorkspaceRoot;
import org.eclipse.core.resources.ResourcesPlugin;
import org.eclipse.jdt.core.IJavaModel;
@@ -82,7 +81,7 @@
// open the dialog and return the object selected if OK was clicked, or null otherwise
if (dialog.open() == Window.OK) {
- return (IJavaProject)dialog.getFirstResult();
+ return (IJavaProject) dialog.getFirstResult();
}
return null;
}
@@ -107,4 +106,24 @@
return mAndroidProjects;
}
+
+ /**
+ * Helper method to get the Android project with the given name
+ *
+ * @param projectName the name of the project to find
+ * @return the {@link IProject} for the Android project. <code>null</code> if not found.
+ */
+ public IProject getAndroidProject(String projectName) {
+ IProject iproject = null;
+ IJavaProject[] javaProjects = getAndroidProjects(null);
+ if (javaProjects != null) {
+ for (IJavaProject javaProject : javaProjects) {
+ if (javaProject.getElementName().equals(projectName)) {
+ iproject = javaProject.getProject();
+ break;
+ }
+ }
+ }
+ return iproject;
+ }
}
diff --git a/tools/eclipse/plugins/com.android.ide.eclipse.tests/unittests/com/android/ide/eclipse/mock/FileMock.java b/tools/eclipse/plugins/com.android.ide.eclipse.tests/unittests/com/android/ide/eclipse/mock/FileMock.java
index 987ea92..5739153 100644
--- a/tools/eclipse/plugins/com.android.ide.eclipse.tests/unittests/com/android/ide/eclipse/mock/FileMock.java
+++ b/tools/eclipse/plugins/com.android.ide.eclipse.tests/unittests/com/android/ide/eclipse/mock/FileMock.java
@@ -461,5 +461,13 @@
public void setHidden(boolean isHidden) throws CoreException {
throw new NotImplementedException();
}
+
+ public boolean isHidden(int options) {
+ throw new NotImplementedException();
+ }
+
+ public boolean isTeamPrivateMember(int options) {
+ throw new NotImplementedException();
+ }
}
diff --git a/tools/eclipse/plugins/com.android.ide.eclipse.tests/unittests/com/android/ide/eclipse/mock/FolderMock.java b/tools/eclipse/plugins/com.android.ide.eclipse.tests/unittests/com/android/ide/eclipse/mock/FolderMock.java
index 73a69aa..26bf53e 100644
--- a/tools/eclipse/plugins/com.android.ide.eclipse.tests/unittests/com/android/ide/eclipse/mock/FolderMock.java
+++ b/tools/eclipse/plugins/com.android.ide.eclipse.tests/unittests/com/android/ide/eclipse/mock/FolderMock.java
@@ -74,7 +74,8 @@
// -------- UNIMPLEMENTED METHODS ----------------
- public void create(boolean force, boolean local, IProgressMonitor monitor) throws CoreException {
+ public void create(boolean force, boolean local, IProgressMonitor monitor)
+ throws CoreException {
throw new NotImplementedException();
}
@@ -106,8 +107,8 @@
throw new NotImplementedException();
}
- public void move(IPath destination, boolean force, boolean keepHistory, IProgressMonitor monitor)
- throws CoreException {
+ public void move(IPath destination, boolean force, boolean keepHistory,
+ IProgressMonitor monitor) throws CoreException {
throw new NotImplementedException();
}
@@ -225,7 +226,8 @@
throw new NotImplementedException();
}
- public void deleteMarkers(String type, boolean includeSubtypes, int depth) throws CoreException {
+ public void deleteMarkers(String type, boolean includeSubtypes, int depth)
+ throws CoreException {
throw new NotImplementedException();
}
@@ -428,24 +430,31 @@
throw new NotImplementedException();
}
- public Map<?,?> getPersistentProperties() throws CoreException {
+ public Map<?,?> getPersistentProperties() throws CoreException {
throw new NotImplementedException();
- }
+ }
- public Map<?,?> getSessionProperties() throws CoreException {
+ public Map<?,?> getSessionProperties() throws CoreException {
throw new NotImplementedException();
- }
+ }
- public boolean isDerived(int options) {
+ public boolean isDerived(int options) {
throw new NotImplementedException();
- }
+ }
- public boolean isHidden() {
+ public boolean isHidden() {
throw new NotImplementedException();
- }
+ }
- public void setHidden(boolean isHidden) throws CoreException {
+ public void setHidden(boolean isHidden) throws CoreException {
throw new NotImplementedException();
- }
+ }
+ public boolean isHidden(int options) {
+ throw new NotImplementedException();
+ }
+
+ public boolean isTeamPrivateMember(int options) {
+ throw new NotImplementedException();
+ }
}
diff --git a/tools/eclipse/plugins/com.android.ide.eclipse.tests/unittests/com/android/ide/eclipse/mock/ProjectMock.java b/tools/eclipse/plugins/com.android.ide.eclipse.tests/unittests/com/android/ide/eclipse/mock/ProjectMock.java
index 0e6fde0..6c32d0f 100644
--- a/tools/eclipse/plugins/com.android.ide.eclipse.tests/unittests/com/android/ide/eclipse/mock/ProjectMock.java
+++ b/tools/eclipse/plugins/com.android.ide.eclipse.tests/unittests/com/android/ide/eclipse/mock/ProjectMock.java
@@ -42,6 +42,14 @@
import java.net.URI;
import java.util.Map;
+/**
+ * Mock implementation of {@link IProject}.
+ * <p/>Supported methods:
+ * <ul>
+ * <li>{@link #build(int kind, IProgressMonitor monitor)}</li>
+ * <li>{@link #members(int kind, String builderName, Map args, IProgressMonitor monitor)}</li>
+ * </ul>
+ */
@SuppressWarnings("deprecation")
public class ProjectMock implements IProject {
@@ -265,7 +273,8 @@
throw new NotImplementedException();
}
- public void deleteMarkers(String type, boolean includeSubtypes, int depth) throws CoreException {
+ public void deleteMarkers(String type, boolean includeSubtypes, int depth)
+ throws CoreException {
throw new NotImplementedException();
}
@@ -473,29 +482,36 @@
throw new NotImplementedException();
}
- public void create(IProjectDescription description, int updateFlags,
- IProgressMonitor monitor) throws CoreException {
+ public void create(IProjectDescription description, int updateFlags,
+ IProgressMonitor monitor) throws CoreException {
throw new NotImplementedException();
- }
+ }
- public Map<?,?> getPersistentProperties() throws CoreException {
+ public Map<?,?> getPersistentProperties() throws CoreException {
throw new NotImplementedException();
- }
+ }
- public Map<?,?> getSessionProperties() throws CoreException {
+ public Map<?,?> getSessionProperties() throws CoreException {
throw new NotImplementedException();
- }
+ }
- public boolean isDerived(int options) {
+ public boolean isDerived(int options) {
throw new NotImplementedException();
- }
+ }
- public boolean isHidden() {
+ public boolean isHidden() {
throw new NotImplementedException();
- }
+ }
- public void setHidden(boolean isHidden) throws CoreException {
+ public void setHidden(boolean isHidden) throws CoreException {
throw new NotImplementedException();
- }
+ }
+ public boolean isHidden(int options) {
+ throw new NotImplementedException();
+ }
+
+ public boolean isTeamPrivateMember(int options) {
+ throw new NotImplementedException();
+ }
}
diff --git a/tools/scripts/divide_and_compress.py b/tools/scripts/divide_and_compress.py
index d369be4..2bcb0ab 100755
--- a/tools/scripts/divide_and_compress.py
+++ b/tools/scripts/divide_and_compress.py
@@ -36,89 +36,99 @@
__author__ = 'jmatt@google.com (Justin Mattson)'
-from optparse import OptionParser
+import optparse
import os
import stat
import sys
import zipfile
-from zipfile import ZipFile
import divide_and_compress_constants
-def Main(argv):
- parser = CreateOptionsParser()
- (options, args) = parser.parse_args()
- VerifyArguments(options, parser)
- zipper = DirectoryZipper(options.destination,
- options.sourcefiles,
- ParseSize(options.filesize),
- options.compress)
- zipper.StartCompress()
-
-
def CreateOptionsParser():
- rtn = OptionParser()
+ """Creates the parser for command line arguments.
+
+ Returns:
+ A configured optparse.OptionParser object.
+ """
+ rtn = optparse.OptionParser()
rtn.add_option('-s', '--sourcefiles', dest='sourcefiles', default=None,
help='The directory containing the files to compress')
rtn.add_option('-d', '--destination', dest='destination', default=None,
help=('Where to put the archive files, this should not be'
' a child of where the source files exist.'))
rtn.add_option('-f', '--filesize', dest='filesize', default='1M',
- help=('Maximum size of archive files. A number followed by'
- 'a magnitude indicator, eg. 1000000B == one million '
- 'BYTES, 500K == five hundred KILOBYTES, 1.2M == one '
- 'point two MEGABYTES. 1M == 1048576 BYTES'))
+ help=('Maximum size of archive files. A number followed by '
+ 'a magnitude indicator either "B", "K", "M", or "G". '
+ 'Examples:\n 1000000B == one million BYTES\n'
+ ' 1.2M == one point two MEGABYTES\n'
+ ' 1M == 1048576 BYTES'))
rtn.add_option('-n', '--nocompress', action='store_false', dest='compress',
- default=True,
+ default=True,
help=('Whether the archive files should be compressed, or '
'just a concatenation of the source files'))
return rtn
def VerifyArguments(options, parser):
+ """Runs simple checks on correctness of commandline arguments.
+
+ Args:
+ options: The command line options passed.
+ parser: The parser object used to parse the command string.
+ """
try:
if options.sourcefiles is None or options.destination is None:
parser.print_help()
sys.exit(-1)
- except (AttributeError), err:
+ except AttributeError:
parser.print_help()
sys.exit(-1)
def ParseSize(size_str):
+ """Parse the file size argument from a string to a number of bytes.
+
+ Args:
+ size_str: The string representation of the file size.
+
+ Returns:
+ The file size in bytes.
+
+ Raises:
+ ValueError: Raises an error if the numeric or qualifier portions of the
+ file size argument is invalid.
+ """
if len(size_str) < 2:
raise ValueError(('filesize argument not understood, please include'
' a numeric value and magnitude indicator'))
- magnitude = size_str[len(size_str)-1:]
- if not magnitude in ('K', 'B', 'M'):
- raise ValueError(('filesize magnitude indicator not valid, must be \'K\','
- '\'B\', or \'M\''))
- numeral = float(size_str[0:len(size_str)-1])
+ magnitude = size_str[-1]
+ if not magnitude in ('B', 'K', 'M', 'G'):
+ raise ValueError(('filesize magnitude indicator not valid, must be "B",'
+ '"K","M", or "G"'))
+ numeral = float(size_str[:-1])
if magnitude == 'K':
numeral *= 1024
elif magnitude == 'M':
numeral *= 1048576
+ elif magnitude == 'G':
+ numeral *= 1073741824
return int(numeral)
class DirectoryZipper(object):
- """Class to compress a directory and all its sub-directories."""
- current_archive = None
- output_dir = None
- base_path = None
- max_size = None
- compress = None
- index_fp = None
+ """Class to compress a directory and all its sub-directories."""
def __init__(self, output_path, base_dir, archive_size, enable_compression):
"""DirectoryZipper constructor.
Args:
- output_path: the path to write the archives and index file to
- base_dir: the directory to compress
- archive_size: the maximum size, in bytes, of a single archive file
- enable_compression: whether or not compression should be enabled, if
- disabled, the files will be written into an uncompresed zip
+ output_path: A string, the path to write the archives and index file to.
+ base_dir: A string, the directory to compress.
+ archive_size: An number, the maximum size, in bytes, of a single
+ archive file.
+ enable_compression: A boolean, whether or not compression should be
+ enabled, if disabled, the files will be written into an uncompresed
+ zip.
"""
self.output_dir = output_path
self.current_archive = '0.zip'
@@ -126,6 +136,9 @@
self.max_size = archive_size
self.compress = enable_compression
+ # Set index_fp to None, because we don't know what it will be yet.
+ self.index_fp = None
+
def StartCompress(self):
"""Start compress of the directory.
@@ -133,7 +146,7 @@
specified output directory. It will also produce an 'index.txt' file in the
output directory that maps from file to archive.
"""
- self.index_fp = open(''.join([self.output_dir, 'main.py']), 'w')
+ self.index_fp = open(os.path.join(self.output_dir, 'main.py'), 'w')
self.index_fp.write(divide_and_compress_constants.file_preamble)
os.path.walk(self.base_path, self.CompressDirectory, 1)
self.index_fp.write(divide_and_compress_constants.file_endpiece)
@@ -149,37 +162,32 @@
Args:
archive_path: Path to the archive to modify. This archive should not be
open elsewhere, since it will need to be deleted.
- Return:
- A new ZipFile object that points to the modified archive file
+
+ Returns:
+ A new ZipFile object that points to the modified archive file.
"""
if archive_path is None:
- archive_path = ''.join([self.output_dir, self.current_archive])
+ archive_path = os.path.join(self.output_dir, self.current_archive)
- # Move the old file and create a new one at its old location
- ext_offset = archive_path.rfind('.')
- old_archive = ''.join([archive_path[0:ext_offset], '-old',
- archive_path[ext_offset:]])
+ # Move the old file and create a new one at its old location.
+ root, ext = os.path.splitext(archive_path)
+ old_archive = ''.join([root, '-old', ext])
os.rename(archive_path, old_archive)
old_fp = self.OpenZipFileAtPath(old_archive, mode='r')
+ # By default, store uncompressed.
+ compress_bit = zipfile.ZIP_STORED
if self.compress:
- new_fp = self.OpenZipFileAtPath(archive_path,
- mode='w',
- compress=zipfile.ZIP_DEFLATED)
- else:
- new_fp = self.OpenZipFileAtPath(archive_path,
- mode='w',
- compress=zipfile.ZIP_STORED)
-
- # Read the old archive in a new archive, except the last one
- zip_members = enumerate(old_fp.infolist())
- num_members = len(old_fp.infolist())
- while num_members > 1:
- this_member = zip_members.next()[1]
- new_fp.writestr(this_member.filename, old_fp.read(this_member.filename))
- num_members -= 1
+ compress_bit = zipfile.ZIP_DEFLATED
+ new_fp = self.OpenZipFileAtPath(archive_path,
+ mode='w',
+ compress=compress_bit)
- # Close files and delete the old one
+ # Read the old archive in a new archive, except the last one.
+ for zip_member in old_fp.infolist()[:-1]:
+ new_fp.writestr(zip_member, old_fp.read(zip_member.filename))
+
+ # Close files and delete the old one.
old_fp.close()
new_fp.close()
os.unlink(old_archive)
@@ -193,11 +201,11 @@
mode = 'w'
if mode == 'r':
- return ZipFile(path, mode)
+ return zipfile.ZipFile(path, mode)
else:
- return ZipFile(path, mode, compress)
+ return zipfile.ZipFile(path, mode, compress)
- def CompressDirectory(self, irrelevant, dir_path, dir_contents):
+ def CompressDirectory(self, unused_id, dir_path, dir_contents):
"""Method to compress the given directory.
This method compresses the directory 'dir_path'. It will add to an existing
@@ -206,40 +214,35 @@
mapping of files to archives to the self.index_fp file descriptor
Args:
- irrelevant: a numeric identifier passed by the os.path.walk method, this
- is not used by this method
- dir_path: the path to the directory to compress
- dir_contents: a list of directory contents to be compressed
+ unused_id: A numeric identifier passed by the os.path.walk method, this
+ is not used by this method.
+ dir_path: A string, the path to the directory to compress.
+ dir_contents: A list of directory contents to be compressed.
"""
-
- # construct the queue of files to be added that this method will use
+ # Construct the queue of files to be added that this method will use
# it seems that dir_contents is given in reverse alphabetical order,
- # so put them in alphabetical order by inserting to front of the list
+ # so put them in alphabetical order by inserting to front of the list.
dir_contents.sort()
zip_queue = []
- if dir_path[len(dir_path) - 1:] == os.sep:
- for filename in dir_contents:
- zip_queue.append(''.join([dir_path, filename]))
- else:
- for filename in dir_contents:
- zip_queue.append(''.join([dir_path, os.sep, filename]))
+ for filename in dir_contents:
+ zip_queue.append(os.path.join(dir_path, filename))
compress_bit = zipfile.ZIP_DEFLATED
if not self.compress:
compress_bit = zipfile.ZIP_STORED
- # zip all files in this directory, adding to existing archives and creating
- # as necessary
- while len(zip_queue) > 0:
+ # Zip all files in this directory, adding to existing archives and creating
+ # as necessary.
+ while zip_queue:
target_file = zip_queue[0]
if os.path.isfile(target_file):
self.AddFileToArchive(target_file, compress_bit)
-
- # see if adding the new file made our archive too large
+
+ # See if adding the new file made our archive too large.
if not self.ArchiveIsValid():
-
+
# IF fixing fails, the last added file was to large, skip it
# ELSE the current archive filled normally, make a new one and try
- # adding the file again
+ # adding the file again.
if not self.FixArchive('SIZE'):
zip_queue.pop(0)
else:
@@ -248,7 +251,7 @@
0:self.current_archive.rfind('.zip')]) + 1)
else:
- # if this the first file in the archive, write an index record
+ # Write an index record if necessary.
self.WriteIndexRecord()
zip_queue.pop(0)
else:
@@ -260,10 +263,10 @@
Only write an index record if this is the first file to go into archive
Returns:
- True if an archive record is written, False if it isn't
+ True if an archive record is written, False if it isn't.
"""
archive = self.OpenZipFileAtPath(
- ''.join([self.output_dir, self.current_archive]), 'r')
+ os.path.join(self.output_dir, self.current_archive), 'r')
archive_index = archive.infolist()
if len(archive_index) == 1:
self.index_fp.write(
@@ -279,54 +282,56 @@
"""Make the archive compliant.
Args:
- problem: the reason the archive is invalid
+ problem: An enum, the reason the archive is invalid.
Returns:
Whether the file(s) removed to fix the archive could conceivably be
in an archive, but for some reason can't be added to this one.
"""
- archive_path = ''.join([self.output_dir, self.current_archive])
- rtn_value = None
-
+ archive_path = os.path.join(self.output_dir, self.current_archive)
+ return_value = None
+
if problem == 'SIZE':
archive_obj = self.OpenZipFileAtPath(archive_path, mode='r')
num_archive_files = len(archive_obj.infolist())
-
+
# IF there is a single file, that means its too large to compress,
# delete the created archive
- # ELSE do normal finalization
+ # ELSE do normal finalization.
if num_archive_files == 1:
print ('WARNING: %s%s is too large to store.' % (
self.base_path, archive_obj.infolist()[0].filename))
archive_obj.close()
os.unlink(archive_path)
- rtn_value = False
+ return_value = False
else:
- self.RemoveLastFile(''.join([self.output_dir, self.current_archive]))
archive_obj.close()
+ self.RemoveLastFile(
+ os.path.join(self.output_dir, self.current_archive))
print 'Final archive size for %s is %i' % (
- self.current_archive, os.stat(archive_path)[stat.ST_SIZE])
- rtn_value = True
- return rtn_value
+ self.current_archive, os.path.getsize(archive_path))
+ return_value = True
+ return return_value
def AddFileToArchive(self, filepath, compress_bit):
"""Add the file at filepath to the current archive.
Args:
- filepath: the path of the file to add
- compress_bit: whether or not this fiel should be compressed when added
+ filepath: A string, the path of the file to add.
+ compress_bit: A boolean, whether or not this file should be compressed
+ when added.
Returns:
True if the file could be added (typically because this is a file) or
- False if it couldn't be added (typically because its a directory)
+ False if it couldn't be added (typically because its a directory).
"""
- curr_archive_path = ''.join([self.output_dir, self.current_archive])
- if os.path.isfile(filepath):
- if os.stat(filepath)[stat.ST_SIZE] > 1048576:
+ curr_archive_path = os.path.join(self.output_dir, self.current_archive)
+ if os.path.isfile(filepath) and not os.path.islink(filepath):
+ if os.path.getsize(filepath) > 1048576:
print 'Warning: %s is potentially too large to serve on GAE' % filepath
archive = self.OpenZipFileAtPath(curr_archive_path,
compress=compress_bit)
- # add the file to the archive
+ # Add the file to the archive.
archive.write(filepath, filepath[len(self.base_path):])
archive.close()
return True
@@ -340,13 +345,22 @@
The thought is that eventually this will do additional validation
Returns:
- True if the archive is valid, False if its not
+ True if the archive is valid, False if its not.
"""
- archive_path = ''.join([self.output_dir, self.current_archive])
- if os.stat(archive_path)[stat.ST_SIZE] > self.max_size:
- return False
- else:
- return True
+ archive_path = os.path.join(self.output_dir, self.current_archive)
+ return os.path.getsize(archive_path) <= self.max_size
+
+
+def main(argv):
+ parser = CreateOptionsParser()
+ (options, unused_args) = parser.parse_args(args=argv[1:])
+ VerifyArguments(options, parser)
+ zipper = DirectoryZipper(options.destination,
+ options.sourcefiles,
+ ParseSize(options.filesize),
+ options.compress)
+ zipper.StartCompress()
+
if __name__ == '__main__':
- Main(sys.argv)
+ main(sys.argv)
diff --git a/tools/scripts/divide_and_compress_constants.py b/tools/scripts/divide_and_compress_constants.py
index 4e11b6f..15162b7 100644
--- a/tools/scripts/divide_and_compress_constants.py
+++ b/tools/scripts/divide_and_compress_constants.py
@@ -19,42 +19,40 @@
__author__ = 'jmatt@google.com (Justin Mattson)'
-file_preamble = ('#!/usr/bin/env python\n'
- '#\n'
- '# Copyright 2008 Google Inc.\n'
- '#\n'
- '# Licensed under the Apache License, Version 2.0 (the'
- '\"License");\n'
- '# you may not use this file except in compliance with the '
- 'License.\n'
- '# You may obtain a copy of the License at\n'
- '#\n'
- '# http://www.apache.org/licenses/LICENSE-2.0\n'
- '#\n'
- '# Unless required by applicable law or agreed to in writing,'
- ' software\n'
- '# distributed under the License is distributed on an \"AS'
- 'IS\" BASIS,\n'
- '# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either '
- 'express or implied.\n'
- '# See the License for the specific language governing'
- ' permissions and\n'
- '# limitations under the License.\n'
- '#\n\n'
- 'import wsgiref.handlers\n'
- 'from google.appengine.ext import zipserve\n'
- 'from google.appengine.ext import webapp\n'
- 'import memcache_zipserve\n\n\n'
- 'class MainHandler(webapp.RequestHandler):\n\n'
- ' def get(self):\n'
- ' self.response.out.write(\'Hello world!\')\n\n'
- 'def main():\n'
- ' application = webapp.WSGIApplication([(\'/(.*)\','
- ' memcache_zipserve.create_handler([')
+file_preamble = """#!/usr/bin/env python
+#
+# Copyright 2008 Google Inc.
+#
+# Licensed under the Apache License, Version 2.0 (the "License");
+# you may not use this file except in compliance with the License.
+# You may obtain a copy of the License at
+#
+# http://www.apache.org/licenses/LICENSE-2.0
+#
+# Unless required by applicable law or agreed to in writing, software
+# distributed under the License is distributed on an \"AS IS\" BASIS,
+# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+# See the License for the specific language governing permissions and
+# limitations under the License.
+#
-file_endpiece = ('])),\n'
- '],\n'
- 'debug=False)\n'
- ' wsgiref.handlers.CGIHandler().run(application)\n\n'
- 'if __name__ == \'__main__\':\n'
- ' main()')
+import wsgiref.handlers\n'
+from google.appengine.ext import zipserve\n'
+from google.appengine.ext import webapp\n'
+import memcache_zipserve\n\n\n'
+class MainHandler(webapp.RequestHandler):
+
+ def get(self):
+ self.response.out.write('Hello world!')
+
+def main():
+ application = webapp.WSGIApplication(['/(.*)',
+ memcache_zipserve.create_handler(["""
+
+file_endpiece = """])),
+],
+debug=False)
+ wsgiref.handlers.CGIHandler().run(application)
+
+if __name__ == __main__:
+ main()"""
diff --git a/tools/scripts/test_divide_and_compress.py b/tools/scripts/divide_and_compress_test.py
similarity index 97%
rename from tools/scripts/test_divide_and_compress.py
rename to tools/scripts/divide_and_compress_test.py
index d0d27b3..426449a 100755
--- a/tools/scripts/test_divide_and_compress.py
+++ b/tools/scripts/divide_and_compress_test.py
@@ -17,7 +17,7 @@
"""Tests for divide_and_compress.py.
-TODO: Add tests for module methods.
+TODO(jmatt): Add tests for module methods.
"""
__author__ = 'jmatt@google.com (Justin Mattson)'
@@ -26,10 +26,9 @@
import stat
import unittest
import zipfile
-from zipfile import ZipFile
import divide_and_compress
-from mox import mox
+import mox
class BagOfParts(object):
@@ -58,6 +57,10 @@
'sdjfljkgsc n;iself')
self.files = {'file1': file1, 'file2': file2}
+ def tearDown(self):
+ """Remove any stubs we've created."""
+ self.my_mox.UnsetStubs()
+
def testArchiveIsValid(self):
"""Test the DirectoryZipper.ArchiveIsValid method.
@@ -119,7 +122,7 @@
A configured mocked
"""
- source_zip = self.my_mox.CreateMock(ZipFile)
+ source_zip = self.my_mox.CreateMock(zipfile.ZipFile)
source_zip.infolist().AndReturn([self.files['file1'], self.files['file1']])
source_zip.infolist().AndReturn([self.files['file1'], self.files['file1']])
source_zip.read(self.files['file1'].filename).AndReturn(
@@ -137,16 +140,12 @@
A configured mocked
"""
- dest_zip = mox.MockObject(ZipFile)
+ dest_zip = mox.MockObject(zipfile.ZipFile)
dest_zip.writestr(self.files['file1'].filename,
self.files['file1'].contents)
dest_zip.close()
return dest_zip
- def tearDown(self):
- """Remove any stubs we've created."""
- self.my_mox.UnsetStubs()
-
class FixArchiveTests(unittest.TestCase):
"""Tests for the DirectoryZipper.FixArchive method."""
@@ -158,6 +157,10 @@
self.file1.filename = 'file1.txt'
self.file1.contents = 'This is a test file'
+ def tearDown(self):
+ """Unset any mocks that we've created."""
+ self.my_mox.UnsetStubs()
+
def _InitMultiFileData(self):
"""Create an array of mock file objects.
@@ -211,7 +214,7 @@
Returns:
A configured mock object
"""
- mock_zip = self.my_mox.CreateMock(ZipFile)
+ mock_zip = self.my_mox.CreateMock(zipfile.ZipFile)
mock_zip.infolist().AndReturn([self.file1])
mock_zip.infolist().AndReturn([self.file1])
mock_zip.close()
@@ -250,15 +253,11 @@
A configured mock object
"""
self._InitMultiFileData()
- mock_zip = self.my_mox.CreateMock(ZipFile)
+ mock_zip = self.my_mox.CreateMock(zipfile.ZipFile)
mock_zip.infolist().AndReturn(self.multi_file_dir)
mock_zip.close()
return mock_zip
- def tearDown(self):
- """Unset any mocks that we've created."""
- self.my_mox.UnsetStubs()
-
class AddFileToArchiveTest(unittest.TestCase):
"""Test behavior of method to add a file to an archive."""
@@ -270,6 +269,9 @@
self.file_to_add = 'file.txt'
self.input_dir = '/foo/bar/baz/'
+ def tearDown(self):
+ self.my_mox.UnsetStubs()
+
def testAddFileToArchive(self):
"""Test the DirectoryZipper.AddFileToArchive method.
@@ -312,15 +314,12 @@
Returns:
A configured mock object
"""
- archive_mock = self.my_mox.CreateMock(ZipFile)
+ archive_mock = self.my_mox.CreateMock(zipfile.ZipFile)
archive_mock.write(''.join([self.input_dir, self.file_to_add]),
self.file_to_add)
archive_mock.close()
return archive_mock
- def tearDown(self):
- self.my_mox.UnsetStubs()
-
class CompressDirectoryTest(unittest.TestCase):
"""Test the master method of the class.
diff --git a/tools/sdkmanager/libs/sdklib/src/com/android/sdklib/PlatformTarget.java b/tools/sdkmanager/libs/sdklib/src/com/android/sdklib/PlatformTarget.java
index a3da70e..5efd553 100644
--- a/tools/sdkmanager/libs/sdklib/src/com/android/sdklib/PlatformTarget.java
+++ b/tools/sdkmanager/libs/sdklib/src/com/android/sdklib/PlatformTarget.java
@@ -28,7 +28,7 @@
/** String used to get a hash to the platform target */
private final static String PLATFORM_HASH = "android-%d";
- private final static String PLATFORM_VENDOR = "Android";
+ private final static String PLATFORM_VENDOR = "Android Open Source Project";
private final static String PLATFORM_NAME = "Android %s";
private final String mLocation;