/* * Copyright (C) 2008 The Android Open Source Project * * 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. */ package com.android.ddmlib.testrunner; import junit.framework.TestCase; import org.easymock.Capture; import org.easymock.EasyMock; import java.util.Collections; import java.util.Map; /** * Unit tests for {@link @InstrumentationResultParser}. */ @SuppressWarnings("unchecked") public class InstrumentationResultParserTest extends TestCase { private InstrumentationResultParser mParser; private ITestRunListener mMockListener; // static dummy test names to use for validation private static final String RUN_NAME = "foo"; private static final String CLASS_NAME = "com.test.FooTest"; private static final String TEST_NAME = "testFoo"; private static final String STACK_TRACE = "java.lang.AssertionFailedException"; private static final TestIdentifier TEST_ID = new TestIdentifier(CLASS_NAME, TEST_NAME); /** * @param name - test name */ public InstrumentationResultParserTest(String name) { super(name); } /** * @see junit.framework.TestCase#setUp() */ @Override protected void setUp() throws Exception { super.setUp(); // use a strict mock to verify order of method calls mMockListener = EasyMock.createStrictMock(ITestRunListener.class); mParser = new InstrumentationResultParser(RUN_NAME, mMockListener); } /** * Tests parsing empty output. */ public void testParse_empty() { mMockListener.testRunStarted(RUN_NAME, 0); mMockListener.testRunFailed(InstrumentationResultParser.NO_TEST_RESULTS_MSG); mMockListener.testRunEnded(0, Collections.EMPTY_MAP); injectAndVerifyTestString(""); } /** * Tests parsing output for a successful test run with no tests. */ public void testParse_noTests() { StringBuilder output = new StringBuilder(); addLine(output, "INSTRUMENTATION_RESULT: stream="); addLine(output, "Test results for InstrumentationTestRunner="); addLine(output, "Time: 0.001"); addLine(output, "OK (0 tests)"); addLine(output, "INSTRUMENTATION_CODE: -1"); mMockListener.testRunStarted(RUN_NAME, 0); mMockListener.testRunEnded(1, Collections.EMPTY_MAP); injectAndVerifyTestString(output.toString()); } /** * Tests parsing output for a single successful test execution. */ public void testParse_singleTest() { StringBuilder output = createSuccessTest(); mMockListener.testRunStarted(RUN_NAME, 1); mMockListener.testStarted(TEST_ID); mMockListener.testEnded(TEST_ID, Collections.EMPTY_MAP); mMockListener.testRunEnded(0, Collections.EMPTY_MAP); injectAndVerifyTestString(output.toString()); } /** * Tests parsing output for a successful test execution with metrics. */ public void testParse_testMetrics() { StringBuilder output = buildCommonResult(); addStatusKey(output, "randomKey", "randomValue"); addSuccessCode(output); final Capture<Map<String, String>> captureMetrics = new Capture<Map<String, String>>(); mMockListener.testRunStarted(RUN_NAME, 1); mMockListener.testStarted(TEST_ID); mMockListener.testEnded(EasyMock.eq(TEST_ID), EasyMock.capture(captureMetrics)); mMockListener.testRunEnded(0, Collections.EMPTY_MAP); injectAndVerifyTestString(output.toString()); assertEquals("randomValue", captureMetrics.getValue().get("randomKey")); } /** * Test parsing output for a test that produces repeated metrics values * <p/> * This mimics launch performance test output. */ public void testParse_repeatedTestMetrics() { StringBuilder output = new StringBuilder(); // add test start output addCommonStatus(output); addStartCode(output); addStatusKey(output, "currentiterations", "1"); addStatusCode(output, "2"); addStatusKey(output, "currentiterations", "2"); addStatusCode(output, "2"); addStatusKey(output, "currentiterations", "3"); addStatusCode(output, "2"); // add test end addCommonStatus(output); addStatusKey(output, "numiterations", "3"); addSuccessCode(output); final Capture<Map<String, String>> captureMetrics = new Capture<Map<String, String>>(); mMockListener.testRunStarted(RUN_NAME, 1); mMockListener.testStarted(TEST_ID); mMockListener.testEnded(EasyMock.eq(TEST_ID), EasyMock.capture(captureMetrics)); mMockListener.testRunEnded(0, Collections.EMPTY_MAP); injectAndVerifyTestString(output.toString()); assertEquals("3", captureMetrics.getValue().get("currentiterations")); assertEquals("3", captureMetrics.getValue().get("numiterations")); } /** * Test parsing output for a test failure. */ public void testParse_testFailed() { StringBuilder output = buildCommonResult(); addStackTrace(output); addFailureCode(output); mMockListener.testRunStarted(RUN_NAME, 1); mMockListener.testStarted(TEST_ID); mMockListener.testFailed(TEST_ID, STACK_TRACE); mMockListener.testEnded(TEST_ID, Collections.EMPTY_MAP); mMockListener.testRunEnded(0, Collections.EMPTY_MAP); injectAndVerifyTestString(output.toString()); } /** * Test parsing and conversion of time output that contains extra chars. */ public void testParse_timeBracket() { StringBuilder output = createSuccessTest(); output.append("Time: 0.001)"); mMockListener.testRunStarted(RUN_NAME, 1); mMockListener.testStarted(TEST_ID); mMockListener.testEnded(TEST_ID, Collections.EMPTY_MAP); mMockListener.testRunEnded(1, Collections.EMPTY_MAP); injectAndVerifyTestString(output.toString()); } /** * Test parsing output for a test run failure. */ public void testParse_runFailed() { 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); mMockListener.testRunStarted(RUN_NAME, 0); mMockListener.testRunFailed(errorMessage); mMockListener.testRunEnded(0, Collections.EMPTY_MAP); injectAndVerifyTestString(output.toString()); } /** * Test parsing output when a status code cannot be parsed */ public void testParse_invalidCode() { StringBuilder output = new StringBuilder(); addLine(output, "android.util.AndroidException: INSTRUMENTATION_FAILED: foo/foo"); addLine(output, "INSTRUMENTATION_STATUS: id=ActivityManagerService"); addLine(output, "INSTRUMENTATION_STATUS: Error=Unable to find instrumentation target package: foo"); addLine(output, "INSTRUMENTATION_STATUS_CODE: -1at com.android.commands.am.Am.runInstrument(Am.java:532)"); addLine(output, ""); addLine(output, " at com.android.commands.am.Am.run(Am.java:111)"); addLineBreak(output); mMockListener.testRunStarted(RUN_NAME, 0); mMockListener.testRunFailed((String)EasyMock.anyObject()); mMockListener.testRunEnded(0, Collections.EMPTY_MAP); injectAndVerifyTestString(output.toString()); } /** * Test parsing output for a test run failure, where an instrumentation component failed to * load. * <p/> * Parsing input takes the from of INSTRUMENTATION_RESULT: fff */ public void testParse_failedResult() { 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); mMockListener.testRunStarted(RUN_NAME, 0); mMockListener.testRunFailed(EasyMock.contains(errorMessage)); mMockListener.testRunEnded(0, Collections.EMPTY_MAP); injectAndVerifyTestString(output.toString()); } /** * Test parsing output for a test run that did not complete. * <p/> * This can occur if device spontaneously reboots, or if test method could not be found. */ public void testParse_incomplete() { StringBuilder output = new StringBuilder(); // add a start test sequence, but without an end test sequence addCommonStatus(output); addStartCode(output); mMockListener.testRunStarted(RUN_NAME, 1); mMockListener.testStarted(TEST_ID); mMockListener.testFailed(EasyMock.eq(TEST_ID), EasyMock.startsWith(InstrumentationResultParser.INCOMPLETE_TEST_ERR_MSG_PREFIX)); mMockListener.testEnded(TEST_ID, Collections.EMPTY_MAP); mMockListener.testRunFailed(EasyMock.startsWith( InstrumentationResultParser.INCOMPLETE_RUN_ERR_MSG_PREFIX)); mMockListener.testRunEnded(0, Collections.EMPTY_MAP); injectAndVerifyTestString(output.toString()); } /** * Test parsing output for a test run that did not start due to incorrect syntax supplied to am. */ public void testParse_amFailed() { StringBuilder output = new StringBuilder(); addLine(output, "usage: am [subcommand] [options]"); addLine(output, "start an Activity: am start [-D] [-W] <INTENT>"); addLine(output, "-D: enable debugging"); addLine(output, "-W: wait for launch to complete"); addLine(output, "start a Service: am startservice <INTENT>"); addLine(output, "Error: Bad component name: wfsdafddfasasdf"); mMockListener.testRunStarted(RUN_NAME, 0); mMockListener.testRunFailed(InstrumentationResultParser.NO_TEST_RESULTS_MSG); mMockListener.testRunEnded(0, Collections.EMPTY_MAP); injectAndVerifyTestString(output.toString()); } /** * Test parsing output for a test run that produces INSTRUMENTATION_RESULT output. * <p/> * This mimics launch performance test output. */ public void testParse_instrumentationResults() { StringBuilder output = new StringBuilder(); addResultKey(output, "other_pss", "2390"); addResultKey(output, "java_allocated", "2539"); addResultKey(output, "foo", "bar"); addResultKey(output, "stream", "should not be captured"); addLine(output, "INSTRUMENTATION_CODE: -1"); Capture<Map<String, String>> captureMetrics = new Capture<Map<String, String>>(); mMockListener.testRunStarted(RUN_NAME, 0); mMockListener.testRunEnded(EasyMock.anyLong(), EasyMock.capture(captureMetrics)); injectAndVerifyTestString(output.toString()); assertEquals("2390", captureMetrics.getValue().get("other_pss")); assertEquals("2539", captureMetrics.getValue().get("java_allocated")); assertEquals("bar", captureMetrics.getValue().get("foo")); assertEquals(3, captureMetrics.getValue().size()); } public void testParse_AssumptionFailuresIgnored() { StringBuilder output = new StringBuilder(); addLine(output, "INSTRUMENTATION_STATUS: numtests=3"); addLine(output, "INSTRUMENTATION_STATUS: stream="); addLine(output, "com.example.helloworld.FailureAssumptionTest:"); addLine(output, "INSTRUMENTATION_STATUS: id=AndroidJUnitRunner"); addLine(output, "INSTRUMENTATION_STATUS: test=checkIgnoreTestsArePossible"); addLine(output, "INSTRUMENTATION_STATUS: class=com.example.helloworld.FailureAssumptionTest"); addLine(output, "INSTRUMENTATION_STATUS: current=1"); addLine(output, "INSTRUMENTATION_STATUS_CODE: -3"); addLine(output, "INSTRUMENTATION_STATUS: numtests=3"); addLine(output, "INSTRUMENTATION_STATUS: stream="); addLine(output, "INSTRUMENTATION_STATUS: id=AndroidJUnitRunner"); addLine(output, "INSTRUMENTATION_STATUS: test=checkAssumptionIsSkipped"); addLine(output, "INSTRUMENTATION_STATUS: class=com.example.helloworld.FailureAssumptionTest"); addLine(output, "INSTRUMENTATION_STATUS: current=2"); addLine(output, "INSTRUMENTATION_STATUS_CODE: 1"); addLine(output, "INSTRUMENTATION_STATUS: numtests=3"); addLine(output, "INSTRUMENTATION_STATUS: stream="); addLine(output, "INSTRUMENTATION_STATUS: id=AndroidJUnitRunner"); addLine(output, "INSTRUMENTATION_STATUS: test=checkAssumptionIsSkipped"); addLine(output, "INSTRUMENTATION_STATUS: class=com.example.helloworld.FailureAssumptionTest"); addLine(output, "INSTRUMENTATION_STATUS: stack=org.junit.AssumptionViolatedException: got: <false>, expected: is <true>"); addLine(output, "at org.junit.Assume.assumeThat(Assume.java:95)"); addLine(output, "at org.junit.Assume.assumeTrue(Assume.java:41)"); addLine(output, "at com.example.helloworld.FailureAssumptionTest.checkAssumptionIsSkipped(FailureAssumptionTest.java:19)"); addLine(output, "at java.lang.reflect.Method.invoke(Native Method)"); addLine(output, "at java.lang.reflect.Method.invoke(Method.java:372)"); addLine(output, "at org.junit.runners.model.FrameworkMethod$1.runReflectiveCall(FrameworkMethod.java:50)"); addLine(output, "at org.junit.internal.runners.model.ReflectiveCallable.run(ReflectiveCallable.java:12)"); addLine(output, "at org.junit.runners.model.FrameworkMethod.invokeExplosively(FrameworkMethod.java:47)"); addLine(output, "at org.junit.internal.runners.statements.InvokeMethod.evaluate(InvokeMethod.java:17)"); addLine(output, "at org.junit.runners.ParentRunner.runLeaf(ParentRunner.java:325)"); addLine(output, "at org.junit.runners.BlockJUnit4ClassRunner.runChild(BlockJUnit4ClassRunner.java:78)"); addLine(output, "at org.junit.runners.BlockJUnit4ClassRunner.runChild(BlockJUnit4ClassRunner.java:57)"); addLine(output, "at org.junit.runners.ParentRunner$3.run(ParentRunner.java:290)"); addLine(output, "at org.junit.runners.ParentRunner$1.schedule(ParentRunner.java:71)"); addLine(output, "at org.junit.runners.ParentRunner.runChildren(ParentRunner.java:288)"); addLine(output, "at org.junit.runners.ParentRunner.access$000(ParentRunner.java:58)"); addLine(output, "at org.junit.runners.ParentRunner$2.evaluate(ParentRunner.java:268)"); addLine(output, "at org.junit.runners.ParentRunner.run(ParentRunner.java:363)"); addLine(output, "at org.junit.runners.Suite.runChild(Suite.java:128)"); addLine(output, "at org.junit.runners.Suite.runChild(Suite.java:27)"); addLine(output, "at org.junit.runners.ParentRunner$3.run(ParentRunner.java:290)"); addLine(output, "at org.junit.runners.ParentRunner$1.schedule(ParentRunner.java:71)"); addLine(output, "at org.junit.runners.ParentRunner.runChildren(ParentRunner.java:288)"); addLine(output, "at org.junit.runners.ParentRunner.access$000(ParentRunner.java:58)"); addLine(output, "at org.junit.runners.ParentRunner$2.evaluate(ParentRunner.java:268)"); addLine(output, "at org.junit.runners.ParentRunner.run(ParentRunner.java:363)"); addLine(output, "at org.junit.runner.JUnitCore.run(JUnitCore.java:137)"); addLine(output, "at org.junit.runner.JUnitCore.run(JUnitCore.java:115)"); addLine(output, "at android.support.test.internal.runner.TestExecutor.execute(TestExecutor.java:54)"); addLine(output, "at android.support.test.runner.AndroidJUnitRunner.onStart(AndroidJUnitRunner.java:228)"); addLine(output, "at android.app.Instrumentation$InstrumentationThread.run(Instrumentation.java:1853)"); addLine(output, ""); addLine(output, "INSTRUMENTATION_STATUS: current=2"); addLine(output, "INSTRUMENTATION_STATUS_CODE: -4"); addLine(output, "INSTRUMENTATION_STATUS: numtests=3"); addLine(output, "INSTRUMENTATION_STATUS: stream="); addLine(output, "com.example.helloworld.HelloWorldTest:"); addLine(output, "INSTRUMENTATION_STATUS: id=AndroidJUnitRunner"); addLine(output, "INSTRUMENTATION_STATUS: test=testPreconditions"); addLine(output, "INSTRUMENTATION_STATUS: class=com.example.helloworld.HelloWorldTest"); addLine(output, "INSTRUMENTATION_STATUS: current=3"); addLine(output, "INSTRUMENTATION_STATUS_CODE: 1"); addLine(output, "INSTRUMENTATION_STATUS: numtests=3"); addLine(output, "INSTRUMENTATION_STATUS: stream=."); addLine(output, "INSTRUMENTATION_STATUS: id=AndroidJUnitRunner"); addLine(output, "INSTRUMENTATION_STATUS: test=testPreconditions"); addLine(output, "INSTRUMENTATION_STATUS: class=com.example.helloworld.HelloWorldTest"); addLine(output, "INSTRUMENTATION_STATUS: current=3"); addLine(output, "INSTRUMENTATION_STATUS_CODE: 0"); addLine(output, "INSTRUMENTATION_RESULT: stream="); addLine(output, ""); addLine(output, "Time: 0.676"); addLine(output, ""); addLine(output, "OK (2 tests)"); addLine(output, ""); addLine(output, ""); addLine(output, "INSTRUMENTATION_CODE: -1"); mMockListener.testRunStarted(RUN_NAME, 3); final TestIdentifier IGNORED_ANNOTATION = new TestIdentifier( "com.example.helloworld.FailureAssumptionTest", "checkIgnoreTestsArePossible"); mMockListener.testStarted(IGNORED_ANNOTATION); mMockListener.testIgnored(IGNORED_ANNOTATION); mMockListener.testEnded(IGNORED_ANNOTATION, Collections.EMPTY_MAP); final TestIdentifier ASSUME_FALSE = new TestIdentifier( "com.example.helloworld.FailureAssumptionTest", "checkAssumptionIsSkipped"); mMockListener.testStarted(ASSUME_FALSE); mMockListener.testAssumptionFailure(EasyMock.eq(ASSUME_FALSE), EasyMock.startsWith( "org.junit.AssumptionViolatedException: got: <false>, expected: is <true>")); mMockListener.testEnded(ASSUME_FALSE, Collections.EMPTY_MAP); final TestIdentifier HELLO_WORLD = new TestIdentifier("com.example.helloworld.HelloWorldTest", "testPreconditions"); mMockListener.testStarted(HELLO_WORLD); mMockListener.testEnded(HELLO_WORLD, Collections.EMPTY_MAP); mMockListener.testRunEnded(EasyMock.eq(676L), EasyMock.anyObject(Map.class)); injectAndVerifyTestString(output.toString()); } /** * Builds a common test result using TEST_NAME and TEST_CLASS. */ private StringBuilder buildCommonResult() { StringBuilder output = new StringBuilder(); // add test start bundle addCommonStatus(output); addStartCode(output); // add end test bundle, without status addCommonStatus(output); return output; } /** * Create instrumentation output for a successful single test case execution. */ private StringBuilder createSuccessTest() { StringBuilder output = buildCommonResult(); addSuccessCode(output); return output; } /** * Adds common status results to the provided output. */ private void addCommonStatus(StringBuilder output) { addStatusKey(output, "stream", "\r\n" + CLASS_NAME); addStatusKey(output, "test", TEST_NAME); addStatusKey(output, "class", CLASS_NAME); addStatusKey(output, "current", "1"); addStatusKey(output, "numtests", "1"); addStatusKey(output, "id", "InstrumentationTestRunner"); } /** * Adds a stack trace status bundle to output. */ private void addStackTrace(StringBuilder output) { addStatusKey(output, "stack", STACK_TRACE); } /** * Helper method to add a status key-value bundle. */ private void addStatusKey(StringBuilder outputBuilder, String key, String value) { outputBuilder.append("INSTRUMENTATION_STATUS: "); outputBuilder.append(key); outputBuilder.append('='); outputBuilder.append(value); addLineBreak(outputBuilder); } /** * Helper method to add a result key value bundle. */ private void addResultKey(StringBuilder outputBuilder, String key, String value) { outputBuilder.append("INSTRUMENTATION_RESULT: "); outputBuilder.append(key); outputBuilder.append('='); outputBuilder.append(value); addLineBreak(outputBuilder); } /** * Append a line to output. */ private void addLine(StringBuilder outputBuilder, String lineContent) { outputBuilder.append(lineContent); addLineBreak(outputBuilder); } /** * Append line break characters to output */ private void addLineBreak(StringBuilder outputBuilder) { outputBuilder.append("\r\n"); } private void addStartCode(StringBuilder outputBuilder) { addStatusCode(outputBuilder, "1"); } private void addSuccessCode(StringBuilder outputBuilder) { addStatusCode(outputBuilder, "0"); } private void addFailureCode(StringBuilder outputBuilder) { addStatusCode(outputBuilder, "-2"); } private void addStatusCode(StringBuilder outputBuilder, String value) { outputBuilder.append("INSTRUMENTATION_STATUS_CODE: "); outputBuilder.append(value); addLineBreak(outputBuilder); } /** * Inject a test string into the result parser, and verify the mock listener. * * @param result the string to inject into parser under test. */ private void injectAndVerifyTestString(String result) { EasyMock.replay(mMockListener); byte[] data = result.getBytes(); mParser.addOutput(data, 0, data.length); mParser.flush(); EasyMock.verify(mMockListener); } }