| /* |
| * 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.StringWriter; |
| 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); |
| //XXX there seems to be a bug in xerces 1.3.0 that doesn't like file object |
| // will investigate later. It does not use the given directory but |
| // the vm dir instead ? Works fine with crimson. |
| Document testsuiteDoc = builder.parse( "file:///" + files[i].getAbsolutePath() ); |
| 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); |
| StringWriter sw = new StringWriter(); |
| e.printStackTrace(new PrintWriter(sw)); |
| log(sw.toString(), Project.MSG_DEBUG); |
| } 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); |
| } |
| } |
| |
| } |