diff options
author | Brett Chabot <brettchabot@android.com> | 2012-11-15 09:07:05 -0800 |
---|---|---|
committer | Brett Chabot <brettchabot@android.com> | 2012-11-15 09:10:50 -0800 |
commit | 24fd7703acd668cc5193d701ced5b420f3a2429d (patch) | |
tree | d80bf601a8f6b9450fedcc492f0a06e930d06358 | |
parent | dc08099a240d50eacc989a230af53273799bc578 (diff) | |
download | sdk-24fd7703acd668cc5193d701ced5b420f3a2429d.zip sdk-24fd7703acd668cc5193d701ced5b420f3a2429d.tar.gz sdk-24fd7703acd668cc5193d701ced5b420f3a2429d.tar.bz2 |
Add a listener that can save test results as an ant XML file.
Bug 7408179
Change-Id: I7c25c70996f98b3a7fc1ec6c4e3c3422626954cb
6 files changed, 873 insertions, 0 deletions
diff --git a/ddms/libs/ddmlib/.classpath b/ddms/libs/ddmlib/.classpath index 9762afc..22d0241 100644 --- a/ddms/libs/ddmlib/.classpath +++ b/ddms/libs/ddmlib/.classpath @@ -2,5 +2,6 @@ <classpath> <classpathentry excluding="Android.mk" kind="src" path="src"/> <classpathentry kind="con" path="org.eclipse.jdt.launching.JRE_CONTAINER"/> + <classpathentry exported="true" kind="var" path="ANDROID_SRC/prebuilts/misc/common/kxml2/kxml2-2.3.0.jar"/> <classpathentry kind="output" path="bin"/> </classpath> diff --git a/ddms/libs/ddmlib/Android.mk b/ddms/libs/ddmlib/Android.mk index 978ac19..929522e 100644 --- a/ddms/libs/ddmlib/Android.mk +++ b/ddms/libs/ddmlib/Android.mk @@ -20,6 +20,8 @@ include $(CLEAR_VARS) LOCAL_SRC_FILES := $(call all-java-files-under, src) LOCAL_JAVA_RESOURCE_DIRS := src +LOCAL_JAVA_LIBRARIES := kxml2-2.3.0 + LOCAL_MODULE := ddmlib include $(BUILD_HOST_JAVA_LIBRARY) diff --git a/ddms/libs/ddmlib/src/com/android/ddmlib/testrunner/TestResult.java b/ddms/libs/ddmlib/src/com/android/ddmlib/testrunner/TestResult.java new file mode 100644 index 0000000..57e91f7 --- /dev/null +++ b/ddms/libs/ddmlib/src/com/android/ddmlib/testrunner/TestResult.java @@ -0,0 +1,147 @@ +/* + * Copyright (C) 2010 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 java.util.Arrays; +import java.util.Map; + +/** + * Container for a result of a single test. + */ +public class TestResult { + + public enum TestStatus { + /** Test error */ + ERROR, + /** Test failed. */ + FAILURE, + /** Test passed */ + PASSED, + /** Test started but not ended */ + INCOMPLETE + } + + private TestStatus mStatus; + private String mStackTrace; + private Map<String, String> mMetrics; + // the start and end time of the test, measured via {@link System#currentTimeMillis()} + private long mStartTime = 0; + private long mEndTime = 0; + + public TestResult() { + mStatus = TestStatus.INCOMPLETE; + mStartTime = System.currentTimeMillis(); + } + + /** + * Get the {@link TestStatus} result of the test. + */ + public TestStatus getStatus() { + return mStatus; + } + + /** + * Get the associated {@link String} stack trace. Should be <code>null</code> if + * {@link #getStatus()} is {@link TestStatus.PASSED}. + */ + public String getStackTrace() { + return mStackTrace; + } + + /** + * Get the associated test metrics. + */ + public Map<String, String> getMetrics() { + return mMetrics; + } + + /** + * Set the test metrics, overriding any previous values. + */ + public void setMetrics(Map<String, String> metrics) { + mMetrics = metrics; + } + + /** + * Return the {@link System#currentTimeMillis()} time that the + * {@link ITestInvocationListener#testStarted(TestIdentifier)} event was received. + */ + public long getStartTime() { + return mStartTime; + } + + /** + * Return the {@link System#currentTimeMillis()} time that the + * {@link ITestInvocationListener#testEnded(TestIdentifier)} event was received. + */ + public long getEndTime() { + return mEndTime; + } + + /** + * Set the {@link TestStatus}. + */ + public TestResult setStatus(TestStatus status) { + mStatus = status; + return this; + } + + /** + * Set the stack trace. + */ + public void setStackTrace(String trace) { + mStackTrace = trace; + } + + /** + * Sets the end time + */ + public void setEndTime(long currentTimeMillis) { + mEndTime = currentTimeMillis; + } + + /** + * {@inheritDoc} + */ + @Override + public int hashCode() { + return Arrays.hashCode(new Object[] {mMetrics, mStackTrace, mStatus}); + } + + /** + * {@inheritDoc} + */ + @Override + public boolean equals(Object obj) { + if (this == obj) { + return true; + } + if (obj == null) { + return false; + } + if (getClass() != obj.getClass()) { + return false; + } + TestResult other = (TestResult) obj; + return equal(mMetrics, other.mMetrics) && + equal(mStackTrace, other.mStackTrace) && + equal(mStatus, other.mStatus); + } + + private static boolean equal(Object a, Object b) { + return a == b || (a != null && a.equals(b)); + } +} diff --git a/ddms/libs/ddmlib/src/com/android/ddmlib/testrunner/TestRunResult.java b/ddms/libs/ddmlib/src/com/android/ddmlib/testrunner/TestRunResult.java new file mode 100644 index 0000000..14bb477 --- /dev/null +++ b/ddms/libs/ddmlib/src/com/android/ddmlib/testrunner/TestRunResult.java @@ -0,0 +1,324 @@ +/* + * Copyright (C) 2010 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 com.android.ddmlib.Log; +import com.android.ddmlib.testrunner.TestResult.TestStatus; + +import java.util.Collections; +import java.util.HashMap; +import java.util.LinkedHashMap; +import java.util.LinkedHashSet; +import java.util.Map; +import java.util.Set; + +/** + * Holds results from a single test run. + * <p/> + * Maintains an accurate count of tests during execution, and tracks incomplete tests. + */ +public class TestRunResult { + private static final String LOG_TAG = TestRunResult.class.getSimpleName(); + private final String mTestRunName; + // Uses a synchronized map to make thread safe. + // Uses a LinkedHashmap to have predictable iteration order + private Map<TestIdentifier, TestResult> mTestResults = + Collections.synchronizedMap(new LinkedHashMap<TestIdentifier, TestResult>()); + private Map<String, String> mRunMetrics = new HashMap<String, String>(); + private boolean mIsRunComplete = false; + private long mElapsedTime = 0; + private int mNumFailedTests = 0; + private int mNumErrorTests = 0; + private int mNumPassedTests = 0; + private int mNumInCompleteTests = 0; + private String mRunFailureError = null; + + /** + * Create a {@link TestRunResult}. + * + * @param runName + */ + public TestRunResult(String runName) { + mTestRunName = runName; + } + + /** + * Create an empty{@link TestRunResult}. + */ + public TestRunResult() { + this("not started"); + } + + /** + * @return the test run name + */ + public String getName() { + return mTestRunName; + } + + /** + * Gets a map of the test results. + * @return + */ + public Map<TestIdentifier, TestResult> getTestResults() { + return mTestResults; + } + + /** + * Adds test run metrics. + * <p/> + * @param runMetrics the run metrics + * @param aggregateMetrics if <code>true</code>, attempt to add given metrics values to any + * currently stored values. If <code>false</code>, replace any currently stored metrics with + * the same key. + */ + public void addMetrics(Map<String, String> runMetrics, boolean aggregateMetrics) { + if (aggregateMetrics) { + for (Map.Entry<String, String> entry : runMetrics.entrySet()) { + String existingValue = mRunMetrics.get(entry.getKey()); + String combinedValue = combineValues(existingValue, entry.getValue()); + mRunMetrics.put(entry.getKey(), combinedValue); + } + } else { + mRunMetrics.putAll(runMetrics); + } + } + + /** + * Combine old and new metrics value + * + * @param existingValue + * @param value + * @return + */ + private String combineValues(String existingValue, String newValue) { + if (existingValue != null) { + try { + Long existingLong = Long.parseLong(existingValue); + Long newLong = Long.parseLong(newValue); + return Long.toString(existingLong + newLong); + } catch (NumberFormatException e) { + // not a long, skip to next + } + try { + Double existingDouble = Double.parseDouble(existingValue); + Double newDouble = Double.parseDouble(newValue); + return Double.toString(existingDouble + newDouble); + } catch (NumberFormatException e) { + // not a double either, fall through + } + } + // default to overriding existingValue + return newValue; + } + + /** + * @return a {@link Map} of the test test run metrics. + */ + public Map<String, String> getRunMetrics() { + return mRunMetrics; + } + + /** + * Gets the set of completed tests. + */ + public Set<TestIdentifier> getCompletedTests() { + Set<TestIdentifier> completedTests = new LinkedHashSet<TestIdentifier>(); + for (Map.Entry<TestIdentifier, TestResult> testEntry : getTestResults().entrySet()) { + if (!testEntry.getValue().getStatus().equals(TestStatus.INCOMPLETE)) { + completedTests.add(testEntry.getKey()); + } + } + return completedTests; + } + + /** + * @return <code>true</code> if test run failed. + */ + public boolean isRunFailure() { + return mRunFailureError != null; + } + + /** + * @return <code>true</code> if test run finished. + */ + public boolean isRunComplete() { + return mIsRunComplete; + } + + void setRunComplete(boolean runComplete) { + mIsRunComplete = runComplete; + } + + void addElapsedTime(long elapsedTime) { + mElapsedTime+= elapsedTime; + } + + void setRunFailureError(String errorMessage) { + mRunFailureError = errorMessage; + } + + /** + * Gets the number of passed tests for this run. + */ + public int getNumPassedTests() { + return mNumPassedTests; + } + + /** + * Gets the number of tests in this run. + */ + public int getNumTests() { + return mTestResults.size(); + } + + /** + * Gets the number of complete tests in this run ie with status != incomplete. + */ + public int getNumCompleteTests() { + return getNumTests() - getNumIncompleteTests(); + } + + /** + * Gets the number of failed tests in this run. + */ + public int getNumFailedTests() { + return mNumFailedTests; + } + + /** + * Gets the number of error tests in this run. + */ + public int getNumErrorTests() { + return mNumErrorTests; + } + + /** + * Gets the number of incomplete tests in this run. + */ + public int getNumIncompleteTests() { + return mNumInCompleteTests; + } + + /** + * @return <code>true</code> if test run had any failed or error tests. + */ + public boolean hasFailedTests() { + return getNumErrorTests() > 0 || getNumFailedTests() > 0; + } + + /** + * @return + */ + public long getElapsedTime() { + return mElapsedTime; + } + + /** + * Return the run failure error message, <code>null</code> if run did not fail. + */ + public String getRunFailureMessage() { + return mRunFailureError; + } + + /** + * Report the start of a test. + * @param test + */ + void reportTestStarted(TestIdentifier test) { + TestResult result = mTestResults.get(test); + + if (result != null) { + Log.d(LOG_TAG, String.format("Replacing result for %s", test)); + switch (result.getStatus()) { + case ERROR: + mNumErrorTests--; + break; + case FAILURE: + mNumFailedTests--; + break; + case PASSED: + mNumPassedTests--; + break; + case INCOMPLETE: + // ignore + break; + } + } else { + mNumInCompleteTests++; + } + mTestResults.put(test, new TestResult()); + } + + /** + * Report a test failure. + * + * @param test + * @param status + * @param trace + */ + void reportTestFailure(TestIdentifier test, TestStatus status, String trace) { + TestResult result = mTestResults.get(test); + if (result == null) { + Log.d(LOG_TAG, String.format("Received test failure for %s without testStarted", test)); + result = new TestResult(); + mTestResults.put(test, result); + } else if (result.getStatus().equals(TestStatus.PASSED)) { + // this should never happen... + Log.d(LOG_TAG, String.format("Replacing passed result for %s", test)); + mNumPassedTests--; + } + + result.setStackTrace(trace); + switch (status) { + case ERROR: + mNumErrorTests++; + result.setStatus(TestStatus.ERROR); + break; + case FAILURE: + result.setStatus(TestStatus.FAILURE); + mNumFailedTests++; + break; + } + } + + /** + * Report the end of the test + * + * @param test + * @param testMetrics + * @return <code>true</code> if test was recorded as passed, false otherwise + */ + boolean reportTestEnded(TestIdentifier test, Map<String, String> testMetrics) { + TestResult result = mTestResults.get(test); + if (result == null) { + Log.d(LOG_TAG, String.format("Received test ended for %s without testStarted", test)); + result = new TestResult(); + mTestResults.put(test, result); + } else { + mNumInCompleteTests--; + } + + result.setEndTime(System.currentTimeMillis()); + result.setMetrics(testMetrics); + if (result.getStatus().equals(TestStatus.INCOMPLETE)) { + result.setStatus(TestStatus.PASSED); + mNumPassedTests++; + return true; + } + return false; + } +} diff --git a/ddms/libs/ddmlib/src/com/android/ddmlib/testrunner/XmlTestRunListener.java b/ddms/libs/ddmlib/src/com/android/ddmlib/testrunner/XmlTestRunListener.java new file mode 100644 index 0000000..2e48afe --- /dev/null +++ b/ddms/libs/ddmlib/src/com/android/ddmlib/testrunner/XmlTestRunListener.java @@ -0,0 +1,240 @@ +/* + * Copyright (C) 2009 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 com.android.ddmlib.Log; +import com.android.ddmlib.Log.LogLevel; +import com.android.ddmlib.testrunner.TestResult.TestStatus; + +import org.kxml2.io.KXmlSerializer; + +import java.io.BufferedOutputStream; +import java.io.File; +import java.io.FileOutputStream; +import java.io.IOException; +import java.io.OutputStream; +import java.text.SimpleDateFormat; +import java.util.Date; +import java.util.Map; +import java.util.TimeZone; + +/** + * Writes JUnit results to an XML files in a format consistent with + * Ant's XMLJUnitResultFormatter. + * <p/> + * Unlike Ant's formatter, this class does not report the execution time of + * tests. + * <p/> + * Creates a separate XML file per test run. + * <p/> + */ +public class XmlTestRunListener implements ITestRunListener { + + private static final String LOG_TAG = "XmlResultReporter"; + + private static final String TEST_RESULT_FILE_SUFFIX = ".xml"; + private static final String TEST_RESULT_FILE_PREFIX = "test_result_"; + + private static final String TESTSUITE = "testsuite"; + private static final String TESTCASE = "testcase"; + private static final String ERROR = "error"; + private static final String FAILURE = "failure"; + private static final String ATTR_NAME = "name"; + private static final String ATTR_TIME = "time"; + private static final String ATTR_ERRORS = "errors"; + private static final String ATTR_FAILURES = "failures"; + private static final String ATTR_TESTS = "tests"; + //private static final String ATTR_TYPE = "type"; + //private static final String ATTR_MESSAGE = "message"; + private static final String PROPERTIES = "properties"; + private static final String ATTR_CLASSNAME = "classname"; + private static final String TIMESTAMP = "timestamp"; + private static final String HOSTNAME = "hostname"; + + /** the XML namespace */ + private static final String ns = null; + + private File mReportDir = new File(System.getProperty("java.io.tmpdir")); + + private String mReportPath = ""; + + private TestRunResult mRunResult = new TestRunResult(); + + /** + * Sets the report file to use. + */ + public void setReportDir(File file) { + mReportDir = file; + } + + @Override + public void testRunStarted(String runName, int numTests) { + mRunResult = new TestRunResult(runName); + } + + @Override + public void testStarted(TestIdentifier test) { + mRunResult.reportTestStarted(test); + } + + @Override + public void testFailed(TestFailure status, TestIdentifier test, String trace) { + if (status.equals(TestFailure.ERROR)) { + mRunResult.reportTestFailure(test, TestStatus.ERROR, trace); + } else { + mRunResult.reportTestFailure(test, TestStatus.FAILURE, trace); + } + Log.d(LOG_TAG, String.format("%s %s: %s", test, status, trace)); + } + + @Override + public void testEnded(TestIdentifier test, Map<String, String> testMetrics) { + mRunResult.reportTestEnded(test, testMetrics); + } + + @Override + public void testRunFailed(String errorMessage) { + mRunResult.setRunFailureError(errorMessage); + } + + @Override + public void testRunStopped(long arg0) { + // ignore + } + + @Override + public void testRunEnded(long elapsedTime, Map<String, String> runMetrics) { + mRunResult.setRunComplete(true); + generateDocument(mReportDir, elapsedTime); + } + + /** + * Creates a report file and populates it with the report data from the completed tests. + */ + private void generateDocument(File reportDir, long elapsedTime) { + String timestamp = getTimestamp(); + + OutputStream stream = null; + try { + stream = createOutputResultStream(reportDir); + KXmlSerializer serializer = new KXmlSerializer(); + serializer.setOutput(stream, "UTF-8"); + serializer.startDocument("UTF-8", null); + serializer.setFeature( + "http://xmlpull.org/v1/doc/features.html#indent-output", true); + // TODO: insert build info + printTestResults(serializer, timestamp, elapsedTime); + serializer.endDocument(); + String msg = String.format("XML test result file generated at %s. Total tests %d, " + + "Failed %d, Error %d", getAbsoluteReportPath(), mRunResult.getNumTests(), + mRunResult.getNumFailedTests(), mRunResult.getNumErrorTests()); + Log.logAndDisplay(LogLevel.INFO, LOG_TAG, msg); + } catch (IOException e) { + Log.e(LOG_TAG, "Failed to generate report data"); + // TODO: consider throwing exception + } finally { + if (stream != null) { + try { + stream.close(); + } catch (IOException ignored) { + } + } + } + } + + private String getAbsoluteReportPath() { + return mReportPath ; + } + + /** + * Return the current timestamp as a {@link String}. + */ + String getTimestamp() { + SimpleDateFormat dateFormat = new SimpleDateFormat("yyyy-MM-dd'T'HH:mm:ss"); + TimeZone gmt = TimeZone.getTimeZone("UTC"); + dateFormat.setTimeZone(gmt); + dateFormat.setLenient(true); + String timestamp = dateFormat.format(new Date()); + return timestamp; + } + + /** + * Creates the output stream to use for test results. Exposed for mocking. + */ + OutputStream createOutputResultStream(File reportDir) throws IOException { + File reportFile = File.createTempFile(TEST_RESULT_FILE_PREFIX, TEST_RESULT_FILE_SUFFIX, + reportDir); + Log.i(LOG_TAG, String.format("Created xml report file at %s", + reportFile.getAbsolutePath())); + mReportPath = reportFile.getAbsolutePath(); + return new BufferedOutputStream(new FileOutputStream(reportFile)); + } + + void printTestResults(KXmlSerializer serializer, String timestamp, long elapsedTime) + throws IOException { + serializer.startTag(ns, TESTSUITE); + serializer.attribute(ns, ATTR_NAME, mRunResult.getName()); + serializer.attribute(ns, ATTR_TESTS, Integer.toString(mRunResult.getNumTests())); + serializer.attribute(ns, ATTR_FAILURES, Integer.toString(mRunResult.getNumFailedTests())); + serializer.attribute(ns, ATTR_ERRORS, Integer.toString(mRunResult.getNumErrorTests())); + serializer.attribute(ns, ATTR_TIME, Long.toString(elapsedTime)); + serializer.attribute(ns, TIMESTAMP, timestamp); + serializer.attribute(ns, HOSTNAME, "localhost"); + serializer.startTag(ns, PROPERTIES); + serializer.endTag(ns, PROPERTIES); + + Map<TestIdentifier, TestResult> testResults = mRunResult.getTestResults(); + for (Map.Entry<TestIdentifier, TestResult> testEntry : testResults.entrySet()) { + print(serializer, testEntry.getKey(), testEntry.getValue()); + } + + serializer.endTag(ns, TESTSUITE); + } + + void print(KXmlSerializer serializer, TestIdentifier testId, TestResult testResult) + throws IOException { + + serializer.startTag(ns, TESTCASE); + serializer.attribute(ns, ATTR_NAME, testId.getTestName()); + serializer.attribute(ns, ATTR_CLASSNAME, testId.getClassName()); + serializer.attribute(ns, ATTR_TIME, "0"); + + if (!TestStatus.PASSED.equals(testResult.getStatus())) { + String result = testResult.getStatus().equals(TestStatus.FAILURE) ? FAILURE : ERROR; + serializer.startTag(ns, result); + // TODO: get message of stack trace ? +// String msg = testResult.getStackTrace(); +// if (msg != null && msg.length() > 0) { +// serializer.attribute(ns, ATTR_MESSAGE, msg); +// } + // TODO: get class name of stackTrace exception + //serializer.attribute(ns, ATTR_TYPE, testId.getClassName()); + String stackText = sanitize(testResult.getStackTrace()); + serializer.text(stackText); + serializer.endTag(ns, result); + } + + serializer.endTag(ns, TESTCASE); + } + + /** + * Returns the text in a format that is safe for use in an XML document. + */ + private String sanitize(String text) { + return text.replace("\0", "<\\0>"); + } +} diff --git a/ddms/libs/ddmlib/tests/src/com/android/ddmlib/testrunner/XmlTestRunListenerTest.java b/ddms/libs/ddmlib/tests/src/com/android/ddmlib/testrunner/XmlTestRunListenerTest.java new file mode 100644 index 0000000..f5672fa --- /dev/null +++ b/ddms/libs/ddmlib/tests/src/com/android/ddmlib/testrunner/XmlTestRunListenerTest.java @@ -0,0 +1,159 @@ +/* + * Copyright (C) 2012 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 com.android.ddmlib.testrunner.ITestRunListener.TestFailure; + +import junit.framework.TestCase; + +import java.io.ByteArrayOutputStream; +import java.io.File; +import java.io.IOException; +import java.io.OutputStream; +import java.util.Collections; +import java.util.Map; + +/** + * Unit tests for {@link XmlTestRunListener}. + */ +public class XmlTestRunListenerTest extends TestCase { + + private XmlTestRunListener mResultReporter; + private ByteArrayOutputStream mOutputStream; + private File mReportDir; + + /** + * {@inheritDoc} + */ + @Override + protected void setUp() throws Exception { + super.setUp(); + + mOutputStream = new ByteArrayOutputStream(); + mResultReporter = new XmlTestRunListener() { + @Override + OutputStream createOutputResultStream(File reportDir) throws IOException { + return mOutputStream; + } + + @Override + String getTimestamp() { + return "ignore"; + } + }; + // TODO: use mock file dir instead + mReportDir = createTmpDir(); + mResultReporter.setReportDir(mReportDir); + } + + private File createTmpDir() throws IOException { + // create a temp file with unique name, then make it a directory + File tmpDir = File.createTempFile("foo", "dir"); + tmpDir.delete(); + if (!tmpDir.mkdirs()) { + throw new IOException("unable to create directory"); + } + return tmpDir; + } + + /** + * Recursively delete given file and all its contents + */ + private static void recursiveDelete(File rootDir) { + if (rootDir.isDirectory()) { + File[] childFiles = rootDir.listFiles(); + if (childFiles != null) { + for (File child : childFiles) { + recursiveDelete(child); + } + } + } + rootDir.delete(); + } + + @Override + protected void tearDown() throws Exception { + if (mReportDir != null) { + recursiveDelete(mReportDir); + } + super.tearDown(); + } + + /** + * A simple test to ensure expected output is generated for test run with no tests. + */ + public void testEmptyGeneration() { + final String expectedOutput = "<?xml version='1.0' encoding='UTF-8' ?>" + + "<testsuite name=\"test\" tests=\"0\" failures=\"0\" errors=\"0\" time=\"1\" " + + "timestamp=\"ignore\" hostname=\"localhost\"> " + + "<properties />" + + "</testsuite>"; + mResultReporter.testRunStarted("test", 1); + mResultReporter.testRunEnded(1, Collections.<String, String> emptyMap()); + assertEquals(expectedOutput, getOutput()); + } + + /** + * A simple test to ensure expected output is generated for test run with a single passed test. + */ + public void testSinglePass() { + Map<String, String> emptyMap = Collections.emptyMap(); + final TestIdentifier testId = new TestIdentifier("FooTest", "testFoo"); + mResultReporter.testRunStarted("run", 1); + mResultReporter.testStarted(testId); + mResultReporter.testEnded(testId, emptyMap); + mResultReporter.testRunEnded(3, emptyMap); + String output = getOutput(); + // TODO: consider doing xml based compare + assertTrue(output.contains("tests=\"1\" failures=\"0\" errors=\"0\"")); + final String testCaseTag = String.format("<testcase name=\"%s\" classname=\"%s\"", + testId.getTestName(), testId.getClassName()); + assertTrue(output.contains(testCaseTag)); + } + + /** + * A simple test to ensure expected output is generated for test run with a single failed test. + */ + public void testSingleFail() { + Map<String, String> emptyMap = Collections.emptyMap(); + final TestIdentifier testId = new TestIdentifier("FooTest", "testFoo"); + final String trace = "this is a trace"; + mResultReporter.testRunStarted("run", 1); + mResultReporter.testStarted(testId); + mResultReporter.testFailed(TestFailure.FAILURE, testId, trace); + mResultReporter.testEnded(testId, emptyMap); + mResultReporter.testRunEnded(3, emptyMap); + String output = getOutput(); + // TODO: consider doing xml based compare + assertTrue(output.contains("tests=\"1\" failures=\"1\" errors=\"0\"")); + final String testCaseTag = String.format("<testcase name=\"%s\" classname=\"%s\"", + testId.getTestName(), testId.getClassName()); + assertTrue(output.contains(testCaseTag)); + final String failureTag = String.format("<failure>%s</failure>", trace); + assertTrue(output.contains(failureTag)); + } + + /** + * Gets the output produced, stripping it of extraneous whitespace characters. + */ + private String getOutput() { + String output = mOutputStream.toString(); + // ignore newlines and tabs whitespace + output = output.replaceAll("[\\r\\n\\t]", ""); + // replace two ws chars with one + return output.replaceAll(" ", " "); + } +} |