1 /* 2 * Copyright (C) 2015 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 package com.android.compatibility.common.util; 17 18 import org.xmlpull.v1.XmlPullParser; 19 import org.xmlpull.v1.XmlPullParserException; 20 import org.xmlpull.v1.XmlPullParserFactory; 21 import org.xmlpull.v1.XmlSerializer; 22 23 import java.io.File; 24 import java.io.FileNotFoundException; 25 import java.io.FileOutputStream; 26 import java.io.FileReader; 27 import java.io.IOException; 28 import java.io.OutputStream; 29 import java.net.InetAddress; 30 import java.net.UnknownHostException; 31 import java.text.SimpleDateFormat; 32 import java.util.ArrayList; 33 import java.util.Comparator; 34 import java.util.Collections; 35 import java.util.Date; 36 import java.util.List; 37 import java.util.Map.Entry; 38 import java.util.Set; 39 40 /** 41 * Handles conversion of results to/from files. 42 */ 43 public class ResultHandler { 44 45 private static final String ENCODING = "UTF-8"; 46 private static final String TYPE = "org.kxml2.io.KXmlParser,org.kxml2.io.KXmlSerializer"; 47 private static final String NS = null; 48 private static final String RESULT_FILE_VERSION = "5.0"; 49 /* package */ static final String TEST_RESULT_FILE_NAME = "test_result.xml"; 50 51 // XML constants 52 private static final String ABI_ATTR = "abi"; 53 private static final String BUGREPORT_TAG = "BugReport"; 54 private static final String BUILD_FINGERPRINT = "build_fingerprint"; 55 private static final String BUILD_ID = "build_id"; 56 private static final String BUILD_PRODUCT = "build_product"; 57 private static final String BUILD_TAG = "Build"; 58 private static final String CASE_TAG = "TestCase"; 59 private static final String COMMAND_LINE_ARGS = "command_line_args"; 60 private static final String DEVICES_ATTR = "devices"; 61 private static final String DONE_ATTR = "done"; 62 private static final String END_DISPLAY_TIME_ATTR = "end_display"; 63 private static final String END_TIME_ATTR = "end"; 64 private static final String FAILED_ATTR = "failed"; 65 private static final String FAILURE_TAG = "Failure"; 66 private static final String HOST_NAME_ATTR = "host_name"; 67 private static final String JAVA_VENDOR_ATTR = "java_vendor"; 68 private static final String JAVA_VERSION_ATTR = "java_version"; 69 private static final String LOGCAT_TAG = "Logcat"; 70 private static final String LOG_URL_ATTR = "log_url"; 71 private static final String MESSAGE_ATTR = "message"; 72 private static final String MODULE_TAG = "Module"; 73 private static final String MODULES_EXECUTED_ATTR = "modules_done"; 74 private static final String MODULES_TOTAL_ATTR = "modules_total"; 75 private static final String NAME_ATTR = "name"; 76 private static final String NOT_EXECUTED_ATTR = "not_executed"; 77 private static final String OS_ARCH_ATTR = "os_arch"; 78 private static final String OS_NAME_ATTR = "os_name"; 79 private static final String OS_VERSION_ATTR = "os_version"; 80 private static final String PASS_ATTR = "pass"; 81 private static final String REPORT_VERSION_ATTR = "report_version"; 82 private static final String REFERENCE_URL_ATTR = "reference_url"; 83 private static final String RESULT_ATTR = "result"; 84 private static final String RESULT_TAG = "Result"; 85 private static final String RUNTIME_ATTR = "runtime"; 86 private static final String SCREENSHOT_TAG = "Screenshot"; 87 private static final String STACK_TAG = "StackTrace"; 88 private static final String START_DISPLAY_TIME_ATTR = "start_display"; 89 private static final String START_TIME_ATTR = "start"; 90 private static final String SUITE_NAME_ATTR = "suite_name"; 91 private static final String SUITE_PLAN_ATTR = "suite_plan"; 92 private static final String SUITE_VERSION_ATTR = "suite_version"; 93 private static final String SUITE_BUILD_ATTR = "suite_build_number"; 94 private static final String SUMMARY_TAG = "Summary"; 95 private static final String TEST_TAG = "Test"; 96 97 /** 98 * @param resultsDir 99 */ 100 public static List<IInvocationResult> getResults(File resultsDir) { 101 List<IInvocationResult> results = new ArrayList<>(); 102 File[] files = resultsDir.listFiles(); 103 if (files == null || files.length == 0) { 104 // No results, just return the empty list 105 return results; 106 } 107 for (File resultDir : files) { 108 if (!resultDir.isDirectory()) { 109 continue; 110 } 111 try { 112 File resultFile = new File(resultDir, TEST_RESULT_FILE_NAME); 113 if (!resultFile.exists()) { 114 continue; 115 } 116 IInvocationResult invocation = new InvocationResult(); 117 XmlPullParserFactory factory = XmlPullParserFactory.newInstance(); 118 XmlPullParser parser = factory.newPullParser(); 119 parser.setInput(new FileReader(resultFile)); 120 121 parser.nextTag(); 122 parser.require(XmlPullParser.START_TAG, NS, RESULT_TAG); 123 invocation.setStartTime(Long.valueOf( 124 parser.getAttributeValue(NS, START_TIME_ATTR))); 125 invocation.setTestPlan(parser.getAttributeValue(NS, SUITE_PLAN_ATTR)); 126 invocation.setCommandLineArgs(parser.getAttributeValue(NS, COMMAND_LINE_ARGS)); 127 String deviceList = parser.getAttributeValue(NS, DEVICES_ATTR); 128 for (String device : deviceList.split(",")) { 129 invocation.addDeviceSerial(device); 130 } 131 132 parser.nextTag(); 133 parser.require(XmlPullParser.START_TAG, NS, BUILD_TAG); 134 invocation.addInvocationInfo(BUILD_ID, parser.getAttributeValue(NS, BUILD_ID)); 135 invocation.addInvocationInfo(BUILD_PRODUCT, parser.getAttributeValue(NS, 136 BUILD_PRODUCT)); 137 invocation.setBuildFingerprint(parser.getAttributeValue(NS, BUILD_FINGERPRINT)); 138 139 // TODO(stuartscott): may want to reload these incase the retry was done with 140 // --skip-device-info flag 141 parser.nextTag(); 142 parser.require(XmlPullParser.END_TAG, NS, BUILD_TAG); 143 parser.nextTag(); 144 parser.require(XmlPullParser.START_TAG, NS, SUMMARY_TAG); 145 parser.nextTag(); 146 parser.require(XmlPullParser.END_TAG, NS, SUMMARY_TAG); 147 while (parser.nextTag() == XmlPullParser.START_TAG) { 148 parser.require(XmlPullParser.START_TAG, NS, MODULE_TAG); 149 String name = parser.getAttributeValue(NS, NAME_ATTR); 150 String abi = parser.getAttributeValue(NS, ABI_ATTR); 151 String moduleId = AbiUtils.createId(abi, name); 152 boolean done = Boolean.parseBoolean(parser.getAttributeValue(NS, DONE_ATTR)); 153 IModuleResult module = invocation.getOrCreateModule(moduleId); 154 module.setDone(done); 155 int notExecuted = 156 Integer.parseInt(parser.getAttributeValue(NS, NOT_EXECUTED_ATTR)); 157 module.setNotExecuted(notExecuted); 158 while (parser.nextTag() == XmlPullParser.START_TAG) { 159 parser.require(XmlPullParser.START_TAG, NS, CASE_TAG); 160 String caseName = parser.getAttributeValue(NS, NAME_ATTR); 161 ICaseResult testCase = module.getOrCreateResult(caseName); 162 while (parser.nextTag() == XmlPullParser.START_TAG) { 163 parser.require(XmlPullParser.START_TAG, NS, TEST_TAG); 164 String testName = parser.getAttributeValue(NS, NAME_ATTR); 165 ITestResult test = testCase.getOrCreateResult(testName); 166 String result = parser.getAttributeValue(NS, RESULT_ATTR); 167 test.setResultStatus(TestStatus.getStatus(result)); 168 test.setRetry(true); 169 while (parser.nextTag() == XmlPullParser.START_TAG) { 170 if (parser.getName().equals(FAILURE_TAG)) { 171 test.setMessage(parser.getAttributeValue(NS, MESSAGE_ATTR)); 172 if (parser.nextTag() == XmlPullParser.START_TAG) { 173 parser.require(XmlPullParser.START_TAG, NS, STACK_TAG); 174 test.setStackTrace(parser.nextText()); 175 parser.require(XmlPullParser.END_TAG, NS, STACK_TAG); 176 parser.nextTag(); 177 } 178 parser.require(XmlPullParser.END_TAG, NS, FAILURE_TAG); 179 } else if (parser.getName().equals(BUGREPORT_TAG)) { 180 test.setBugReport(parser.nextText()); 181 parser.require(XmlPullParser.END_TAG, NS, BUGREPORT_TAG); 182 } else if (parser.getName().equals(LOGCAT_TAG)) { 183 test.setLog(parser.nextText()); 184 parser.require(XmlPullParser.END_TAG, NS, LOGCAT_TAG); 185 } else if (parser.getName().equals(SCREENSHOT_TAG)) { 186 test.setScreenshot(parser.nextText()); 187 parser.require(XmlPullParser.END_TAG, NS, SCREENSHOT_TAG); 188 } else { 189 test.setReportLog(ReportLog.parse(parser)); 190 } 191 } 192 parser.require(XmlPullParser.END_TAG, NS, TEST_TAG); 193 } 194 parser.require(XmlPullParser.END_TAG, NS, CASE_TAG); 195 } 196 parser.require(XmlPullParser.END_TAG, NS, MODULE_TAG); 197 } 198 parser.require(XmlPullParser.END_TAG, NS, RESULT_TAG); 199 results.add(invocation); 200 } catch (XmlPullParserException e) { 201 e.printStackTrace(); 202 } catch (FileNotFoundException e) { 203 e.printStackTrace(); 204 } catch (IOException e) { 205 e.printStackTrace(); 206 } 207 } 208 // Sort the table entries on each entry's timestamp. 209 Collections.sort(results, new Comparator<IInvocationResult>() { 210 public int compare(IInvocationResult result1, IInvocationResult result2) { 211 return Long.compare(result1.getStartTime(), result2.getStartTime()); 212 } 213 }); 214 return results; 215 } 216 217 /** 218 * @param result 219 * @param resultDir 220 * @param startTime 221 * @param referenceUrl A nullable string that can contain a URL to a related data 222 * @param logUrl A nullable string that can contain a URL to related log files 223 * @param commandLineArgs A string containing the arguments to the run command 224 * @return The result file created. 225 * @throws IOException 226 * @throws XmlPullParserException 227 */ 228 public static File writeResults(String suiteName, String suiteVersion, String suitePlan, 229 String suiteBuild, IInvocationResult result, File resultDir, 230 long startTime, long endTime, String referenceUrl, String logUrl, 231 String commandLineArgs) 232 throws IOException, XmlPullParserException { 233 int passed = result.countResults(TestStatus.PASS); 234 int failed = result.countResults(TestStatus.FAIL); 235 int notExecuted = result.getNotExecuted(); 236 File resultFile = new File(resultDir, TEST_RESULT_FILE_NAME); 237 OutputStream stream = new FileOutputStream(resultFile); 238 XmlSerializer serializer = XmlPullParserFactory.newInstance(TYPE, null).newSerializer(); 239 serializer.setOutput(stream, ENCODING); 240 serializer.startDocument(ENCODING, false); 241 serializer.setFeature("http://xmlpull.org/v1/doc/features.html#indent-output", true); 242 serializer.processingInstruction( 243 "xml-stylesheet type=\"text/xsl\" href=\"compatibility_result.xsl\""); 244 serializer.startTag(NS, RESULT_TAG); 245 serializer.attribute(NS, START_TIME_ATTR, String.valueOf(startTime)); 246 serializer.attribute(NS, END_TIME_ATTR, String.valueOf(endTime)); 247 serializer.attribute(NS, START_DISPLAY_TIME_ATTR, toReadableDateString(startTime)); 248 serializer.attribute(NS, END_DISPLAY_TIME_ATTR, toReadableDateString(endTime)); 249 250 serializer.attribute(NS, SUITE_NAME_ATTR, suiteName); 251 serializer.attribute(NS, SUITE_VERSION_ATTR, suiteVersion); 252 serializer.attribute(NS, SUITE_PLAN_ATTR, suitePlan); 253 serializer.attribute(NS, SUITE_BUILD_ATTR, suiteBuild); 254 serializer.attribute(NS, REPORT_VERSION_ATTR, RESULT_FILE_VERSION); 255 serializer.attribute(NS, COMMAND_LINE_ARGS, nullToEmpty(commandLineArgs)); 256 257 if (referenceUrl != null) { 258 serializer.attribute(NS, REFERENCE_URL_ATTR, referenceUrl); 259 } 260 261 if (logUrl != null) { 262 serializer.attribute(NS, LOG_URL_ATTR, logUrl); 263 } 264 265 // Device Info 266 Set<String> devices = result.getDeviceSerials(); 267 StringBuilder deviceList = new StringBuilder(); 268 boolean first = true; 269 for (String device : devices) { 270 if (first) { 271 first = false; 272 } else { 273 deviceList.append(","); 274 } 275 deviceList.append(device); 276 } 277 serializer.attribute(NS, DEVICES_ATTR, deviceList.toString()); 278 279 // Host Info 280 String hostName = ""; 281 try { 282 hostName = InetAddress.getLocalHost().getHostName(); 283 } catch (UnknownHostException ignored) {} 284 serializer.attribute(NS, HOST_NAME_ATTR, hostName); 285 serializer.attribute(NS, OS_NAME_ATTR, System.getProperty("os.name")); 286 serializer.attribute(NS, OS_VERSION_ATTR, System.getProperty("os.version")); 287 serializer.attribute(NS, OS_ARCH_ATTR, System.getProperty("os.arch")); 288 serializer.attribute(NS, JAVA_VENDOR_ATTR, System.getProperty("java.vendor")); 289 serializer.attribute(NS, JAVA_VERSION_ATTR, System.getProperty("java.version")); 290 291 // Build Info 292 serializer.startTag(NS, BUILD_TAG); 293 for (Entry<String, String> entry : result.getInvocationInfo().entrySet()) { 294 serializer.attribute(NS, entry.getKey(), entry.getValue()); 295 } 296 serializer.endTag(NS, BUILD_TAG); 297 298 // Summary 299 serializer.startTag(NS, SUMMARY_TAG); 300 serializer.attribute(NS, PASS_ATTR, Integer.toString(passed)); 301 serializer.attribute(NS, FAILED_ATTR, Integer.toString(failed)); 302 serializer.attribute(NS, NOT_EXECUTED_ATTR, Integer.toString(notExecuted)); 303 serializer.attribute(NS, MODULES_EXECUTED_ATTR, 304 Integer.toString(result.getModuleCompleteCount())); 305 serializer.attribute(NS, MODULES_TOTAL_ATTR, 306 Integer.toString(result.getModules().size())); 307 serializer.endTag(NS, SUMMARY_TAG); 308 309 // Results 310 for (IModuleResult module : result.getModules()) { 311 serializer.startTag(NS, MODULE_TAG); 312 serializer.attribute(NS, NAME_ATTR, module.getName()); 313 serializer.attribute(NS, ABI_ATTR, module.getAbi()); 314 serializer.attribute(NS, RUNTIME_ATTR, String.valueOf(module.getRuntime())); 315 serializer.attribute(NS, DONE_ATTR, Boolean.toString(module.isDone())); 316 serializer.attribute(NS, NOT_EXECUTED_ATTR, Integer.toString(module.getNotExecuted())); 317 for (ICaseResult cr : module.getResults()) { 318 serializer.startTag(NS, CASE_TAG); 319 serializer.attribute(NS, NAME_ATTR, cr.getName()); 320 for (ITestResult r : cr.getResults()) { 321 TestStatus status = r.getResultStatus(); 322 if (status == null) { 323 continue; // test was not executed, don't report 324 } 325 serializer.startTag(NS, TEST_TAG); 326 serializer.attribute(NS, RESULT_ATTR, status.getValue()); 327 serializer.attribute(NS, NAME_ATTR, r.getName()); 328 String message = r.getMessage(); 329 if (message != null) { 330 serializer.startTag(NS, FAILURE_TAG); 331 serializer.attribute(NS, MESSAGE_ATTR, message); 332 String stackTrace = r.getStackTrace(); 333 if (stackTrace != null) { 334 serializer.startTag(NS, STACK_TAG); 335 serializer.text(stackTrace); 336 serializer.endTag(NS, STACK_TAG); 337 } 338 serializer.endTag(NS, FAILURE_TAG); 339 } 340 String bugreport = r.getBugReport(); 341 if (bugreport != null) { 342 serializer.startTag(NS, BUGREPORT_TAG); 343 serializer.text(bugreport); 344 serializer.endTag(NS, BUGREPORT_TAG); 345 } 346 String logcat = r.getLog(); 347 if (logcat != null) { 348 serializer.startTag(NS, LOGCAT_TAG); 349 serializer.text(logcat); 350 serializer.endTag(NS, LOGCAT_TAG); 351 } 352 String screenshot = r.getScreenshot(); 353 if (screenshot != null) { 354 serializer.startTag(NS, SCREENSHOT_TAG); 355 serializer.text(screenshot); 356 serializer.endTag(NS, SCREENSHOT_TAG); 357 } 358 ReportLog report = r.getReportLog(); 359 if (report != null) { 360 ReportLog.serialize(serializer, report); 361 } 362 serializer.endTag(NS, TEST_TAG); 363 } 364 serializer.endTag(NS, CASE_TAG); 365 } 366 serializer.endTag(NS, MODULE_TAG); 367 } 368 serializer.endDocument(); 369 return resultFile; 370 } 371 372 /** 373 * Find the IInvocationResult for the given sessionId. 374 */ 375 public static IInvocationResult findResult(File resultsDir, Integer sessionId) 376 throws FileNotFoundException { 377 if (sessionId < 0) { 378 throw new IllegalArgumentException( 379 String.format("Invalid session id [%d] ", sessionId)); 380 } 381 382 List<IInvocationResult> results = getResults(resultsDir); 383 if (results == null || sessionId >= results.size()) { 384 throw new RuntimeException(String.format("Could not find session [%d]", sessionId)); 385 } 386 return results.get(sessionId); 387 } 388 389 /** 390 * Return the given time as a {@link String} suitable for displaying. 391 * <p/> 392 * Example: Fri Aug 20 15:13:03 PDT 2010 393 * 394 * @param time the epoch time in ms since midnight Jan 1, 1970 395 */ 396 static String toReadableDateString(long time) { 397 SimpleDateFormat dateFormat = new SimpleDateFormat("EEE MMM dd HH:mm:ss zzz yyyy"); 398 return dateFormat.format(new Date(time)); 399 } 400 401 /** 402 * When nullable is null, return an empty string. Otherwise, return the value in nullable. 403 */ 404 private static String nullToEmpty(String nullable) { 405 return nullable == null ? "" : nullable; 406 } 407 } 408