/* * Licensed to the Apache Software Foundation (ASF) under one or more * contributor license agreements. See the NOTICE file distributed with * this work for additional information regarding copyright ownership. * The ASF licenses this file to You 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.coretests; import junit.framework.Test; import junit.framework.TestCase; import junit.framework.TestFailure; import junit.framework.TestResult; import junit.runner.BaseTestRunner; import org.kxml2.io.KXmlSerializer; import java.io.File; import java.io.FileOutputStream; import java.io.IOException; import java.text.SimpleDateFormat; import java.util.ArrayList; import java.util.Date; import java.util.Enumeration; import java.util.HashMap; import java.util.LinkedHashMap; import java.util.List; import java.util.Map; import java.util.TimeZone; /** * Writes JUnit results to a series of 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. */ public class XmlReportPrinter { 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; /** the test suites, which each contain tests */ private final Map<String, Suite> suites = new LinkedHashMap<String, Suite>(); /** * Create a report printer that prints the specified test suite. Since the * CoreTestSuite nulls-out tests after they're run (to limit memory * consumption), it is necessary to create the report printer prior to test * execution. */ public XmlReportPrinter(CoreTestSuite allTests) { // partition the tests by suite to be consistent with Ant's printer for (Enumeration<Test> e = allTests.tests(); e.hasMoreElements(); ) { TestId test = new TestId(e.nextElement()); // create the suite's entry in the map if necessary Suite suite = suites.get(test.className); if (suite == null) { suite = new Suite(test.className); suites.put(test.className, suite); } suite.tests.add(test); } } public void setResults(TestResult result) { populateFailures(true, result.errors()); populateFailures(false, result.failures()); } /** * Populate the list of failures in each of the suites. */ private void populateFailures(boolean errors, Enumeration<TestFailure> failures) { while (failures.hasMoreElements()) { TestFailure failure = failures.nextElement(); TestId test = new TestId(failure.failedTest()); Suite suite = suites.get(test.className); if (suite == null) { throw new IllegalStateException( "received a failure for a " + "test that wasn't in the original test suite!"); } if (errors) { suite.errors.put(test, failure); } else { suite.failures.put(test, failure); } } } public int generateReports(String directory) { File parent = new File(directory); parent.mkdirs(); SimpleDateFormat dateFormat = new SimpleDateFormat("yyyy-MM-dd'T'HH:mm:ss"); TimeZone gmt = TimeZone.getTimeZone("GMT"); dateFormat.setTimeZone(gmt); dateFormat.setLenient(true); String timestamp = dateFormat.format(new Date()); for (Suite suite : suites.values()) { FileOutputStream stream = null; try { stream = new FileOutputStream(new File(parent, "TEST-" + suite.name + ".xml")); KXmlSerializer serializer = new KXmlSerializer(); serializer.setOutput(stream, "UTF-8"); serializer.startDocument("UTF-8", null); serializer.setFeature( "http://xmlpull.org/v1/doc/features.html#indent-output", true); suite.print(serializer, timestamp); serializer.endDocument(); } catch (IOException e) { throw new RuntimeException(e); } finally { if (stream != null) { try { stream.close(); } catch (IOException ignored) { ignored.printStackTrace(); } } } } return suites.size(); } static class Suite { private final String name; private final List<TestId> tests = new ArrayList<TestId>(); private final Map<TestId, TestFailure> failures = new HashMap<TestId, TestFailure>(); private final Map<TestId, TestFailure> errors = new HashMap<TestId, TestFailure>(); Suite(String name) { this.name = name; } void print(KXmlSerializer serializer, String timestamp) throws IOException { serializer.startTag(ns, TESTSUITE); serializer.attribute(ns, ATTR_NAME, name); serializer.attribute(ns, ATTR_TESTS, Integer.toString(tests.size())); serializer.attribute(ns, ATTR_FAILURES, Integer.toString(failures.size())); serializer.attribute(ns, ATTR_ERRORS, Integer.toString(errors.size())); serializer.attribute(ns, ATTR_TIME, "0"); serializer.attribute(ns, TIMESTAMP, timestamp); serializer.attribute(ns, HOSTNAME, "localhost"); serializer.startTag(ns, PROPERTIES); serializer.endTag(ns, PROPERTIES); for (TestId testId : tests) { TestFailure error = errors.get(testId); TestFailure failure = failures.get(testId); if (error != null) { testId.printFailure(serializer, ERROR, error.thrownException()); } else if (failure != null) { testId.printFailure(serializer, FAILURE, failure.thrownException()); } else { testId.printSuccess(serializer); } } serializer.endTag(ns, TESTSUITE); } } private static class TestId { private final String name; private final String className; TestId(Test test) { this.name = test instanceof TestCase ? ((TestCase) test).getName() : test.toString(); this.className = test.getClass().getName(); } void printSuccess(KXmlSerializer serializer) throws IOException { serializer.startTag(ns, TESTCASE); printAttributes(serializer); serializer.endTag(ns, TESTCASE); } void printFailure(KXmlSerializer serializer, String type, Throwable t) throws IOException { serializer.startTag(ns, TESTCASE); printAttributes(serializer); serializer.startTag(ns, type); String message = t.getMessage(); if (message != null && message.length() > 0) { serializer.attribute(ns, ATTR_MESSAGE, t.getMessage()); } serializer.attribute(ns, ATTR_TYPE, t.getClass().getName()); serializer.text(BaseTestRunner.getFilteredTrace(t)); serializer.endTag(ns, type); serializer.endTag(ns, TESTCASE); } void printAttributes(KXmlSerializer serializer) throws IOException { serializer.attribute(ns, ATTR_NAME, name); serializer.attribute(ns, ATTR_CLASSNAME, className); serializer.attribute(ns, ATTR_TIME, "0"); } @Override public boolean equals(Object o) { return o instanceof TestId && ((TestId) o).name.equals(name) && ((TestId) o).className.equals(className); } @Override public int hashCode() { return name.hashCode() ^ className.hashCode(); } } }