| // Copyright 2015 The Bazel Authors. All Rights Reserved. |
| // |
| // 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.google.testing.junit.runner.model; |
| |
| import java.io.IOException; |
| import java.io.PrintWriter; |
| import java.io.StringWriter; |
| import java.util.ArrayList; |
| import java.util.List; |
| import java.util.Map; |
| import javax.annotation.Nullable; |
| import javax.inject.Inject; |
| |
| /** |
| * Writes the JUnit test nodes and their results into Ant-JUnit XML. Ant-JUnit XML is not a |
| * standardized format. For this implementation the |
| * <a href="http://windyroad.com.au/dl/Open%20Source/JUnit.xsd">XML schema</a> that is generally |
| * referred to as the best available source was used as a reference. |
| */ |
| public final class AntXmlResultWriter implements XmlResultWriter { |
| private static final String JUNIT_ELEMENT_TESTSUITES = "testsuites"; |
| private static final String JUNIT_ELEMENT_TESTSUITE = "testsuite"; |
| private static final String JUNIT_ELEMENT_TESTSUITE_PROPERTIES = "properties"; |
| private static final String JUNIT_ELEMENT_TESTSUITE_SYSTEM_OUT = "system-out"; |
| private static final String JUNIT_ELEMENT_TESTSUITE_SYSTEM_ERR = "system-err"; |
| private static final String JUNIT_ELEMENT_PROPERTY = "property"; |
| private static final String JUNIT_ELEMENT_TESTCASE = "testcase"; |
| private static final String JUNIT_ELEMENT_FAILURE = "failure"; |
| |
| private static final String JUNIT_ATTR_TESTSUITE_ERRORS = "errors"; |
| private static final String JUNIT_ATTR_TESTSUITE_FAILURES = "failures"; |
| private static final String JUNIT_ATTR_TESTSUITE_HOSTNAME = "hostname"; |
| private static final String JUNIT_ATTR_TESTSUITE_NAME = "name"; |
| private static final String JUNIT_ATTR_TESTSUITE_TESTS = "tests"; |
| private static final String JUNIT_ATTR_TESTSUITE_TIME = "time"; |
| private static final String JUNIT_ATTR_TESTSUITE_TIMESTAMP = "timestamp"; |
| private static final String JUNIT_ATTR_TESTSUITE_ID = "id"; |
| private static final String JUNIT_ATTR_TESTSUITE_PACKAGE = "package"; |
| private static final String JUNIT_ATTR_PROPERTY_NAME = "name"; |
| private static final String JUNIT_ATTR_PROPERTY_VALUE = "value"; |
| private static final String JUNIT_ATTR_FAILURE_MESSAGE = "message"; |
| private static final String JUNIT_ATTR_FAILURE_TYPE = "type"; |
| private static final String JUNIT_ATTR_TESTCASE_NAME = "name"; |
| private static final String JUNIT_ATTR_TESTCASE_CLASSNAME = "classname"; |
| private static final String JUNIT_ATTR_TESTCASE_TIME = "time"; |
| |
| private int testSuiteId; |
| |
| @Inject |
| public AntXmlResultWriter() {} |
| |
| @Override |
| public void writeTestSuites(XmlWriter writer, TestResult result) throws IOException { |
| testSuiteId = 0; |
| writer.startDocument(); |
| writer.startElement(JUNIT_ELEMENT_TESTSUITES); |
| for (TestResult child : result.getChildResults()) { |
| writeTestSuite(writer, child, result.getFailures()); |
| } |
| writer.endElement(); |
| writer.close(); |
| } |
| |
| private void writeTestSuite(XmlWriter writer, TestResult result, |
| Iterable<Throwable> parentFailures) |
| throws IOException { |
| List<Throwable> allFailures = new ArrayList<>(); |
| for (Throwable failure : parentFailures) { |
| allFailures.add(failure); |
| } |
| allFailures.addAll(result.getFailures()); |
| parentFailures = allFailures; |
| |
| writer.startElement(JUNIT_ELEMENT_TESTSUITE); |
| |
| writeTestSuiteAttributes(writer, result); |
| writeTestSuiteProperties(writer, result); |
| writeTestCases(writer, result, parentFailures); |
| writeTestSuiteOutput(writer); |
| |
| writer.endElement(); |
| |
| for (TestResult child : result.getChildResults()) { |
| if (!child.getChildResults().isEmpty()) { |
| writeTestSuite(writer, child, parentFailures); |
| } |
| } |
| } |
| |
| private void writeTestSuiteProperties(XmlWriter writer, TestResult result) throws IOException { |
| writer.startElement(JUNIT_ELEMENT_TESTSUITE_PROPERTIES); |
| for (Map.Entry<String, String> entry : result.getProperties().entrySet()) { |
| writer.startElement(JUNIT_ELEMENT_PROPERTY); |
| writer.writeAttribute(JUNIT_ATTR_PROPERTY_NAME, entry.getKey()); |
| writer.writeAttribute(JUNIT_ATTR_PROPERTY_VALUE, entry.getValue()); |
| writer.endElement(); |
| } |
| writer.endElement(); |
| } |
| |
| private void writeTestCases(XmlWriter writer, TestResult result, |
| Iterable<Throwable> parentFailures) throws IOException { |
| for (TestResult child : result.getChildResults()) { |
| if (child.getStatus() == TestResult.Status.FILTERED) { |
| continue; |
| } |
| if (child.getChildResults().isEmpty()) { |
| writeTestCase(writer, child, parentFailures); |
| } |
| } |
| } |
| |
| private void writeTestSuiteOutput(XmlWriter writer) throws IOException { |
| writer.startElement(JUNIT_ELEMENT_TESTSUITE_SYSTEM_OUT); |
| // TODO(bazel-team) - where to get this from? |
| writer.endElement(); |
| writer.startElement(JUNIT_ELEMENT_TESTSUITE_SYSTEM_ERR); |
| // TODO(bazel-team) - where to get this from? |
| writer.endElement(); |
| } |
| |
| private void writeTestSuiteAttributes(XmlWriter writer, TestResult result) throws IOException { |
| writer.writeAttribute(JUNIT_ATTR_TESTSUITE_NAME, result.getName()); |
| writer.writeAttribute(JUNIT_ATTR_TESTSUITE_TIMESTAMP, getFormattedTimestamp( |
| result.getRunTimeInterval())); |
| writer.writeAttribute(JUNIT_ATTR_TESTSUITE_HOSTNAME, "localhost"); |
| writer.writeAttribute(JUNIT_ATTR_TESTSUITE_TESTS, result.getNumTests()); |
| writer.writeAttribute(JUNIT_ATTR_TESTSUITE_FAILURES, result.getNumFailures()); |
| // JUnit 4.x no longer distinguishes between errors and failures, so it should be safe to just |
| // report errors as 0 and put everything into failures. |
| writer.writeAttribute(JUNIT_ATTR_TESTSUITE_ERRORS, 0); |
| writer.writeAttribute(JUNIT_ATTR_TESTSUITE_TIME, getFormattedRunTime( |
| result.getRunTimeInterval())); |
| // TODO(bazel-team) - do we want to report the package name here? Could we simply get it from |
| // result.getClassName() by stripping the last element of the class name? |
| writer.writeAttribute(JUNIT_ATTR_TESTSUITE_PACKAGE, ""); |
| writer.writeAttribute(JUNIT_ATTR_TESTSUITE_ID, this.testSuiteId++); |
| } |
| |
| private static String getFormattedRunTime(@Nullable TestInterval runTimeInterval) { |
| return runTimeInterval == null ? "0.0" |
| : String.valueOf(runTimeInterval.toDurationMillis() / 1000.0D); |
| } |
| |
| private static String getFormattedTimestamp(@Nullable TestInterval runTimeInterval) { |
| return runTimeInterval == null ? "" : runTimeInterval.startInstantToString(); |
| } |
| |
| private void writeTestCase(XmlWriter writer, TestResult result, |
| Iterable<Throwable> parentFailures) |
| throws IOException { |
| writer.startElement(JUNIT_ELEMENT_TESTCASE); |
| writer.writeAttribute(JUNIT_ATTR_TESTCASE_NAME, result.getName()); |
| writer.writeAttribute(JUNIT_ATTR_TESTCASE_CLASSNAME, result.getClassName()); |
| writer.writeAttribute(JUNIT_ATTR_TESTCASE_TIME, getFormattedRunTime( |
| result.getRunTimeInterval())); |
| |
| for (Throwable failure : parentFailures) { |
| writeThrowableToXmlWriter(writer, failure); |
| } |
| |
| for (Throwable failure : result.getFailures()) { |
| writeThrowableToXmlWriter(writer, failure); |
| } |
| |
| writer.endElement(); |
| } |
| |
| private static void writeThrowableToXmlWriter(XmlWriter writer, Throwable failure) |
| throws IOException { |
| writer.startElement(JUNIT_ELEMENT_FAILURE); |
| writer.writeAttribute( |
| JUNIT_ATTR_FAILURE_MESSAGE, (failure.getMessage() == null) ? "" : failure.getMessage()); |
| writer.writeAttribute(JUNIT_ATTR_FAILURE_TYPE, failure.getClass().getName()); |
| writer.writeCharacters(formatStackTrace(failure)); |
| writer.endElement(); |
| } |
| |
| private static String formatStackTrace(Throwable throwable) { |
| StringWriter stringWriter = new StringWriter(); |
| PrintWriter writer = new PrintWriter(stringWriter); |
| throwable.printStackTrace(writer); |
| return stringWriter.getBuffer().toString(); |
| } |
| } |