| /* |
| * The Apache Software License, Version 1.1 |
| * |
| * Copyright (c) 2000 The Apache Software Foundation. All rights |
| * reserved. |
| * |
| * Redistribution and use in source and binary forms, with or without |
| * modification, are permitted provided that the following conditions |
| * are met: |
| * |
| * 1. Redistributions of source code must retain the above copyright |
| * notice, this list of conditions and the following disclaimer. |
| * |
| * 2. Redistributions in binary form must reproduce the above copyright |
| * notice, this list of conditions and the following disclaimer in |
| * the documentation and/or other materials provided with the |
| * distribution. |
| * |
| * 3. The end-user documentation included with the redistribution, if |
| * any, must include the following acknowlegement: |
| * "This product includes software developed by the |
| * Apache Software Foundation (http://www.apache.org/)." |
| * Alternately, this acknowlegement may appear in the software itself, |
| * if and wherever such third-party acknowlegements normally appear. |
| * |
| * 4. The names "The Jakarta Project", "Ant", and "Apache Software |
| * Foundation" must not be used to endorse or promote products derived |
| * from this software without prior written permission. For written |
| * permission, please contact apache@apache.org. |
| * |
| * 5. Products derived from this software may not be called "Apache" |
| * nor may "Apache" appear in their names without prior written |
| * permission of the Apache Group. |
| * |
| * THIS SOFTWARE IS PROVIDED ``AS IS'' AND ANY EXPRESSED OR IMPLIED |
| * WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES |
| * OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE |
| * DISCLAIMED. IN NO EVENT SHALL THE APACHE SOFTWARE FOUNDATION OR |
| * ITS CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, |
| * SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT |
| * LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF |
| * USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND |
| * ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, |
| * OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT |
| * OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF |
| * SUCH DAMAGE. |
| * ==================================================================== |
| * |
| * This software consists of voluntary contributions made by many |
| * individuals on behalf of the Apache Software Foundation. For more |
| * information on the Apache Software Foundation, please see |
| * <http://www.apache.org/>. |
| */ |
| package org.apache.tools.ant.taskdefs.optional.junit; |
| |
| import org.apache.tools.ant.Task; |
| import org.apache.tools.ant.Project; |
| import org.apache.tools.ant.BuildException; |
| |
| import java.io.File; |
| import java.io.FileInputStream; |
| import java.io.FileOutputStream; |
| import java.io.InputStream; |
| import java.io.OutputStream; |
| import java.io.FileNotFoundException; |
| import java.io.IOException; |
| |
| import java.util.Enumeration; |
| import java.util.Hashtable; |
| |
| import org.apache.xalan.xslt.XSLTProcessorFactory; |
| import org.apache.xalan.xslt.XSLTProcessor; |
| import org.apache.xalan.xslt.XSLTInputSource; |
| import org.apache.xalan.xslt.XSLTResultTarget; |
| |
| import javax.xml.parsers.DocumentBuilderFactory; |
| import javax.xml.parsers.DocumentBuilder; |
| import javax.xml.parsers.ParserConfigurationException; |
| |
| import org.w3c.dom.Document; |
| import org.w3c.dom.Element; |
| import org.w3c.dom.Node; |
| import org.w3c.dom.NodeList; |
| import org.w3c.dom.NamedNodeMap; |
| |
| import org.xml.sax.SAXException; |
| |
| /** |
| * Transform a JUnit xml report. |
| * The default transformation generates an html report in either framed or non-framed |
| * style. The non-framed style is convenient to have a concise report via mail, the |
| * framed report is much more convenient if you want to browse into different |
| * packages or testcases since it is a Javadoc like report. |
| * In the framed report, there are 3 frames: |
| * <ul> |
| * <li>packageListFrame - list of all packages. |
| * <li>classListFrame - summary of all testsuites belonging to a package or tests list. |
| * <li>classFrame - details of all tests made for a package or for a testsuite. |
| * </ul> |
| * As a default, the transformer will use its default stylesheets, they may not be |
| * be appropriate for users who wants to customize their reports, so you can indicates |
| * your own stylesheets by using <tt>setStyleDir()</tt>. |
| * Stylesheets must be as follows: |
| * <ul> |
| * <li><b>all-packages.xsl</b> create the package list. It creates |
| * all-packages.html file in the html folder and it is load in the packageListFrame</li> |
| * <li><b>all-classes.xsl</b> creates the class list. It creates the all-classes.html |
| * file in the html folder is loaded by the 'classListFrame' frame</li> |
| * <li><b>overview-packages.xsl</b> allows to get summary on all tests made |
| * for each packages and each class that not include in a package. The filename |
| * is overview-packages.html</li> |
| * <li><b>class-detail.xsl</b> is the style for the detail of the tests made on a class. |
| * the Html resulting page in write in the directory of the package and the name |
| * of this page is the name of the class with "-detail" element. For instance, |
| * the style is applied on the MyClass testsuite, the resulting filename is |
| * <u>MyClass-detail.html</u>. This file is load in the "classFrame" frame.</li> |
| * <li><b>package-summary.xsl</b> allows to create a summary on the package. |
| * The resulting html file is write in the package directory. The name of this |
| * file is <u>package-summary.html</u> This file is load in the "classFrame" frame.</li> |
| * <li><b>classes-list.xsl</b> create the list of the class in this package. |
| * The resulting html file is write in the package directory and it is load in |
| * the 'classListFrame' frame. The name of the resulting file is <u>class-list.html</u></li> |
| * <li> |
| * |
| * @author <a href="mailto:sbailliez@imediation.com">Stephane Bailliez</a> |
| * @author <a href="mailto:ndelahaye@imediation.com">Nicolas Delahaye</a> |
| */ |
| public class AggregateTransformer { |
| |
| public final static String ALLPACKAGES = "all-packages"; |
| |
| public final static String ALLCLASSES = "all-classes"; |
| |
| public final static String OVERVIEW_PACKAGES = "overview-packages"; |
| |
| public final static String CLASS_DETAILS = "class-details"; |
| |
| public final static String CLASSES_LIST = "classes-list"; |
| |
| public final static String PACKAGE_SUMMARY = "package-summary"; |
| |
| public final static String OVERVIEW_SUMMARY = "overview-summary"; |
| |
| public final static String FRAMES = "frames"; |
| |
| public final static String NOFRAMES = "noframes"; |
| |
| /** Task */ |
| protected Task task; |
| |
| /** the xml document to process */ |
| protected Document document; |
| |
| /** the style directory. XSLs should be read from here if necessary */ |
| protected File styleDir; |
| |
| /** the destination directory, this is the root from where html should be generated */ |
| protected File toDir; |
| |
| /** the format to use for the report. Must be <tt>FRAMES</tt> or <tt>NOFRAMES</tt> */ |
| protected String format; |
| |
| /** the file extension of the generated files. As a default it will be <tt>.html</tt> */ |
| protected String extension; |
| |
| /** XML Parser factory */ |
| protected static final DocumentBuilderFactory dbfactory = DocumentBuilderFactory.newInstance(); |
| |
| public AggregateTransformer(Task task){ |
| this.task = task; |
| } |
| |
| public void setFormat(String format){ |
| this.format = format; |
| } |
| |
| public void setXmlDocument(Document doc){ |
| this.document = doc; |
| } |
| |
| /** |
| * Set the xml file to be processed. This is a helper if you want |
| * to set the file directly. Much more for testing purposes. |
| * @param xmlfile xml file to be processed |
| */ |
| void setXmlfile(File xmlfile) throws BuildException { |
| try { |
| setXmlDocument(readDocument(xmlfile)); |
| } catch (Exception e){ |
| throw new BuildException("Error while parsing document: " + xmlfile, e); |
| } |
| } |
| |
| /** |
| * set the style directory. It is optional and will override the |
| * default xsl used. |
| * @param styledir the directory containing the xsl files if the user |
| * would like to override with its own style. |
| */ |
| public void setStyledir(File styledir){ |
| this.styleDir = styledir; |
| } |
| |
| /** set the destination directory */ |
| public void setTodir(File todir){ |
| this.toDir = todir; |
| } |
| |
| /** set the extension of the output files */ |
| public void setExtension(String ext){ |
| this.extension = ext; |
| } |
| |
| /** get the extension, if it is null, it will use .html as the default */ |
| protected String getExtension(){ |
| if (extension == null) { |
| extension = ".html"; |
| } |
| return extension; |
| } |
| |
| public void transform() throws BuildException { |
| checkOptions(); |
| try { |
| Element root = document.getDocumentElement(); |
| |
| if (NOFRAMES.equals(format)) { |
| //createCascadingStyleSheet(); |
| createSinglePageSummary(root); |
| } else { |
| createFrameStructure(); |
| createCascadingStyleSheet(); |
| createPackageList(root); |
| createClassList(root); |
| createPackageOverview(root); |
| createAllTestSuiteDetails(root); |
| createAllPackageDetails(root); |
| } |
| } catch (Exception e){ |
| e.printStackTrace(); |
| throw new BuildException("Errors while applying transformations", e); |
| } |
| } |
| |
| /** check for invalid options */ |
| protected void checkOptions() throws BuildException { |
| if ( !FRAMES.equals(format) && !NOFRAMES.equals(format)) { |
| throw new BuildException("Invalid format. Must be 'frames' or 'noframes' but was: '" + format + "'"); |
| } |
| // set the destination directory relative from the project if needed. |
| if (toDir == null) { |
| toDir = task.getProject().resolveFile("."); |
| } else if ( !toDir.isAbsolute() ) { |
| toDir = task.getProject().resolveFile(toDir.getPath()); |
| } |
| // create the directories if needed |
| if (!toDir.exists()) { |
| if (!toDir.mkdirs()){ |
| throw new BuildException("Could not create directory " + toDir); |
| } |
| } |
| } |
| |
| /** create a single page summary */ |
| protected void createSinglePageSummary(Element root) throws IOException, SAXException { |
| transform(root, OVERVIEW_SUMMARY + ".xsl", OVERVIEW_SUMMARY + getExtension()); |
| } |
| |
| /** |
| * read the xml file that should be the resuiting file of the testcase. |
| * @param filename name of the xml resulting file of the testcase. |
| */ |
| protected Document readDocument(File file) throws IOException, SAXException, ParserConfigurationException { |
| DocumentBuilder builder = dbfactory.newDocumentBuilder(); |
| InputStream in = new FileInputStream(file); |
| try { |
| return builder.parse(in); |
| } finally { |
| in.close(); |
| } |
| } |
| |
| protected void createCascadingStyleSheet() throws IOException, SAXException { |
| if (styleDir == null) { |
| InputStream in = getResourceAsStream("html/stylesheet.css"); |
| OutputStream out = new FileOutputStream( new File(toDir, "stylesheet.css")); |
| copy(in, out); |
| } |
| } |
| |
| protected void createFrameStructure() throws IOException, SAXException{ |
| if (styleDir == null) { |
| InputStream in = getResourceAsStream("html/index.html"); |
| OutputStream out = new FileOutputStream( new File(toDir, "index.html") ); |
| copy(in, out); |
| } |
| } |
| |
| /** |
| * Create the list of all packages. |
| * @param root root of the xml document. |
| */ |
| protected void createPackageList(Node root) throws SAXException { |
| transform(root, ALLPACKAGES + ".xsl", ALLPACKAGES + getExtension()); |
| } |
| |
| /** |
| * Create the list of all classes. |
| * @param root root of the xml document. |
| */ |
| protected void createClassList(Node root) throws SAXException { |
| transform(root, ALLCLASSES + ".xsl", ALLCLASSES + getExtension()); |
| } |
| |
| /** |
| * Create the summary used in the overview. |
| * @param root root of the xml document. |
| */ |
| protected void createPackageOverview(Node root) throws SAXException { |
| transform(root, OVERVIEW_PACKAGES + ".xsl", OVERVIEW_PACKAGES + getExtension()); |
| } |
| |
| /** |
| * @return the list of all packages that exists defined in testsuite nodes |
| */ |
| protected Enumeration getPackages(Element root){ |
| Hashtable map = new Hashtable(); |
| NodeList testsuites = root.getElementsByTagName(XMLConstants.TESTSUITE); |
| final int size = testsuites.getLength(); |
| for (int i = 0; i < size; i++){ |
| Element testsuite = (Element) testsuites.item(i); |
| String packageName = testsuite.getAttribute(XMLConstants.ATTR_PACKAGE); |
| if (packageName == null){ |
| //@todo replace the exception by something else |
| throw new IllegalStateException("Invalid 'testsuite' node: should contains 'package' attribute"); |
| } |
| map.put(packageName, packageName); |
| } |
| return map.keys(); |
| } |
| |
| /** |
| * create all resulting html pages for all testsuites. |
| * @param root should be 'testsuites' node. |
| */ |
| protected void createAllTestSuiteDetails(Element root) throws SAXException { |
| NodeList testsuites = root.getElementsByTagName(XMLConstants.TESTSUITE); |
| final int size = testsuites.getLength(); |
| for (int i = 0; i < size; i++){ |
| Element testsuite = (Element) testsuites.item(i); |
| createTestSuiteDetails(testsuite); |
| } |
| } |
| |
| /** |
| * create the html resulting page of one testsuite. |
| * @param root should be 'testsuite' node. |
| */ |
| protected void createTestSuiteDetails(Element testsuite) throws SAXException { |
| |
| String packageName = testsuite.getAttribute(XMLConstants.ATTR_PACKAGE); |
| String pkgPath = packageToPath(packageName); |
| |
| // get the class name |
| String name = testsuite.getAttribute(XMLConstants.ATTR_NAME); |
| |
| // get the name of the testsuite and create the filename of the ouput html page |
| String filename = name + "-details" + getExtension(); |
| String fullpathname = pkgPath + filename; // there's already end path separator to pkgPath |
| |
| // apply the style on the document. |
| transform(testsuite, CLASS_DETAILS + ".xsl", fullpathname); |
| } |
| |
| /** |
| * create the html resulting page of the summary of each package of the root element. |
| * @param root should be 'testsuites' node. |
| */ |
| protected void createAllPackageDetails(Element root) throws SAXException, ParserConfigurationException { |
| Enumeration packages = getPackages(root); |
| while ( packages.hasMoreElements() ){ |
| String pkgname = (String)packages.nextElement(); |
| // for each package get the list of its testsuite. |
| DOMUtil.NodeFilter pkgFilter = new PackageFilter(pkgname); |
| NodeList testsuites = DOMUtil.listChildNodes(root, pkgFilter, false); |
| Element doc = buildDocument(testsuites); |
| // skip package details if the package does not exist (root package) |
| if( !pkgname.equals("") ){ |
| createPackageDetails(doc, pkgname); |
| } |
| } |
| } |
| |
| protected String packageToPath(String pkgname){ |
| if (!pkgname.equals("")) { |
| return pkgname.replace('.', File.separatorChar) + File.separatorChar; |
| } |
| return "." + File.separatorChar; |
| } |
| |
| /** |
| * create the html resulting page of the summary of a package . |
| * @param root should be 'testsuites' node. |
| * @param pkgname Name of the package that we want a summary. |
| */ |
| protected void createPackageDetails(Node root, String pkgname) throws SAXException { |
| String path = packageToPath(pkgname); |
| |
| // apply style to get the list of the classes of this package and |
| // display it in the classListFrame. |
| transform(root, CLASSES_LIST + ".xsl", path + CLASSES_LIST + getExtension()); |
| |
| // apply style to get a summary on this package. |
| transform(root, PACKAGE_SUMMARY + ".xsl", path + PACKAGE_SUMMARY + getExtension()); |
| } |
| |
| /** |
| * Create an element root ("testsuites") |
| * and import all nodes as children of this element. |
| * |
| */ |
| protected Element buildDocument(NodeList list) throws ParserConfigurationException { |
| DocumentBuilder builder = dbfactory.newDocumentBuilder(); |
| Document doc = builder.newDocument(); |
| Element elem = doc.createElement(XMLConstants.TESTSUITES); |
| final int len = list.getLength(); |
| for(int i=0 ; i < len ; i++) { |
| DOMUtil.importNode(elem, list.item(i)); |
| } |
| return elem; |
| } |
| |
| /** |
| * Apply a template on a part of the xml document. |
| * |
| * @param root root of the document fragment |
| * @param xslfile style file |
| * @param outfilename filename of the result of the style applied on the Node |
| * |
| * @throws SAXException SAX Parsing Error on the style Sheet. |
| */ |
| protected void transform(Node root, String xslname, String htmlname) throws SAXException { |
| try{ |
| final long t0 = System.currentTimeMillis(); |
| XSLTInputSource xsl_source = getXSLStreamSource(xslname); |
| XSLTProcessor processor = XSLTProcessorFactory.getProcessor(); |
| File htmlfile = new File(toDir, htmlname); |
| // create the directory if it does not exist |
| File dir = new File(htmlfile.getParent()); // getParentFile is in JDK1.2+ |
| if (!dir.exists()) { |
| dir.mkdirs(); |
| } |
| task.log("Applying '" + xslname + "'. Generating '" + htmlfile + "'", Project.MSG_VERBOSE); |
| processor.process( new XSLTInputSource(root), xsl_source, new XSLTResultTarget(htmlfile.getAbsolutePath()) ); |
| final long dt = System.currentTimeMillis() - t0; |
| task.log("Transform time for " + xslname + ": " + dt + "ms"); |
| } catch (IOException e){ |
| task.log(e.getMessage(), Project.MSG_ERR); |
| e.printStackTrace(); //@todo bad, change this |
| throw new SAXException(e.getMessage()); |
| } |
| } |
| |
| |
| /** |
| * default xsls are embedded in the distribution jar. As a default we will use |
| * them, otherwise we will get the one supplied by the client in a given |
| * directory. It must have the same name. |
| */ |
| protected XSLTInputSource getXSLStreamSource(String name) throws IOException { |
| InputStream in; |
| String systemId; //we need this because there are references in xsls |
| if (styleDir == null){ |
| in = getResourceAsStream("xsl/" + name); |
| systemId = getClass().getResource("xsl/" + name).toString(); |
| } else { |
| File f = new File(styleDir, name); |
| in= new FileInputStream(f); |
| systemId = f.getAbsolutePath(); |
| } |
| XSLTInputSource ss = new XSLTInputSource(in); |
| ss.setSystemId(systemId); |
| return ss; |
| } |
| |
| private InputStream getResourceAsStream(String name) throws FileNotFoundException { |
| InputStream in = getClass().getResourceAsStream(name); |
| if (in == null) { |
| throw new FileNotFoundException("Could not find resource '" + name + "'"); |
| } |
| return in; |
| } |
| |
| /** Do some raw stream copying */ |
| private static void copy(InputStream in, OutputStream out) throws IOException { |
| int size = -1; |
| byte[] buffer = new byte[1024]; |
| // Make the copy |
| while( (size = in.read(buffer)) != -1){ |
| out.write(buffer,0,size); |
| } |
| } |
| |
| |
| /** |
| * allow us to check if the node is a object of a specific package. |
| */ |
| protected static class PackageFilter implements DOMUtil.NodeFilter { |
| private final String pkgName; |
| |
| PackageFilter(String pkgname) { |
| this.pkgName = pkgname; |
| } |
| /** |
| * if the node receive is not a element then return false |
| * check if the node is a class of this package. |
| */ |
| public boolean accept(Node node) { |
| String pkgname = DOMUtil.getNodeAttribute(node, XMLConstants.ATTR_PACKAGE); |
| return pkgName.equals(pkgname); |
| } |
| } |
| } |