Ant JUnit Nice Output Formatter

Nice output formatter for Apache Ant + JUnit

I've seached for nice Apache Ant formatter which do not writes full stack trace in console/file and just writes what methods are passed, what methods are failed, and if failed - where and why they failed (and only trace about methods from test class). I haven't found any. So I've written one.

For example:

    [junit] ----------------------------------------------------------
    [junit] Testsuite: com.undefined.MyTest
    [junit] Ran [0.322] testMethodOne ... OK
    [junit] Ran [0.023] testMethodOne... OK
    [junit] Ran [0.333] testMethodTwo ... FAILED
    [junit] Ran [0.343] testMethodThree ... FAILED
    [junit] 
    [junit] Testcase: testMethodTwo(com.undefined.MyTest):    FAILED
    [junit]     (AssertionFailedError): expected:<Bender> but was:<null>
    [junit]     (MyTest) assertEqualsInfo: 887
    [junit]     (MyTest) testGetUserInfoByUserId: 188

There is ant task for this:

    <target name="easy-test" depends="test-clean, compile">
        <mkdir dir="out/junit"/>
 
        <junit printsummary="off" fork="on" 
            failureproperty="test.failed" showoutput="off" dir="out"
           outputtoformatters="false" filtertrace="on" >
 
            <classpath>
 
               ....
 
            </classpath>
 
            <formatter classname="com.undefined.testing.OneLinerFormatter" usefile="false" />
 
            <batchtest fork="on" todir="out/junit" filtertrace="on">
                <fileset dir="src/java/test">
                    <include name="**/*Test.java"/>
                </fileset>                                
            </batchtest>
 
        </junit>
 
        <fail if="test.failed">tests.failed=${test.failed}</fail>
 
    </target>

And finally, the implementation:

package com.undefined.testing;
 
import java.io.BufferedReader;
import java.io.OutputStream;
import java.io.PrintWriter;
import java.io.StringReader;
import java.io.StringWriter;
import java.text.NumberFormat;
import java.util.Hashtable;
import java.util.regex.Matcher;
import java.util.regex.Pattern;
 
import junit.framework.AssertionFailedError;
import junit.framework.Test;
 
import org.apache.tools.ant.taskdefs.optional.junit.JUnitResultFormatter;
import org.apache.tools.ant.taskdefs.optional.junit.JUnitTest;
import org.apache.tools.ant.taskdefs.optional.junit.JUnitVersionHelper;
 
import org.apache.tools.ant.util.FileUtils;
import org.apache.tools.ant.util.StringUtils;
 
/**
 * Prints a single lines of tests to a specified Writer.
 * Inspired by the BriefJUnitResultFormatter and
 * XMLJUnitResultFormatter.
 *
 * @see FormatterElement
 * @see BriefJUnitResultFormatter
 * @see XMLJUnitResultFormatter
 */
 
public class OneLinerFormatter implements JUnitResultFormatter {
 
    private final String TAB_STR = "    ";
 
    private final boolean showCausesLines = true;
    // (\w+\.)+(\w+)\((\w+).(?:\w+):(\d+)\)
    private final Pattern traceLinePattern = Pattern.compile("(\\w+\\.)+(\\w+)\\((\\w+).(?:\\w+):(\\d+)\\)");
 
    /**
     * Where to write the log to.
     */
    private OutputStream out;
 
    /**
     * Used for writing the results.
     */
    private PrintWriter output;
 
    /**
     * Used as part of formatting the results.
     */
    private StringWriter results;
 
    /**
     * Used for writing formatted results to.
     */
    private PrintWriter resultWriter;
 
    /**
     * Formatter for timings.
     */
    private NumberFormat numberFormat = NumberFormat.getInstance();
 
    /**
     * Output suite has written to System.out
     */
    private String systemOutput = null;
 
    /**
     * Output suite has written to System.err
     */
    private String systemError = null;
 
    /**
     * tests that failed.
     */
    private Hashtable failedTests = new Hashtable();
    /**
     * Timing helper.
     */
    private Hashtable testStarts = new Hashtable();
 
    /**
     * Constructor for OneLinerFormatter.
     */
    public OneLinerFormatter() {
        results = new StringWriter();
        resultWriter = new PrintWriter(results);
    }
 
    /**
     * Sets the stream the formatter is supposed to write its results to.
     * @param out the output stream to write to
     */
    public void setOutput(OutputStream out) {
        this.out = out;
        output = new PrintWriter(out);
    }
 
    /**
     * @see JUnitResultFormatter#setSystemOutput(String)
     */
    public void setSystemOutput(String out) {
        systemOutput = out;
    }
 
    /**
     * @see JUnitResultFormatter#setSystemError(String)
     */
    public void setSystemError(String err) {
        systemError = err;
    }
 
    /**
     * The whole testsuite started.
     * @param suite the test suite
     */
    public void startTestSuite(JUnitTest suite) {
        if (output == null) {
            return; // Quick return - no output do nothing.
        }
        StringBuffer sb = new StringBuffer(StringUtils.LINE_SEP);
        sb.append("----------------------------------------------------------");
        sb.append(StringUtils.LINE_SEP);        
        sb.append("Testsuite: ");
        sb.append(suite.getName());
        sb.append(StringUtils.LINE_SEP);
        output.write(sb.toString());
        output.flush();
    }
 
    /**
     * The whole testsuite ended.
     * @param suite the test suite
     */
    public void endTestSuite(JUnitTest suite) {
        StringBuffer sb = new StringBuffer("Tests run: ");
        sb.append(suite.runCount());
        sb.append(", Failures: ");
        sb.append(suite.failureCount());
        sb.append(", Errors: ");
        sb.append(suite.errorCount());
        sb.append(", Time elapsed: ");
        sb.append(numberFormat.format(suite.getRunTime() / 1000.0));
        sb.append(" sec");
        sb.append(StringUtils.LINE_SEP);
        sb.append(StringUtils.LINE_SEP);
 
        // append the err and output streams to the log
        if (systemOutput != null && systemOutput.length() > 0) {
            sb.append("------------- Standard Output ---------------")
                    .append(StringUtils.LINE_SEP)
                    .append(systemOutput)
                    .append("------------- ---------------- ---------------")
                    .append(StringUtils.LINE_SEP);
        }
 
        if (systemError != null && systemError.length() > 0) {
            sb.append("------------- Standard Error -----------------")
                    .append(StringUtils.LINE_SEP)
                    .append(systemError)
                    .append("------------- ---------------- ---------------")
                    .append(StringUtils.LINE_SEP);
        }
 
        if (output != null) {
            try {
                output.write(sb.toString());
                resultWriter.close();
                output.write(results.toString());
                output.flush();
            } finally {
                if (out != System.out && out != System.err) {
                    FileUtils.close(out);
                }
            }
        }
    }
 
    /**
     * A test started.
     * @param test a test
     */
    public void startTest(Test test) {
        testStarts.put(test, new Long(System.currentTimeMillis()));
    }
 
    /**
     * A test ended.
     * @param test a test
     */
    public void endTest(Test test) {
        // Fix for bug #5637 - if a junit.extensions.TestSetup is
        // used and throws an exception during setUp then startTest
        // would never have been called
        if (!testStarts.containsKey(test)) {
            startTest(test);
        }
 
        boolean failed = failedTests.containsKey(test);
 
        Long l = (Long) testStarts.get(test);
 
        output.write("Ran [");
        output.write(((System.currentTimeMillis() - l.longValue()) / 1000.0) + "] ");
        output.write(getTestName(test) + " ... " + (failed ? "FAILED" : "OK"));
        output.write(StringUtils.LINE_SEP);
        output.flush();
    }
 
    /**
     * Interface TestListener for JUnit &lt;= 3.4.
     *
     * <p>A Test failed.
     * @param test a test
     * @param t    the exception thrown by the test
     */
    public void addFailure(Test test, Throwable t) {
        formatError("\tFAILED", test, t);
    }
 
    /**
     * Interface TestListener for JUnit &gt; 3.4.
     *
     * <p>A Test failed.
     * @param test a test
     * @param t    the assertion failed by the test
     */
    public void addFailure(Test test, AssertionFailedError t) {
        addFailure(test, (Throwable) t);
    }
 
    /**
     * A test caused an error.
     * @param test  a test
     * @param error the error thrown by the test
     */
    public void addError(Test test, Throwable error) {
        formatError("\tCaused an ERROR", test, error);
    }
 
    /**
     * Get test name 
     * 
     * @param test a test
     * @return test name
     */
    protected String getTestName(Test test) {
        if (test == null) {
            return "null";
        } else {
            return /* JUnitVersionHelper.getTestCaseClassName(test) + ": " + */
                JUnitVersionHelper.getTestCaseName(test);
        }
    }
 
    /**
     * Get test case full class name 
     * 
     * @param test a test
     * @return test full class name 
     */
    protected String getTestCaseClassName(Test test) {
        if (test == null) {
            return "null";
        } else {
            return JUnitVersionHelper.getTestCaseClassName(test);
        }
    }    
 
    /**
     * Format the test for printing..
     * @param test a test
     * @return the formatted testname
     */
    protected String formatTest(Test test) {
        if (test == null) {
            return "Null Test: ";
        } else {
            return "Testcase: " + test.toString() + ":";
        }
    }
 
    /**
     * Format an error and print it.
     * @param type the type of error
     * @param test the test that failed
     * @param error the exception that the test threw
     */
    protected synchronized void formatError(String type, Test test,
                                            Throwable error) {
        if (test != null) {
            failedTests.put(test, test);
            endTest(test);            
        }
 
        resultWriter.println(formatTest(test) + type);
        resultWriter.println(TAB_STR + "(" + error.getClass().getSimpleName() + "): " + 
                ((error.getMessage() != null) ? error.getMessage() : error));
 
        if (showCausesLines) {
            // resultWriter.append(StringUtils.LINE_SEP);
            resultWriter.println(filterErrorTrace(test, error));
        }
 
        resultWriter.println();
 
        /* String strace = JUnitTestRunner.getFilteredTrace(error);
        resultWriter.println(strace);
        resultWriter.println(); */
    }
 
    protected String filterErrorTrace(Test test, Throwable error) {
        String trace = StringUtils.getStackTrace(error);
        StringWriter sw = new StringWriter();
        PrintWriter pw = new PrintWriter(sw);        
        StringReader sr = new StringReader(trace);
        BufferedReader br = new BufferedReader(sr);        
 
        String line;
        try {
            while ((line = br.readLine()) != null) {
                if (line.indexOf(getTestCaseClassName(test)) != -1) {
                    Matcher matcher = traceLinePattern.matcher(line);
                    // pw.println(matcher + ": " + matcher.find());
                    if (matcher.find()) {
                        pw.print(TAB_STR);
                        pw.print("(" + matcher.group(3) + ") ");                        
                        pw.print(matcher.group(2) + ": ");
                        pw.println(matcher.group(4));
                    } else {
                        pw.println(line);
                    }
 
                }
            }
        } catch (Exception e) {
            return trace; // return the treca unfiltered
        }
 
        return sw.toString();
 
}
Unless otherwise stated, the content of this page is licensed under Creative Commons Attribution-ShareAlike 3.0 License