FileDocCategorySizeDatePackage
XMLResultAggregator.javaAPI DocApache Ant 1.7012760Wed Dec 13 06:16:20 GMT 2006org.apache.tools.ant.taskdefs.optional.junit

XMLResultAggregator

public class XMLResultAggregator extends org.apache.tools.ant.Task implements XMLConstants
Aggregates all <junit> XML formatter testsuite data under a specific directory and transforms the results via XSLT. 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.

ant.task
name="junitreport" category="testing"

Fields Summary
protected Vector
filesets
the list of all filesets, that should contains the xml to aggregate
protected String
toFile
the name of the result file
protected File
toDir
the directory to write the file to
protected Vector
transformers
public static final String
DEFAULT_DIR
The default directory: .. It is resolved from the project directory
public static final String
DEFAULT_FILENAME
the default file name: TESTS-TestSuites.xml
protected int
generatedId
the current generated id
static final String
WARNING_IS_POSSIBLY_CORRUPTED
text checked for in tests, {@value}
static final String
WARNING_INVALID_ROOT_ELEMENT
text checked for in tests, {@value}
static final String
WARNING_EMPTY_FILE
text checked for in tests, {@value}
Constructors Summary
Methods Summary
public voidaddFileSet(org.apache.tools.ant.types.FileSet fs)
Add a new fileset containing the XML results to aggregate

param
fs the new fileset of xml results.

        filesets.addElement(fs);
    
protected voidaddTestSuite(org.w3c.dom.Element root, org.w3c.dom.Element testsuite)

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: org.apache.Whatever will be split into org.apache and Whatever.

param
root the root element to which the testsuite 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.

        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);
        copy.setAttribute(ATTR_ID, Integer.toString(generatedId));
    
protected org.w3c.dom.ElementcreateDocument()

Create a DOM tree. Has 'testsuites' as firstchild and aggregates all testsuite results that exists in the base directory.

return
the root element of DOM tree that aggregates all testsuites.

        // create the dom tree
        DocumentBuilder builder = getDocumentBuilder();
        Document doc = builder.newDocument();
        Element rootElement = doc.createElement(TESTSUITES);
        doc.appendChild(rootElement);

        generatedId = 0;

        // get all files and add them to the document
        File[] files = getFiles();
        for (int i = 0; i < files.length; i++) {
            File file = files[i];
            try {
                log("Parsing file: '" + file + "'", Project.MSG_VERBOSE);
                if (file.length() > 0) {
                    Document testsuiteDoc
                            = builder.parse(
                                FileUtils.getFileUtils().toURI(files[i].getAbsolutePath()));
                    Element elem = testsuiteDoc.getDocumentElement();
                    // make sure that this is REALLY a testsuite.
                    if (TESTSUITE.equals(elem.getNodeName())) {
                        addTestSuite(rootElement, elem);
                        generatedId++;
                    } else {
                        //wrong root element name
                        // issue a warning.
                        log("the file " + file
                                + WARNING_INVALID_ROOT_ELEMENT,
                                Project.MSG_WARN);
                    }
                } else {
                    log("the file " + file
                            + WARNING_EMPTY_FILE,
                            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 " + file + WARNING_IS_POSSIBLY_CORRUPTED, Project.MSG_WARN);
                log(StringUtils.getStackTrace(e), Project.MSG_DEBUG);
            } catch (IOException e) {
                log("Error while accessing file " + file + ": "
                    + e.getMessage(), Project.MSG_ERR);
            }
        }
        return rootElement;
    
public AggregateTransformercreateReport()
Generate a report based on the document created by the merge.

return
the report

    // CheckStyle:VisibilityModifier ON

                       
       
        AggregateTransformer transformer = new AggregateTransformer(this);
        transformers.addElement(transformer);
        return transformer;
    
public voidexecute()
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.

        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 e = transformers.elements();
        while (e.hasMoreElements()) {
            AggregateTransformer transformer =
                (AggregateTransformer) e.nextElement();
            transformer.setXmlDocument(rootElement.getOwnerDocument());
            transformer.transform();
        }
    
public java.io.FilegetDestinationFile()
Get the full destination file where to write the result. It is made of the todir and tofile attributes.

return
the destination file where should be written the result file.

        if (toFile == null) {
            toFile = DEFAULT_FILENAME;
        }
        if (toDir == null) {
            toDir = getProject().resolveFile(DEFAULT_DIR);
        }
        return new File(toDir, toFile);
    
private static javax.xml.parsers.DocumentBuildergetDocumentBuilder()
Create a new document builder. Will issue an ExceptionInitializerError if something is going wrong. It is fatal anyway.

todo
factorize this somewhere else. It is duplicated code.
return
a new document builder to create a DOM

        try {
            return DocumentBuilderFactory.newInstance().newDocumentBuilder();
        } catch (Exception exc) {
            throw new ExceptionInInitializerError(exc);
        }
    
protected java.io.File[]getFiles()
Get all .xml files in the fileset.

return
all files in the fileset that end with a '.xml'.

        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(getProject());
            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 = getProject().resolveFile(file.getPath());
                    v.addElement(file);
                }
            }
        }

        File[] files = new File[v.size()];
        v.copyInto(files);
        return files;
    
public voidsetTodir(java.io.File 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.

        toDir = value;
    
public voidsetTofile(java.lang.String value)
Set the name of the aggregegated results file. It must be relative from the todir attribute. If not set it will use {@link #DEFAULT_FILENAME}

param
value the name of the file.
see
#setTodir(File)

        toFile = value;
    
protected voidwriteDOMTree(org.w3c.dom.Document doc, java.io.File file)
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.

        OutputStream out = null;
        PrintWriter wri = null;
        try {
            out = new BufferedOutputStream(new FileOutputStream(file));
            wri = new PrintWriter(new OutputStreamWriter(out, "UTF8"));
            wri.write("<?xml version=\"1.0\" encoding=\"UTF-8\" ?>\n");
            (new DOMElementWriter()).write(doc.getDocumentElement(), wri, 0, "  ");
            wri.flush();
            // writers do not throw exceptions, so check for them.
            if (wri.checkError()) {
                throw new IOException("Error while writing DOM content");
            }
        } finally {
            FileUtils.close(wri);
            FileUtils.close(out);
        }