diff options
author | Brett Chabot <brettchabot@android.com> | 2010-03-02 10:53:37 -0800 |
---|---|---|
committer | Brett Chabot <brettchabot@android.com> | 2010-03-04 18:04:28 -0800 |
commit | b98892c6797a275e2bfbcb969c89d760591e4b90 (patch) | |
tree | 7d3ff09a5517d352dd8e6b77ad23bbbdec66c48c | |
parent | 91ed8175993fb6eca29e5281bf3f7f5b7a3b3ded (diff) | |
download | sdk-b98892c6797a275e2bfbcb969c89d760591e4b90.zip sdk-b98892c6797a275e2bfbcb969c89d760591e4b90.tar.gz sdk-b98892c6797a275e2bfbcb969c89d760591e4b90.tar.bz2 |
Add ability to detect incomplete test runs to InstrumentationResultParser.
And clean-up whitespace.
Change-Id: Icb6bd5c8bf403fda0de436d4b9e732e7ea976267
4 files changed, 147 insertions, 100 deletions
@@ -2,4 +2,4 @@ *.bak *.pyc Thumbs.db - +*.class diff --git a/ddms/libs/ddmlib/src/com/android/ddmlib/testrunner/InstrumentationResultParser.java b/ddms/libs/ddmlib/src/com/android/ddmlib/testrunner/InstrumentationResultParser.java index 1a0b21f..30c72a5 100755 --- a/ddms/libs/ddmlib/src/com/android/ddmlib/testrunner/InstrumentationResultParser.java +++ b/ddms/libs/ddmlib/src/com/android/ddmlib/testrunner/InstrumentationResultParser.java @@ -21,25 +21,25 @@ import com.android.ddmlib.Log; import com.android.ddmlib.MultiLineReceiver; /** - * Parses the 'raw output mode' results of an instrumentation test run from shell and informs a + * Parses the 'raw output mode' results of an instrumentation test run from shell and informs a * ITestRunListener of the results. - * + * * <p>Expects the following output: - * + * * <p>If fatal error occurred when attempted to run the tests: * <pre> * INSTRUMENTATION_STATUS: Error=error Message - * INSTRUMENTATION_FAILED: + * 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 - * run, expects that the elapsed test time in seconds will be displayed - * + * pairs, delimited by a start(1)/pass(0)/fail(-2)/error(-1) status code result. At end of test + * run, expects that the elapsed test time in seconds will be displayed + * * <p>For example: * <pre> * INSTRUMENTATION_STATUS_CODE: 1 @@ -48,15 +48,15 @@ import com.android.ddmlib.MultiLineReceiver; * INSTRUMENTATION_STATUS: numtests=2 * INSTRUMENTATION_STATUS: stack=com.foo.FooTest#testFoo:312 * com.foo.X - * INSTRUMENTATION_STATUS_CODE: -2 - * ... - * + * INSTRUMENTATION_STATUS_CODE: -2 + * ... + * * Time: X * </pre> * <p>Note that the "value" portion of the key-value pair may wrap over several text lines */ public class InstrumentationResultParser extends MultiLineReceiver { - + /** Relevant test status keys. */ private static class StatusKeys { private static final String TEST = "test"; @@ -66,7 +66,7 @@ public class InstrumentationResultParser extends MultiLineReceiver { private static final String ERROR = "Error"; private static final String SHORTMSG = "shortMsg"; } - + /** Test result status codes. */ private static class StatusCodes { private static final int FAILURE = -2; @@ -84,10 +84,10 @@ public class InstrumentationResultParser extends MultiLineReceiver { private static final String RESULT = "INSTRUMENTATION_RESULT: "; private static final String TIME_REPORT = "Time: "; } - + private final ITestRunListener mTestListener; - /** + /** * Test result data */ private static class TestResult { @@ -96,12 +96,12 @@ public class InstrumentationResultParser extends MultiLineReceiver { private String mTestClass = null; private String mStackTrace = null; private Integer mNumTests = null; - + /** Returns true if all expected values have been parsed */ boolean isComplete() { return mCode != null && mTestName != null && mTestClass != null; } - + /** Provides a more user readable string for TestResult, if possible */ @Override public String toString() { @@ -109,49 +109,58 @@ public class InstrumentationResultParser extends MultiLineReceiver { 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 */ private TestResult mCurrentTestResult = null; - + /** Stores the current "key" portion of the status key-value being parsed. */ private String mCurrentKey = null; - + /** Stores the current "value" portion of the status key-value being parsed. */ private StringBuilder mCurrentValue = null; - + /** True if start of test has already been reported to listener. */ private boolean mTestStartReported = false; - + + /** True if test run failure has already been reported to listener. */ + private boolean mTestRunFailReported = false; + /** The elapsed time of the test run, in milliseconds. */ private long mTestTime = 0; - + /** True if current test run has been canceled by user. */ private boolean mIsCancelled = false; - + + /** The number of tests currently run */ + private int mNumTestsRun = 0; + + /** The number of tests expected to run */ + private int mNumTestsExpected = 0; + private static final String LOG_TAG = "InstrumentationResultParser"; - + /** * Creates the InstrumentationResultParser. - * + * * @param listener informed of test results as the tests are executing */ public InstrumentationResultParser(ITestRunListener listener) { mTestListener = listener; } - + /** * Processes the instrumentation test output from shell. - * + * * @see MultiLineReceiver#processNewLines */ @Override @@ -162,23 +171,23 @@ public class InstrumentationResultParser extends MultiLineReceiver { Log.v(LOG_TAG, line); } } - + /** * Parse an individual output line. Expects a line that is one of: * <ul> - * <li> - * The start of a new status line (starts with Prefixes.STATUS or Prefixes.STATUS_CODE), - * and thus there is a new key=value pair to parse, and the previous key-value pair is - * finished. + * <li> + * The start of a new status line (starts with Prefixes.STATUS or Prefixes.STATUS_CODE), + * and thus there is a new key=value pair to parse, and the previous key-value pair is + * finished. * </li> * <li> * A continuation of the previous status (the "value" portion of the key has wrapped * to the next line). - * </li> + * </li> * <li> A line reporting a fatal error in the test run (Prefixes.STATUS_FAILED) </li> - * <li> A line reporting the total elapsed time of the test run. (Prefixes.TIME_REPORT) </li> + * <li> A line reporting the total elapsed time of the test run. (Prefixes.TIME_REPORT) </li> * </ul> - * + * * @param line Text output line */ private void parse(String line) { @@ -193,25 +202,25 @@ public class InstrumentationResultParser extends MultiLineReceiver { } 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) || + 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 + // just ignore the remaining data on this line } else if (line.startsWith(Prefixes.TIME_REPORT)) { parseTime(line, Prefixes.TIME_REPORT.length()); } else { if (mCurrentValue != null) { - // this is a value that has wrapped to next line. + // this is a value that has wrapped to next line. mCurrentValue.append("\r\n"); mCurrentValue.append(line); } else { - Log.w(LOG_TAG, "unrecognized line " + line); + Log.i(LOG_TAG, "unrecognized line " + line); } } } - + /** * Stores the currently parsed key-value pair into mCurrentTestInfo. */ @@ -230,10 +239,10 @@ public class InstrumentationResultParser extends MultiLineReceiver { } catch (NumberFormatException e) { Log.e(LOG_TAG, "Unexpected integer number of tests, received " + statusValue); } - } else if (mCurrentKey.equals(StatusKeys.ERROR) || + } else if (mCurrentKey.equals(StatusKeys.ERROR) || mCurrentKey.equals(StatusKeys.SHORTMSG)) { // test run must have failed - handleTestRunFailed(statusValue); + handleTestRunFailed(statusValue); } else if (mCurrentKey.equals(StatusKeys.STACK)) { testInfo.mStackTrace = statusValue; } @@ -242,23 +251,23 @@ public class InstrumentationResultParser extends MultiLineReceiver { mCurrentValue = null; } } - + private TestResult getCurrentTestInfo() { if (mCurrentTestResult == null) { mCurrentTestResult = new TestResult(); } return mCurrentTestResult; } - + private void clearCurrentTestInfo() { mCurrentTestResult = null; } - + /** * Parses the key from the current line. * Expects format of "key=value". - * - * @param line full line of text to parse + * + * @param line full line of text to parse * @param keyStartPos the starting position of the key in the given line */ private void parseKey(String line, int keyStartPos) { @@ -268,55 +277,55 @@ public class InstrumentationResultParser extends MultiLineReceiver { parseValue(line, endKeyPos + 1); } } - + /** * Parses the start of a key=value pair. - * - * @param line - full line of text to parse + * + * @param line - full line of text to parse * @param valueStartPos - the starting position of the value in the given line */ private void parseValue(String line, int valueStartPos) { mCurrentValue = new StringBuilder(); mCurrentValue.append(line.substring(valueStartPos)); } - + /** - * Parses out a status code result. + * Parses out a status code result. */ private void parseStatusCode(String line) { String value = line.substring(Prefixes.STATUS_CODE.length()).trim(); TestResult testInfo = getCurrentTestInfo(); try { - testInfo.mCode = Integer.parseInt(value); + testInfo.mCode = Integer.parseInt(value); } catch (NumberFormatException e) { Log.e(LOG_TAG, "Expected integer status code, received: " + value); } - + // this means we're done with current test result bundle reportResult(testInfo); clearCurrentTestInfo(); } - + /** * Returns true if test run canceled. - * + * * @see IShellOutputReceiver#isCancelled() */ public boolean isCancelled() { return mIsCancelled; } - + /** * Requests cancellation of test run. */ public void cancel() { mIsCancelled = true; } - + /** * Reports a test result to the test run listener. Must be called when a individual test - * result has been fully parsed. - * + * result has been fully parsed. + * * @param statusMap key-value status pairs of test result */ private void reportResult(TestResult testInfo) { @@ -332,52 +341,57 @@ public class InstrumentationResultParser extends MultiLineReceiver { mTestListener.testStarted(testId); break; case StatusCodes.FAILURE: - mTestListener.testFailed(ITestRunListener.TestFailure.FAILURE, testId, + mTestListener.testFailed(ITestRunListener.TestFailure.FAILURE, testId, getTrace(testInfo)); mTestListener.testEnded(testId); + mNumTestsRun++; break; case StatusCodes.ERROR: - mTestListener.testFailed(ITestRunListener.TestFailure.ERROR, testId, + mTestListener.testFailed(ITestRunListener.TestFailure.ERROR, testId, getTrace(testInfo)); mTestListener.testEnded(testId); + mNumTestsRun++; break; case StatusCodes.OK: mTestListener.testEnded(testId); + mNumTestsRun++; break; default: Log.e(LOG_TAG, "Unknown status code received: " + testInfo.mCode); mTestListener.testEnded(testId); + mNumTestsRun++; break; } } - + /** - * Reports the start of a test run, and the total test count, if it has not been previously + * Reports the start of a test run, and the total test count, if it has not been previously * reported. - * + * * @param testInfo current test status values */ private void reportTestRunStarted(TestResult testInfo) { // if start test run not reported yet if (!mTestStartReported && testInfo.mNumTests != null) { mTestListener.testRunStarted(testInfo.mNumTests); + mNumTestsExpected = testInfo.mNumTests; mTestStartReported = true; } } - + /** * Returns the stack trace of the current failed test, from the provided testInfo. */ private String getTrace(TestResult testInfo) { if (testInfo.mStackTrace != null) { - return testInfo.mStackTrace; + return testInfo.mStackTrace; } else { Log.e(LOG_TAG, "Could not find stack trace for failed test "); return new Throwable("Unknown failure").toString(); } } - + /** * Parses out and store the elapsed time. */ @@ -385,25 +399,35 @@ public class InstrumentationResultParser extends MultiLineReceiver { String timeString = line.substring(startPos); try { float timeSeconds = Float.parseFloat(timeString); - mTestTime = (long) (timeSeconds * 1000); + 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) { + Log.i(LOG_TAG, String.format("test run failed %s", errorMsg)); mTestListener.testRunFailed(errorMsg == null ? "Unknown error" : errorMsg); + mTestRunFailReported = true; } - + /** - * Called by parent when adb session is complete. + * Called by parent when adb session is complete. */ @Override public void done() { super.done(); - mTestListener.testRunEnded(mTestTime); + if (!mTestRunFailReported && mNumTestsExpected > mNumTestsRun) { + final String message = + String.format("Test run incomplete. Expected %d tests, received %d", + mNumTestsExpected, mNumTestsRun); + Log.w(LOG_TAG, message); + mTestListener.testRunFailed(message); + } else { + mTestListener.testRunEnded(mTestTime); + } } } diff --git a/ddms/libs/ddmlib/src/com/android/ddmlib/testrunner/TestIdentifier.java b/ddms/libs/ddmlib/src/com/android/ddmlib/testrunner/TestIdentifier.java index 4d3b108..f0cf4c8 100644 --- a/ddms/libs/ddmlib/src/com/android/ddmlib/testrunner/TestIdentifier.java +++ b/ddms/libs/ddmlib/src/com/android/ddmlib/testrunner/TestIdentifier.java @@ -17,44 +17,44 @@ package com.android.ddmlib.testrunner; /** - * Identifies a parsed instrumentation test + * Identifies a parsed instrumentation test. */ public class TestIdentifier { private final String mClassName; private final String mTestName; - + /** - * Creates a test identifier - * + * Creates a test identifier. + * * @param className fully qualified class name of the test. Cannot be null. * @param testName name of the test. Cannot be null. */ public TestIdentifier(String className, String testName) { if (className == null || testName == null) { - throw new IllegalArgumentException("className and testName must " + + throw new IllegalArgumentException("className and testName must " + "be non-null"); } mClassName = className; mTestName = testName; } - + /** - * Returns the fully qualified class name of the test + * Returns the fully qualified class name of the test. */ public String getClassName() { return mClassName; } /** - * Returns the name of the test + * Returns the name of the test. */ public String getTestName() { return mTestName; } - + /** - * Tests equality by comparing class and method name + * Tests equality by comparing class and method name. */ @Override public boolean equals(Object other) { @@ -62,10 +62,10 @@ public class TestIdentifier { return false; } TestIdentifier otherTest = (TestIdentifier)other; - return getClassName().equals(otherTest.getClassName()) && + return getClassName().equals(otherTest.getClassName()) && getTestName().equals(otherTest.getTestName()); } - + /** * Generates hashCode based on class and method name. */ @@ -73,4 +73,12 @@ public class TestIdentifier { public int hashCode() { return getClassName().hashCode() * 31 + getTestName().hashCode(); } + + /** + * Generates user friendly string. + */ + @Override + public String toString() { + return String.format("%s#%s", getClassName(), getTestName()); + } } diff --git a/ddms/libs/ddmlib/tests/src/com/android/ddmlib/testrunner/InstrumentationResultParserTest.java b/ddms/libs/ddmlib/tests/src/com/android/ddmlib/testrunner/InstrumentationResultParserTest.java index 7742dd6..7df4956 100644 --- a/ddms/libs/ddmlib/tests/src/com/android/ddmlib/testrunner/InstrumentationResultParserTest.java +++ b/ddms/libs/ddmlib/tests/src/com/android/ddmlib/testrunner/InstrumentationResultParserTest.java @@ -94,7 +94,7 @@ public class InstrumentationResultParserTest extends TestCase { assertEquals(ITestRunListener.TestFailure.FAILURE, mTestResult.mTestStatus); assertEquals(STACK_TRACE, mTestResult.mTrace); } - + /** * Test basic parsing and conversion of time from output. */ @@ -103,42 +103,57 @@ public class InstrumentationResultParserTest extends TestCase { injectTestString(timeString); assertEquals(4900, mTestResult.mTestTime); } - + /** * Test basic parsing of a test run failure. */ public void testRunFailed() { - StringBuilder output = new StringBuilder(); + 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(); + 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); } /** + * Test parsing of a test run that did not complete. This can occur if device spontaneously + * reboots, or if test method could not be found + */ + public void testRunIncomplete() { + StringBuilder output = new StringBuilder(); + // add a start test sequence, but without an end test sequence + addCommonStatus(output); + addStartCode(output); + + injectTestString(output.toString()); + + assertTrue(mTestResult.mRunFailedMessage.startsWith("Test run incomplete.")); + } + + /** * Builds a common test result using TEST_NAME and TEST_CLASS. */ private StringBuilder buildCommonResult() { @@ -210,7 +225,7 @@ public class InstrumentationResultParserTest extends TestCase { /** * inject a test string into the result parser. - * + * * @param result */ private void injectTestString(String result) { |