001: /*
002: * Licensed to the Apache Software Foundation (ASF) under one or more
003: * contributor license agreements. See the NOTICE file distributed with
004: * this work for additional information regarding copyright ownership.
005: * The ASF licenses this file to You under the Apache License, Version 2.0
006: * (the "License"); you may not use this file except in compliance with
007: * the License. You may obtain a copy of the License at
008: *
009: * http://www.apache.org/licenses/LICENSE-2.0
010: *
011: * Unless required by applicable law or agreed to in writing, software
012: * distributed under the License is distributed on an "AS IS" BASIS,
013: * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
014: * See the License for the specific language governing permissions and
015: * limitations under the License.
016: *
017: */
018:
019: package org.apache.tools.ant.taskdefs.optional.junit;
020:
021: import java.io.BufferedWriter;
022: import java.io.IOException;
023: import java.io.OutputStream;
024: import java.io.OutputStreamWriter;
025: import java.io.Writer;
026: import java.util.Enumeration;
027: import java.util.Hashtable;
028: import java.util.Properties;
029: import java.util.Date;
030: import java.net.InetAddress;
031: import java.net.UnknownHostException;
032: import javax.xml.parsers.DocumentBuilder;
033: import javax.xml.parsers.DocumentBuilderFactory;
034: import junit.framework.AssertionFailedError;
035: import junit.framework.Test;
036: import org.apache.tools.ant.BuildException;
037: import org.apache.tools.ant.util.DOMElementWriter;
038: import org.apache.tools.ant.util.DateUtils;
039: import org.apache.tools.ant.util.FileUtils;
040: import org.w3c.dom.Document;
041: import org.w3c.dom.Element;
042: import org.w3c.dom.Text;
043:
044: /**
045: * Prints XML output of the test to a specified Writer.
046: *
047: * @see FormatterElement
048: */
049:
050: public class XMLJUnitResultFormatter implements JUnitResultFormatter,
051: XMLConstants {
052:
053: /** constant for unnnamed testsuites/cases */
054: private static final String UNKNOWN = "unknown";
055:
056: private static DocumentBuilder getDocumentBuilder() {
057: try {
058: return DocumentBuilderFactory.newInstance()
059: .newDocumentBuilder();
060: } catch (Exception exc) {
061: throw new ExceptionInInitializerError(exc);
062: }
063: }
064:
065: /**
066: * The XML document.
067: */
068: private Document doc;
069: /**
070: * The wrapper for the whole testsuite.
071: */
072: private Element rootElement;
073: /**
074: * Element for the current test.
075: */
076: private Hashtable testElements = new Hashtable();
077: /**
078: * tests that failed.
079: */
080: private Hashtable failedTests = new Hashtable();
081: /**
082: * Timing helper.
083: */
084: private Hashtable testStarts = new Hashtable();
085: /**
086: * Where to write the log to.
087: */
088: private OutputStream out;
089:
090: /** No arg constructor. */
091: public XMLJUnitResultFormatter() {
092: }
093:
094: /** {@inheritDoc}. */
095: public void setOutput(OutputStream out) {
096: this .out = out;
097: }
098:
099: /** {@inheritDoc}. */
100: public void setSystemOutput(String out) {
101: formatOutput(SYSTEM_OUT, out);
102: }
103:
104: /** {@inheritDoc}. */
105: public void setSystemError(String out) {
106: formatOutput(SYSTEM_ERR, out);
107: }
108:
109: /**
110: * The whole testsuite started.
111: * @param suite the testsuite.
112: */
113: public void startTestSuite(JUnitTest suite) {
114: doc = getDocumentBuilder().newDocument();
115: rootElement = doc.createElement(TESTSUITE);
116: String n = suite.getName();
117: rootElement.setAttribute(ATTR_NAME, n == null ? UNKNOWN : n);
118:
119: //add the timestamp
120: final String timestamp = DateUtils.format(new Date(),
121: DateUtils.ISO8601_DATETIME_PATTERN);
122: rootElement.setAttribute(TIMESTAMP, timestamp);
123: //and the hostname.
124: rootElement.setAttribute(HOSTNAME, getHostname());
125:
126: // Output properties
127: Element propsElement = doc.createElement(PROPERTIES);
128: rootElement.appendChild(propsElement);
129: Properties props = suite.getProperties();
130: if (props != null) {
131: Enumeration e = props.propertyNames();
132: while (e.hasMoreElements()) {
133: String name = (String) e.nextElement();
134: Element propElement = doc.createElement(PROPERTY);
135: propElement.setAttribute(ATTR_NAME, name);
136: propElement.setAttribute(ATTR_VALUE, props
137: .getProperty(name));
138: propsElement.appendChild(propElement);
139: }
140: }
141: }
142:
143: /**
144: * get the local hostname
145: * @return the name of the local host, or "localhost" if we cannot work it out
146: */
147: private String getHostname() {
148: try {
149: return InetAddress.getLocalHost().getHostName();
150: } catch (UnknownHostException e) {
151: return "localhost";
152: }
153: }
154:
155: /**
156: * The whole testsuite ended.
157: * @param suite the testsuite.
158: * @throws BuildException on error.
159: */
160: public void endTestSuite(JUnitTest suite) throws BuildException {
161: rootElement.setAttribute(ATTR_TESTS, "" + suite.runCount());
162: rootElement.setAttribute(ATTR_FAILURES, ""
163: + suite.failureCount());
164: rootElement.setAttribute(ATTR_ERRORS, "" + suite.errorCount());
165: rootElement.setAttribute(ATTR_TIME, ""
166: + (suite.getRunTime() / 1000.0));
167: if (out != null) {
168: Writer wri = null;
169: try {
170: wri = new BufferedWriter(new OutputStreamWriter(out,
171: "UTF8"));
172: wri
173: .write("<?xml version=\"1.0\" encoding=\"UTF-8\" ?>\n");
174: (new DOMElementWriter()).write(rootElement, wri, 0,
175: " ");
176: wri.flush();
177: } catch (IOException exc) {
178: throw new BuildException("Unable to write log file",
179: exc);
180: } finally {
181: if (out != System.out && out != System.err) {
182: FileUtils.close(wri);
183: }
184: }
185: }
186: }
187:
188: /**
189: * Interface TestListener.
190: *
191: * <p>A new Test is started.
192: * @param t the test.
193: */
194: public void startTest(Test t) {
195: testStarts.put(t, new Long(System.currentTimeMillis()));
196: }
197:
198: /**
199: * Interface TestListener.
200: *
201: * <p>A Test is finished.
202: * @param test the test.
203: */
204: public void endTest(Test test) {
205: // Fix for bug #5637 - if a junit.extensions.TestSetup is
206: // used and throws an exception during setUp then startTest
207: // would never have been called
208: if (!testStarts.containsKey(test)) {
209: startTest(test);
210: }
211:
212: Element currentTest = null;
213: if (!failedTests.containsKey(test)) {
214: currentTest = doc.createElement(TESTCASE);
215: String n = JUnitVersionHelper.getTestCaseName(test);
216: currentTest
217: .setAttribute(ATTR_NAME, n == null ? UNKNOWN : n);
218: // a TestSuite can contain Tests from multiple classes,
219: // even tests with the same name - disambiguate them.
220: currentTest.setAttribute(ATTR_CLASSNAME, JUnitVersionHelper
221: .getTestCaseClassName(test));
222: rootElement.appendChild(currentTest);
223: testElements.put(test, currentTest);
224: } else {
225: currentTest = (Element) testElements.get(test);
226: }
227:
228: Long l = (Long) testStarts.get(test);
229: currentTest
230: .setAttribute(ATTR_TIME,
231: ""
232: + ((System.currentTimeMillis() - l
233: .longValue()) / 1000.0));
234: }
235:
236: /**
237: * Interface TestListener for JUnit <= 3.4.
238: *
239: * <p>A Test failed.
240: * @param test the test.
241: * @param t the exception.
242: */
243: public void addFailure(Test test, Throwable t) {
244: formatError(FAILURE, test, t);
245: }
246:
247: /**
248: * Interface TestListener for JUnit > 3.4.
249: *
250: * <p>A Test failed.
251: * @param test the test.
252: * @param t the assertion.
253: */
254: public void addFailure(Test test, AssertionFailedError t) {
255: addFailure(test, (Throwable) t);
256: }
257:
258: /**
259: * Interface TestListener.
260: *
261: * <p>An error occurred while running the test.
262: * @param test the test.
263: * @param t the error.
264: */
265: public void addError(Test test, Throwable t) {
266: formatError(ERROR, test, t);
267: }
268:
269: private void formatError(String type, Test test, Throwable t) {
270: if (test != null) {
271: endTest(test);
272: failedTests.put(test, test);
273: }
274:
275: Element nested = doc.createElement(type);
276: Element currentTest = null;
277: if (test != null) {
278: currentTest = (Element) testElements.get(test);
279: } else {
280: currentTest = rootElement;
281: }
282:
283: currentTest.appendChild(nested);
284:
285: String message = t.getMessage();
286: if (message != null && message.length() > 0) {
287: nested.setAttribute(ATTR_MESSAGE, t.getMessage());
288: }
289: nested.setAttribute(ATTR_TYPE, t.getClass().getName());
290:
291: String strace = JUnitTestRunner.getFilteredTrace(t);
292: Text trace = doc.createTextNode(strace);
293: nested.appendChild(trace);
294: }
295:
296: private void formatOutput(String type, String output) {
297: Element nested = doc.createElement(type);
298: rootElement.appendChild(nested);
299: nested.appendChild(doc.createCDATASection(output));
300: }
301:
302: } // XMLJUnitResultFormatter
|