changeset 238:f1615c79971d

added posibility to export junit testsuite results as xml
author Jiri Vanek <jvanek@redhat.com>
date Tue, 17 May 2011 16:43:45 +0200
parents 0e6b12424423
children d878cc072253
files ChangeLog Makefile.am tests/junit-runner/CommandLine.java tests/junit-runner/JunitLikeXmlOutputListener.java
diffstat 4 files changed, 241 insertions(+), 1 deletions(-) [+]
line wrap: on
line diff
--- a/ChangeLog	Wed May 11 14:56:32 2011 +0100
+++ b/ChangeLog	Tue May 17 16:43:45 2011 +0200
@@ -1,3 +1,12 @@
+2011-05-17 Jiri Vanek <jvanek@redhat.com>
+
+	* tests/junit-runner/JunitLikeXmlOutputListener: This listener exports
+	results of junit in xml which "follows junit-output schema". Extended
+	for date, duration and some statististics for future purpose
+	* Makefile.am (run-netx-unit-tests): backuping stdout/stderr of tests 
+	* tests/junit-runner/CommandLine.java: registered 
+	JunitLikeXmlOutputListener
+
 2011-05-10  Andrew Su  <asu@redhat.com>
 
 	* netx/net/sourceforge/jnlp/controlpanel/CachePane.java:
--- a/Makefile.am	Wed May 11 14:56:32 2011 +0100
+++ b/Makefile.am	Tue May 17 16:43:45 2011 +0200
@@ -472,7 +472,10 @@
 	done ; \
 	echo $$class_names ; \
 	CLASSPATH=$(NETX_DIR)/lib/classes.jar:$(JUNIT_JAR):$(JUNIT_RUNNER_JAR):. \
-	  $(BOOT_DIR)/bin/java -Xbootclasspath:$(RUNTIME) CommandLine $$class_names
+	  $(BOOT_DIR)/bin/java -Xbootclasspath:$(RUNTIME) CommandLine $$class_names \
+	  > stdout.log 2> stderr.log ; \
+	cat stdout.log ; \
+	cat stderr.log >&2
 
 clean-netx-tests: clean-netx-unit-tests clean-junit-runner
 	if [ -e $(TESTS_DIR)/netx ]; then \
--- a/tests/junit-runner/CommandLine.java	Wed May 11 14:56:32 2011 +0100
+++ b/tests/junit-runner/CommandLine.java	Tue May 17 16:43:45 2011 +0200
@@ -7,6 +7,7 @@
  * http://www.eclipse.org/legal/cpl-v10.html
  */
 
+import java.io.File;
 import java.util.ArrayList;
 import java.util.List;
 
@@ -41,6 +42,8 @@
                 system.out().println("ERROR: Could not find class: " + each);
             }
         }
+        RunListener jXmlOutput = new JunitLikeXmlOutputListener(system, new File("tests-output.xml"));
+        addListener(jXmlOutput);
         RunListener listener = new LessVerboseTextListener(system);
         addListener(listener);
         Result result = run(classes.toArray(new Class[0]));
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/tests/junit-runner/JunitLikeXmlOutputListener.java	Tue May 17 16:43:45 2011 +0200
@@ -0,0 +1,225 @@
+/*
+ * Copyright 2011 Red Hat, Inc.
+ *
+ * This file is made available under the terms of the Common Public License
+ * v1.0 which accompanies this distribution, and is available at
+ * http://www.eclipse.org/legal/cpl-v10.html
+ */
+
+import java.io.BufferedWriter;
+import java.io.File;
+
+import java.io.FileOutputStream;
+import java.io.IOException;
+import java.io.OutputStreamWriter;
+import java.text.DecimalFormat;
+import java.text.NumberFormat;
+import java.util.Date;
+import java.util.HashMap;
+import java.util.Map;
+import java.util.Map.Entry;
+import java.util.Set;
+
+
+import org.junit.internal.JUnitSystem;
+import org.junit.runner.Description;
+import org.junit.runner.Result;
+import org.junit.runner.notification.Failure;
+import org.junit.runner.notification.RunListener;
+/**
+ * This class listens for events in junit testsuite and wrote output to xml.
+ * Xml tryes to follow ant-tests schema, and is enriched for by-class statistics
+ * stdout and err elements are added, but must be filled from elsewhere (eg tee
+ * in make) as junit suite and listener run from our executer have no access to
+ * them.
+ * 
+ */
+public class JunitLikeXmlOutputListener extends RunListener {
+
+    private BufferedWriter writer;
+    private Failure testFailed = null;
+    private static final String ROOT = "testsuite";
+    private static final String DATE_ELEMENT = "date";
+    private static final String TEST_ELEMENT = "testcase";
+    private static final String TEST_NAME_ATTRIBUTE = "name";
+    private static final String TEST_TIME_ATTRIBUTE = "time";
+    private static final String TEST_ERROR_ELEMENT = "error";
+    private static final String TEST_CLASS_ATTRIBUTE = "classname";
+    private static final String ERROR_MESSAGE_ATTRIBUTE = "message";
+    private static final String ERROR_TYPE_ATTRIBUTE = "type";
+    private static final String SOUT_ELEMENT = "system-out";
+    private static final String SERR_ELEMENT = "system-err";
+    private static final String CDATA_START = "<![CDATA[";
+    private static final String CDATA_END = "]]>";
+    private static final String TEST_CLASS_ELEMENT = "class";
+    private static final String STATS_ELEMENT = "stats";
+    private static final String CLASSES_ELEMENT = "classes";
+    private static final String SUMMARY_ELEMENT = "summary";
+    private static final String SUMMARY_TOTAL_ELEMENT = "total";
+    private static final String SUMMARY_PASSED_ELEMENT = "passed";
+    private static final String SUMMARY_FAILED_ELEMENT = "failed";
+    private static final String SUMMARY_IGNORED_ELEMENT = "ignored";
+    private long testStart;
+
+    private class ClassCounter {
+
+        int total;
+        int failed;
+        int passed;
+        long time = 0;
+    }
+    Map<String, ClassCounter> classStats = new HashMap<String, ClassCounter>();
+
+    public JunitLikeXmlOutputListener(JUnitSystem system, File f) {
+        try {
+            writer = new BufferedWriter(new OutputStreamWriter(new FileOutputStream(f), "UTF-8"));
+        } catch (Exception ex) {
+            throw new RuntimeException(ex);
+        }
+    }
+
+    @Override
+    public void testRunStarted(Description description) throws Exception {
+        openElement(ROOT);
+        writeElement(DATE_ELEMENT, new Date().toString());
+    }
+
+    private void openElement(String name) throws IOException {
+        openElement(name, null);
+    }
+
+    private void openElement(String name, Map<String, String> atts) throws IOException {
+        StringBuilder attString = new StringBuilder();
+        if (atts != null) {
+            attString.append(" ");
+            Set<Entry<String, String>> entries = atts.entrySet();
+            for (Entry<String, String> entry : entries) {
+                attString.append(entry.getKey()).append("=\"").append(attributize(entry.getValue())).append("\"");
+                attString.append(" ");
+            }
+        }
+        writer.write("<" + name + attString.toString() + ">");
+        writer.newLine();
+    }
+
+    private static String attributize(String s) {
+        return s.replace("&", "&amp;").replace("<", "&lt;").replace("\"","&quot;");
+    }
+
+    private void closeElement(String name) throws IOException {
+        writer.newLine();
+        writer.write("</" + name + ">");
+        writer.newLine();
+    }
+
+    private void writeContent(String content) throws IOException {
+        writer.write(CDATA_START + content + CDATA_END);
+    }
+
+    private void writeElement(String name, String content) throws IOException {
+        writeElement(name, content, null);
+    }
+
+    private void writeElement(String name, String content, Map<String, String> atts) throws IOException {
+        openElement(name, atts);
+        writeContent(content);
+        closeElement(name);
+    }
+
+    @Override
+    public void testStarted(Description description) throws Exception {
+        testFailed = null;
+        testStart = System.nanoTime()/1000l/1000l;
+    }
+
+    @Override
+    public void testFailure(Failure failure) throws IOException {
+        testFailed = failure;
+    }
+
+    @Override
+    public void testFinished(org.junit.runner.Description description) throws Exception {
+        long testTime = System.nanoTime()/1000l/1000l - testStart;
+        double testTimeSeconds = ((double) testTime) / 1000d;
+
+        Map<String, String> testcaseAtts = new HashMap<String, String>(3);
+        NumberFormat formatter = new DecimalFormat("#0.0000");
+        String stringedTime = formatter.format(testTimeSeconds);
+        stringedTime.replace(",", ".");
+        testcaseAtts.put(TEST_TIME_ATTRIBUTE, stringedTime);
+        testcaseAtts.put(TEST_CLASS_ATTRIBUTE, description.getClassName());
+        testcaseAtts.put(TEST_NAME_ATTRIBUTE, description.getMethodName());
+
+        openElement(TEST_ELEMENT, testcaseAtts);
+        if (testFailed != null) {
+            Map<String, String> errorAtts = new HashMap<String, String>(3);
+
+            errorAtts.put(ERROR_MESSAGE_ATTRIBUTE, testFailed.getMessage());
+            int i = testFailed.getTrace().indexOf(":");
+            if (i >= 0) {
+                errorAtts.put(ERROR_TYPE_ATTRIBUTE, testFailed.getTrace().substring(0, i));
+            } else {
+                errorAtts.put(ERROR_TYPE_ATTRIBUTE, "?");
+            }
+
+            writeElement(TEST_ERROR_ELEMENT, testFailed.getTrace(), errorAtts);
+        }
+
+        closeElement(TEST_ELEMENT);
+        writer.flush();
+
+        ClassCounter cc = classStats.get(description.getClassName());
+        if (cc == null) {
+            cc = new ClassCounter();
+            classStats.put(description.getClassName(), cc);
+        }
+        cc.total++;
+        cc.time += testTime;
+        if (testFailed == null) {
+            cc.passed++;
+        } else {
+
+            cc.failed++;
+        }
+    }
+
+    @Override
+    public void testRunFinished(Result result) throws Exception {
+
+        writeElement(SOUT_ELEMENT, "@sout@");
+        writeElement(SERR_ELEMENT, "@serr@");
+        openElement(STATS_ELEMENT);
+        openElement(SUMMARY_ELEMENT);
+        int passed = result.getRunCount() - result.getFailureCount() - result.getIgnoreCount();
+        int failed = result.getFailureCount();
+        int ignored = result.getIgnoreCount();
+        writeElement(SUMMARY_TOTAL_ELEMENT, String.valueOf(result.getRunCount()));
+        writeElement(SUMMARY_FAILED_ELEMENT, String.valueOf(failed));
+        writeElement(SUMMARY_IGNORED_ELEMENT, String.valueOf(ignored));
+        writeElement(SUMMARY_PASSED_ELEMENT, String.valueOf(passed));
+        closeElement(SUMMARY_ELEMENT);
+        openElement(CLASSES_ELEMENT);
+        Set<Entry<String, ClassCounter>> e = classStats.entrySet();
+        for (Entry<String, ClassCounter> entry : e) {
+
+            Map<String, String> testcaseAtts = new HashMap<String, String>(3);
+            testcaseAtts.put(TEST_NAME_ATTRIBUTE, entry.getKey());
+            testcaseAtts.put(TEST_TIME_ATTRIBUTE, String.valueOf(entry.getValue().time));
+
+            openElement(TEST_CLASS_ELEMENT, testcaseAtts);
+            writeElement(SUMMARY_PASSED_ELEMENT, String.valueOf(entry.getValue().passed));
+            writeElement(SUMMARY_FAILED_ELEMENT, String.valueOf(entry.getValue().failed));
+            writeElement(SUMMARY_IGNORED_ELEMENT, String.valueOf(entry.getValue().total - entry.getValue().failed - entry.getValue().passed));
+            writeElement(SUMMARY_TOTAL_ELEMENT, String.valueOf(entry.getValue().total));
+
+            closeElement(TEST_CLASS_ELEMENT);
+        }
+        closeElement(CLASSES_ELEMENT);
+        closeElement(STATS_ELEMENT);
+
+        closeElement(ROOT);
+        writer.flush();
+        writer.close();
+
+    }
+}