001: /* XMLFormatter.java */
002:
003: package org.quilt.reports;
004:
005: import java.io.OutputStream;
006: import java.io.Writer;
007: import java.io.OutputStreamWriter;
008: import java.io.IOException;
009: import java.util.Properties;
010: import java.util.Enumeration;
011: import java.util.Hashtable;
012: import javax.xml.parsers.DocumentBuilder;
013: import javax.xml.parsers.DocumentBuilderFactory;
014:
015: import org.w3c.dom.Document;
016: import org.w3c.dom.Element;
017: import org.w3c.dom.Text;
018:
019: import org.apache.tools.ant.BuildException;
020: import org.apache.tools.ant.util.DOMElementWriter;
021:
022: import junit.framework.*;
023:
024: import org.quilt.framework.*;
025: import org.quilt.runner.Runner;
026:
027: /**
028: * Produce an XML document containing the test data for the run.
029: * This will in general contain the results of many tests, but
030: * only those resulting from one QuiltTest.
031: *
032: * @todo Restructure to produce one XML document for the entire
033: * Ant/Quilt run. This needs to be held by the class
034: * managing the whole run, QuiltTask if it is an Ant run.
035: * Simplest solution: when the test is not forked, pass back
036: * the document as a tree instead of serializing it in text
037: * form to output. The manager can then merge the trees and
038: * output a single document for the run.
039: *
040: * @todo Add flag to suppress generation of (quite verbose) properties
041: * element.
042: */
043: public class XMLFormatter implements Formatter {
044:
045: /** Whether to filter Ant/Quilt/JUnit stack traces. */
046: private boolean filtertrace = false;
047:
048: // MODIFY TO EXTEND BaseFormatter, then drop this ///////////////
049: /**
050: * Root around in a junit Test and find a name, should there be one.
051: * @return Test/suite name.
052: */
053: protected static String getTestName(Test test) {
054: if (test instanceof TestSuite) {
055: return ((TestSuite) test).getName();
056: } else if (test instanceof TestCase) {
057: return ((TestCase) test).getName();
058: } else {
059: return "unknown";
060: }
061: }
062:
063: // END BaseFormatter CODE ///////////////////////////////////////
064:
065: private static DocumentBuilderFactory dbf = null;
066:
067: // none of this is thread safe; JavaDocs recommend ensuring that
068: // there is only one DocumentBuilder per thread
069: private static DocumentBuilder getDocumentBuilder() {
070: try {
071: if (dbf == null) {
072: dbf = DocumentBuilderFactory.newInstance();
073: }
074: return dbf.newDocumentBuilder();
075: } catch (Exception exc) {
076: throw new ExceptionInInitializerError(exc);
077: }
078: }
079:
080: /**
081: * The XML document. Unfortunately one XML Document is produced
082: * per QuiltTest. Generally there will be many QuiltTests per run.
083: */
084: private Document doc;
085: /** Where the output goes. */
086: private OutputStream out;
087: /** Root node, the Ant/Quilt test run as a whole. */
088: private Element rootNode;
089: /** The runner, usually an instance of runner.BaseTestRunner. */
090: private Runner runner = null;
091: /** Nodes in the document that tests hang off of. */
092: private Hashtable testNodes = new Hashtable(); // key = Test test
093: /** Hash holding information about individual tests. */
094: private Hashtable testStarts = new Hashtable();
095:
096: public XMLFormatter() {
097: }
098:
099: // FORMATTER INTERFACE //////////////////////////////////////////
100:
101: /** Method called at end of test run. */
102:
103: public void endTestSuite(QuiltTest qt) throws BuildException {
104: rootNode.setAttribute("tests", "" + qt.runCount());
105: rootNode.setAttribute("errors", "" + qt.errorCount());
106: rootNode.setAttribute("failures", "" + qt.failureCount());
107: rootNode.setAttribute("time", "" + (qt.getRunTime() / 1000.0));
108: if (out != null) {
109: Writer wri = null;
110: try {
111: wri = new OutputStreamWriter(out, "UTF8");
112: wri
113: .write("<?xml version=\"1.0\" encoding=\"UTF-8\" ?>\n");
114: (new DOMElementWriter()).write(rootNode, wri, 0, " ");
115: wri.flush();
116: } catch (IOException e) {
117: throw new BuildException("Unable to write log file", e);
118: } finally {
119: if (out != System.out && out != System.err) {
120: if (wri != null) {
121: try {
122: wri.close();
123: } catch (IOException e) {
124: }
125: }
126: }
127: }
128: }
129: }
130:
131: /** Enable filtering of Ant/Quilt/JUnit lines from stack traces. */
132: public void setFiltertrace(boolean b) {
133: filtertrace = b;
134: }
135:
136: /** Set the output file. */
137: public void setOutput(OutputStream out) {
138: this .out = out;
139: }
140:
141: /** Set the test runner to be used. */
142: public void setRunner(Runner testrunner) {
143: runner = testrunner;
144: }
145:
146: /** Direct the error output. */
147: public void setSystemError(String out) {
148: formatOutput("system-err", out);
149: }
150:
151: /** Direct standard output. */
152: public void setSystemOutput(String out) {
153: formatOutput("system-out", out);
154: }
155:
156: /** Method called at the beginning of the test run. */
157: public void startTestSuite(QuiltTest qt) {
158: doc = getDocumentBuilder().newDocument();
159: rootNode = doc.createElement("testsuite");
160: rootNode.setAttribute("name", qt.getName());
161:
162: // Output properties - this creates a lot of meaningless
163: // data, far exceeding the JUnit data of real interest.
164: Element propsElement = doc.createElement("properties");
165: rootNode.appendChild(propsElement);
166: Properties props = qt.getProperties();
167: if (props != null) {
168: Enumeration e = props.propertyNames();
169: while (e.hasMoreElements()) {
170: String name = (String) e.nextElement();
171: Element propElement = doc.createElement("property");
172: propElement.setAttribute("name", name);
173: propElement.setAttribute("value", props
174: .getProperty(name));
175: propsElement.appendChild(propElement);
176: }
177: }
178: }
179:
180: // TESTLISTENER INTERFACE ///////////////////////////////////////
181: /** Method called when an unexpected error occurs. */
182: public void addError(Test test, Throwable t) {
183: formatError("error", test, t);
184: }
185:
186: /** Method called when a failure (or unexpected error) occurs. */
187: public void addFailure(Test test, Throwable t) {
188: formatError("failure", test, t);
189: }
190:
191: /** Method called when a failure (or unexpected error) occurs. */
192: public void addFailure(Test test, AssertionFailedError t) {
193: addFailure(test, (Throwable) t);
194: }
195:
196: /** Method called when a JUnit test ends. */
197: public void endTest(Test test) {
198: Element testNode = (Element) testNodes.get(test);
199: if (testNode == null) {
200: startTest(test);
201: testNode = (Element) testNodes.get(test);
202: }
203: Long l = (Long) testStarts.get(test);
204: testNode
205: .setAttribute("time",
206: ""
207: + ((System.currentTimeMillis() - l
208: .longValue()) / 1000.0));
209: }
210:
211: /** Called at the beginning of a JUnit test. */
212: public void startTest(Test test) {
213: testStarts.put(test, new Long(System.currentTimeMillis()));
214: Element testNode = doc.createElement("testcase");
215: testNode.setAttribute("name", getTestName(test));
216: rootNode.appendChild(testNode);
217: testNodes.put(test, testNode);
218: }
219:
220: // OTHER METHODS ////////////////////////////////////////////////
221: /** Hang an error message off the document tree. */
222: private void formatError(String type, Test test, Throwable t) {
223: if (test != null) {
224: endTest(test);
225: }
226: Element msgNode = doc.createElement(type);
227: Element curNode = null;
228: if (test != null) {
229: curNode = (Element) testNodes.get(test);
230: } else {
231: curNode = rootNode;
232: }
233:
234: curNode.appendChild(msgNode);
235:
236: String message = t.getMessage();
237: if (message != null && message.length() > 0) {
238: msgNode.setAttribute("message", t.getMessage());
239: }
240: msgNode.setAttribute("type", t.getClass().getName());
241:
242: String strace = runner.getFilteredTrace(t, filtertrace);
243: Text trace = doc.createTextNode(strace);
244: msgNode.appendChild(trace);
245: }
246:
247: /**
248: * Hang a text message off the document tree. The message
249: * might be quite long, for example all System.err output
250: * for the run.
251: */
252: private void formatOutput(String type, String output) {
253: Element txtNode = doc.createElement(type);
254: rootNode.appendChild(txtNode);
255: txtNode.appendChild(doc.createCDATASection(output));
256: }
257: }
|