2
* Copyright 2011 Red Hat, Inc.
4
* This file is made available under the terms of the Common Public License
5
* v1.0 which accompanies this distribution, and is available at
6
* http://www.eclipse.org/legal/cpl-v10.html
9
import java.io.BufferedWriter;
12
import java.io.FileOutputStream;
13
import java.io.IOException;
14
import java.io.OutputStreamWriter;
15
import java.text.DecimalFormat;
16
import java.text.NumberFormat;
17
import java.util.Date;
18
import java.util.HashMap;
20
import java.util.Map.Entry;
24
import org.junit.internal.JUnitSystem;
25
import org.junit.runner.Description;
26
import org.junit.runner.Result;
27
import org.junit.runner.notification.Failure;
28
import org.junit.runner.notification.RunListener;
30
* This class listens for events in junit testsuite and wrote output to xml.
31
* Xml tryes to follow ant-tests schema, and is enriched for by-class statistics
32
* stdout and err elements are added, but must be filled from elsewhere (eg tee
33
* in make) as junit suite and listener run from our executer have no access to
37
public class JunitLikeXmlOutputListener extends RunListener {
39
private BufferedWriter writer;
40
private Failure testFailed = null;
41
private static final String ROOT = "testsuite";
42
private static final String DATE_ELEMENT = "date";
43
private static final String TEST_ELEMENT = "testcase";
44
private static final String TEST_NAME_ATTRIBUTE = "name";
45
private static final String TEST_TIME_ATTRIBUTE = "time";
46
private static final String TEST_ERROR_ELEMENT = "error";
47
private static final String TEST_CLASS_ATTRIBUTE = "classname";
48
private static final String ERROR_MESSAGE_ATTRIBUTE = "message";
49
private static final String ERROR_TYPE_ATTRIBUTE = "type";
50
private static final String SOUT_ELEMENT = "system-out";
51
private static final String SERR_ELEMENT = "system-err";
52
private static final String CDATA_START = "<![CDATA[";
53
private static final String CDATA_END = "]]>";
54
private static final String TEST_CLASS_ELEMENT = "class";
55
private static final String STATS_ELEMENT = "stats";
56
private static final String CLASSES_ELEMENT = "classes";
57
private static final String SUMMARY_ELEMENT = "summary";
58
private static final String SUMMARY_TOTAL_ELEMENT = "total";
59
private static final String SUMMARY_PASSED_ELEMENT = "passed";
60
private static final String SUMMARY_FAILED_ELEMENT = "failed";
61
private static final String SUMMARY_IGNORED_ELEMENT = "ignored";
62
private long testStart;
64
private class ClassCounter {
71
Map<String, ClassCounter> classStats = new HashMap<String, ClassCounter>();
73
public JunitLikeXmlOutputListener(JUnitSystem system, File f) {
75
writer = new BufferedWriter(new OutputStreamWriter(new FileOutputStream(f), "UTF-8"));
76
} catch (Exception ex) {
77
throw new RuntimeException(ex);
82
public void testRunStarted(Description description) throws Exception {
84
writeElement(DATE_ELEMENT, new Date().toString());
87
private void openElement(String name) throws IOException {
88
openElement(name, null);
91
private void openElement(String name, Map<String, String> atts) throws IOException {
92
StringBuilder attString = new StringBuilder();
94
attString.append(" ");
95
Set<Entry<String, String>> entries = atts.entrySet();
96
for (Entry<String, String> entry : entries) {
97
attString.append(entry.getKey()).append("=\"").append(attributize(entry.getValue())).append("\"");
98
attString.append(" ");
101
writer.write("<" + name + attString.toString() + ">");
105
private static String attributize(String s) {
106
return s.replace("&", "&").replace("<", "<").replace("\"",""");
109
private void closeElement(String name) throws IOException {
111
writer.write("</" + name + ">");
115
private void writeContent(String content) throws IOException {
116
writer.write(CDATA_START + content + CDATA_END);
119
private void writeElement(String name, String content) throws IOException {
120
writeElement(name, content, null);
123
private void writeElement(String name, String content, Map<String, String> atts) throws IOException {
124
openElement(name, atts);
125
writeContent(content);
130
public void testStarted(Description description) throws Exception {
132
testStart = System.nanoTime()/1000l/1000l;
136
public void testFailure(Failure failure) throws IOException {
137
testFailed = failure;
141
public void testFinished(org.junit.runner.Description description) throws Exception {
142
long testTime = System.nanoTime()/1000l/1000l - testStart;
143
double testTimeSeconds = ((double) testTime) / 1000d;
145
Map<String, String> testcaseAtts = new HashMap<String, String>(3);
146
NumberFormat formatter = new DecimalFormat("#0.0000");
147
String stringedTime = formatter.format(testTimeSeconds);
148
stringedTime.replace(",", ".");
149
testcaseAtts.put(TEST_TIME_ATTRIBUTE, stringedTime);
150
testcaseAtts.put(TEST_CLASS_ATTRIBUTE, description.getClassName());
151
testcaseAtts.put(TEST_NAME_ATTRIBUTE, description.getMethodName());
153
openElement(TEST_ELEMENT, testcaseAtts);
154
if (testFailed != null) {
155
Map<String, String> errorAtts = new HashMap<String, String>(3);
157
errorAtts.put(ERROR_MESSAGE_ATTRIBUTE, testFailed.getMessage());
158
int i = testFailed.getTrace().indexOf(":");
160
errorAtts.put(ERROR_TYPE_ATTRIBUTE, testFailed.getTrace().substring(0, i));
162
errorAtts.put(ERROR_TYPE_ATTRIBUTE, "?");
165
writeElement(TEST_ERROR_ELEMENT, testFailed.getTrace(), errorAtts);
168
closeElement(TEST_ELEMENT);
171
ClassCounter cc = classStats.get(description.getClassName());
173
cc = new ClassCounter();
174
classStats.put(description.getClassName(), cc);
178
if (testFailed == null) {
187
public void testRunFinished(Result result) throws Exception {
189
writeElement(SOUT_ELEMENT, "@sout@");
190
writeElement(SERR_ELEMENT, "@serr@");
191
openElement(STATS_ELEMENT);
192
openElement(SUMMARY_ELEMENT);
193
int passed = result.getRunCount() - result.getFailureCount() - result.getIgnoreCount();
194
int failed = result.getFailureCount();
195
int ignored = result.getIgnoreCount();
196
writeElement(SUMMARY_TOTAL_ELEMENT, String.valueOf(result.getRunCount()));
197
writeElement(SUMMARY_FAILED_ELEMENT, String.valueOf(failed));
198
writeElement(SUMMARY_IGNORED_ELEMENT, String.valueOf(ignored));
199
writeElement(SUMMARY_PASSED_ELEMENT, String.valueOf(passed));
200
closeElement(SUMMARY_ELEMENT);
201
openElement(CLASSES_ELEMENT);
202
Set<Entry<String, ClassCounter>> e = classStats.entrySet();
203
for (Entry<String, ClassCounter> entry : e) {
205
Map<String, String> testcaseAtts = new HashMap<String, String>(3);
206
testcaseAtts.put(TEST_NAME_ATTRIBUTE, entry.getKey());
207
testcaseAtts.put(TEST_TIME_ATTRIBUTE, String.valueOf(entry.getValue().time));
209
openElement(TEST_CLASS_ELEMENT, testcaseAtts);
210
writeElement(SUMMARY_PASSED_ELEMENT, String.valueOf(entry.getValue().passed));
211
writeElement(SUMMARY_FAILED_ELEMENT, String.valueOf(entry.getValue().failed));
212
writeElement(SUMMARY_IGNORED_ELEMENT, String.valueOf(entry.getValue().total - entry.getValue().failed - entry.getValue().passed));
213
writeElement(SUMMARY_TOTAL_ELEMENT, String.valueOf(entry.getValue().total));
215
closeElement(TEST_CLASS_ELEMENT);
217
closeElement(CLASSES_ELEMENT);
218
closeElement(STATS_ELEMENT);