| /* |
| * Copyright (C) 2015 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.compatibility.common.util; |
| |
| import com.android.compatibility.common.util.ChecksumReporter.ChecksumValidationException; |
| |
| import com.google.common.base.Strings; |
| |
| import org.xmlpull.v1.XmlPullParser; |
| import org.xmlpull.v1.XmlPullParserException; |
| import org.xmlpull.v1.XmlPullParserFactory; |
| import org.xmlpull.v1.XmlSerializer; |
| |
| import java.io.File; |
| import java.io.FileOutputStream; |
| import java.io.FileReader; |
| import java.io.IOException; |
| import java.io.InputStream; |
| import java.io.OutputStream; |
| import java.net.InetAddress; |
| import java.net.UnknownHostException; |
| import java.nio.file.FileSystems; |
| import java.nio.file.Files; |
| import java.nio.file.Path; |
| import java.text.SimpleDateFormat; |
| import java.util.ArrayList; |
| import java.util.Collection; |
| import java.util.Collections; |
| import java.util.Date; |
| import java.util.List; |
| import java.util.Locale; |
| import java.util.Map; |
| import java.util.Map.Entry; |
| import java.util.Set; |
| |
| import javax.xml.transform.Transformer; |
| import javax.xml.transform.TransformerException; |
| import javax.xml.transform.TransformerFactory; |
| import javax.xml.transform.stream.StreamResult; |
| import javax.xml.transform.stream.StreamSource; |
| /** |
| * Handles conversion of results to/from files. |
| */ |
| public class ResultHandler { |
| |
| private static final String ENCODING = "UTF-8"; |
| private static final String TYPE = "org.kxml2.io.KXmlParser,org.kxml2.io.KXmlSerializer"; |
| private static final String NS = null; |
| private static final String RESULT_FILE_VERSION = "5.0"; |
| public static final String TEST_RESULT_FILE_NAME = "test_result.xml"; |
| public static final String FAILURE_REPORT_NAME = "test_result_failures.html"; |
| private static final String FAILURE_XSL_FILE_NAME = "compatibility_failures.xsl"; |
| |
| public static final String[] RESULT_RESOURCES = { |
| "compatibility_result.css", |
| "compatibility_result.xsd", |
| "compatibility_result.xsl", |
| "logo.png" |
| }; |
| |
| // XML constants |
| private static final String ABI_ATTR = "abi"; |
| private static final String BUGREPORT_TAG = "BugReport"; |
| private static final String BUILD_FINGERPRINT = "build_fingerprint"; |
| private static final String BUILD_FINGERPRINT_UNALTERED = "build_fingerprint_unaltered"; |
| private static final String BUILD_ID = "build_id"; |
| private static final String BUILD_PRODUCT = "build_product"; |
| private static final String BUILD_TAG = "Build"; |
| private static final String CASE_TAG = "TestCase"; |
| private static final String COMMAND_LINE_ARGS = "command_line_args"; |
| private static final String DEVICES_ATTR = "devices"; |
| private static final String DONE_ATTR = "done"; |
| private static final String END_DISPLAY_TIME_ATTR = "end_display"; |
| private static final String END_TIME_ATTR = "end"; |
| private static final String FAILED_ATTR = "failed"; |
| private static final String FAILURE_TAG = "Failure"; |
| private static final String HOST_NAME_ATTR = "host_name"; |
| private static final String JAVA_VENDOR_ATTR = "java_vendor"; |
| private static final String JAVA_VERSION_ATTR = "java_version"; |
| private static final String LOGCAT_TAG = "Logcat"; |
| private static final String LOG_URL_ATTR = "log_url"; |
| private static final String MESSAGE_ATTR = "message"; |
| private static final String MODULE_TAG = "Module"; |
| private static final String MODULES_DONE_ATTR = "modules_done"; |
| private static final String MODULES_TOTAL_ATTR = "modules_total"; |
| private static final String NAME_ATTR = "name"; |
| private static final String OS_ARCH_ATTR = "os_arch"; |
| private static final String OS_NAME_ATTR = "os_name"; |
| private static final String OS_VERSION_ATTR = "os_version"; |
| private static final String PASS_ATTR = "pass"; |
| private static final String REPORT_VERSION_ATTR = "report_version"; |
| private static final String REFERENCE_URL_ATTR = "reference_url"; |
| private static final String RESULT_ATTR = "result"; |
| private static final String RESULT_TAG = "Result"; |
| private static final String RUNTIME_ATTR = "runtime"; |
| private static final String RUN_HISTORY_ATTR = "run_history"; |
| private static final String RUN_HISTORY_TAG = "RunHistory"; |
| private static final String RUN_TAG = "Run"; |
| private static final String SCREENSHOT_TAG = "Screenshot"; |
| private static final String SKIPPED_ATTR = "skipped"; |
| private static final String STACK_TAG = "StackTrace"; |
| private static final String START_DISPLAY_TIME_ATTR = "start_display"; |
| private static final String START_TIME_ATTR = "start"; |
| private static final String SUITE_NAME_ATTR = "suite_name"; |
| private static final String SUITE_PLAN_ATTR = "suite_plan"; |
| private static final String SUITE_VERSION_ATTR = "suite_version"; |
| private static final String SUITE_BUILD_ATTR = "suite_build_number"; |
| private static final String SUMMARY_TAG = "Summary"; |
| private static final String METRIC_TAG = "Metric"; |
| private static final String TEST_TAG = "Test"; |
| |
| private static final String LATEST_RESULT_DIR = "latest"; |
| |
| /** |
| * Returns IInvocationResults that can be queried for general reporting information, but that |
| * do not store underlying module data. Useful for summarizing invocation history. |
| * @param resultsDir |
| */ |
| public static List<IInvocationResult> getLightResults(File resultsDir) { |
| List<IInvocationResult> results = new ArrayList<>(); |
| List<File> files = getResultDirectories(resultsDir); |
| for (File resultDir : files) { |
| if (LATEST_RESULT_DIR.equals(resultDir.getName())) { |
| continue; |
| } |
| IInvocationResult result = getResultFromDir(resultDir, false); |
| if (result != null) { |
| results.add(new LightInvocationResult(result)); |
| result = null; // ensure all references are removed to free memory |
| } |
| } |
| // Sort the table entries on each entry's timestamp. |
| Collections.sort(results, (result1, result2) -> Long.compare( |
| result1.getStartTime(), |
| result2.getStartTime())); |
| return results; |
| } |
| |
| /** |
| * @param resultDir |
| * @return an IInvocationResult for this result, or null upon error |
| */ |
| public static IInvocationResult getResultFromDir(File resultDir) { |
| return getResultFromDir(resultDir, false); |
| } |
| |
| /** |
| * @param resultDir |
| * @param useChecksum |
| * @return an IInvocationResult for this result, or null upon error |
| */ |
| public static IInvocationResult getResultFromDir(File resultDir, Boolean useChecksum) { |
| File resultFile = null; |
| try { |
| resultFile = new File(resultDir, TEST_RESULT_FILE_NAME); |
| if (!resultFile.exists()) { |
| return null; |
| } |
| Boolean invocationUseChecksum = useChecksum; |
| IInvocationResult invocation = new InvocationResult(); |
| invocation.setRetryDirectory(resultDir); |
| ChecksumReporter checksumReporter = null; |
| if (invocationUseChecksum) { |
| try { |
| checksumReporter = ChecksumReporter.load(resultDir); |
| invocation.setRetryChecksumStatus(RetryChecksumStatus.RetryWithChecksum); |
| } catch (ChecksumValidationException e) { |
| // Unable to read checksum form previous execution |
| invocation.setRetryChecksumStatus(RetryChecksumStatus.RetryWithoutChecksum); |
| invocationUseChecksum = false; |
| } |
| } |
| XmlPullParserFactory factory = XmlPullParserFactory.newInstance(); |
| XmlPullParser parser = factory.newPullParser(); |
| parser.setInput(new FileReader(resultFile)); |
| |
| parser.nextTag(); |
| parser.require(XmlPullParser.START_TAG, NS, RESULT_TAG); |
| invocation.setStartTime(Long.valueOf( |
| parser.getAttributeValue(NS, START_TIME_ATTR))); |
| invocation.setTestPlan(parser.getAttributeValue(NS, SUITE_PLAN_ATTR)); |
| invocation.setCommandLineArgs(parser.getAttributeValue(NS, COMMAND_LINE_ARGS)); |
| String deviceList = parser.getAttributeValue(NS, DEVICES_ATTR); |
| for (String device : deviceList.split(",")) { |
| invocation.addDeviceSerial(device); |
| } |
| |
| parser.nextTag(); |
| parser.require(XmlPullParser.START_TAG, NS, BUILD_TAG); |
| invocation.addInvocationInfo(BUILD_ID, parser.getAttributeValue(NS, BUILD_ID)); |
| invocation.addInvocationInfo(BUILD_PRODUCT, parser.getAttributeValue(NS, |
| BUILD_PRODUCT)); |
| String runHistoryValue = parser.getAttributeValue(NS, RUN_HISTORY_ATTR); |
| if (runHistoryValue != null) { |
| invocation.addInvocationInfo(RUN_HISTORY_ATTR, runHistoryValue); |
| } |
| |
| // The build fingerprint needs to reflect the true fingerprint of the device under test, |
| // ignoring potential overrides made by test suites (namely STS) for APFE build |
| // association. |
| String reportFingerprint = parser.getAttributeValue(NS, BUILD_FINGERPRINT); |
| String unalteredFingerprint = parser.getAttributeValue(NS, BUILD_FINGERPRINT_UNALTERED); |
| Boolean fingerprintWasAltered = !Strings.isNullOrEmpty(unalteredFingerprint); |
| invocation.setBuildFingerprint(fingerprintWasAltered ? unalteredFingerprint : |
| reportFingerprint ); |
| |
| // TODO(stuartscott): may want to reload these incase the retry was done with |
| // --skip-device-info flag |
| parser.nextTag(); |
| parser.require(XmlPullParser.END_TAG, NS, BUILD_TAG); |
| |
| // Parse RunHistory tag. |
| parser.nextTag(); |
| boolean hasRunHistoryTag = true; |
| try { |
| parser.require(parser.START_TAG, NS, RUN_HISTORY_TAG); |
| } catch (XmlPullParserException e) { |
| hasRunHistoryTag = false; |
| } |
| if (hasRunHistoryTag) { |
| parseRunHistory(parser); |
| } |
| |
| parser.require(XmlPullParser.START_TAG, NS, SUMMARY_TAG); |
| parser.nextTag(); |
| parser.require(XmlPullParser.END_TAG, NS, SUMMARY_TAG); |
| while (parser.nextTag() == XmlPullParser.START_TAG) { |
| parser.require(XmlPullParser.START_TAG, NS, MODULE_TAG); |
| String name = parser.getAttributeValue(NS, NAME_ATTR); |
| String abi = parser.getAttributeValue(NS, ABI_ATTR); |
| String moduleId = AbiUtils.createId(abi, name); |
| boolean done = Boolean.parseBoolean(parser.getAttributeValue(NS, DONE_ATTR)); |
| IModuleResult module = invocation.getOrCreateModule(moduleId); |
| module.initializeDone(done); |
| long runtime = Long.parseLong(parser.getAttributeValue(NS, RUNTIME_ATTR)); |
| module.addRuntime(runtime); |
| while (parser.nextTag() == XmlPullParser.START_TAG) { |
| parser.require(XmlPullParser.START_TAG, NS, CASE_TAG); |
| String caseName = parser.getAttributeValue(NS, NAME_ATTR); |
| ICaseResult testCase = module.getOrCreateResult(caseName); |
| while (parser.nextTag() == XmlPullParser.START_TAG) { |
| parser.require(XmlPullParser.START_TAG, NS, TEST_TAG); |
| String testName = parser.getAttributeValue(NS, NAME_ATTR); |
| ITestResult test = testCase.getOrCreateResult(testName); |
| String result = parser.getAttributeValue(NS, RESULT_ATTR); |
| String skipped = parser.getAttributeValue(NS, SKIPPED_ATTR); |
| if (skipped != null && Boolean.parseBoolean(skipped)) { |
| // mark test passed and skipped |
| test.skipped(); |
| } else { |
| // only apply result status directly if test was not skipped |
| test.setResultStatus(TestStatus.getStatus(result)); |
| } |
| test.setRetry(true); |
| while (parser.nextTag() == XmlPullParser.START_TAG) { |
| if (parser.getName().equals(FAILURE_TAG)) { |
| test.setMessage(parser.getAttributeValue(NS, MESSAGE_ATTR)); |
| if (parser.nextTag() == XmlPullParser.START_TAG) { |
| parser.require(XmlPullParser.START_TAG, NS, STACK_TAG); |
| test.setStackTrace(parser.nextText()); |
| parser.require(XmlPullParser.END_TAG, NS, STACK_TAG); |
| parser.nextTag(); |
| } |
| parser.require(XmlPullParser.END_TAG, NS, FAILURE_TAG); |
| } else if (parser.getName().equals(BUGREPORT_TAG)) { |
| test.setBugReport(parser.nextText()); |
| parser.require(XmlPullParser.END_TAG, NS, BUGREPORT_TAG); |
| } else if (parser.getName().equals(LOGCAT_TAG)) { |
| test.setLog(parser.nextText()); |
| parser.require(XmlPullParser.END_TAG, NS, LOGCAT_TAG); |
| } else if (parser.getName().equals(SCREENSHOT_TAG)) { |
| test.setScreenshot(parser.nextText()); |
| parser.require(XmlPullParser.END_TAG, NS, SCREENSHOT_TAG); |
| } else if (SUMMARY_TAG.equals(parser.getName())) { |
| test.setReportLog(ReportLog.parse(parser)); |
| } else if (METRIC_TAG.equals(parser.getName())) { |
| // Ignore the new format in the old parser. |
| parser.nextText(); |
| parser.require(XmlPullParser.END_TAG, NS, METRIC_TAG); |
| } else if (RUN_HISTORY_TAG.equals(parser.getName())) { |
| // Ignore the test result history since it only exists in |
| // CTS Verifier, which will not use parsing feature. |
| skipCurrentTag(parser); |
| } else { |
| parser.nextTag(); |
| } |
| } |
| parser.require(XmlPullParser.END_TAG, NS, TEST_TAG); |
| // If the fingerprint was altered, then checksum against the fingerprint |
| // originally reported |
| Boolean checksumMismatch = invocationUseChecksum && |
| !checksumReporter.containsTestResult(test, module, reportFingerprint) |
| && (fingerprintWasAltered ? !checksumReporter.containsTestResult( |
| test, module, unalteredFingerprint) : true); |
| if (checksumMismatch) { |
| test.removeResult(); |
| } |
| } |
| parser.require(XmlPullParser.END_TAG, NS, CASE_TAG); |
| } |
| parser.require(XmlPullParser.END_TAG, NS, MODULE_TAG); |
| // If the fingerprint was altered, then checksum against the fingerprint |
| // originally reported |
| Boolean checksumMismatch = invocationUseChecksum && |
| !checksumReporter.containsModuleResult(module, reportFingerprint) && |
| (fingerprintWasAltered ? !checksumReporter.containsModuleResult( |
| module, unalteredFingerprint) : true); |
| if (checksumMismatch) { |
| module.initializeDone(false); |
| } |
| } |
| parser.require(XmlPullParser.END_TAG, NS, RESULT_TAG); |
| return invocation; |
| } catch (XmlPullParserException | IOException e) { |
| System.out.println( |
| String.format("Exception when trying to load %s", |
| resultFile.getAbsolutePath())); |
| e.printStackTrace(); |
| return null; |
| } |
| } |
| |
| /** Parse and replay all run history information. */ |
| private static void parseRunHistory(XmlPullParser parser) |
| throws IOException, XmlPullParserException { |
| while (parser.nextTag() == XmlPullParser.START_TAG) { |
| parser.require(XmlPullParser.START_TAG, NS, RUN_TAG); |
| parser.nextTag(); |
| parser.require(XmlPullParser.END_TAG, NS, RUN_TAG); |
| } |
| parser.require(XmlPullParser.END_TAG, NS, RUN_HISTORY_TAG); |
| parser.nextTag(); |
| } |
| |
| /** Skip the current XML tags. */ |
| private static void skipCurrentTag(XmlPullParser parser) |
| throws XmlPullParserException, IOException { |
| int depth = 1; |
| while (depth != 0) { |
| switch (parser.next()) { |
| case XmlPullParser.END_TAG: |
| depth--; |
| break; |
| case XmlPullParser.START_TAG: |
| depth++; |
| break; |
| } |
| } |
| } |
| |
| /** |
| * @param result |
| * @param resultDir |
| * @param startTime |
| * @param referenceUrl A nullable string that can contain a URL to a related data |
| * @param logUrl A nullable string that can contain a URL to related log files |
| * @param commandLineArgs A string containing the arguments to the run command |
| * @param resultAttributes Extra key-value pairs to be added as attributes and corresponding |
| * values into the result XML file |
| * @return The result file created. |
| * @throws IOException |
| * @throws XmlPullParserException |
| */ |
| public static File writeResults( |
| String suiteName, |
| String suiteVersion, |
| String suitePlan, |
| String suiteBuild, |
| IInvocationResult result, |
| File resultDir, |
| long startTime, |
| long endTime, |
| String referenceUrl, |
| String logUrl, |
| String commandLineArgs, |
| Map<String, String> resultAttributes) |
| throws IOException, XmlPullParserException { |
| int passed = result.countResults(TestStatus.PASS); |
| int failed = result.countResults(TestStatus.FAIL); |
| File resultFile = new File(resultDir, TEST_RESULT_FILE_NAME); |
| OutputStream stream = new FileOutputStream(resultFile); |
| XmlSerializer serializer = XmlPullParserFactory.newInstance(TYPE, null).newSerializer(); |
| serializer.setOutput(stream, ENCODING); |
| serializer.startDocument(ENCODING, false); |
| serializer.setFeature("http://xmlpull.org/v1/doc/features.html#indent-output", true); |
| serializer.processingInstruction( |
| "xml-stylesheet type=\"text/xsl\" href=\"compatibility_result.xsl\""); |
| serializer.startTag(NS, RESULT_TAG); |
| serializer.attribute(NS, START_TIME_ATTR, String.valueOf(startTime)); |
| serializer.attribute(NS, END_TIME_ATTR, String.valueOf(endTime)); |
| serializer.attribute(NS, START_DISPLAY_TIME_ATTR, toReadableDateString(startTime)); |
| serializer.attribute(NS, END_DISPLAY_TIME_ATTR, toReadableDateString(endTime)); |
| |
| serializer.attribute(NS, SUITE_NAME_ATTR, suiteName); |
| serializer.attribute(NS, SUITE_VERSION_ATTR, suiteVersion); |
| serializer.attribute(NS, SUITE_PLAN_ATTR, suitePlan); |
| serializer.attribute(NS, SUITE_BUILD_ATTR, suiteBuild); |
| serializer.attribute(NS, REPORT_VERSION_ATTR, RESULT_FILE_VERSION); |
| serializer.attribute(NS, COMMAND_LINE_ARGS, nullToEmpty(commandLineArgs)); |
| |
| if (resultAttributes != null) { |
| for (Entry<String, String> entry : resultAttributes.entrySet()) { |
| serializer.attribute(NS, entry.getKey(), entry.getValue()); |
| } |
| } |
| |
| if (referenceUrl != null) { |
| serializer.attribute(NS, REFERENCE_URL_ATTR, referenceUrl); |
| } |
| |
| if (logUrl != null) { |
| serializer.attribute(NS, LOG_URL_ATTR, logUrl); |
| } |
| |
| // Device Info |
| Set<String> devices = result.getDeviceSerials(); |
| StringBuilder deviceList = new StringBuilder(); |
| boolean first = true; |
| for (String device : devices) { |
| if (first) { |
| first = false; |
| } else { |
| deviceList.append(","); |
| } |
| deviceList.append(device); |
| } |
| serializer.attribute(NS, DEVICES_ATTR, deviceList.toString()); |
| |
| // Host Info |
| String hostName = ""; |
| try { |
| hostName = InetAddress.getLocalHost().getHostName(); |
| } catch (UnknownHostException ignored) {} |
| serializer.attribute(NS, HOST_NAME_ATTR, hostName); |
| serializer.attribute(NS, OS_NAME_ATTR, System.getProperty("os.name")); |
| serializer.attribute(NS, OS_VERSION_ATTR, System.getProperty("os.version")); |
| serializer.attribute(NS, OS_ARCH_ATTR, System.getProperty("os.arch")); |
| serializer.attribute(NS, JAVA_VENDOR_ATTR, System.getProperty("java.vendor")); |
| serializer.attribute(NS, JAVA_VERSION_ATTR, System.getProperty("java.version")); |
| |
| // Build Info |
| serializer.startTag(NS, BUILD_TAG); |
| for (Entry<String, String> entry : result.getInvocationInfo().entrySet()) { |
| serializer.attribute(NS, entry.getKey(), entry.getValue()); |
| if (Strings.isNullOrEmpty(result.getBuildFingerprint()) && |
| entry.getKey().equals(BUILD_FINGERPRINT)) { |
| result.setBuildFingerprint(entry.getValue()); |
| } |
| } |
| serializer.endTag(NS, BUILD_TAG); |
| |
| // Run history - this contains a list of start and end times of previous runs. More |
| // information may be added in the future. |
| Collection<InvocationResult.RunHistory> runHistories = |
| ((InvocationResult) result).getRunHistories(); |
| if (!runHistories.isEmpty()) { |
| serializer.startTag(NS, RUN_HISTORY_TAG); |
| for (InvocationResult.RunHistory runHistory : runHistories) { |
| serializer.startTag(NS, RUN_TAG); |
| serializer.attribute(NS, START_TIME_ATTR, String.valueOf(runHistory.startTime)); |
| serializer.attribute(NS, END_TIME_ATTR, String.valueOf(runHistory.endTime)); |
| serializer.endTag(NS, RUN_TAG); |
| } |
| serializer.endTag(NS, RUN_HISTORY_TAG); |
| } |
| |
| // Summary |
| serializer.startTag(NS, SUMMARY_TAG); |
| serializer.attribute(NS, PASS_ATTR, Integer.toString(passed)); |
| serializer.attribute(NS, FAILED_ATTR, Integer.toString(failed)); |
| serializer.attribute(NS, MODULES_DONE_ATTR, |
| Integer.toString(result.getModuleCompleteCount())); |
| serializer.attribute(NS, MODULES_TOTAL_ATTR, |
| Integer.toString(result.getModules().size())); |
| serializer.endTag(NS, SUMMARY_TAG); |
| |
| // Results |
| for (IModuleResult module : result.getModules()) { |
| serializer.startTag(NS, MODULE_TAG); |
| serializer.attribute(NS, NAME_ATTR, module.getName()); |
| serializer.attribute(NS, ABI_ATTR, module.getAbi()); |
| serializer.attribute(NS, RUNTIME_ATTR, String.valueOf(module.getRuntime())); |
| serializer.attribute(NS, DONE_ATTR, Boolean.toString(module.isDone())); |
| serializer.attribute(NS, PASS_ATTR, |
| Integer.toString(module.countResults(TestStatus.PASS))); |
| for (ICaseResult cr : module.getResults()) { |
| serializer.startTag(NS, CASE_TAG); |
| serializer.attribute(NS, NAME_ATTR, cr.getName()); |
| for (ITestResult r : cr.getResults()) { |
| TestStatus status = r.getResultStatus(); |
| if (status == null) { |
| continue; // test was not executed, don't report |
| } |
| serializer.startTag(NS, TEST_TAG); |
| serializer.attribute(NS, RESULT_ATTR, status.getValue()); |
| serializer.attribute(NS, NAME_ATTR, r.getName()); |
| if (r.isSkipped()) { |
| serializer.attribute(NS, SKIPPED_ATTR, Boolean.toString(true)); |
| } |
| String message = r.getMessage(); |
| if (message != null) { |
| serializer.startTag(NS, FAILURE_TAG); |
| serializer.attribute(NS, MESSAGE_ATTR, message); |
| String stackTrace = r.getStackTrace(); |
| if (stackTrace != null) { |
| serializer.startTag(NS, STACK_TAG); |
| serializer.text(stackTrace); |
| serializer.endTag(NS, STACK_TAG); |
| } |
| serializer.endTag(NS, FAILURE_TAG); |
| } |
| String bugreport = r.getBugReport(); |
| if (bugreport != null) { |
| serializer.startTag(NS, BUGREPORT_TAG); |
| serializer.text(bugreport); |
| serializer.endTag(NS, BUGREPORT_TAG); |
| } |
| String logcat = r.getLog(); |
| if (logcat != null) { |
| serializer.startTag(NS, LOGCAT_TAG); |
| serializer.text(logcat); |
| serializer.endTag(NS, LOGCAT_TAG); |
| } |
| String screenshot = r.getScreenshot(); |
| if (screenshot != null) { |
| serializer.startTag(NS, SCREENSHOT_TAG); |
| serializer.text(screenshot); |
| serializer.endTag(NS, SCREENSHOT_TAG); |
| } |
| ReportLog report = r.getReportLog(); |
| if (report != null) { |
| ReportLog.serialize(serializer, report); |
| } |
| |
| // Test result history contains a list of execution time for each test item. |
| List<TestResultHistory> testResultHistories = r.getTestResultHistories(); |
| if (testResultHistories != null) { |
| for (TestResultHistory resultHistory : testResultHistories) { |
| TestResultHistory.serialize(serializer, resultHistory, r.getName()); |
| } |
| } |
| |
| serializer.endTag(NS, TEST_TAG); |
| } |
| serializer.endTag(NS, CASE_TAG); |
| } |
| serializer.endTag(NS, MODULE_TAG); |
| } |
| serializer.endDocument(); |
| createChecksum(resultDir, result); |
| return resultFile; |
| } |
| |
| /** |
| * Generate html report listing an failed tests |
| */ |
| public static File createFailureReport(File inputXml) { |
| File failureReport = new File(inputXml.getParentFile(), FAILURE_REPORT_NAME); |
| try (InputStream xslStream = ResultHandler.class.getResourceAsStream( |
| String.format("/report/%s", FAILURE_XSL_FILE_NAME)); |
| OutputStream outputStream = new FileOutputStream(failureReport)) { |
| |
| Transformer transformer = TransformerFactory.newInstance().newTransformer( |
| new StreamSource(xslStream)); |
| transformer.transform(new StreamSource(inputXml), new StreamResult(outputStream)); |
| } catch (IOException | TransformerException ignored) { } |
| return failureReport; |
| } |
| |
| private static void createChecksum(File resultDir, IInvocationResult invocationResult) { |
| RetryChecksumStatus retryStatus = invocationResult.getRetryChecksumStatus(); |
| switch (retryStatus) { |
| case NotRetry: case RetryWithChecksum: |
| // Do not disrupt the process if there is a problem generating checksum. |
| ChecksumReporter.tryCreateChecksum(resultDir, invocationResult); |
| break; |
| case RetryWithoutChecksum: |
| // If the previous run has an invalid checksum file, |
| // copy it into current results folder for future troubleshooting |
| File retryDirectory = invocationResult.getRetryDirectory(); |
| Path retryChecksum = FileSystems.getDefault().getPath( |
| retryDirectory.getAbsolutePath(), ChecksumReporter.NAME); |
| if (!retryChecksum.toFile().exists()) { |
| // if no checksum file, check for a copy from a previous retry |
| retryChecksum = FileSystems.getDefault().getPath( |
| retryDirectory.getAbsolutePath(), ChecksumReporter.PREV_NAME); |
| } |
| |
| if (retryChecksum.toFile().exists()) { |
| File checksumCopy = new File(resultDir, ChecksumReporter.PREV_NAME); |
| try (FileOutputStream stream = new FileOutputStream(checksumCopy)) { |
| Files.copy(retryChecksum, stream); |
| } catch (IOException e) { |
| // Do not disrupt the process if there is a problem copying checksum |
| } |
| } |
| } |
| } |
| |
| |
| /** |
| * Find the IInvocationResult for the given sessionId. |
| */ |
| public static IInvocationResult findResult(File resultsDir, Integer sessionId) { |
| return findResult(resultsDir, sessionId, true); |
| } |
| |
| /** |
| * Find the IInvocationResult for the given sessionId. |
| */ |
| private static IInvocationResult findResult( |
| File resultsDir, Integer sessionId, Boolean useChecksum) { |
| if (sessionId < 0) { |
| throw new IllegalArgumentException( |
| String.format("Invalid session id [%d] ", sessionId)); |
| } |
| File resultDir = getResultDirectory(resultsDir, sessionId); |
| IInvocationResult result = getResultFromDir(resultDir, useChecksum); |
| if (result == null) { |
| throw new RuntimeException(String.format("Could not find session [%d]", sessionId)); |
| } |
| return result; |
| } |
| |
| /** |
| * Get the result directory for the given sessionId. |
| */ |
| public static File getResultDirectory(File resultsDir, Integer sessionId) { |
| if (sessionId < 0) { |
| throw new IllegalArgumentException( |
| String.format("Invalid session id [%d] ", sessionId)); |
| } |
| List<File> allResultDirs = getResultDirectories(resultsDir); |
| if (sessionId >= allResultDirs.size()) { |
| throw new IllegalArgumentException(String.format("Invalid session id [%d], results " + |
| "directory (%s) contains only %d results", |
| sessionId, resultsDir.getAbsolutePath(), allResultDirs.size())); |
| } |
| return allResultDirs.get(sessionId); |
| } |
| |
| /** |
| * Get a list of child directories that contain test invocation results |
| * @param resultsDir the root test result directory |
| * @return the list of {@link File} results directory. |
| */ |
| public static List<File> getResultDirectories(File resultsDir) { |
| List<File> directoryList = new ArrayList<>(); |
| File[] files = resultsDir.listFiles(); |
| if (files == null || files.length == 0) { |
| // No results, just return the empty list |
| return directoryList; |
| } |
| for (File resultDir : files) { |
| if (!resultDir.isDirectory()) { |
| continue; |
| } |
| // Only include if it contain results file |
| File resultFile = new File(resultDir, TEST_RESULT_FILE_NAME); |
| if (!resultFile.exists()) { |
| continue; |
| } |
| directoryList.add(resultDir); |
| } |
| Collections.sort(directoryList, (d1, d2) -> d1.getName().compareTo(d2.getName())); |
| return directoryList; |
| } |
| |
| /** |
| * Return the given time as a {@link String} suitable for displaying. |
| * <p/> |
| * Example: Fri Aug 20 15:13:03 PDT 2010 |
| * |
| * @param time the epoch time in ms since midnight Jan 1, 1970 |
| */ |
| static String toReadableDateString(long time) { |
| SimpleDateFormat dateFormat = |
| new SimpleDateFormat("EEE MMM dd HH:mm:ss zzz yyyy", Locale.ENGLISH); |
| return dateFormat.format(new Date(time)); |
| } |
| |
| /** |
| * When nullable is null, return an empty string. Otherwise, return the value in nullable. |
| */ |
| private static String nullToEmpty(String nullable) { |
| return nullable == null ? "" : nullable; |
| } |
| } |