~ubuntu-branches/ubuntu/jaunty/ant/jaunty-proposed

« back to all changes in this revision

Viewing changes to src/main/org/apache/tools/ant/taskdefs/optional/junit/XMLResultAggregator.java

  • Committer: Bazaar Package Importer
  • Author(s): Stefan Gybas
  • Date: 2002-02-14 14:28:48 UTC
  • Revision ID: james.westby@ubuntu.com-20020214142848-2ww7ynmqkj31vlmn
Tags: upstream-1.4.1
ImportĀ upstreamĀ versionĀ 1.4.1

Show diffs side-by-side

added added

removed removed

Lines of Context:
 
1
/*
 
2
 * The Apache Software License, Version 1.1
 
3
 *
 
4
 * Copyright (c) 2001 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 "The Jakarta Project", "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.File;
 
57
import java.io.IOException;
 
58
import java.io.OutputStream;
 
59
import java.io.OutputStreamWriter;
 
60
import java.io.PrintWriter;
 
61
import java.io.StringWriter;
 
62
import java.io.FileOutputStream;
 
63
import java.util.Enumeration;
 
64
import java.util.Vector;
 
65
 
 
66
import org.w3c.dom.Element;
 
67
import org.w3c.dom.Document;
 
68
import org.w3c.dom.Node;
 
69
import org.w3c.dom.*;
 
70
import org.xml.sax.SAXException;
 
71
import javax.xml.parsers.DocumentBuilder;
 
72
import javax.xml.parsers.DocumentBuilderFactory;
 
73
import org.apache.tools.ant.Project;
 
74
import org.apache.tools.ant.Task;
 
75
import org.apache.tools.ant.DirectoryScanner;
 
76
import org.apache.tools.ant.BuildException;
 
77
import org.apache.tools.ant.types.FileSet;
 
78
import org.apache.tools.ant.util.DOMElementWriter;
 
79
 
 
80
 
 
81
/**
 
82
 * <p> This is an helper class that will aggregate all testsuites under a specific
 
83
 * directory and create a new single document. It is not particulary clean but
 
84
 * should be helpful while I am thinking about another technique.
 
85
 *
 
86
 * <p> The main problem is due to the fact that a JVM can be forked for a testcase
 
87
 * thus making it impossible to aggregate all testcases since the listener is
 
88
 * (obviously) in the forked JVM. A solution could be to write a
 
89
 * TestListener that will receive events from the TestRunner via sockets. This
 
90
 * is IMHO the simplest way to do it to avoid this file hacking thing.
 
91
 *
 
92
 * @author <a href="mailto:sbailliez@imediation.com">Stephane Bailliez</a>
 
93
 */
 
94
public class XMLResultAggregator extends Task implements XMLConstants {
 
95
    
 
96
    /** the list of all filesets, that should contains the xml to aggregate */
 
97
    protected Vector filesets = new Vector();
 
98
    
 
99
    /** the name of the result file */
 
100
    protected String toFile;
 
101
    
 
102
    /** the directory to write the file to */
 
103
    protected File toDir;
 
104
    
 
105
    protected Vector transformers = new Vector();
 
106
    
 
107
    /** the default directory: <tt>.</tt>. It is resolved from the project directory */
 
108
    public final static String DEFAULT_DIR = ".";
 
109
    
 
110
    /** the default file name: <tt>TESTS-TestSuites.xml</tt> */
 
111
    public final static String DEFAULT_FILENAME = "TESTS-TestSuites.xml";
 
112
    
 
113
    
 
114
    public AggregateTransformer createReport(){
 
115
        AggregateTransformer transformer = new AggregateTransformer(this);
 
116
        transformers.addElement(transformer);
 
117
        return transformer;
 
118
    }
 
119
    
 
120
    /**
 
121
     * Set the name of the file aggregating the results. It must be relative
 
122
     * from the <tt>todir</tt> attribute. If not set it will use {@link #DEFAULT_FILENAME}
 
123
     * @param  value   the name of the file.
 
124
     * @see #setTodir(File)
 
125
     */
 
126
    public void setTofile(String value){
 
127
        toFile = value;
 
128
    }
 
129
    
 
130
    /**
 
131
     * Set the destination directory where the results should be written. If not
 
132
     * set if will use {@link #DEFAULT_DIR}. When given a relative directory
 
133
     * it will resolve it from the project directory.
 
134
     * @param value    the directory where to write the results, absolute or
 
135
     * relative.
 
136
     */
 
137
    public void setTodir(File value){
 
138
        toDir = value;
 
139
    }
 
140
    
 
141
    /**
 
142
     * Add a new fileset containing the xml results to aggregate
 
143
     * @param    fs      the new fileset of xml results.
 
144
     */
 
145
    public void addFileSet(FileSet fs) {
 
146
        filesets.addElement(fs);
 
147
    }
 
148
    
 
149
    /**
 
150
     * Aggregate all testsuites into a single document and write it to the
 
151
     * specified directory and file.
 
152
     * @throws  BuildException  thrown if there is a serious error while writing
 
153
     *                  the document.
 
154
     */
 
155
    public void execute() throws BuildException {
 
156
        Element rootElement = createDocument();
 
157
        File destFile = getDestinationFile();
 
158
        // write the document
 
159
        try {
 
160
            writeDOMTree(rootElement.getOwnerDocument(),  destFile );
 
161
        } catch (IOException e){
 
162
            throw new BuildException("Unable to write test aggregate to '" + destFile + "'", e);
 
163
        }
 
164
        // apply transformation
 
165
        Enumeration enum = transformers.elements();
 
166
        while (enum.hasMoreElements()) {
 
167
            AggregateTransformer transformer =
 
168
                (AggregateTransformer) enum.nextElement();
 
169
            transformer.setXmlDocument(rootElement.getOwnerDocument());
 
170
            transformer.transform();
 
171
        }
 
172
    }
 
173
    
 
174
    /**
 
175
     * Get the full destination file where to write the result. It is made of
 
176
     * the <tt>todir</tt> and <tt>tofile</tt> attributes.
 
177
     * @return the destination file where should be written the result file.
 
178
     */
 
179
    protected File getDestinationFile(){
 
180
        if (toFile == null){
 
181
            toFile = DEFAULT_FILENAME;
 
182
        }
 
183
        if (toDir == null){
 
184
            toDir = project.resolveFile(DEFAULT_DIR);
 
185
        }
 
186
        return new File(toDir, toFile);
 
187
    }
 
188
    
 
189
    /**
 
190
     * Get all <code>.xml</code> files in the fileset.
 
191
     *
 
192
     * @return all files in the fileset that end with a '.xml'.
 
193
     */
 
194
    protected File[] getFiles() {
 
195
        Vector v = new Vector();
 
196
        final int size = filesets.size();
 
197
        for (int i = 0; i < size; i++) {
 
198
            FileSet fs = (FileSet) filesets.elementAt(i);
 
199
            DirectoryScanner ds = fs.getDirectoryScanner(project);
 
200
            ds.scan();
 
201
            String[] f = ds.getIncludedFiles();
 
202
            for (int j = 0; j < f.length; j++) {
 
203
                String pathname = f[j];
 
204
                if ( pathname.endsWith(".xml") ) {
 
205
                    File file = new File(ds.getBasedir(), pathname);
 
206
                    file = project.resolveFile(file.getPath());
 
207
                    v.addElement( file );
 
208
                }
 
209
            }
 
210
        }
 
211
        
 
212
        File[] files = new File[v.size()];
 
213
        v.copyInto(files);
 
214
        return files;
 
215
    }
 
216
    
 
217
    //----- from now, the methods are all related to DOM tree manipulation
 
218
    
 
219
    /**
 
220
     * Write the DOM tree to a file.
 
221
     * @param doc the XML document to dump to disk.
 
222
     * @param file the filename to write the document to. Should obviouslly be a .xml file.
 
223
     * @throws IOException thrown if there is an error while writing the content.
 
224
     */
 
225
    protected void writeDOMTree(Document doc, File file) throws IOException {
 
226
        OutputStream out = new FileOutputStream( file );
 
227
        PrintWriter wri = new PrintWriter(new OutputStreamWriter(out, "UTF8"));
 
228
        wri.write("<?xml version=\"1.0\"?>\n");
 
229
        (new DOMElementWriter()).write(doc.getDocumentElement(), wri, 0, "  ");
 
230
        wri.flush();
 
231
        wri.close();
 
232
        // writers do not throw exceptions, so check for them.
 
233
        if (wri.checkError()){
 
234
            throw new IOException("Error while writing DOM content");
 
235
        }
 
236
    }
 
237
    
 
238
    /**
 
239
     * <p> Create a DOM tree. 
 
240
     * Has 'testsuites' as firstchild and aggregates all
 
241
     * testsuite results that exists in the base directory.
 
242
     * @return  the root element of DOM tree that aggregates all testsuites.
 
243
     */
 
244
    protected Element createDocument() {
 
245
        // create the dom tree
 
246
        DocumentBuilder builder = getDocumentBuilder();
 
247
        Document doc = builder.newDocument();
 
248
        Element rootElement = doc.createElement(TESTSUITES);
 
249
        doc.appendChild(rootElement);
 
250
        
 
251
        // get all files and add them to the document
 
252
        File[] files = getFiles();
 
253
        for (int i = 0; i < files.length; i++) {
 
254
            try {
 
255
                log("Parsing file: '" + files[i] + "'", Project.MSG_VERBOSE);
 
256
                //XXX there seems to be a bug in xerces 1.3.0 that doesn't like file object
 
257
                // will investigate later. It does not use the given directory but
 
258
                // the vm dir instead ? Works fine with crimson.
 
259
                Document testsuiteDoc = builder.parse( "file:///" + files[i].getAbsolutePath() );
 
260
                Element elem = testsuiteDoc.getDocumentElement();
 
261
                // make sure that this is REALLY a testsuite.
 
262
                if ( TESTSUITE.equals(elem.getNodeName()) ) {
 
263
                    addTestSuite(rootElement, elem);
 
264
                } else {
 
265
                    // issue a warning.
 
266
                    log("the file " + files[i] + " is not a valid testsuite XML document", Project.MSG_WARN);
 
267
                }
 
268
            } catch (SAXException e){
 
269
                // a testcase might have failed and write a zero-length document,
 
270
                // It has already failed, but hey.... mm. just put a warning
 
271
                log("The file " + files[i] + " is not a valid XML document. It is possibly corrupted.", Project.MSG_WARN);
 
272
                StringWriter sw = new StringWriter();
 
273
                e.printStackTrace(new PrintWriter(sw));
 
274
                log(sw.toString(), Project.MSG_DEBUG);
 
275
            } catch (IOException e){
 
276
                log("Error while accessing file " + files[i] + ": " + e.getMessage(), Project.MSG_ERR);
 
277
            }
 
278
        }
 
279
        return rootElement;
 
280
    }
 
281
    
 
282
    /**
 
283
     * <p> Add a new testsuite node to the document.
 
284
     * The main difference is that it
 
285
     * split the previous fully qualified name into a package and a name.
 
286
     * <p> For example: <tt>org.apache.Whatever</tt> will be split into
 
287
     * <tt>org.apache</tt> and <tt>Whatever</tt>.
 
288
     * @param root the root element to which the <tt>testsuite</tt> node should
 
289
     *        be appended.
 
290
     * @param testsuite the element to append to the given root. It will slightly
 
291
     *        modify the original node to change the name attribute and add
 
292
     *        a package one.
 
293
     */
 
294
    protected void addTestSuite(Element root, Element testsuite){
 
295
        String fullclassname = testsuite.getAttribute(ATTR_NAME);
 
296
        int pos = fullclassname.lastIndexOf('.');
 
297
        
 
298
        // a missing . might imply no package at all. Don't get fooled.
 
299
        String pkgName = (pos == -1) ? "" : fullclassname.substring(0, pos);
 
300
        String classname = (pos == -1) ? fullclassname : fullclassname.substring(pos + 1);
 
301
        Element copy = (Element)DOMUtil.importNode(root, testsuite);
 
302
        
 
303
        // modify the name attribute and set the package
 
304
        copy.setAttribute(ATTR_NAME, classname);
 
305
        copy.setAttribute(ATTR_PACKAGE, pkgName);
 
306
    }
 
307
    
 
308
    /**
 
309
     * Create a new document builder. Will issue an <tt>ExceptionInitializerError</tt>
 
310
     * if something is going wrong. It is fatal anyway.
 
311
     * @return a new document builder to create a DOM
 
312
     * @todo factorize this somewhere else. It is duplicated code.
 
313
     */
 
314
    private static DocumentBuilder getDocumentBuilder() {
 
315
        try {
 
316
            return DocumentBuilderFactory.newInstance().newDocumentBuilder();
 
317
        } catch(Exception exc) {
 
318
            throw new ExceptionInInitializerError(exc);
 
319
        }
 
320
    }
 
321
    
 
322
}