1 /* 2 * Copyright (C) 2009 The Android Open Source Project 3 * 4 * Licensed under the Apache License, Version 2.0 (the "License"); 5 * you may not use this file except in compliance with the License. 6 * You may obtain a copy of the License at 7 * 8 * http://www.apache.org/licenses/LICENSE-2.0 9 * 10 * Unless required by applicable law or agreed to in writing, software 11 * distributed under the License is distributed on an "AS IS" BASIS, 12 * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 13 * See the License for the specific language governing permissions and 14 * limitations under the License. 15 */ 16 17 package com.android.tradefed.result; 18 19 import com.android.ddmlib.Log; 20 import com.android.ddmlib.Log.LogLevel; 21 import com.android.ddmlib.testrunner.TestResult.TestStatus; 22 import com.android.tradefed.config.OptionClass; 23 import com.android.tradefed.log.LogUtil.CLog; 24 import com.android.tradefed.util.StreamUtil; 25 26 import org.kxml2.io.KXmlSerializer; 27 28 import java.io.ByteArrayInputStream; 29 import java.io.ByteArrayOutputStream; 30 import java.io.IOException; 31 import java.io.InputStream; 32 import java.text.SimpleDateFormat; 33 import java.util.Date; 34 import java.util.Map; 35 import java.util.TimeZone; 36 37 /** 38 * Writes JUnit results to an XML files in a format consistent with 39 * Ant's XMLJUnitResultFormatter. 40 * <p/> 41 * Unlike Ant's formatter, this class does not report the execution time of 42 * tests. 43 * <p/> 44 * Collects all test info in memory, then dumps to file when invocation is complete. 45 * <p/> 46 * Ported from dalvik runner XmlReportPrinter. 47 * <p/> 48 * Result files will be stored in path constructed via [--output-file-path]/[build_id] 49 */ 50 @OptionClass(alias = "xml") 51 public class XmlResultReporter extends CollectingTestListener implements ILogSaverListener { 52 53 private static final String LOG_TAG = "XmlResultReporter"; 54 55 private static final String TEST_RESULT_FILE_PREFIX = "test_result_"; 56 57 private static final String TESTSUITE = "testsuite"; 58 private static final String TESTCASE = "testcase"; 59 private static final String ERROR = "error"; 60 private static final String FAILURE = "failure"; 61 private static final String ATTR_NAME = "name"; 62 private static final String ATTR_TIME = "time"; 63 private static final String ATTR_ERRORS = "errors"; 64 private static final String ATTR_FAILURES = "failures"; 65 private static final String ATTR_TESTS = "tests"; 66 //private static final String ATTR_TYPE = "type"; 67 //private static final String ATTR_MESSAGE = "message"; 68 private static final String PROPERTIES = "properties"; 69 private static final String ATTR_CLASSNAME = "classname"; 70 private static final String TIMESTAMP = "timestamp"; 71 private static final String HOSTNAME = "hostname"; 72 73 /** the XML namespace */ 74 private static final String NS = null; 75 76 private ILogSaver mLogSaver; 77 78 /** 79 * {@inheritDoc} 80 */ 81 @Override invocationEnded(long elapsedTime)82 public void invocationEnded(long elapsedTime) { 83 super.invocationEnded(elapsedTime); 84 generateSummary(elapsedTime); 85 } 86 87 @Override testFailed(TestDescription test, String trace)88 public void testFailed(TestDescription test, String trace) { 89 super.testFailed(test, trace); 90 CLog.d("%s : %s", test, trace); 91 } 92 93 /** 94 * Creates a report file and populates it with the report data from the completed tests. 95 */ generateSummary(long elapsedTime)96 private void generateSummary(long elapsedTime) { 97 String timestamp = getTimestamp(); 98 99 ByteArrayOutputStream outputStream = null; 100 InputStream inputStream = null; 101 102 try { 103 outputStream = createOutputStream(); 104 KXmlSerializer serializer = new KXmlSerializer(); 105 serializer.setOutput(outputStream, "UTF-8"); 106 serializer.startDocument("UTF-8", null); 107 serializer.setFeature( 108 "http://xmlpull.org/v1/doc/features.html#indent-output", true); 109 // TODO: insert build info 110 printTestResults(serializer, timestamp, elapsedTime); 111 serializer.endDocument(); 112 113 inputStream = new ByteArrayInputStream(outputStream.toByteArray()); 114 LogFile log = mLogSaver.saveLogData(TEST_RESULT_FILE_PREFIX, LogDataType.XML, 115 inputStream); 116 117 String msg = String.format("XML test result file generated at %s. Total tests %d, " + 118 "Failed %d", log.getPath(), getNumTotalTests(), getNumAllFailedTests()); 119 Log.logAndDisplay(LogLevel.INFO, LOG_TAG, msg); 120 } catch (IOException e) { 121 Log.e(LOG_TAG, "Failed to generate report data"); 122 // TODO: consider throwing exception 123 } finally { 124 StreamUtil.close(outputStream); 125 StreamUtil.close(inputStream); 126 } 127 } 128 129 /** 130 * Return the current timestamp as a {@link String}. 131 */ getTimestamp()132 String getTimestamp() { 133 SimpleDateFormat dateFormat = new SimpleDateFormat("yyyy-MM-dd'T'HH:mm:ss"); 134 TimeZone gmt = TimeZone.getTimeZone("UTC"); 135 dateFormat.setTimeZone(gmt); 136 dateFormat.setLenient(true); 137 String timestamp = dateFormat.format(new Date()); 138 return timestamp; 139 } 140 141 /** 142 * Creates the output stream to use for test results. Exposed for mocking. 143 */ createOutputStream()144 ByteArrayOutputStream createOutputStream() { 145 return new ByteArrayOutputStream(); 146 } 147 printTestResults(KXmlSerializer serializer, String timestamp, long elapsedTime)148 void printTestResults(KXmlSerializer serializer, String timestamp, long elapsedTime) 149 throws IOException { 150 serializer.startTag(NS, TESTSUITE); 151 serializer.attribute(NS, ATTR_NAME, getInvocationContext().getTestTag()); 152 serializer.attribute(NS, ATTR_TESTS, Integer.toString(getNumTotalTests())); 153 serializer.attribute( 154 NS, ATTR_FAILURES, Integer.toString(getNumTestsInState(TestStatus.FAILURE))); 155 serializer.attribute(NS, ATTR_ERRORS, "0"); 156 serializer.attribute(NS, ATTR_TIME, Long.toString(elapsedTime)); 157 serializer.attribute(NS, TIMESTAMP, timestamp); 158 serializer.attribute(NS, HOSTNAME, "localhost"); 159 serializer.startTag(NS, PROPERTIES); 160 serializer.endTag(NS, PROPERTIES); 161 162 for (TestRunResult runResult : getMergedTestRunResults()) { 163 // TODO: add test run summaries as TESTSUITES ? 164 Map<TestDescription, TestResult> testResults = runResult.getTestResults(); 165 for (Map.Entry<TestDescription, TestResult> testEntry : testResults.entrySet()) { 166 print(serializer, testEntry.getKey(), testEntry.getValue()); 167 } 168 } 169 170 serializer.endTag(NS, TESTSUITE); 171 } 172 print(KXmlSerializer serializer, TestDescription testId, TestResult testResult)173 void print(KXmlSerializer serializer, TestDescription testId, TestResult testResult) 174 throws IOException { 175 176 serializer.startTag(NS, TESTCASE); 177 serializer.attribute(NS, ATTR_NAME, testId.getTestName()); 178 serializer.attribute(NS, ATTR_CLASSNAME, testId.getClassName()); 179 serializer.attribute(NS, ATTR_TIME, "0"); 180 181 if (!TestStatus.PASSED.equals(testResult.getStatus())) { 182 String result = testResult.getStatus().equals(TestStatus.FAILURE) ? FAILURE : ERROR; 183 serializer.startTag(NS, result); 184 // TODO: get message of stack trace ? 185 // String msg = testResult.getStackTrace(); 186 // if (msg != null && msg.length() > 0) { 187 // serializer.attribute(ns, ATTR_MESSAGE, msg); 188 // } 189 // TODO: get class name of stackTrace exception 190 //serializer.attribute(ns, ATTR_TYPE, testId.getClassName()); 191 String stackText = sanitize(testResult.getStackTrace()); 192 serializer.text(stackText); 193 serializer.endTag(NS, result); 194 } 195 196 serializer.endTag(NS, TESTCASE); 197 } 198 199 /** 200 * Returns the text in a format that is safe for use in an XML document. 201 */ sanitize(String text)202 private String sanitize(String text) { 203 return text.replace("\0", "<\\0>"); 204 } 205 206 /** 207 * {@inheritDoc} 208 */ 209 @Override testLog(String dataName, LogDataType dataType, InputStreamSource dataStream)210 public void testLog(String dataName, LogDataType dataType, InputStreamSource dataStream) { 211 // Ignore 212 } 213 214 /** 215 * {@inheritDoc} 216 */ 217 @Override testLogSaved(String dataName, LogDataType dataType, InputStreamSource dataStream, LogFile logFile)218 public void testLogSaved(String dataName, LogDataType dataType, InputStreamSource dataStream, 219 LogFile logFile) { 220 Log.logAndDisplay(LogLevel.INFO, LOG_TAG, String.format("Saved %s log to %s", dataName, 221 logFile.getPath())); 222 } 223 224 /** 225 * {@inheritDoc} 226 */ 227 @Override setLogSaver(ILogSaver logSaver)228 public void setLogSaver(ILogSaver logSaver) { 229 mLogSaver = logSaver; 230 } 231 } 232