Clover coverage report - Ant Coverage
Coverage timestamp: Tue Apr 8 2003 20:43:55 EST
file stats: LOC: 336   Methods: 11
NCLOC: 154   Classes: 1
 
 Source file Conditionals Statements Methods TOTAL
XMLResultAggregator.java 68.2% 87.3% 90.9% 83.9%
 1   
 /*
 2   
  * The Apache Software License, Version 1.1
 3   
  *
 4   
  * Copyright (c) 2001-2003 The Apache Software Foundation.  All rights
 5   
  * reserved.
 6   
  *
 7   
  * Redistribution and use in source and binary forms, with or without
 8   
  * modification, are permitted provided that the following conditions
 9   
  * are met:
 10   
  *
 11   
  * 1. Redistributions of source code must retain the above copyright
 12   
  *    notice, this list of conditions and the following disclaimer.
 13   
  *
 14   
  * 2. Redistributions in binary form must reproduce the above copyright
 15   
  *    notice, this list of conditions and the following disclaimer in
 16   
  *    the documentation and/or other materials provided with the
 17   
  *    distribution.
 18   
  *
 19   
  * 3. The end-user documentation included with the redistribution, if
 20   
  *    any, must include the following acknowlegement:
 21   
  *       "This product includes software developed by the
 22   
  *        Apache Software Foundation (http://www.apache.org/)."
 23   
  *    Alternately, this acknowlegement may appear in the software itself,
 24   
  *    if and wherever such third-party acknowlegements normally appear.
 25   
  *
 26   
  * 4. The names "Ant" and "Apache Software
 27   
  *    Foundation" must not be used to endorse or promote products derived
 28   
  *    from this software without prior written permission. For written
 29   
  *    permission, please contact apache@apache.org.
 30   
  *
 31   
  * 5. Products derived from this software may not be called "Apache"
 32   
  *    nor may "Apache" appear in their names without prior written
 33   
  *    permission of the Apache Group.
 34   
  *
 35   
  * THIS SOFTWARE IS PROVIDED ``AS IS'' AND ANY EXPRESSED OR IMPLIED
 36   
  * WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES
 37   
  * OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE
 38   
  * DISCLAIMED.  IN NO EVENT SHALL THE APACHE SOFTWARE FOUNDATION OR
 39   
  * ITS CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL,
 40   
  * SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT
 41   
  * LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF
 42   
  * USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND
 43   
  * ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY,
 44   
  * OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT
 45   
  * OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF
 46   
  * SUCH DAMAGE.
 47   
  * ====================================================================
 48   
  *
 49   
  * This software consists of voluntary contributions made by many
 50   
  * individuals on behalf of the Apache Software Foundation.  For more
 51   
  * information on the Apache Software Foundation, please see
 52   
  * <http://www.apache.org/>.
 53   
  */
 54   
 package org.apache.tools.ant.taskdefs.optional.junit;
 55   
 
 56   
 import java.io.BufferedOutputStream;
 57   
 import java.io.File;
 58   
 import java.io.FileOutputStream;
 59   
 import java.io.IOException;
 60   
 import java.io.OutputStream;
 61   
 import java.io.OutputStreamWriter;
 62   
 import java.io.PrintWriter;
 63   
 import java.util.Enumeration;
 64   
 import java.util.Vector;
 65   
 import javax.xml.parsers.DocumentBuilder;
 66   
 import javax.xml.parsers.DocumentBuilderFactory;
 67   
 import org.apache.tools.ant.BuildException;
 68   
 import org.apache.tools.ant.DirectoryScanner;
 69   
 import org.apache.tools.ant.Project;
 70   
 import org.apache.tools.ant.Task;
 71   
 import org.apache.tools.ant.types.FileSet;
 72   
 import org.apache.tools.ant.util.DOMElementWriter;
 73   
 import org.apache.tools.ant.util.StringUtils;
 74   
 import org.w3c.dom.Document;
 75   
 import org.w3c.dom.Element;
 76   
 import org.xml.sax.SAXException;
 77   
 
 78   
 
 79   
 /**
 80   
  * Aggregates all &lt;junit&gt; XML formatter testsuite data under
 81   
  * a specific directory and transforms the results via XSLT.
 82   
  * It is not particulary clean but
 83   
  * should be helpful while I am thinking about another technique.
 84   
  *
 85   
  * <p> The main problem is due to the fact that a JVM can be forked for a testcase
 86   
  * thus making it impossible to aggregate all testcases since the listener is
 87   
  * (obviously) in the forked JVM. A solution could be to write a
 88   
  * TestListener that will receive events from the TestRunner via sockets. This
 89   
  * is IMHO the simplest way to do it to avoid this file hacking thing.
 90   
  *
 91   
  * @author <a href="mailto:sbailliez@imediation.com">Stephane Bailliez</a>
 92   
  *
 93   
  * @ant.task name="junitreport" category="testing"
 94   
  */
 95   
 public class XMLResultAggregator extends Task implements XMLConstants {
 96   
 
 97   
     /** the list of all filesets, that should contains the xml to aggregate */
 98   
     protected Vector filesets = new Vector();
 99   
 
 100   
     /** the name of the result file */
 101   
     protected String toFile;
 102   
 
 103   
     /** the directory to write the file to */
 104   
     protected File toDir;
 105   
 
 106   
     protected Vector transformers = new Vector();
 107   
 
 108   
     /** The default directory: <tt>&#046;</tt>. It is resolved from the project directory */
 109   
     public static final String DEFAULT_DIR = ".";
 110   
 
 111   
     /** the default file name: <tt>TESTS-TestSuites.xml</tt> */
 112   
     public static final String DEFAULT_FILENAME = "TESTS-TestSuites.xml";
 113   
 
 114   
     /**
 115   
      * Generate a report based on the document created by the merge.
 116   
      */
 117  1
     public AggregateTransformer createReport(){
 118  1
         AggregateTransformer transformer = new AggregateTransformer(this);
 119  1
         transformers.addElement(transformer);
 120  1
         return transformer;
 121   
     }
 122   
 
 123   
     /**
 124   
      * Set the name of the aggregegated results file. It must be relative
 125   
      * from the <tt>todir</tt> attribute. If not set it will use {@link #DEFAULT_FILENAME}
 126   
      * @param  value   the name of the file.
 127   
      * @see #setTodir(File)
 128   
      */
 129  0
     public void setTofile(String value){
 130  0
         toFile = value;
 131   
     }
 132   
 
 133   
     /**
 134   
      * Set the destination directory where the results should be written. If not
 135   
      * set if will use {@link #DEFAULT_DIR}. When given a relative directory
 136   
      * it will resolve it from the project directory.
 137   
      * @param value    the directory where to write the results, absolute or
 138   
      * relative.
 139   
      */
 140  1
     public void setTodir(File value){
 141  1
         toDir = value;
 142   
     }
 143   
 
 144   
     /**
 145   
      * Add a new fileset containing the XML results to aggregate
 146   
      * @param    fs      the new fileset of xml results.
 147   
      */
 148  1
     public void addFileSet(FileSet fs) {
 149  1
         filesets.addElement(fs);
 150   
     }
 151   
 
 152   
     /**
 153   
      * Aggregate all testsuites into a single document and write it to the
 154   
      * specified directory and file.
 155   
      * @throws  BuildException  thrown if there is a serious error while writing
 156   
      *          the document.
 157   
      */
 158  1
     public void execute() throws BuildException {
 159  1
         Element rootElement = createDocument();
 160  1
         File destFile = getDestinationFile();
 161   
         // write the document
 162  1
         try {
 163  1
             writeDOMTree(rootElement.getOwnerDocument(), destFile);
 164   
         } catch (IOException e){
 165  0
             throw new BuildException("Unable to write test aggregate to '" + destFile + "'", e);
 166   
         }
 167   
         // apply transformation
 168  1
         Enumeration enum = transformers.elements();
 169  1
         while (enum.hasMoreElements()) {
 170  1
             AggregateTransformer transformer =
 171   
                 (AggregateTransformer) enum.nextElement();
 172  1
             transformer.setXmlDocument(rootElement.getOwnerDocument());
 173  1
             transformer.transform();
 174   
         }
 175   
     }
 176   
 
 177   
     /**
 178   
      * Get the full destination file where to write the result. It is made of
 179   
      * the <tt>todir</tt> and <tt>tofile</tt> attributes.
 180   
      * @return the destination file where should be written the result file.
 181   
      */
 182  1
     protected File getDestinationFile(){
 183  1
         if (toFile == null){
 184  1
             toFile = DEFAULT_FILENAME;
 185   
         }
 186  1
         if (toDir == null){
 187  0
             toDir = getProject().resolveFile(DEFAULT_DIR);
 188   
         }
 189  1
         return new File(toDir, toFile);
 190   
     }
 191   
 
 192   
     /**
 193   
      * Get all <code>.xml</code> files in the fileset.
 194   
      *
 195   
      * @return all files in the fileset that end with a '.xml'.
 196   
      */
 197  1
     protected File[] getFiles() {
 198  1
         Vector v = new Vector();
 199  1
         final int size = filesets.size();
 200  1
         for (int i = 0; i < size; i++) {
 201  1
             FileSet fs = (FileSet) filesets.elementAt(i);
 202  1
             DirectoryScanner ds = fs.getDirectoryScanner(getProject());
 203  1
             ds.scan();
 204  1
             String[] f = ds.getIncludedFiles();
 205  1
             for (int j = 0; j < f.length; j++) {
 206  2
                 String pathname = f[j];
 207  2
                 if (pathname.endsWith(".xml")) {
 208  2
                     File file = new File(ds.getBasedir(), pathname);
 209  2
                     file = getProject().resolveFile(file.getPath());
 210  2
                     v.addElement(file);
 211   
                 }
 212   
             }
 213   
         }
 214   
 
 215  1
         File[] files = new File[v.size()];
 216  1
         v.copyInto(files);
 217  1
         return files;
 218   
     }
 219   
 
 220   
     //----- from now, the methods are all related to DOM tree manipulation
 221   
 
 222   
     /**
 223   
      * Write the DOM tree to a file.
 224   
      * @param doc the XML document to dump to disk.
 225   
      * @param file the filename to write the document to. Should obviouslly be a .xml file.
 226   
      * @throws IOException thrown if there is an error while writing the content.
 227   
      */
 228  1
     protected void writeDOMTree(Document doc, File file) throws IOException {
 229  1
         OutputStream out = null;
 230  1
         PrintWriter wri = null;
 231  1
         try {
 232  1
             out = new BufferedOutputStream(new FileOutputStream(file));
 233  1
             wri = new PrintWriter(new OutputStreamWriter(out, "UTF8"));
 234  1
             wri.write("<?xml version=\"1.0\" encoding=\"UTF-8\" ?>\n");
 235  1
             (new DOMElementWriter()).write(doc.getDocumentElement(), wri, 0, "  ");
 236  1
             wri.flush();
 237   
             // writers do not throw exceptions, so check for them.
 238  1
             if (wri.checkError()){
 239  0
                 throw new IOException("Error while writing DOM content");
 240   
             }
 241   
         } finally {
 242  1
             if (wri != null) {
 243  1
                 wri.close();
 244  1
                 out = null;
 245   
             }
 246  1
             if (out != null) {
 247  0
                 out.close();
 248   
             }
 249   
         }
 250   
     }
 251   
 
 252   
     /**
 253   
      * <p> Create a DOM tree.
 254   
      * Has 'testsuites' as firstchild and aggregates all
 255   
      * testsuite results that exists in the base directory.
 256   
      * @return  the root element of DOM tree that aggregates all testsuites.
 257   
      */
 258  1
     protected Element createDocument() {
 259   
         // create the dom tree
 260  1
         DocumentBuilder builder = getDocumentBuilder();
 261  1
         Document doc = builder.newDocument();
 262  1
         Element rootElement = doc.createElement(TESTSUITES);
 263  1
         doc.appendChild(rootElement);
 264   
 
 265   
         // get all files and add them to the document
 266  1
         File[] files = getFiles();
 267  1
         for (int i = 0; i < files.length; i++) {
 268  2
             try {
 269  2
                 log("Parsing file: '" + files[i] + "'", Project.MSG_VERBOSE);
 270   
                 //XXX there seems to be a bug in xerces 1.3.0 that doesn't like file object
 271   
                 // will investigate later. It does not use the given directory but
 272   
                 // the vm dir instead ? Works fine with crimson.
 273  2
                 Document testsuiteDoc 
 274   
                     = builder.parse("file:///" + files[i].getAbsolutePath());
 275  2
                 Element elem = testsuiteDoc.getDocumentElement();
 276   
                 // make sure that this is REALLY a testsuite.
 277  2
                 if (TESTSUITE.equals(elem.getNodeName())) {
 278  2
                     addTestSuite(rootElement, elem);
 279   
                 } else {
 280   
                     // issue a warning.
 281  0
                     log("the file " + files[i] + " is not a valid testsuite XML document", Project.MSG_WARN);
 282   
                 }
 283   
             } catch (SAXException e){
 284   
                 // a testcase might have failed and write a zero-length document,
 285   
                 // It has already failed, but hey.... mm. just put a warning
 286  0
                 log("The file " + files[i] + " is not a valid XML document. It is possibly corrupted.", Project.MSG_WARN);
 287  0
                 log(StringUtils.getStackTrace(e), Project.MSG_DEBUG);
 288   
             } catch (IOException e){
 289  0
                 log("Error while accessing file " + files[i] + ": " + e.getMessage(), Project.MSG_ERR);
 290   
             }
 291   
         }
 292  1
         return rootElement;
 293   
     }
 294   
 
 295   
     /**
 296   
      * <p> Add a new testsuite node to the document.
 297   
      * The main difference is that it
 298   
      * split the previous fully qualified name into a package and a name.
 299   
      * <p> For example: <tt>org.apache.Whatever</tt> will be split into
 300   
      * <tt>org.apache</tt> and <tt>Whatever</tt>.
 301   
      * @param root the root element to which the <tt>testsuite</tt> node should
 302   
      *        be appended.
 303   
      * @param testsuite the element to append to the given root. It will slightly
 304   
      *        modify the original node to change the name attribute and add
 305   
      *        a package one.
 306   
      */
 307  2
     protected void addTestSuite(Element root, Element testsuite){
 308  2
         String fullclassname = testsuite.getAttribute(ATTR_NAME);
 309  2
         int pos = fullclassname.lastIndexOf('.');
 310   
 
 311   
         // a missing . might imply no package at all. Don't get fooled.
 312  2
         String pkgName = (pos == -1) ? "" : fullclassname.substring(0, pos);
 313  2
         String classname = (pos == -1) ? fullclassname : fullclassname.substring(pos + 1);
 314  2
         Element copy = (Element) DOMUtil.importNode(root, testsuite);
 315   
 
 316   
         // modify the name attribute and set the package
 317  2
         copy.setAttribute(ATTR_NAME, classname);
 318  2
         copy.setAttribute(ATTR_PACKAGE, pkgName);
 319   
     }
 320   
 
 321   
     /**
 322   
      * Create a new document builder. Will issue an <tt>ExceptionInitializerError</tt>
 323   
      * if something is going wrong. It is fatal anyway.
 324   
      * @todo factorize this somewhere else. It is duplicated code.
 325   
      * @return a new document builder to create a DOM
 326   
      */
 327  1
     private static DocumentBuilder getDocumentBuilder() {
 328  1
         try {
 329  1
             return DocumentBuilderFactory.newInstance().newDocumentBuilder();
 330   
         } catch (Exception exc) {
 331  0
             throw new ExceptionInInitializerError(exc);
 332   
         }
 333   
     }
 334   
 
 335   
 }
 336