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 -~----------~----~----~----~------~----~------~--~---