| /* |
| * 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.tradefed.result; |
| |
| import com.android.ddmlib.Log; |
| import com.android.ddmlib.Log.LogLevel; |
| import com.android.ddmlib.testrunner.TestIdentifier; |
| import com.android.ddmlib.testrunner.TestResult; |
| import com.android.ddmlib.testrunner.TestResult.TestStatus; |
| import com.android.ddmlib.testrunner.TestRunResult; |
| import com.android.tradefed.build.IBuildInfo; |
| import com.android.tradefed.config.OptionClass; |
| import com.android.tradefed.log.LogUtil.CLog; |
| import com.android.tradefed.util.StreamUtil; |
| |
| import org.kxml2.io.KXmlSerializer; |
| |
| import java.io.ByteArrayInputStream; |
| import java.io.ByteArrayOutputStream; |
| import java.io.IOException; |
| import java.io.InputStream; |
| 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/> |
| * Collects all test info in memory, then dumps to file when invocation is complete. |
| * <p/> |
| * Ported from dalvik runner XmlReportPrinter. |
| * <p/> |
| * Result files will be stored in path constructed via [--output-file-path]/[build_id] |
| */ |
| @OptionClass(alias = "xml") |
| public class XmlResultReporter extends CollectingTestListener implements ILogSaverListener { |
| |
| private static final String LOG_TAG = "XmlResultReporter"; |
| |
| 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 ILogSaver mLogSaver; |
| private IBuildInfo mBuildInfo; |
| |
| /** |
| * {@inheritDoc} |
| */ |
| @Override |
| public void invocationEnded(long elapsedTime) { |
| super.invocationEnded(elapsedTime); |
| generateSummary(elapsedTime); |
| } |
| |
| /** |
| * {@inheritDoc} |
| */ |
| @Override |
| public void invocationStarted(IBuildInfo buildInfo) { |
| super.invocationStarted(buildInfo); |
| |
| mBuildInfo = buildInfo; |
| } |
| |
| @Override |
| public void testFailed(TestIdentifier test, String trace) { |
| super.testFailed(test, trace); |
| CLog.d("%s : %s", test, trace); |
| } |
| |
| /** |
| * Creates a report file and populates it with the report data from the completed tests. |
| */ |
| private void generateSummary(long elapsedTime) { |
| String timestamp = getTimestamp(); |
| |
| ByteArrayOutputStream outputStream = null; |
| InputStream inputStream = null; |
| |
| try { |
| outputStream = createOutputStream(); |
| KXmlSerializer serializer = new KXmlSerializer(); |
| serializer.setOutput(outputStream, "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(); |
| |
| inputStream = new ByteArrayInputStream(outputStream.toByteArray()); |
| LogFile log = mLogSaver.saveLogData(TEST_RESULT_FILE_PREFIX, LogDataType.XML, |
| inputStream); |
| |
| String msg = String.format("XML test result file generated at %s. Total tests %d, " + |
| "Failed %d", log.getPath(), getNumTotalTests(), getNumAllFailedTests()); |
| Log.logAndDisplay(LogLevel.INFO, LOG_TAG, msg); |
| } catch (IOException e) { |
| Log.e(LOG_TAG, "Failed to generate report data"); |
| // TODO: consider throwing exception |
| } finally { |
| StreamUtil.close(outputStream); |
| StreamUtil.close(inputStream); |
| } |
| } |
| |
| /** |
| * 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. |
| */ |
| ByteArrayOutputStream createOutputStream() { |
| return new ByteArrayOutputStream(); |
| } |
| |
| void printTestResults(KXmlSerializer serializer, String timestamp, long elapsedTime) |
| throws IOException { |
| serializer.startTag(ns, TESTSUITE); |
| serializer.attribute(ns, ATTR_NAME, mBuildInfo.getTestTag()); |
| serializer.attribute(ns, ATTR_TESTS, Integer.toString(getNumTotalTests())); |
| serializer.attribute(ns, ATTR_FAILURES, |
| Integer.toString(getNumTestsInState(TestStatus.FAILURE))); |
| serializer.attribute(ns, ATTR_ERRORS, "0"); |
| 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); |
| |
| for (TestRunResult runResult : getRunResults()) { |
| // TODO: add test run summaries as TESTSUITES ? |
| Map<TestIdentifier, TestResult> testResults = runResult.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>"); |
| } |
| |
| /** |
| * {@inheritDoc} |
| */ |
| @Override |
| public void testLog(String dataName, LogDataType dataType, InputStreamSource dataStream) { |
| // Ignore |
| } |
| |
| /** |
| * {@inheritDoc} |
| */ |
| @Override |
| public void testLogSaved(String dataName, LogDataType dataType, InputStreamSource dataStream, |
| LogFile logFile) { |
| Log.logAndDisplay(LogLevel.INFO, LOG_TAG, String.format("Saved %s log to %s", dataName, |
| logFile.getPath())); |
| } |
| |
| /** |
| * {@inheritDoc} |
| */ |
| @Override |
| public void setLogSaver(ILogSaver logSaver) { |
| mLogSaver = logSaver; |
| } |
| } |