2
* The Apache Software License, Version 1.1
4
* Copyright (c) 2001 The Apache Software Foundation. All rights
7
* Redistribution and use in source and binary forms, with or without
8
* modification, are permitted provided that the following conditions
11
* 1. Redistributions of source code must retain the above copyright
12
* notice, this list of conditions and the following disclaimer.
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
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.
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.
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.
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
47
* ====================================================================
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/>.
54
package org.apache.tools.ant.taskdefs.optional.junit;
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;
66
import org.w3c.dom.Element;
67
import org.w3c.dom.Document;
68
import org.w3c.dom.Node;
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;
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.
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.
92
* @author <a href="mailto:sbailliez@imediation.com">Stephane Bailliez</a>
94
public class XMLResultAggregator extends Task implements XMLConstants {
96
/** the list of all filesets, that should contains the xml to aggregate */
97
protected Vector filesets = new Vector();
99
/** the name of the result file */
100
protected String toFile;
102
/** the directory to write the file to */
103
protected File toDir;
105
protected Vector transformers = new Vector();
107
/** the default directory: <tt>.</tt>. It is resolved from the project directory */
108
public final static String DEFAULT_DIR = ".";
110
/** the default file name: <tt>TESTS-TestSuites.xml</tt> */
111
public final static String DEFAULT_FILENAME = "TESTS-TestSuites.xml";
114
public AggregateTransformer createReport(){
115
AggregateTransformer transformer = new AggregateTransformer(this);
116
transformers.addElement(transformer);
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)
126
public void setTofile(String value){
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
137
public void setTodir(File value){
142
* Add a new fileset containing the xml results to aggregate
143
* @param fs the new fileset of xml results.
145
public void addFileSet(FileSet fs) {
146
filesets.addElement(fs);
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
155
public void execute() throws BuildException {
156
Element rootElement = createDocument();
157
File destFile = getDestinationFile();
158
// write the document
160
writeDOMTree(rootElement.getOwnerDocument(), destFile );
161
} catch (IOException e){
162
throw new BuildException("Unable to write test aggregate to '" + destFile + "'", e);
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();
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.
179
protected File getDestinationFile(){
181
toFile = DEFAULT_FILENAME;
184
toDir = project.resolveFile(DEFAULT_DIR);
186
return new File(toDir, toFile);
190
* Get all <code>.xml</code> files in the fileset.
192
* @return all files in the fileset that end with a '.xml'.
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);
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 );
212
File[] files = new File[v.size()];
217
//----- from now, the methods are all related to DOM tree manipulation
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.
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, " ");
232
// writers do not throw exceptions, so check for them.
233
if (wri.checkError()){
234
throw new IOException("Error while writing DOM content");
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.
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);
251
// get all files and add them to the document
252
File[] files = getFiles();
253
for (int i = 0; i < files.length; i++) {
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);
266
log("the file " + files[i] + " is not a valid testsuite XML document", Project.MSG_WARN);
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);
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
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
294
protected void addTestSuite(Element root, Element testsuite){
295
String fullclassname = testsuite.getAttribute(ATTR_NAME);
296
int pos = fullclassname.lastIndexOf('.');
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);
303
// modify the name attribute and set the package
304
copy.setAttribute(ATTR_NAME, classname);
305
copy.setAttribute(ATTR_PACKAGE, pkgName);
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.
314
private static DocumentBuilder getDocumentBuilder() {
316
return DocumentBuilderFactory.newInstance().newDocumentBuilder();
317
} catch(Exception exc) {
318
throw new ExceptionInInitializerError(exc);