I was not able to find a unit test runner that output XML compatible
with the junitreport task and compatible with the way GWT likes to run
unit tests. So I created GWTTestRunner. It is a drop in replacement
for the textui.TestRunner with the addition of a --todir command line
option for writing the XML output. The XML schema is identical to the
XML generated by the Ant junit task xml formatter. Since I think this
may be of use to others I am including the source here. I have put the
source under the apache license so it should be all nice and
compatible with whatever you are doing with GWT. I'd appreciate
hearing how this works for folks and if anyone has improvements, post
to the group.

Cheers,
Baron

--------------

/*
 * Copyright 2009 C Thing Software
 *
 * Licensed under the Apache License, Version 2.0 (the "License"); you
may not
 * use this file except in compliance with the License. You may obtain
a copy of
 * the License at
 *
 * http://www.apache.org/licenses/LICENSE-2.0
 *
 * Unless required by applicable law or agreed to in writing, software
 * distributed under the License is distributed on an "AS IS" BASIS,
WITHOUT
 * WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
See the
 * License for the specific language governing permissions and
limitations under
 * the License.
 */

package com.cthing.pia.test;

import java.io.BufferedWriter;
import java.io.File;
import java.io.FileWriter;
import java.io.IOException;
import java.io.Writer;
import java.net.InetAddress;
import java.net.UnknownHostException;
import java.util.Date;
import java.util.HashMap;
import java.util.Map;
import java.util.regex.Matcher;
import java.util.regex.Pattern;

import javax.xml.parsers.DocumentBuilder;
import javax.xml.parsers.DocumentBuilderFactory;

import org.apache.tools.ant.util.DOMElementWriter;
import org.apache.tools.ant.util.DateUtils;
import org.apache.tools.ant.util.FileUtils;
import org.w3c.dom.Document;
import org.w3c.dom.Element;
import org.w3c.dom.Text;

import junit.framework.AssertionFailedError;
import junit.framework.Test;
import junit.framework.TestResult;
import junit.runner.BaseTestRunner;

/**
 * Test runner for use with GWT unit tests. This test runner produces
XML
 * output compatible with the junitreport Ant task. Configuration of
the
 * runner is done using Java properties.
 */
public class GWTTestRunner extends BaseTestRunner
{
    /** Exit code for a successful test run. */
    public static final int SUCCESS_EXIT = 0;
    /** Exit code when one or more tests failed. */
    public static final int FAILURE_EXIT = 1;
    /** Exit code when a test has crashed due to an exception. */
    public static final int EXCEPTION_EXIT = 2;

    private static final String ELEM_TESTSUITE = "testsuite";
    private static final String ELEM_TESTCASE = "testcase";
    private static final String ELEM_ERROR = "error";
    private static final String ELEM_FAILURE = "failure";

    private static final String ATTR_NAME = "name";
    private static final String ATTR_CLASSNAME = "classname";
    private static final String ATTR_TIMESTAMP = "timestamp";
    private static final String ATTR_HOSTNAME = "hostname";
    private static final String ATTR_TESTS = "tests";
    private static final String ATTR_ERRORS = "errors";
    private static final String ATTR_FAILURES = "failures";
    private static final String ATTR_TIME = "time";
    private static final String ATTR_TYPE = "type";
    private static final String ATTR_MESSAGE = "message";

    private String toDir;
    private TestResult testResult;
    private TestDescriptor currentTest;
    private Document doc;
    private Element rootElement;


    /**
     * Records information for a test case.
     */
    private static final class TestDescriptor extends TestResult
    {
        private static final Pattern COMPOSITE_NAME_PATTERN =
Pattern.compile("(.+)\\((.+)\\)");

        private String testCase;
        private String testName;
        private long testCaseStartTime;
        private long testStartTime;
        private Map<Test, Throwable> errorMap;
        private Map<Test, AssertionFailedError> failureMap;

        /**
         * Constructs a descriptor for the specified test.
         *
         * @param test  Test whose test case is to be monitored.
         */
        public TestDescriptor(Test test)
        {
            this.testCaseStartTime = System.currentTimeMillis();

            this.errorMap = new HashMap<Test, Throwable>();
            this.failureMap = new HashMap<Test, AssertionFailedError>
();

            Matcher m = COMPOSITE_NAME_PATTERN.matcher(test.toString
());
            if (m.matches()) {
                this.testName = m.group(1);
                this.testCase = m.group(2);
            }
        }

        /**
         * {...@inheritdoc}
         * @see junit.framework.TestResult#addError
(junit.framework.Test, java.lang.Throwable)
         */
        @Override
        public synchronized void addError(Test test, Throwable
throwable)
        {
            super.addError(test, throwable);
            this.errorMap.put(test, throwable);
        }

        /**
         * {...@inheritdoc}
         * @see junit.framework.TestResult#addFailure
(junit.framework.Test, junit.framework.AssertionFailedError)
         */
        @Override
        public synchronized void addFailure(Test test,
AssertionFailedError error)
        {
            super.addFailure(test, error);
            this.failureMap.put(test, error);
        }

        /**
         * @return The name of the test case.
         */
        public String getTestCase()
        {
            return this.testCase;
        }

        /**
         * @return The name of the current test.
         */
        public String getTestName()
        {
            return this.testName;
        }

        /**
         * Provides error information for the specified test.
         *
         * @param test  Test whose error information is desired.
         * @return Error information for the specified test or
<code>null</code>
         *      if the test did not have an error.
         */
        public Throwable getError(Test test)
        {
            return this.errorMap.get(test);
        }

        /**
         * Provides failure information for the specified test.
         *
         * @param test  Test whose failure information is desired.
         * @return Failure information for the specified test or
         *      <code>null</code> if the test did not have a failure.
         */
        public AssertionFailedError getFailure(Test test)
        {
            return this.failureMap.get(test);
        }

        /**
         * {...@inheritdoc}
         * @see junit.framework.TestResult#startTest
(junit.framework.Test)
         */
        @Override
        public void startTest(Test test)
        {
            this.testStartTime = System.currentTimeMillis();
            super.startTest(test);

            Matcher m = COMPOSITE_NAME_PATTERN.matcher(test.toString
());
            if (m.matches()) {
                this.testName = m.group(1);
            }
        }

        /**
         * @return The elapsed time for the current test in
milliseconds.
         */
        public long getTestElapsedTime()
        {
            return System.currentTimeMillis() - this.testStartTime;
        }

        /**
         * @return The elapsed time for the current test case in
milliseconds.
         */
        public long getTestCaseElapsedTime()
        {
            return System.currentTimeMillis() -
this.testCaseStartTime;
        }

        /**
         * Tests whether the specified test's test case is equal to
the
         * current test case.
         *
         * @param test  Test whose test case is to be tested
         * @return <code>true</code> if the specified test's test case
is
         *      equal to the current test case.
         */
        public boolean equalsTestCase(Test test)
        {
            Matcher m = COMPOSITE_NAME_PATTERN.matcher(test.toString
());
            return (m.matches() && m.group(2).equals(this.testCase));
        }
    }


    /**
     * Constructs a TestRunner.
     */
    public GWTTestRunner()
    {
        this.testResult = new TestResult();
        this.testResult.addListener(this);
    }

    /**
     * Obtains an XML document builder.
     *
     * @return XML document builder.
     */
    private static DocumentBuilder getDocumentBuilder()
    {
        try {
            return DocumentBuilderFactory.newInstance
().newDocumentBuilder();
        }
        catch (Exception e) {
            throw new IllegalStateException(e);
        }
    }

    /**
     * Obtain the local host name.
     *
     * @return Name of the local host, or "localhost" if we cannot
work it out.
     */
    private String getHostname()
    {
        String hostname;

        try {
            hostname = InetAddress.getLocalHost().getHostName();
        }
        catch (UnknownHostException e) {
            hostname = "localhost";
        }

        return hostname;
    }

    /**
     * {...@inheritdoc}
     * @see junit.runner.BaseTestRunner#testEnded(java.lang.String)
     */
    @Override
    public void testEnded(String testName)
    {
    }

    /**
     * {...@inheritdoc}
     * @see junit.runner.BaseTestRunner#testFailed(int,
junit.framework.Test, java.lang.Throwable)
     */
    @Override
    public void testFailed(int status, Test test, Throwable throwable)
    {
    }

    /**
     * {...@inheritdoc}
     * @see junit.runner.BaseTestRunner#testStarted(java.lang.String)
     */
    @Override
    public void testStarted(String testName)
    {
    }

    /**
     * {...@inheritdoc}
     * @see junit.runner.BaseTestRunner#addError(junit.framework.Test,
java.lang.Throwable)
     */
    @Override
    public synchronized void addError(Test test, Throwable t)
    {
        super.addError(test, t);
        this.currentTest.addError(test, t);
    }

    /**
     * {...@inheritdoc}
     * @see junit.runner.BaseTestRunner#addFailure
(junit.framework.Test, junit.framework.AssertionFailedError)
     */
    @Override
    public synchronized void addFailure(Test test,
AssertionFailedError t)
    {
        super.addFailure(test, t);
        this.currentTest.addFailure(test, t);
    }

    /**
     * {...@inheritdoc}
     * @see junit.runner.BaseTestRunner#startTest
(junit.framework.Test)
     */
    @Override
    public synchronized void startTest(Test test)
    {
        super.startTest(test);

        if ((this.currentTest == null) || !
this.currentTest.equalsTestCase(test)) {
            startTestCase(test);
        }

        this.currentTest.startTest(test);
    }

    /**
     * {...@inheritdoc}
     * @see junit.runner.BaseTestRunner#endTest(junit.framework.Test)
     */
    @Override
    public synchronized void endTest(Test test)
    {
        super.endTest(test);

        this.currentTest.endTest(test);

        Element testCaseElement = this.doc.createElement
(ELEM_TESTCASE);
        this.rootElement.appendChild(testCaseElement);
        testCaseElement.setAttribute(ATTR_NAME,
this.currentTest.getTestName());
        testCaseElement.setAttribute(ATTR_CLASSNAME,
this.currentTest.getTestCase());
        testCaseElement.setAttribute(ATTR_TIME, "" +
(this.currentTest.getTestElapsedTime() / 1000.0));

        Throwable throwable = this.currentTest.getError(test);
        if (throwable != null) {
            processError(throwable, ELEM_ERROR, testCaseElement);
        }

        AssertionFailedError failure = this.currentTest.getFailure
(test);
        if (failure != null) {
            processError(failure, ELEM_FAILURE, testCaseElement);
        }
    }

    /**
     * Starts a test case.
     *
     * @param test  Test whose test case is starting
     */
    private void startTestCase(Test test)
    {
        if (this.currentTest != null) {
            endTestCase();
        }

        this.currentTest = new TestDescriptor(test);

        // Create the root element.
        this.doc = getDocumentBuilder().newDocument();
        this.rootElement = this.doc.createElement(ELEM_TESTSUITE);
        this.rootElement.setAttribute(ATTR_NAME,
this.currentTest.getTestCase());

        // Add a timestamp
        final String timestamp = DateUtils.format(new Date(),
DateUtils.ISO8601_DATETIME_PATTERN);
        this.rootElement.setAttribute(ATTR_TIMESTAMP, timestamp);

        // Add the host name.
        this.rootElement.setAttribute(ATTR_HOSTNAME, getHostname());

        // Tell the world.
        System.out.println("Running " + this.currentTest.getTestCase
());
    }

    /**
     * Ends the current test case.
     */
    private void endTestCase()
    {
        long elapsed = this.currentTest.getTestCaseElapsedTime();

        this.rootElement.setAttribute(ATTR_TESTS, "" +
this.currentTest.runCount());
        this.rootElement.setAttribute(ATTR_FAILURES, "" +
this.currentTest.failureCount());
        this.rootElement.setAttribute(ATTR_ERRORS, "" +
this.currentTest.errorCount());
        this.rootElement.setAttribute(ATTR_TIME, "" + (elapsed /
1000.0));

        File outFile = new File(this.toDir, "TEST-" +
this.currentTest.getTestCase() + ".xml");
        Writer writer = null;
        try {
            writer = new BufferedWriter(new FileWriter(outFile));
            writer.write("<?xml version=\"1.0\" encoding=\"UTF-8\" ?>
\n");
            (new DOMElementWriter()).write(this.rootElement, writer,
0, "  ");
            writer.flush();
        }
        catch (IOException e) {
            throw new RuntimeException(e);
        }
        finally {
            FileUtils.close(writer);
        }

        System.out.printf("Tests run: %d, Failures: %d, Errors: %d,
Time elapsed: %.3f sec\n",
                          this.currentTest.runCount(),
                          this.currentTest.failureCount(),
                          this.currentTest.errorCount(),
                          (elapsed / 1000.0));

        this.currentTest = null;
    }

    /**
     * Starts a test run.
     *
     * @param testCase  Test to run
     * @param dir  Directory to write the output
     * @return Test results
     * @throws Exception If a test crashed causing an exception
     */
    public TestResult start(String testCase, String dir) throws
Exception
    {
        this.toDir = dir;

        try {
            // Run the tests
            Test suite = getTest(testCase);
            suite.run(this.testResult);

            // Ensure the final test case is written out.
            if (this.currentTest != null) {
                endTestCase();
            }
        }
        catch (Exception e) {
            throw new Exception("Could not create and run test suite:
" + e);
        }

        return this.testResult;
    }

    /**
     * Adds error and failure information to the output DOM.
     *
     * @param throwable  Error or failure information
     * @param elementName  Name of the element to report the error or
failure
     * @param parent  Parent element for the error or failure
information
     */
    private void processError(Throwable throwable, String elementName,
Element parent)
    {
        Element element = this.doc.createElement(elementName);
        parent.appendChild(element);

        String message = throwable.getMessage();
        if (message != null && message.length() > 0 && !"null".equals
(message)) {
            element.setAttribute(ATTR_MESSAGE, message);
        }
        element.setAttribute(ATTR_TYPE, throwable.getClass().getName
());

        String strace = BaseTestRunner.getFilteredTrace(throwable);
        Text trace = this.doc.createTextNode(strace);
        element.appendChild(trace);
    }

    /**
     * {...@inheritdoc}
     * @see junit.runner.BaseTestRunner#runFailed(java.lang.String)
     */
    @Override
    protected void runFailed(String message)
    {
        System.err.println(message);
        System.exit(FAILURE_EXIT);
    }

    /**
     * Test runner entry point.
     *
     * @param args  Command-line arguments.
     */
    public static void main(String[] args)
    {
        int exitStatus;
        GWTTestRunner testRunner = new GWTTestRunner();
        try {
            String testCase = null;
            String toDir = null;

            int idx = 0;
            while (idx < args.length) {
                if ("--todir".equals(args[idx])) {
                    toDir = args[++idx];
                }
                else {
                    testCase = args[idx];
                }

                idx++;
            }

            if (testCase == null) {
                throw new Exception("Usage: TestRunner [--todir dir]
testCaseName");
            }

            TestResult results = testRunner.start(testCase, toDir);
            exitStatus = results.wasSuccessful() ? SUCCESS_EXIT :
FAILURE_EXIT;
        }
        catch (Exception e) {
            System.err.println(e.getMessage());
            exitStatus = EXCEPTION_EXIT;
        }

        System.exit(exitStatus);
    }
}

--~--~---------~--~----~------------~-------~--~----~
You received this message because you are subscribed to the Google Groups 
"Google Web Toolkit" group.
To post to this group, send email to Google-Web-Toolkit@googlegroups.com
To unsubscribe from this group, send email to 
google-web-toolkit+unsubscr...@googlegroups.com
For more options, visit this group at 
http://groups.google.com/group/Google-Web-Toolkit?hl=en
-~----------~----~----~----~------~----~------~--~---

Reply via email to