Browse Source

New task <junitreport> and some improvements of DOMElementWriter

Submitted by:	Stephane Bailliez <sbailliez@imediation.com>


git-svn-id: https://svn.apache.org/repos/asf/ant/core/trunk@268562 13f79535-47bb-0310-9956-ffa450edef68
master
Stefan Bodewig 24 years ago
parent
commit
d1895cd5aa
16 changed files with 1650 additions and 5 deletions
  1. +1
    -1
      WHATSNEW
  2. +12
    -0
      build.xml
  3. +1
    -0
      src/main/org/apache/tools/ant/taskdefs/defaults.properties
  4. +528
    -0
      src/main/org/apache/tools/ant/taskdefs/optional/junit/AggregateTransformer.java
  5. +310
    -0
      src/main/org/apache/tools/ant/taskdefs/optional/junit/XMLResultAggregator.java
  6. +21
    -0
      src/main/org/apache/tools/ant/taskdefs/optional/junit/html/index.html
  7. +35
    -0
      src/main/org/apache/tools/ant/taskdefs/optional/junit/html/stylesheet.css
  8. +40
    -0
      src/main/org/apache/tools/ant/taskdefs/optional/junit/xsl/all-classes.xsl
  9. +42
    -0
      src/main/org/apache/tools/ant/taskdefs/optional/junit/xsl/all-packages.xsl
  10. +37
    -0
      src/main/org/apache/tools/ant/taskdefs/optional/junit/xsl/class-details.xsl
  11. +46
    -0
      src/main/org/apache/tools/ant/taskdefs/optional/junit/xsl/classes-list.xsl
  12. +90
    -0
      src/main/org/apache/tools/ant/taskdefs/optional/junit/xsl/overview-packages.xsl
  13. +184
    -0
      src/main/org/apache/tools/ant/taskdefs/optional/junit/xsl/overview-summary.xsl
  14. +73
    -0
      src/main/org/apache/tools/ant/taskdefs/optional/junit/xsl/package-summary.xsl
  15. +204
    -0
      src/main/org/apache/tools/ant/taskdefs/optional/junit/xsl/toolkit.xsl
  16. +26
    -4
      src/main/org/apache/tools/ant/util/DOMElementWriter.java

+ 1
- 1
WHATSNEW View File

@@ -22,7 +22,7 @@ Other changes:
* A GUI Frontend: Antidote. This is currently in development.

* New tasks: stylebook, propertyfile, depend, antlr, telnet, csc,
ilasm, apply, javah, several clearcase tasks
ilasm, apply, javah, several clearcase tasks, junitreport

* Added output attribute to <java>.



+ 12
- 0
build.xml View File

@@ -193,6 +193,10 @@
<exclude name="${optional.package}/ide/VAJ*.java" unless="vaj.present" />
<exclude name="${optional.package}/perforce/*.java" unless="jakarta.oro.present" />
<exclude name="${optional.package}/sound/*.java" unless="jmf.present" />
<exclude name="${optional.package}/junit/XMLResultAggregator.java"
unless="trax.present" />
<exclude name="${optional.package}/junit/AggregateTransformer.java"
unless="trax.present" />
</javac>
<copy todir="${build.classes}">
@@ -212,6 +216,14 @@
<include name="**/defaultManifest.mf" />
</fileset>
</copy>

<copy todir="${build.classes}/${optional.package}/junit">
<fileset dir="${java.dir}/${optional.package}/junit">
<include name="html/**" />
<include name="xsl/**" />
</fileset>
</copy>

</target>
<!--


+ 1
- 0
src/main/org/apache/tools/ant/taskdefs/defaults.properties View File

@@ -87,6 +87,7 @@ cccheckout=org.apache.tools.ant.taskdefs.optional.clearcase.CCCheckout
cccheckin=org.apache.tools.ant.taskdefs.optional.clearcase.CCCheckin
ccuncheckout=org.apache.tools.ant.taskdefs.optional.clearcase.CCUnCheckout
sound=org.apache.tools.ant.taskdefs.optional.sound.SoundTask
junitreport=org.apache.tools.ant.taskdefs.optional.junit.XMLResultAggregator

# deprecated ant tasks (kept for back compatibility)
javadoc2=org.apache.tools.ant.taskdefs.Javadoc


+ 528
- 0
src/main/org/apache/tools/ant/taskdefs/optional/junit/AggregateTransformer.java View File

@@ -0,0 +1,528 @@
/*
* 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 javax.xml.transform.Transformer;
import javax.xml.transform.TransformerFactory;
import javax.xml.transform.stream.StreamSource;
import javax.xml.transform.stream.StreamResult;
import javax.xml.transform.dom.DOMSource;
import javax.xml.transform.TransformerConfigurationException;
import javax.xml.transform.TransformerException;

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();

/** XSL Parser factory */
protected static final TransformerFactory tfactory = TransformerFactory.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, TransformerException {
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, TransformerException {
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, TransformerException{
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 TransformerException {
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 TransformerException {
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 TransformerException {
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 TransformerException {
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 TransformerException {
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 TransformerException, 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 TransformerException {
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 TransformerException SAX Parsing Error on the style Sheet.
*/
protected void transform(Node root, String xslname, String htmlname) throws TransformerException {
try{
final long t0 = System.currentTimeMillis();
StreamSource xsl_source = getXSLStreamSource(xslname);
Transformer transformer = tfactory.newTransformer(xsl_source);
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);
transformer.transform( new DOMSource(root), new StreamResult(htmlfile));
final long dt = System.currentTimeMillis() - t0;
task.log("Transform time: " + dt + "ms");
} catch (IOException e){
task.log(e.getMessage(), Project.MSG_ERR);
e.printStackTrace(); //@todo bad, change this
throw new TransformerException(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 StreamSource 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();
}
StreamSource ss = new StreamSource(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);
}
}
}

+ 310
- 0
src/main/org/apache/tools/ant/taskdefs/optional/junit/XMLResultAggregator.java View File

@@ -0,0 +1,310 @@
/*
* The Apache Software License, Version 1.1
*
* Copyright (c) 2001 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 java.io.File;
import java.io.IOException;
import java.io.OutputStream;
import java.io.PrintWriter;
import java.io.FileOutputStream;
import java.util.Enumeration;
import java.util.Vector;

import org.w3c.dom.Element;
import org.w3c.dom.Document;
import org.w3c.dom.Node;
import org.w3c.dom.*;
import org.xml.sax.SAXException;
import javax.xml.parsers.DocumentBuilder;
import javax.xml.parsers.DocumentBuilderFactory;
import org.apache.tools.ant.Project;
import org.apache.tools.ant.Task;
import org.apache.tools.ant.DirectoryScanner;
import org.apache.tools.ant.BuildException;
import org.apache.tools.ant.types.FileSet;
import org.apache.tools.ant.util.DOMElementWriter;


/**
* This is an helper class that will aggregate all testsuites under a specific
* directory and create a new single document. It is not particulary clean but
* should be helpful while I am thinking about another technique.
*
* The main problem is due to the fact that a JVM can be forked for a testcase
* thus making it impossible to aggregate all testcases since the listener is
* (obviously) in the forked JVM. A solution could be to write a
* TestListener that will receive events from the TestRunner via sockets. This
* is IMHO the simplest way to do it to avoid this file hacking thing.
*
* @author <a href="mailto:sbailliez@imediation.com">Stephane Bailliez</a>
*/
public class XMLResultAggregator extends Task implements XMLConstants {
/** the list of all filesets, that should contains the xml to aggregate */
protected Vector filesets = new Vector();
/** the name of the result file */
protected String toFile;
/** the directory to write the file to */
protected File toDir;
protected Vector transformers = new Vector();
/** the default directory: <tt>.</tt>. It is resolved from the project directory */
public final static String DEFAULT_DIR = ".";
/** the default file name: <tt>TESTS-TestSuites.xml</tt> */
public final static String DEFAULT_FILENAME = "TESTS-TestSuites.xml";
public AggregateTransformer createReport(){
AggregateTransformer transformer = new AggregateTransformer(this);
transformers.add(transformer);
return transformer;
}
/**
* Set the name of the file aggregating the results. It must be relative
* from the <tt>todir</tt> attribute. If not set it will use {@link DEFAULT_FILENAME}
* @param value the name of the file.
* @see #setTodir(File)
*/
public void setTofile(String value){
toFile = value;
}
/**
* Set the destination directory where the results should be written. If not
* set if will use {@link DEFAULT_DIR}. When given a relative directory
* it will resolve it from the project directory.
* @param value the directory where to write the results, absolute or
* relative.
*/
public void setTodir(File value){
toDir = value;
}
/**
* Add a new fileset containing the xml results to aggregate
* @param fs the new fileset of xml results.
*/
public void addFileSet(FileSet fs) {
filesets.addElement(fs);
}
/**
* Aggregate all testsuites into a single document and write it to the
* specified directory and file.
* @throws BuildException thrown if there is a serious error while writing
* the document.
*/
public void execute() throws BuildException {
Element rootElement = createDocument();
File destFile = getDestinationFile();
// write the document
try {
writeDOMTree(rootElement.getOwnerDocument(), destFile );
} catch (IOException e){
throw new BuildException("Unable to write test aggregate to '" + destFile + "'", e);
}
// apply transformation
Enumeration enum = transformers.elements();
while (enum.hasMoreElements()) {
AggregateTransformer transformer =
(AggregateTransformer) enum.nextElement();
transformer.setXmlDocument(rootElement.getOwnerDocument());
transformer.transform();
}
}
/**
* get the full destination file where to write the result. It is made of
* the <tt>todir</tt> and <tt>tofile</tt> attributes.
* @return the destination file where should be written the result file.
*/
protected File getDestinationFile(){
if (toFile == null){
toFile = DEFAULT_FILENAME;
}
if (toDir == null){
toDir = project.resolveFile(DEFAULT_DIR);
}
return new File(toDir, toFile);
}
/**
* @return all files in the fileset that end with a '.xml'.
*/
protected File[] getFiles() {
Vector v = new Vector();
final int size = filesets.size();
for (int i = 0; i < size; i++) {
FileSet fs = (FileSet) filesets.elementAt(i);
DirectoryScanner ds = fs.getDirectoryScanner(project);
ds.scan();
String[] f = ds.getIncludedFiles();
for (int j = 0; j < f.length; j++) {
String pathname = f[j];
if ( pathname.endsWith(".xml") ) {
File file = new File(ds.getBasedir(), pathname);
file = project.resolveFile(file.getPath());
v.addElement( file );
}
}
}
File[] files = new File[v.size()];
v.copyInto(files);
return files;
}
//----- from now, the methods are all related to DOM tree manipulation
/**
* Write the DOM tree to a file.
* @param doc the XML document to dump to disk.
* @param file the filename to write the document to. Should obviouslly be a .xml file.
* @throws IOException thrown if there is an error while writing the content.
*/
protected void writeDOMTree(Document doc, File file) throws IOException {
OutputStream out = new FileOutputStream( file );
PrintWriter wri = new PrintWriter(out);
wri.write("<?xml version=\"1.0\"?>\n");
(new DOMElementWriter()).write(doc.getDocumentElement(), wri, 0, " ");
wri.flush();
wri.close();
// writers do not throw exceptions, so check for them.
if (wri.checkError()){
throw new IOException("Error while writing DOM content");
}
}
/**
* Create a DOM tree with firstchild as 'testsuites' and aggregates all
* testsuite results that exists in the base directory.
* @return the root element of DOM tree that aggregates all testsuites.
*/
protected Element createDocument() {
// create the dom tree
DocumentBuilder builder = getDocumentBuilder();
Document doc = builder.newDocument();
Element rootElement = doc.createElement(TESTSUITES);
doc.appendChild(rootElement);
// get all files and add them to the document
File[] files = getFiles();
for (int i = 0; i < files.length; i++) {
try {
log("Parsing file: '" + files[i] + "'", Project.MSG_VERBOSE);
Document testsuiteDoc = builder.parse( files[i] );
Element elem = testsuiteDoc.getDocumentElement();
// make sure that this is REALLY a testsuite.
if ( TESTSUITE.equals(elem.getNodeName()) ) {
addTestSuite(rootElement, elem);
} else {
// issue a warning.
log("the file " + files[i] + " is not a valid testsuite XML document", Project.MSG_WARN);
}
} catch (SAXException e){
// a testcase might have failed and write a zero-length document,
// It has already failed, but hey.... mm. just put a warning
log("The file " + files[i] + " is not a valid XML document. It is possibly corrupted.", Project.MSG_WARN);
} catch (IOException e){
log("Error while accessing file " + files[i] + ": " + e.getMessage(), Project.MSG_ERR);
}
}
return rootElement;
}
/**
* Add a new testsuite node to the document, the main difference is that it
* split the previous fully qualified name into a package and a name.
* For example: <tt>org.apache.Whatever</tt> will be splitted in
* <tt>org.apache</tt> and <tt>Whatever</tt>.
* @param root the root element to which the <tt>testsuite</tt> node should
* be appended.
* @param testsuite the element to append to the given root. It will slightly
* modify the original node to change the name attribute and add
* a package one.
*/
protected void addTestSuite(Element root, Element testsuite){
String fullclassname = testsuite.getAttribute(ATTR_NAME);
int pos = fullclassname.lastIndexOf('.');
// a missing . might imply no package at all. Don't get fooled.
String pkgName = (pos == -1) ? "" : fullclassname.substring(0, pos);
String classname = (pos == -1) ? fullclassname : fullclassname.substring(pos + 1);
Element copy = (Element)DOMUtil.importNode(root, testsuite);
// modify the name attribute and set the package
copy.setAttribute(ATTR_NAME, classname);
copy.setAttribute(ATTR_PACKAGE, pkgName);
}
/**
* Create a new document builder. Will issue an <tt>ExceptionInitializerError</tt>
* if something is going wrong. It is fatal anyway.
* @return a new document builder to create a DOM
* @todo factorize this somewhere else. It is duplicated code.
*/
private static DocumentBuilder getDocumentBuilder() {
try {
return DocumentBuilderFactory.newInstance().newDocumentBuilder();
} catch(Exception exc) {
throw new ExceptionInInitializerError(exc);
}
}
}

+ 21
- 0
src/main/org/apache/tools/ant/taskdefs/optional/junit/html/index.html View File

@@ -0,0 +1,21 @@
<!DOCTYPE HTML PUBLIC "-//W3C//DTD HTML 4.0 Transitional//EN""http://www.w3.org/TR/REC-html40/loose.dtd>

<HTML>
<HEAD>
<TITLE>
Unit Tests Results.
</TITLE>
</HEAD>
<FRAMESET cols="20%,80%">
<FRAMESET rows="30%,70%">
<FRAME src="all-packages.html" name="packageListFrame">
<FRAME src="all-classes.html" name="classListFrame">
</FRAMESET>
<FRAME src="overview-packages.html" name="classFrame">
</FRAMESET>
<NOFRAMES>
<H2>Frame Alert</H2>
<P>
This document is designed to be viewed using the frames feature. If you see this message, you are using a non-frame-capable web client.
</P>
</HTML>

+ 35
- 0
src/main/org/apache/tools/ant/taskdefs/optional/junit/html/stylesheet.css View File

@@ -0,0 +1,35 @@
BODY {
font:normal 68% verdana,arial,helvetica;
color:#000000;
}
TD {
FONT-SIZE: 68%
}
P {
line-height:1.5em;
margin-top:0.5em; margin-bottom:1.0em;
}
H1 {
MARGIN: 0px 0px 5px; FONT: 165% verdana,arial,helvetica
}
H2 {
MARGIN-TOP: 1em; MARGIN-BOTTOM: 0.5em; FONT: bold 125% verdana,arial,helvetica
}
H3 {
MARGIN-BOTTOM: 0.5em; FONT: bold 115% verdana,arial,helvetica
}
H4 {
MARGIN-BOTTOM: 0.5em; FONT: bold 100% verdana,arial,helvetica
}
H5 {
MARGIN-BOTTOM: 0.5em; FONT: bold 100% verdana,arial,helvetica
}
H6 {
MARGIN-BOTTOM: 0.5em; FONT: bold 100% verdana,arial,helvetica
}
.Error {
font-weight:bold; color:red;
}
.Failure {
font-weight:bold; color:purple;
}

+ 40
- 0
src/main/org/apache/tools/ant/taskdefs/optional/junit/xsl/all-classes.xsl View File

@@ -0,0 +1,40 @@
<?xml version="1.0" encoding="ISO-8859-1"?>

<!-- This style sheet should contain just a named templates that used in the other specific templates -->
<xsl:stylesheet version="1.0" xmlns:xsl="http://www.w3.org/1999/XSL/Transform">

<xsl:include href="toolkit.xsl"/>

<xsl:template match="testsuites">
<HTML>
<HEAD>
<LINK REL ="stylesheet" TYPE="text/css" HREF="./stylesheet.css" TITLE="Style"/>
</HEAD>
<BODY onload="open('overview-packages.html','classFrame')">
<H2>Classes</H2>
<p>
<TABLE WIDTH="100%">
<xsl:apply-templates select="testsuite">
<xsl:sort select="@name"/>
</xsl:apply-templates>
</TABLE>
</p>
</BODY>
</HTML>
</xsl:template>

<xsl:template match="testsuite">
<tr>
<td nowrap="nowrap">
<a target="classFrame">
<xsl:attribute name="href">
<xsl:if test="not(@package='')">
<xsl:value-of select="translate(@package,'.','/')"/><xsl:text>/</xsl:text>
</xsl:if><xsl:value-of select="@name"/><xsl:text>-details.html</xsl:text>
</xsl:attribute>
<xsl:value-of select="@name"/></a>
</td>
</tr>
</xsl:template>

</xsl:stylesheet>

+ 42
- 0
src/main/org/apache/tools/ant/taskdefs/optional/junit/xsl/all-packages.xsl View File

@@ -0,0 +1,42 @@
<?xml version="1.0" encoding="ISO-8859-1"?>

<!-- This style sheet should contain just a named templates that used in the other specific templates -->
<xsl:stylesheet version="1.0" xmlns:xsl="http://www.w3.org/1999/XSL/Transform">

<!-- import the commun templates -->
<xsl:include href="toolkit.xsl"/>

<xsl:template match="testsuites">
<HTML>
<HEAD>
<LINK REL ="stylesheet" TYPE="text/css" HREF="./stylesheet.css" TITLE="Style"/>
</HEAD>
<BODY>
<H2><a href="all-classes.html" target="classListFrame">Home</a></H2>
<!-- create a summary on this testcase-->
<!--xsl:call-template name="SummaryTableHeadRootPackage"/-->
<H2>Packages</H2>

<!-- Get the list of the subpackage -->
<p>
<table width="100%">
<!-- For each packages node apply the style describe in the below template-->
<xsl:apply-templates select="testsuite[not(./@package = preceding-sibling::testsuite/@package)]">
<xsl:sort select="@package"/>
</xsl:apply-templates>
</table>
</p>
</BODY>
</HTML>
</xsl:template>

<xsl:template match="testsuite">
<tr>
<td nowrap="nowrap">
<a href="{translate(@package,'.','/')}/package-summary.html" target="classFrame"><xsl:value-of select="@package"/></a>
</td>
</tr>
</xsl:template>

</xsl:stylesheet>

+ 37
- 0
src/main/org/apache/tools/ant/taskdefs/optional/junit/xsl/class-details.xsl View File

@@ -0,0 +1,37 @@
<?xml version="1.0" encoding="ISO-8859-1"?>

<!-- This style sheet should contain just a named templates that used in the other specific templates -->
<xsl:stylesheet version="1.0" xmlns:xsl="http://www.w3.org/1999/XSL/Transform">

<!-- import the commun templates -->
<xsl:include href="toolkit.xsl"/>

<xsl:template match="testsuite">
<HTML>
<HEAD>
<LINK REL ="stylesheet" TYPE="text/css" TITLE="Style">
<xsl:attribute name="href"><xsl:call-template name="path"><xsl:with-param name="path" select="@package"/></xsl:call-template>stylesheet.css</xsl:attribute>
</LINK>
</HEAD>
<BODY>
<xsl:call-template name="header">
<xsl:with-param name="useFrame">yes</xsl:with-param>
<xsl:with-param name="path" select="@package"/>
</xsl:call-template>
<H2>Class <xsl:if test="not(@package = '')"><xsl:value-of select="@package"/>.</xsl:if><xsl:value-of select="@name"/></H2>
<p>
<h3>TestCase <xsl:value-of select="@name"/></h3>
<table border="0" cellpadding="5" cellspacing="2" width="95%">
<!-- Header -->
<xsl:call-template name="classesSummaryHeader"/>
<!-- match the testcases of this package -->
<xsl:apply-templates select="testcase"/>
</table>
</p>
</BODY>
</HTML>
</xsl:template>

</xsl:stylesheet>

+ 46
- 0
src/main/org/apache/tools/ant/taskdefs/optional/junit/xsl/classes-list.xsl View File

@@ -0,0 +1,46 @@
<?xml version="1.0" encoding="ISO-8859-1"?>

<!-- This style sheet should contain just a named templates that used in the other specific templates -->
<xsl:stylesheet version="1.0" xmlns:xsl="http://www.w3.org/1999/XSL/Transform">

<!-- import the commun templates -->
<xsl:include href="toolkit.xsl"/>

<xsl:template match="testsuites">
<HTML>
<HEAD>
<LINK REL ="stylesheet" TYPE="text/css" TITLE="Style">
<xsl:attribute name="href"><xsl:call-template name="path"><xsl:with-param name="path" select="testsuite[position() = 1]/@package"/></xsl:call-template>stylesheet.css</xsl:attribute>
</LINK>
</HEAD>
<BODY>
<table width="100%">
<tr>
<td nowrap="nowrap">
<H2><a href="package-summary.html" target="classFrame"><xsl:value-of select="testsuite/@package"/></a></H2>
</td>
</tr>
</table>
<H2>Classes</H2>
<p>
<TABLE WIDTH="100%">
<xsl:apply-templates select="testsuite">
<xsl:sort select="@name"/>
</xsl:apply-templates>
</TABLE>
</p>
</BODY>
</HTML>
</xsl:template>

<xsl:template match="testsuite">
<tr>
<td nowrap="nowrap">
<a href="{@name}-details.html" target="classFrame"><xsl:value-of select="@name"/></a>
</td>
</tr>
</xsl:template>

</xsl:stylesheet>

+ 90
- 0
src/main/org/apache/tools/ant/taskdefs/optional/junit/xsl/overview-packages.xsl View File

@@ -0,0 +1,90 @@
<?xml version="1.0" encoding="ISO-8859-1"?>

<!-- This style sheet should contain just a named templates that used in the other specific templates -->
<xsl:stylesheet version="1.0" xmlns:xsl="http://www.w3.org/1999/XSL/Transform">

<xsl:include href="toolkit.xsl"/>

<!-- Calculate all summary values -->
<xsl:variable name="testCount" select="sum(//testsuite/@tests)"/>
<xsl:variable name="errorCount" select="sum(//testsuite/@errors)"/>
<xsl:variable name="failureCount" select="sum(//testsuite/@failures)"/>
<xsl:variable name="timeCount" select="sum(//testsuite/@time)"/>
<xsl:variable name="successRate" select="($testCount - $failureCount - $errorCount) div $testCount"/>

<xsl:template match="testsuites">
<HTML>
<HEAD>
<LINK REL ="stylesheet" TYPE="text/css" HREF="stylesheet.css" TITLE="Style"/>
</HEAD>
<BODY>
<xsl:call-template name="header">
<xsl:with-param name="useFrame">yes</xsl:with-param>
</xsl:call-template>

<xsl:call-template name="summary"/>


<xsl:if test="count(testsuite[not(./@package = preceding-sibling::testsuite/@package)])&gt;0">
<h2>Packages</h2>
<table border="0" cellpadding="5" cellspacing="2" width="95%">
<!--Header-->
<xsl:call-template name="packageSummaryHeader"/>
<!-- write a summary for the package -->
<xsl:apply-templates select="testsuite[not(./@package = preceding-sibling::testsuite/@package) and not(./@package = '') ]" mode="package">
<xsl:sort select="@package"/>
</xsl:apply-templates>
</table>
<br/>
</xsl:if>


<xsl:if test="count(testsuite[./@package = ''])&gt;0">
<h2>Classes</h2>
<table border="0" cellpadding="5" cellspacing="2" width="95%">
<!--Header-->
<xsl:call-template name="packageSummaryHeader"/>
<!-- write a summary for the package -->
<xsl:apply-templates select="testsuite[./@package = '']" mode="class">
<xsl:sort select="@name"/>
</xsl:apply-templates>
</table>
</xsl:if>
</BODY>
</HTML>
</xsl:template>

<xsl:template match="testsuite" mode="package">
<xsl:variable name="isError" select="(sum(//testsuite[@package = current()/@package]/@errors) + sum(//testsuite[@package = current()/@package]/@failures))&gt;0"/>
<!-- write a summary for the package -->
<tr bgcolor="#EEEEE" valign="top">
<td><xsl:if test="$isError"><xsl:attribute name="class">Error</xsl:attribute></xsl:if><a href="{translate(@package,'.','/')}/package-summary.html"><xsl:value-of select="@package"/></a></td>
<xsl:call-template name="statistics">
<xsl:with-param name="isError" select="$isError"/>
</xsl:call-template>
</tr>
</xsl:template>

<xsl:template match="testsuite" mode="class">
<xsl:variable name="isError" select="(@errors + @failures)&gt;0"/>
<!-- write a summary for the package -->
<tr bgcolor="#EEEEE" valign="top">
<td><xsl:if test="$isError"><xsl:attribute name="class">Error</xsl:attribute></xsl:if><a href="{translate(@package,'.','/')}/summary.html"><xsl:value-of select="@name"/></a></td>
<xsl:call-template name="statistics">
<xsl:with-param name="isError" select="$isError"/>
</xsl:call-template>
</tr>
</xsl:template>


<xsl:template name="statistics">
<xsl:variable name="isError"/>
<td><xsl:if test="$isError"><xsl:attribute name="class">Error</xsl:attribute></xsl:if><xsl:value-of select="sum(//testsuite[@package = current()/@package]/@tests)"/></td>
<td><xsl:if test="$isError"><xsl:attribute name="class">Error</xsl:attribute></xsl:if><xsl:value-of select="sum(//testsuite[@package = current()/@package]/@errors)"/></td>
<td><xsl:if test="$isError"><xsl:attribute name="class">Error</xsl:attribute></xsl:if><xsl:value-of select="sum(//testsuite[@package = current()/@package]/@failures)"/></td>
<td><xsl:if test="$isError"><xsl:attribute name="class">Error</xsl:attribute></xsl:if><xsl:value-of select="format-number(sum(//testsuite[@package = current()/@package]/@time),'#,###0.000')"/></td>
</xsl:template>

</xsl:stylesheet>

+ 184
- 0
src/main/org/apache/tools/ant/taskdefs/optional/junit/xsl/overview-summary.xsl View File

@@ -0,0 +1,184 @@
<?xml version="1.0" encoding="ISO-8859-1"?>

<xsl:stylesheet xmlns:xsl="http://www.w3.org/1999/XSL/Transform" version="1.0" xmlns:html="http://www.w3.org/Profiles/XHTML-transitional">

<xsl:include href="toolkit.xsl"/>

<!--
====================================================
Create the page structure
====================================================
-->
<xsl:template match="testsuites">
<HTML>
<HEAD>
<!--LINK REL ="stylesheet" TYPE="text/css" HREF="stylesheet.css" TITLE="Style"/-->
<!-- put the style in the html so that we can mail it w/o problem -->
<style type="text/css">
BODY {
font:normal 68% verdana,arial,helvetica;
color:#000000;
}
TD {
FONT-SIZE: 68%
}
P {
line-height:1.5em;
margin-top:0.5em; margin-bottom:1.0em;
}
H1 {
MARGIN: 0px 0px 5px; FONT: 165% verdana,arial,helvetica
}
H2 {
MARGIN-TOP: 1em; MARGIN-BOTTOM: 0.5em; FONT: bold 125% verdana,arial,helvetica
}
H3 {
MARGIN-BOTTOM: 0.5em; FONT: bold 115% verdana,arial,helvetica
}
H4 {
MARGIN-BOTTOM: 0.5em; FONT: bold 100% verdana,arial,helvetica
}
H5 {
MARGIN-BOTTOM: 0.5em; FONT: bold 100% verdana,arial,helvetica
}
H6 {
MARGIN-BOTTOM: 0.5em; FONT: bold 100% verdana,arial,helvetica
}
.Error {
font-weight:bold; color:red;
}
.Failure {
font-weight:bold; color:purple;
}
</style>
</HEAD>
<body text="#000000" bgColor="#ffffff">
<a name="#top"></a>
<xsl:call-template name="header"/>
<!-- Summary part -->
<xsl:call-template name="summary"/>
<hr size="1" width="95%" align="left"/>
<!-- Package List part -->
<xsl:call-template name="packagelist"/>
<hr size="1" width="95%" align="left"/>
<!-- For each package create its part -->
<xsl:call-template name="packages"/>
<hr size="1" width="95%" align="left"/>
<!-- For each class create the part -->
<xsl:call-template name="classes"/>
</body>
</HTML>
</xsl:template>
<!-- ================================================================== -->
<!-- Write a list of all packages with an hyperlink to the anchor of -->
<!-- of the package name. -->
<!-- ================================================================== -->
<xsl:template name="packagelist">
<h2>Packages</h2>
Note: package statistics are not computed recursively, they only sum up all of its testsuites numbers.
<table border="0" cellpadding="5" cellspacing="2" width="95%">
<xsl:call-template name="packageSummaryHeader"/>
<!-- list all packages recursively -->
<xsl:for-each select="./testsuite[not(./@package = preceding-sibling::testsuite/@package)]">
<xsl:sort select="@package"/>
<xsl:variable name="testCount" select="sum(../testsuite[./@package = current()/@package]/@tests)"/>
<xsl:variable name="errorCount" select="sum(../testsuite[./@package = current()/@package]/@errors)"/>
<xsl:variable name="failureCount" select="sum(../testsuite[./@package = current()/@package]/@failures)"/>
<xsl:variable name="timeCount" select="sum(../testsuite[./@package = current()/@package]/@time)"/>
<!-- write a summary for the package -->
<tr bgcolor="#EEEEE" valign="top">
<!-- set a nice color depending if there is an error/failure -->
<xsl:attribute name="class">
<xsl:choose>
<xsl:when test="$errorCount &gt; 0">Error</xsl:when>
<xsl:when test="$failureCount &gt; 0">Failure</xsl:when>
</xsl:choose>
</xsl:attribute>
<td><a href="#{@package}"><xsl:value-of select="@package"/></a></td>
<td><xsl:value-of select="$testCount"/></td>
<td><xsl:value-of select="$errorCount"/></td>
<td><xsl:value-of select="$failureCount"/></td>
<td><xsl:value-of select="format-number($timeCount,'#,###0.000')"/></td>
</tr>
</xsl:for-each>
</table>
</xsl:template>
<!-- ================================================================== -->
<!-- Write a package level report -->
<!-- It creates a table with values from the document: -->
<!-- Name | Tests | Errors | Failures | Time -->
<!-- ================================================================== -->
<xsl:template name="packages">
<!-- create an anchor to this package name -->
<xsl:for-each select="./testsuite[not(./@package = preceding-sibling::testsuite/@package)]">
<xsl:sort select="@package"/>
<a name="#{@package}"></a>
<h3>Package <xsl:value-of select="@package"/></h3>
<table border="0" cellpadding="5" cellspacing="2" width="95%">
<xsl:call-template name="packageSummaryHeader"/>
<!-- match the testsuites of this package -->
<xsl:apply-templates select="../testsuite[./@package = current()/@package]"/>
</table>
<a href="#top">Back to top</a>
<p/>
<p/>
</xsl:for-each>
</xsl:template>

<!-- ================================================================== -->
<!-- Process a testsuite node -->
<!-- It creates a table with values from the document: -->
<!-- Name | Tests | Errors | Failures | Time -->
<!-- It must match the table definition at the package level -->
<!-- ================================================================== -->
<xsl:template match="testsuite">
<tr bgcolor="#EEEEE" valign="top">
<!-- set a nice color depending if there is an error/failure -->
<xsl:attribute name="class">
<xsl:choose>
<xsl:when test="@errors[.&gt; 0]">Error</xsl:when>
<xsl:when test="@failures[.&gt; 0]">Failure</xsl:when>
</xsl:choose>
</xsl:attribute>
<!-- print testsuite information -->
<td><a href="#{@name}"><xsl:value-of select="@name"/></a></td>
<td><xsl:value-of select="@tests"/></td>
<td><xsl:value-of select="@errors"/></td>
<td><xsl:value-of select="@failures"/></td>
<td><xsl:value-of select="format-number(@time,'#,###0.000')"/></td>
</tr>
</xsl:template>
<xsl:template name="classes">
<xsl:for-each select="./testsuite">
<xsl:sort select="@name"/>
<!-- create an anchor to this class name -->
<a name="#{@name}"></a>
<h3>TestCase <xsl:value-of select="@name"/></h3>
<table border="0" cellpadding="5" cellspacing="2" width="95%">
<!-- Header -->
<xsl:call-template name="classesSummaryHeader"/>

<!-- match the testcases of this package -->
<xsl:apply-templates select="testcase"/>
</table>
<a href="#top">Back to top</a>
</xsl:for-each>
</xsl:template>
</xsl:stylesheet>

+ 73
- 0
src/main/org/apache/tools/ant/taskdefs/optional/junit/xsl/package-summary.xsl View File

@@ -0,0 +1,73 @@
<?xml version="1.0" encoding="ISO-8859-1"?>

<!-- This style sheet should contain just a named templates that used in the other specific templates -->
<xsl:stylesheet version="1.0" xmlns:xsl="http://www.w3.org/1999/XSL/Transform">

<!-- import the commun templates -->
<xsl:include href="toolkit.xsl"/>

<xsl:template match="testsuites">
<HTML>
<HEAD>
<LINK REL ="stylesheet" TYPE="text/css" TITLE="Style">
<xsl:attribute name="href"><xsl:call-template name="path"><xsl:with-param name="path" select="testsuite[position() = 1]/@package"/></xsl:call-template>stylesheet.css</xsl:attribute>
</LINK>
</HEAD>
<BODY><xsl:attribute name="onload">open('classes-list.html','classListFrame')</xsl:attribute>
<xsl:call-template name="header">
<xsl:with-param name="useFrame">yes</xsl:with-param>
<xsl:with-param name="path" select="testsuite/@package"/>
</xsl:call-template>
<!-- create an anchor to this package name -->
<h3>Package <xsl:value-of select="testsuite/@package"/></h3>
<table border="0" cellpadding="5" cellspacing="2" width="95%">
<!--Header-->
<xsl:call-template name="packageSummaryHeader"/>
<!-- write a summary for the package -->
<tr bgcolor="#EEEEE" valign="top">
<td><xsl:value-of select="testsuite/@package"/></td>
<td><xsl:value-of select="sum(testsuite/@tests)"/></td>
<td><xsl:value-of select="sum(testsuite/@errors)"/></td>
<td><xsl:value-of select="sum(testsuite/@failures)"/></td>
<td><xsl:value-of select="format-number(sum(testsuite/@time),'#,###0.000')"/></td>
</tr>
</table>
<H2>Classes</H2>
<p>
<table border="0" cellpadding="5" cellspacing="2" width="95%">
<!--Header-->
<xsl:call-template name="packageSummaryHeader"/>
<!--Value-->
<xsl:apply-templates select="testsuite">
<xsl:sort select="@name"/>
</xsl:apply-templates>
</table>
</p>
</BODY>
</HTML>
</xsl:template>

<xsl:template match="testsuite">
<tr bgcolor="#EEEEE" valign="top">
<!-- set a nice color depending if there is an error/failure -->
<xsl:attribute name="class">
<xsl:choose>
<xsl:when test="@errors[.&gt; 0]">Error</xsl:when>
<xsl:when test="@failures[.&gt; 0]">Failure</xsl:when>
</xsl:choose>
</xsl:attribute>
<!-- print testsuite information -->
<td><a href="{@name}-details.html" target="classFrame"><xsl:value-of select="@name"/></a></td>
<td><xsl:value-of select="@tests"/></td>
<td><xsl:value-of select="@errors"/></td>
<td><xsl:value-of select="@failures"/></td>
<td><xsl:value-of select="format-number(@time,'#,###0.000')"/></td>
</tr>
</xsl:template>

</xsl:stylesheet>

+ 204
- 0
src/main/org/apache/tools/ant/taskdefs/optional/junit/xsl/toolkit.xsl View File

@@ -0,0 +1,204 @@
<?xml version="1.0" encoding="ISO-8859-1"?>

<!-- This style sheet should contain just a named templates that used in the other specific templates -->
<xsl:stylesheet version="1.0" xmlns:xsl="http://www.w3.org/1999/XSL/Transform">

<!-- transform string like a.b.c to ../../../ -->
<xsl:template name="path">
<xsl:param name="path"/>
<xsl:if test="contains($path,'.')">
<xsl:text>../</xsl:text>
<xsl:call-template name="path">
<xsl:with-param name="path"><xsl:value-of select="substring-after($path,'.')"/></xsl:with-param>
</xsl:call-template>
</xsl:if>
<xsl:if test="not(contains($path,'.')) and not($path = '')">
<xsl:text>../</xsl:text>
</xsl:if>
</xsl:template>

<!--
template that will convert a carriage return into a br tag
@param word the text from which to convert CR to BR tag
-->
<xsl:template name="br-replace">
<xsl:param name="word"/>
<xsl:choose>
<xsl:when test="contains($word,'&#xA;')">
<xsl:value-of select="substring-before($word,'&#xA;')"/>
<br/>
<xsl:call-template name="br-replace">
<xsl:with-param name="word" select="substring-after($word,'&#xA;')"/>
</xsl:call-template>
</xsl:when>
<xsl:otherwise>
<xsl:value-of select="$word"/>
</xsl:otherwise>
</xsl:choose>
</xsl:template>

<!--
=====================================================================
classes summary header
=====================================================================
-->
<xsl:template name="header">
<xsl:param name="useFrame">no</xsl:param>
<xsl:param name="path"/>
<h1>Unit Tests Results</h1>
<table width="100%">
<tr>
<td align="left">
<!--xsl:choose>
<xsl:when test="$useFrame='yes'">Frames&#160;
<a target="_top">
<xsl:attribute name="href"><xsl:call-template name="path"><xsl:with-param name="path" select="$path"/></xsl:call-template>noframes.html</xsl:attribute>No frames</a></xsl:when>
<xsl:when test="$useFrame='no'"><a target="_top"><xsl:attribute name="href"><xsl:call-template name="path"><xsl:with-param name="path" select="$path"/></xsl:call-template>index.html</xsl:attribute>Frames</a>&#160;No frames</xsl:when>
<xsl:otherwise><code>ERROR : useFrame must have 'no' or 'yes' as value.</code></xsl:otherwise>
</xsl:choose-->
</td>
<td align="right">Designed for use with <a href='http://www.junit.org'>JUnit</a> and <a href='http://jakarta.apache.org'>Ant</a>.</td>
</tr>
</table>
<hr size="1"/>
</xsl:template>

<xsl:template name="summaryHeader">
<tr bgcolor="#A6CAF0" valign="top">
<td><b>Tests</b></td>
<td><b>Failures</b></td>
<td><b>Errors</b></td>
<td><b>Success Rate</b></td>
<td nowrap="nowrap"><b>Time(s)</b></td>
</tr>
</xsl:template>

<!--
=====================================================================
package summary header
=====================================================================
-->
<xsl:template name="packageSummaryHeader">
<tr bgcolor="#A6CAF0" valign="top">
<td width="75%"><b>Name</b></td>
<td width="5%"><b>Tests</b></td>
<td width="5%"><b>Errors</b></td>
<td width="5%"><b>Failures</b></td>
<td width="10%" nowrap="nowrap"><b>Time(s)</b></td>
</tr>
</xsl:template>

<!--
=====================================================================
classes summary header
=====================================================================
-->
<xsl:template name="classesSummaryHeader">
<tr bgcolor="#A6CAF0" valign="top">
<td width="18%"><b>Name</b></td>
<td width="7%"><b>Status</b></td>
<td width="70%"><b>Type</b></td>
<td width="5%" nowrap="nowrap"><b>Time(s)</b></td>
</tr>
</xsl:template>

<!--
=====================================================================
Write the summary report
It creates a table with computed values from the document:
User | Date | Environment | Tests | Failures | Errors | Rate | Time
Note : this template must call at the testsuites level
=====================================================================
-->
<xsl:template name="summary">
<h2>Summary</h2>
<xsl:variable name="testCount" select="sum(./testsuite/@tests)"/>
<xsl:variable name="errorCount" select="sum(./testsuite/@errors)"/>
<xsl:variable name="failureCount" select="sum(./testsuite/@failures)"/>
<xsl:variable name="timeCount" select="sum(./testsuite/@time)"/>
<xsl:variable name="successRate" select="($testCount - $failureCount - $errorCount) div $testCount"/>
<table border="0" cellpadding="5" cellspacing="2" width="95%">
<xsl:call-template name="summaryHeader"/>
<tr bgcolor="#EEEEE" valign="top">
<xsl:attribute name="class">
<xsl:choose>
<xsl:when test="./failure | ./error">Error</xsl:when>
<xsl:otherwise>TableRowColor</xsl:otherwise>
</xsl:choose>
</xsl:attribute>
<td><xsl:value-of select="$testCount"/></td>
<td><xsl:value-of select="$failureCount"/></td>
<td><xsl:value-of select="$errorCount"/></td>
<td><xsl:value-of select="format-number($successRate,'#,##0.00%')"/></td>
<td><xsl:value-of select="format-number($timeCount,'#,###0.000')"/></td>
</tr>
</table>
Note: <i>failures</i> are anticipated and checked for with assertions while <i>errors</i> are unanticipated.
</xsl:template>

<!--
=====================================================================
testcase report
=====================================================================
-->
<xsl:template match="testcase">
<TR bgcolor="#EEEEE" valign="top"><xsl:attribute name="class">
<xsl:choose>
<xsl:when test="./failure | ./error">Error</xsl:when>
<xsl:otherwise>TableRowColor</xsl:otherwise>
</xsl:choose>
</xsl:attribute>
<TD><xsl:value-of select="./@name"/></TD>
<xsl:choose>
<xsl:when test="./failure">
<td>Failure</td>
<td><xsl:apply-templates select="./failure"/></td>
</xsl:when>
<xsl:when test="./error">
<TD>Error</TD>
<td><xsl:apply-templates select="./error"/></td>
</xsl:when>
<xsl:otherwise>
<TD>Success</TD>
<TD></TD>
</xsl:otherwise>
</xsl:choose>
<td><xsl:value-of select="format-number(@time,'#,###0.000')"/></td>
</TR>
</xsl:template>

<!-- Note : the below template error and failure are the same style
so just call the same style store in the toolkit template -->
<xsl:template match="failure">
<xsl:call-template name="display-failures"/>
</xsl:template>

<xsl:template match="error">
<xsl:call-template name="display-failures"/>
</xsl:template>

<!-- Style for the error and failure in the tescase template -->
<xsl:template name="display-failures">
<xsl:choose>
<xsl:when test="not(@message)">N/A</xsl:when>
<xsl:otherwise>
<xsl:value-of select="@message"/>
</xsl:otherwise>
</xsl:choose>
<!-- display the stacktrace -->
<code>
<p/>
<xsl:call-template name="br-replace">
<xsl:with-param name="word" select="."/>
</xsl:call-template>
</code>
<!-- the later is better but might be problematic for non-21" monitors... -->
<!--pre><xsl:value-of select="."/></pre-->
</xsl:template>

<!-- I am sure that all nodes are called -->
<xsl:template match="*">
<xsl:apply-templates/>
</xsl:template>

</xsl:stylesheet>

+ 26
- 4
src/main/org/apache/tools/ant/util/DOMElementWriter.java View File

@@ -67,6 +67,7 @@ import org.w3c.dom.*;
*
* @author The original author of XmlLogger
* @author <a href="mailto:stefan.bodewig@epost.de">Stefan Bodewig</a>
* @author <a href="mailto:bailliez@noos.fr">Stephane Bailliez</tt>
*/
public class DOMElementWriter {

@@ -119,18 +120,39 @@ public class DOMElementWriter {
for (int i = 0; i < children.getLength(); i++) {
Node child = children.item(i);

if (child.getNodeType() == Node.ELEMENT_NODE) {
switch (child.getNodeType()) {
case Node.ELEMENT_NODE:
if (!hasChildren) {
out.write(lSep);
hasChildren = true;
}
write((Element)child, out, indent + 1, indentWith);
}

if (child.getNodeType() == Node.TEXT_NODE) {
break;
case Node.TEXT_NODE:
case Node.CDATA_SECTION_NODE:
out.write("<![CDATA[");
out.write(((Text)child).getData());
out.write("]]>");
break;

case Node.ENTITY_REFERENCE_NODE:
out.write('&');
out.write(child.getNodeName());
out.write(';');
break;

case Node.PROCESSING_INSTRUCTION_NODE:
out.write("<?");
out.write(child.getNodeName());
String data = child.getNodeValue();
if ( data != null && data.length() > 0 ) {
out.write(' ');
out.write(data);
}
out.write("?>");
break;
}
}



Loading…
Cancel
Save