auto import from //branches/cupcake_rel/...@140373
This commit is contained in:
@@ -27,7 +27,14 @@ import com.android.ddmlib.MultiLineReceiver;
|
||||
* <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 @@ public class InstrumentationResultParser extends MultiLineReceiver {
|
||||
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 @@ public class InstrumentationResultParser extends MultiLineReceiver {
|
||||
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 @@ public class InstrumentationResultParser extends MultiLineReceiver {
|
||||
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 class InstrumentationResultParser extends MultiLineReceiver {
|
||||
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 @@ public class InstrumentationResultParser extends MultiLineReceiver {
|
||||
// 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 @@ public class InstrumentationResultParser extends MultiLineReceiver {
|
||||
|
||||
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 @@ public class InstrumentationResultParser extends MultiLineReceiver {
|
||||
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 @@ public class InstrumentationResultParser extends MultiLineReceiver {
|
||||
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 @@ public class InstrumentationResultParser extends MultiLineReceiver {
|
||||
*/
|
||||
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 @@ public class InstrumentationResultParser extends MultiLineReceiver {
|
||||
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,13 +385,19 @@ public class InstrumentationResultParser extends MultiLineReceiver {
|
||||
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.
|
||||
*/
|
||||
|
||||
@@ -21,27 +21,35 @@ import com.android.ddmlib.IDevice;
|
||||
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 @@ public class RemoteAndroidTestRunner {
|
||||
|
||||
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 @@ public class RemoteAndroidTestRunner {
|
||||
IDevice remoteDevice) {
|
||||
this(packageName, null, remoteDevice);
|
||||
}
|
||||
|
||||
|
||||
/**
|
||||
* Returns the application package name.
|
||||
*/
|
||||
@@ -89,14 +95,14 @@ public class RemoteAndroidTestRunner {
|
||||
}
|
||||
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 @@ public class RemoteAndroidTestRunner {
|
||||
* @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 class RemoteAndroidTestRunner {
|
||||
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 @@ public class RemoteAndroidTestRunner {
|
||||
* @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 @@ public class RemoteAndroidTestRunner {
|
||||
listener.testRunFailed(e.toString());
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
/**
|
||||
* Requests cancellation of this test run.
|
||||
*/
|
||||
@@ -193,36 +222,19 @@ public class RemoteAndroidTestRunner {
|
||||
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";
|
||||
}
|
||||
else {
|
||||
return "";
|
||||
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);
|
||||
}
|
||||
return commandBuilder.toString();
|
||||
}
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user