FYI [icedtea-web] xml output for junit, for daily report - no styles

Jiri Vanek jvanek at redhat.com
Mon May 16 07:06:40 PDT 2011


On 05/16/2011 03:52 PM, Dr Andrew John Hughes wrote:
> On 14:15 Thu 12 May     , Jiri Vanek wrote:
>> 2011-05-12 Jiri Vanek<jvanek at 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
>>
>>
>> Regards J.
>
> Comments below.
>
>> diff -r 0e6b12424423 ChangeLog
>> --- a/ChangeLog	Wed May 11 14:56:32 2011 +0100
>> +++ b/ChangeLog	Thu May 12 14:13:40 2011 +0200
>> @@ -1,3 +1,10 @@
>> +2011-05-12 Jiri Vanek<jvanek at 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
>> +
>>   2011-05-10  Andrew Su<asu at redhat.com>
>>
>>   	* netx/net/sourceforge/jnlp/controlpanel/CachePane.java:
>> diff -r 0e6b12424423 Makefile.am
>> --- a/Makefile.am	Wed May 11 14:56:32 2011 +0100
>> +++ b/Makefile.am	Thu May 12 14:13:40 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
>>
>
> Is there any reason you want these separate and don't just use 2>&1 | tee log ?

yap. I want to keep each stream where it belongs.
I tried something like  > >(tee stdout.log) 2> >(tee stderr.log >&2) m which work fine in shell, but  I was unable to make it work in make :(
>
>>   clean-netx-tests: clean-netx-unit-tests clean-junit-runner
>>   	if [ -e $(TESTS_DIR)/netx ]; then \
>> diff -r 0e6b12424423 tests/junit-runner/CommandLine.java
>> --- a/tests/junit-runner/CommandLine.java	Wed May 11 14:56:32 2011 +0100
>> +++ b/tests/junit-runner/CommandLine.java	Thu May 12 14:13:40 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]));
>> diff -r 0e6b12424423 tests/junit-runner/JunitLikeXmlOutputListener.java
>> --- /dev/null	Thu Jan 01 00:00:00 1970 +0000
>> +++ b/tests/junit-runner/JunitLikeXmlOutputListener.java	Thu May 12 14:13:40 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();
>> +
>> +    }
>> +}
>
> I assume this produces the correct output.  What confuses me is why you didn't use the standard XML APIs
> rather than rolling your own?

After some rather nasty experience with standart java xml api/tools, I fell in love to love Apache axiom for everyhing worthy. And for every simple output much more rather to keep the class stright and without terrible standart api.
If you mind, I will rewrite it. But I would rather to keep it straight-forward.

Regards J.




More information about the distro-pkg-dev mailing list