source: other-projects/trunk/gs3-release-maker/apache-ant-1.6.5/src/main/org/apache/tools/ant/taskdefs/optional/junit/XMLResultAggregator.java@ 14627

Last change on this file since 14627 was 14627, checked in by oranfry, 17 years ago

initial import of the gs3-release-maker

File size: 11.9 KB
Line 
1/*
2 * Copyright 2001-2005 The Apache Software Foundation
3 *
4 * Licensed under the Apache License, Version 2.0 (the "License");
5 * you may not use this file except in compliance with the License.
6 * You may obtain a copy of the License at
7 *
8 * http://www.apache.org/licenses/LICENSE-2.0
9 *
10 * Unless required by applicable law or agreed to in writing, software
11 * distributed under the License is distributed on an "AS IS" BASIS,
12 * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
13 * See the License for the specific language governing permissions and
14 * limitations under the License.
15 *
16 */
17package org.apache.tools.ant.taskdefs.optional.junit;
18
19import java.io.BufferedOutputStream;
20import java.io.File;
21import java.io.FileOutputStream;
22import java.io.IOException;
23import java.io.OutputStream;
24import java.io.OutputStreamWriter;
25import java.io.PrintWriter;
26import java.util.Enumeration;
27import java.util.Vector;
28import javax.xml.parsers.DocumentBuilder;
29import javax.xml.parsers.DocumentBuilderFactory;
30import org.apache.tools.ant.BuildException;
31import org.apache.tools.ant.DirectoryScanner;
32import org.apache.tools.ant.Project;
33import org.apache.tools.ant.Task;
34import org.apache.tools.ant.types.FileSet;
35import org.apache.tools.ant.util.DOMElementWriter;
36import org.apache.tools.ant.util.StringUtils;
37import org.w3c.dom.Document;
38import org.w3c.dom.Element;
39import org.xml.sax.SAXException;
40
41
42/**
43 * Aggregates all <junit> XML formatter testsuite data under
44 * a specific directory and transforms the results via XSLT.
45 * It is not particulary clean but
46 * should be helpful while I am thinking about another technique.
47 *
48 * <p> The main problem is due to the fact that a JVM can be forked for a testcase
49 * thus making it impossible to aggregate all testcases since the listener is
50 * (obviously) in the forked JVM. A solution could be to write a
51 * TestListener that will receive events from the TestRunner via sockets. This
52 * is IMHO the simplest way to do it to avoid this file hacking thing.
53 *
54 *
55 * @ant.task name="junitreport" category="testing"
56 */
57public class XMLResultAggregator extends Task implements XMLConstants {
58
59 /** the list of all filesets, that should contains the xml to aggregate */
60 protected Vector filesets = new Vector();
61
62 /** the name of the result file */
63 protected String toFile;
64
65 /** the directory to write the file to */
66 protected File toDir;
67
68 protected Vector transformers = new Vector();
69
70 /** The default directory: <tt>&#046;</tt>. It is resolved from the project directory */
71 public static final String DEFAULT_DIR = ".";
72
73 /** the default file name: <tt>TESTS-TestSuites.xml</tt> */
74 public static final String DEFAULT_FILENAME = "TESTS-TestSuites.xml";
75
76 /** the current generated id */
77 protected int generatedId = 0;
78
79 /**
80 * Generate a report based on the document created by the merge.
81 * @return the report
82 */
83 public AggregateTransformer createReport() {
84 AggregateTransformer transformer = new AggregateTransformer(this);
85 transformers.addElement(transformer);
86 return transformer;
87 }
88
89 /**
90 * Set the name of the aggregegated results file. It must be relative
91 * from the <tt>todir</tt> attribute. If not set it will use {@link #DEFAULT_FILENAME}
92 * @param value the name of the file.
93 * @see #setTodir(File)
94 */
95 public void setTofile(String value) {
96 toFile = value;
97 }
98
99 /**
100 * Set the destination directory where the results should be written. If not
101 * set if will use {@link #DEFAULT_DIR}. When given a relative directory
102 * it will resolve it from the project directory.
103 * @param value the directory where to write the results, absolute or
104 * relative.
105 */
106 public void setTodir(File value) {
107 toDir = value;
108 }
109
110 /**
111 * Add a new fileset containing the XML results to aggregate
112 * @param fs the new fileset of xml results.
113 */
114 public void addFileSet(FileSet fs) {
115 filesets.addElement(fs);
116 }
117
118 /**
119 * Aggregate all testsuites into a single document and write it to the
120 * specified directory and file.
121 * @throws BuildException thrown if there is a serious error while writing
122 * the document.
123 */
124 public void execute() throws BuildException {
125 Element rootElement = createDocument();
126 File destFile = getDestinationFile();
127 // write the document
128 try {
129 writeDOMTree(rootElement.getOwnerDocument(), destFile);
130 } catch (IOException e) {
131 throw new BuildException("Unable to write test aggregate to '" + destFile + "'", e);
132 }
133 // apply transformation
134 Enumeration e = transformers.elements();
135 while (e.hasMoreElements()) {
136 AggregateTransformer transformer =
137 (AggregateTransformer) e.nextElement();
138 transformer.setXmlDocument(rootElement.getOwnerDocument());
139 transformer.transform();
140 }
141 }
142
143 /**
144 * Get the full destination file where to write the result. It is made of
145 * the <tt>todir</tt> and <tt>tofile</tt> attributes.
146 * @return the destination file where should be written the result file.
147 */
148 protected File getDestinationFile() {
149 if (toFile == null) {
150 toFile = DEFAULT_FILENAME;
151 }
152 if (toDir == null) {
153 toDir = getProject().resolveFile(DEFAULT_DIR);
154 }
155 return new File(toDir, toFile);
156 }
157
158 /**
159 * Get all <code>.xml</code> files in the fileset.
160 *
161 * @return all files in the fileset that end with a '.xml'.
162 */
163 protected File[] getFiles() {
164 Vector v = new Vector();
165 final int size = filesets.size();
166 for (int i = 0; i < size; i++) {
167 FileSet fs = (FileSet) filesets.elementAt(i);
168 DirectoryScanner ds = fs.getDirectoryScanner(getProject());
169 ds.scan();
170 String[] f = ds.getIncludedFiles();
171 for (int j = 0; j < f.length; j++) {
172 String pathname = f[j];
173 if (pathname.endsWith(".xml")) {
174 File file = new File(ds.getBasedir(), pathname);
175 file = getProject().resolveFile(file.getPath());
176 v.addElement(file);
177 }
178 }
179 }
180
181 File[] files = new File[v.size()];
182 v.copyInto(files);
183 return files;
184 }
185
186 //----- from now, the methods are all related to DOM tree manipulation
187
188 /**
189 * Write the DOM tree to a file.
190 * @param doc the XML document to dump to disk.
191 * @param file the filename to write the document to. Should obviouslly be a .xml file.
192 * @throws IOException thrown if there is an error while writing the content.
193 */
194 protected void writeDOMTree(Document doc, File file) throws IOException {
195 OutputStream out = null;
196 PrintWriter wri = null;
197 try {
198 out = new BufferedOutputStream(new FileOutputStream(file));
199 wri = new PrintWriter(new OutputStreamWriter(out, "UTF8"));
200 wri.write("<?xml version=\"1.0\" encoding=\"UTF-8\" ?>\n");
201 (new DOMElementWriter()).write(doc.getDocumentElement(), wri, 0, " ");
202 wri.flush();
203 // writers do not throw exceptions, so check for them.
204 if (wri.checkError()) {
205 throw new IOException("Error while writing DOM content");
206 }
207 } finally {
208 if (wri != null) {
209 wri.close();
210 out = null;
211 }
212 if (out != null) {
213 out.close();
214 }
215 }
216 }
217
218 /**
219 * <p> Create a DOM tree.
220 * Has 'testsuites' as firstchild and aggregates all
221 * testsuite results that exists in the base directory.
222 * @return the root element of DOM tree that aggregates all testsuites.
223 */
224 protected Element createDocument() {
225 // create the dom tree
226 DocumentBuilder builder = getDocumentBuilder();
227 Document doc = builder.newDocument();
228 Element rootElement = doc.createElement(TESTSUITES);
229 doc.appendChild(rootElement);
230
231 generatedId = 0;
232
233 // get all files and add them to the document
234 File[] files = getFiles();
235 for (int i = 0; i < files.length; i++) {
236 try {
237 log("Parsing file: '" + files[i] + "'", Project.MSG_VERBOSE);
238 //XXX there seems to be a bug in xerces 1.3.0 that doesn't like file object
239 // will investigate later. It does not use the given directory but
240 // the vm dir instead ? Works fine with crimson.
241 Document testsuiteDoc
242 = builder.parse("file:///" + files[i].getAbsolutePath());
243 Element elem = testsuiteDoc.getDocumentElement();
244 // make sure that this is REALLY a testsuite.
245 if (TESTSUITE.equals(elem.getNodeName())) {
246 addTestSuite(rootElement, elem);
247 generatedId++;
248 } else {
249 // issue a warning.
250 log("the file " + files[i]
251 + " is not a valid testsuite XML document",
252 Project.MSG_WARN);
253 }
254 } catch (SAXException e) {
255 // a testcase might have failed and write a zero-length document,
256 // It has already failed, but hey.... mm. just put a warning
257 log("The file " + files[i] + " is not a valid XML document. "
258 + "It is possibly corrupted.", Project.MSG_WARN);
259 log(StringUtils.getStackTrace(e), Project.MSG_DEBUG);
260 } catch (IOException e) {
261 log("Error while accessing file " + files[i] + ": "
262 + e.getMessage(), Project.MSG_ERR);
263 }
264 }
265 return rootElement;
266 }
267
268 /**
269 * <p> Add a new testsuite node to the document.
270 * The main difference is that it
271 * split the previous fully qualified name into a package and a name.
272 * <p> For example: <tt>org.apache.Whatever</tt> will be split into
273 * <tt>org.apache</tt> and <tt>Whatever</tt>.
274 * @param root the root element to which the <tt>testsuite</tt> node should
275 * be appended.
276 * @param testsuite the element to append to the given root. It will slightly
277 * modify the original node to change the name attribute and add
278 * a package one.
279 */
280 protected void addTestSuite(Element root, Element testsuite) {
281 String fullclassname = testsuite.getAttribute(ATTR_NAME);
282 int pos = fullclassname.lastIndexOf('.');
283
284 // a missing . might imply no package at all. Don't get fooled.
285 String pkgName = (pos == -1) ? "" : fullclassname.substring(0, pos);
286 String classname = (pos == -1) ? fullclassname : fullclassname.substring(pos + 1);
287 Element copy = (Element) DOMUtil.importNode(root, testsuite);
288
289 // modify the name attribute and set the package
290 copy.setAttribute(ATTR_NAME, classname);
291 copy.setAttribute(ATTR_PACKAGE, pkgName);
292 copy.setAttribute(ATTR_ID, Integer.toString(generatedId));
293 }
294
295 /**
296 * Create a new document builder. Will issue an <tt>ExceptionInitializerError</tt>
297 * if something is going wrong. It is fatal anyway.
298 * @todo factorize this somewhere else. It is duplicated code.
299 * @return a new document builder to create a DOM
300 */
301 private static DocumentBuilder getDocumentBuilder() {
302 try {
303 return DocumentBuilderFactory.newInstance().newDocumentBuilder();
304 } catch (Exception exc) {
305 throw new ExceptionInInitializerError(exc);
306 }
307 }
308
309}
Note: See TracBrowser for help on using the repository browser.