Java tutorial
/* * The MIT License * * Copyright (c) 2004-2009, Sun Microsystems, Inc., Kohsuke Kawaguchi, Erik Ramfelt, Xavier Le Vourch, Tom Huybrechts, Yahoo!, Inc. * * Permission is hereby granted, free of charge, to any person obtaining a copy * of this software and associated documentation files (the "Software"), to deal * in the Software without restriction, including without limitation the rights * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell * copies of the Software, and to permit persons to whom the Software is * furnished to do so, subject to the following conditions: * * The above copyright notice and this permission notice shall be included in * all copies or substantial portions of the Software. * * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN * THE SOFTWARE. */ package com.cwctravel.hudson.plugins.suitegroupedtests.junit; import hudson.tasks.test.TestObject; import hudson.util.IOException2; import java.io.File; import java.io.IOException; import java.io.Serializable; import java.util.ArrayList; import java.util.HashSet; import java.util.List; import java.util.Set; import java.util.regex.Matcher; import java.util.regex.Pattern; import org.apache.commons.io.FileUtils; import org.dom4j.Document; import org.dom4j.DocumentException; import org.dom4j.Element; import org.dom4j.io.SAXReader; import org.kohsuke.stapler.export.Exported; import org.kohsuke.stapler.export.ExportedBean; /** * Result of one test suite. * <p> * The notion of "test suite" is rather arbitrary in JUnit ant task. It's basically one invocation of junit. * <p> * This object is really only used as a part of the persisted object tree. * * @author Kohsuke Kawaguchi */ @ExportedBean public final class SuiteResult implements Serializable { private final String file; private final String name; private final String stdout; private final String stderr; private float duration; /** * The 'timestamp' attribute of the test suite. AFAICT, this is not a required attribute in XML, so the value may be null. */ private String timestamp; /** * All test cases. */ private final List<CaseResult> cases = new ArrayList<CaseResult>(); private transient TestResult parent; SuiteResult(String name, String stdout, String stderr) { this.name = name; this.stderr = stderr; this.stdout = stdout; this.file = null; } /** * Parses the JUnit XML file into {@link SuiteResult}s. This method returns a collection, as a single XML may have multiple <testsuite> * elements wrapped into the top-level <testsuites>. */ static List<SuiteResult> parse(File xmlReport, boolean keepLongStdio) throws DocumentException, IOException { List<SuiteResult> r = new ArrayList<SuiteResult>(); // parse into DOM SAXReader saxReader = new SAXReader(); // install EntityResolver for resolving DTDs, which are in files created by TestNG. // (see https://hudson.dev.java.net/servlets/ReadMsg?listName=users&msgNo=5530) XMLEntityResolver resolver = new XMLEntityResolver(); saxReader.setEntityResolver(resolver); Document result = saxReader.read(xmlReport); Element root = result.getRootElement(); if (root.getName().equals("testsuites")) { // multi-suite file for (Element suite : (List<Element>) root.elements("testsuite")) r.add(new SuiteResult(xmlReport, suite, keepLongStdio)); } else { // single suite file r.add(new SuiteResult(xmlReport, root, keepLongStdio)); } return r; } /** * @param xmlReport * A JUnit XML report file whose top level element is 'testsuite'. * @param suite * The parsed result of {@code xmlReport} */ private SuiteResult(File xmlReport, Element suite, boolean keepLongStdio) throws DocumentException, IOException { this.file = xmlReport.getAbsolutePath(); String name = suite.attributeValue("name"); if (name == null) // some user reported that name is null in their environment. // see http://www.nabble.com/Unexpected-Null-Pointer-Exception-in-Hudson-1.131-tf4314802.html name = '(' + xmlReport.getName() + ')'; else { String pkg = suite.attributeValue("package"); if (pkg != null && pkg.length() > 0) name = pkg + '.' + name; } this.name = TestObject.safe(name); this.timestamp = suite.attributeValue("timestamp"); Element ex = suite.element("error"); if (ex != null) { // according to junit-noframes.xsl l.229, this happens when the test class failed to load addCase(new CaseResult(this, suite, "<init>", keepLongStdio)); } for (Element e : (List<Element>) suite.elements("testcase")) { // https://hudson.dev.java.net/issues/show_bug.cgi?id=1233 indicates that // when <testsuites> is present, we are better off using @classname on the // individual testcase class. // https://hudson.dev.java.net/issues/show_bug.cgi?id=1463 indicates that // @classname may not exist in individual testcase elements. We now // also test if the testsuite element has a package name that can be used // as the class name instead of the file name which is default. String classname = e.attributeValue("classname"); if (classname == null) { classname = suite.attributeValue("name"); } // https://hudson.dev.java.net/issues/show_bug.cgi?id=1233 and // http://www.nabble.com/difference-in-junit-publisher-and-ant-junitreport-tf4308604.html#a12265700 // are at odds with each other --- when both are present, // one wants to use @name from <testsuite>, // the other wants to use @classname from <testcase>. addCase(new CaseResult(this, e, classname, keepLongStdio)); } String stdout = suite.elementText("system-out"); String stderr = suite.elementText("system-err"); if (stdout == null && stderr == null) { // Surefire never puts stdout/stderr in the XML. Instead, it goes to a separate file Matcher m = SUREFIRE_FILENAME.matcher(xmlReport.getName()); if (m.matches()) { // look for ***-output.txt from TEST-***.xml File mavenOutputFile = new File(xmlReport.getParentFile(), m.group(1) + "-output.txt"); if (mavenOutputFile.exists()) { try { stdout = FileUtils.readFileToString(mavenOutputFile); } catch (IOException e) { throw new IOException2("Failed to read " + mavenOutputFile, e); } } } } this.stdout = CaseResult.possiblyTrimStdio(cases, keepLongStdio, stdout); this.stderr = CaseResult.possiblyTrimStdio(cases, keepLongStdio, stderr); } /*package*/void addCase(CaseResult cr) { cases.add(cr); duration += cr.getDuration(); } @Exported(visibility = 9) public String getName() { return name; } @Exported(visibility = 9) public float getDuration() { return duration; } /** * The stdout of this test. * * @since 1.281 * @see CaseResult#getStdout() */ @Exported public String getStdout() { return stdout; } /** * The stderr of this test. * * @since 1.281 * @see CaseResult#getStderr() */ @Exported public String getStderr() { return stderr; } /** * The absolute path to the original test report. OS-dependent. */ public String getFile() { return file; } public TestResult getParent() { return parent; } @Exported(visibility = 9) public String getTimestamp() { return timestamp; } @Exported(inline = true, visibility = 9) public List<CaseResult> getCases() { return cases; } public SuiteResult getPreviousResult() { hudson.tasks.test.TestResult pr = parent.getPreviousResult(); if (pr == null) return null; if (pr instanceof TestResult) return ((TestResult) pr).getSuite(name); return null; } /** * Returns the {@link CaseResult} whose {@link CaseResult#getName()} is the same as the given string. * <p> * Note that test name needs not be unique. */ public CaseResult getCase(String name) { for (CaseResult c : cases) { if (c.getName().equals(name)) return c; } return null; } public Set<String> getClassNames() { Set<String> result = new HashSet<String>(); for (CaseResult c : cases) { result.add(c.getClassName()); } return result; } /** * KLUGE. We have to call this to prevent freeze() from calling c.freeze() on all its children, because that in turn calls c.getOwner(), which * requires a non-null parent. * * @param parent */ void setParent(TestResult parent) { this.parent = parent; } /*package*/boolean freeze(TestResult owner) { if (this.parent != null) return false; // already frozen this.parent = owner; for (CaseResult c : cases) c.freeze(this); return true; } private static final long serialVersionUID = 1L; private static final Pattern SUREFIRE_FILENAME = Pattern.compile("TEST-(.+)\\.xml"); }