Submitted by: Stephane Bailliez <sbailliez@imediation.com> git-svn-id: https://svn.apache.org/repos/asf/ant/core/trunk@268562 13f79535-47bb-0310-9956-ffa450edef68master
@@ -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>. | |||
@@ -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> | |||
<!-- | |||
@@ -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 | |||
@@ -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); | |||
} | |||
} | |||
} |
@@ -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); | |||
} | |||
} | |||
} |
@@ -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> |
@@ -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; | |||
} |
@@ -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> |
@@ -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> |
@@ -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> |
@@ -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> |
@@ -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)])>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 = ''])>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))>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)>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> |
@@ -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 > 0">Error</xsl:when> | |||
<xsl:when test="$failureCount > 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[.> 0]">Error</xsl:when> | |||
<xsl:when test="@failures[.> 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> |
@@ -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[.> 0]">Error</xsl:when> | |||
<xsl:when test="@failures[.> 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> |
@@ -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,'
')"> | |||
<xsl:value-of select="substring-before($word,'
')"/> | |||
<br/> | |||
<xsl:call-template name="br-replace"> | |||
<xsl:with-param name="word" select="substring-after($word,'
')"/> | |||
</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  | |||
<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> 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> |
@@ -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; | |||
} | |||
} | |||