diff --git a/proposal/sandbox/junit/src/main/org/apache/tools/ant/taskdefs/optional/rjunit/remote/Server.java b/proposal/sandbox/junit/src/main/org/apache/tools/ant/taskdefs/optional/rjunit/remote/Server.java index 42729df58..b25628f3d 100644 --- a/proposal/sandbox/junit/src/main/org/apache/tools/ant/taskdefs/optional/rjunit/remote/Server.java +++ b/proposal/sandbox/junit/src/main/org/apache/tools/ant/taskdefs/optional/rjunit/remote/Server.java @@ -116,19 +116,24 @@ public class Server { } /** start a server to the specified port */ - public void start() { - try { - start(false); - } catch (InterruptedException e){ - } - } - - /** start a server to the specified port and wait for end */ - public void start(boolean flag) throws InterruptedException { - Worker worker = new Worker(); - worker.start(); - if (flag){ - worker.join(); + public void start(boolean loop) throws IOException { + server = new ServerSocket(port); + while (server != null) { + client = server.accept(); + messenger = new Messenger(client.getInputStream(), client.getOutputStream()); + TestRunEvent evt = null; + try { + while ( (evt = messenger.read()) != null ) { + dispatcher.dispatchEvent(evt); + } + } catch (Exception e){ + e.printStackTrace(); + //@fixme this stacktrace might be normal when closing + // the socket. So decompose the above in distinct steps + } + if (!loop){ + break; + } } } @@ -169,26 +174,4 @@ public class Server { } catch (IOException e) { } } - -//----- - - private class Worker extends Thread { - public void run() { - try { - server = new ServerSocket(port); - client = server.accept(); - messenger = new Messenger(client.getInputStream(), client.getOutputStream()); - TestRunEvent evt = null; - while ( (evt = messenger.read()) != null ) { - dispatcher.dispatchEvent(evt); - } - } catch (Exception e) { - //@fixme this stacktrace might be normal when closing - // the socket. So decompose the above in distinct steps - } finally { - cancel(); - shutdown(); - } - } - } } diff --git a/proposal/sandbox/junit/src/main/org/apache/tools/ant/taskdefs/optional/rjunit/remote/SocketUtil.java b/proposal/sandbox/junit/src/main/org/apache/tools/ant/taskdefs/optional/rjunit/remote/SocketUtil.java deleted file mode 100644 index 70bbe06c7..000000000 --- a/proposal/sandbox/junit/src/main/org/apache/tools/ant/taskdefs/optional/rjunit/remote/SocketUtil.java +++ /dev/null @@ -1,95 +0,0 @@ -/* - * The Apache Software License, Version 1.1 - * - * Copyright (c) 2002 The Apache Software Foundation. All rights - * reserved. - * - * Redistribution and use in source and binary forms, with or without - * modification, are permitted provided that the following conditions - * are met: - * - * 1. Redistributions of source code must retain the above copyright - * notice, this list of conditions and the following disclaimer. - * - * 2. Redistributions in binary form must reproduce the above copyright - * notice, this list of conditions and the following disclaimer in - * the documentation and/or other materials provided with the - * distribution. - * - * 3. The end-user documentation included with the redistribution, if - * any, must include the following acknowlegement: - * "This product includes software developed by the - * Apache Software Foundation (http://www.apache.org/)." - * Alternately, this acknowlegement may appear in the software itself, - * if and wherever such third-party acknowlegements normally appear. - * - * 4. The names "The Jakarta Project", "Ant", and "Apache Software - * Foundation" must not be used to endorse or promote products derived - * from this software without prior written permission. For written - * permission, please contact apache@apache.org. - * - * 5. Products derived from this software may not be called "Apache" - * nor may "Apache" appear in their names without prior written - * permission of the Apache Group. - * - * THIS SOFTWARE IS PROVIDED ``AS IS'' AND ANY EXPRESSED OR IMPLIED - * WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES - * OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE - * DISCLAIMED. IN NO EVENT SHALL THE APACHE SOFTWARE FOUNDATION OR - * ITS CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, - * SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT - * LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF - * USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND - * ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, - * OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT - * OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF - * SUCH DAMAGE. - * ==================================================================== - * - * This software consists of voluntary contributions made by many - * individuals on behalf of the Apache Software Foundation. For more - * information on the Apache Software Foundation, please see - * . - */ -package org.apache.tools.ant.taskdefs.optional.rjunit.remote; - -import java.io.ByteArrayInputStream; -import java.io.ByteArrayOutputStream; -import java.io.ObjectInputStream; -import java.io.ObjectOutputStream; - -/** - * A set of helper methods related to sockets. - * - * @author Stephane Bailliez - */ -public class SocketUtil { - - - /** - * Helper method to deserialize an object - * @param bytes the binary data representing the serialized object. - * @return the deserialized object. - * @throws Exception a generic exception if an error occurs when - * deserializing the object. - */ - public static Object deserialize(byte[] bytes) throws Exception { - ObjectInputStream ois = new ObjectInputStream(new ByteArrayInputStream(bytes)); - return ois.readObject(); - } - - /** - * Helper method to serialize an object - * @param o the object to serialize. - * @return the binary data representing the serialized object. - * @throws Exception a generic exception if an error occurs when - * serializing the object. - */ - public static byte[] serialize(Object o) throws Exception { - ByteArrayOutputStream out = new ByteArrayOutputStream(); - ObjectOutputStream oos = new ObjectOutputStream(out); - oos.writeObject(o); - oos.close(); - return out.toByteArray(); - } -} diff --git a/proposal/sandbox/junit/src/main/org/apache/tools/ant/taskdefs/optional/rjunit/remote/TestRunEvent.java b/proposal/sandbox/junit/src/main/org/apache/tools/ant/taskdefs/optional/rjunit/remote/TestRunEvent.java index 23e57718a..c44ef3e74 100644 --- a/proposal/sandbox/junit/src/main/org/apache/tools/ant/taskdefs/optional/rjunit/remote/TestRunEvent.java +++ b/proposal/sandbox/junit/src/main/org/apache/tools/ant/taskdefs/optional/rjunit/remote/TestRunEvent.java @@ -59,6 +59,9 @@ import java.util.Properties; import org.apache.tools.ant.util.StringUtils; /** + * Provide the basic events to be used during the tests. + * This is not very extensible but since the events should be somewhat + * limited, for now this is better to do it like this. * * @author Stephane Bailliez */ @@ -81,7 +84,7 @@ public class TestRunEvent extends EventObject { /** the type of event */ private int type = -1; - /** timestamp for all tests */ + /** timestamp for all events */ private long timestamp = System.currentTimeMillis(); /** name of testcase(method name) or testsuite (classname) */ @@ -93,19 +96,28 @@ public class TestRunEvent extends EventObject { /** properties for end of testrun */ private Properties props; + /** handy result for each end of sequence */ + private TestSummary result; + public TestRunEvent(Integer id, int type){ super(id); this.type = type; } + public TestRunEvent(Integer id, int type, String name, TestSummary result){ + this(id, type, name); + this.result = result; + } + public TestRunEvent(Integer id, int type, String name){ this(id, type); this.name = name; } - public TestRunEvent(Integer id, int type, Properties props){ + public TestRunEvent(Integer id, int type, Properties props, TestSummary result){ this(id, type); this.props = props; + this.result = result; } public TestRunEvent(Integer id, int type, String name, Throwable t){ @@ -145,6 +157,10 @@ public class TestRunEvent extends EventObject { return name; } + public TestSummary getSummary(){ + return result; + } + public String getStackTrace(){ return stacktrace; } @@ -160,7 +176,8 @@ public class TestRunEvent extends EventObject { (timestamp == other.timestamp) && ( name == null ? other.name == null : name.equals(other.name) ) && ( stacktrace == null ? other.stacktrace == null : stacktrace.equals(other.stacktrace) ) && - ( props == null ? other.props == null : props.equals(other.props) ) ) ; + ( props == null ? other.props == null : props.equals(other.props) ) && + ( result == null ? other.result == null : result.equals(other.result) ) ); } return false; } diff --git a/proposal/sandbox/junit/src/main/org/apache/tools/ant/taskdefs/optional/rjunit/remote/TestRunner.java b/proposal/sandbox/junit/src/main/org/apache/tools/ant/taskdefs/optional/rjunit/remote/TestRunner.java index dd9c04fb4..d475b649a 100644 --- a/proposal/sandbox/junit/src/main/org/apache/tools/ant/taskdefs/optional/rjunit/remote/TestRunner.java +++ b/proposal/sandbox/junit/src/main/org/apache/tools/ant/taskdefs/optional/rjunit/remote/TestRunner.java @@ -62,6 +62,11 @@ import java.util.Properties; import java.util.StringTokenizer; import java.util.Vector; import java.util.Random; +import java.util.Map; +import java.util.HashMap; +import java.util.ArrayList; +import java.util.Collection; +import java.util.Iterator; import junit.framework.AssertionFailedError; import junit.framework.Test; @@ -71,6 +76,8 @@ import junit.framework.TestResult; import junit.framework.TestSuite; import org.apache.tools.ant.taskdefs.optional.rjunit.JUnitHelper; +import org.apache.tools.ant.taskdefs.optional.rjunit.formatter.Formatter; +import org.apache.tools.ant.taskdefs.optional.rjunit.formatter.PlainFormatter; import org.apache.tools.ant.util.StringUtils; /** @@ -95,10 +102,11 @@ public class TestRunner implements TestListener { /** port to connect to */ private int port = -1; + /** handy debug flag */ private boolean debug = false; /** the list of test class names to run */ - private Vector testClassNames = new Vector(); + private final ArrayList testClassNames = new ArrayList(); /** result of the current test */ private TestResult testResult; @@ -109,8 +117,14 @@ public class TestRunner implements TestListener { /** writer to send message to the server */ private Messenger messenger; + /** helpful formatter to debug events directly here */ + private final Formatter debugFormatter = new PlainFormatter(); + /** bean constructor */ public TestRunner() { + Properties props = new Properties(); + props.setProperty("file", "rjunit-client-debug.log"); + debugFormatter.init(props); } /** @@ -142,7 +156,7 @@ public class TestRunner implements TestListener { * @param classname the class name of the test to run. */ public void addTestClassName(String classname) { - testClassNames.addElement(classname); + testClassNames.add(classname); } /** @@ -265,16 +279,16 @@ public class TestRunner implements TestListener { * @throws Exception a generic exception that can be thrown while * instantiating a test case. */ - protected Test[] getSuites() throws Exception { + protected Map getSuites() throws Exception { final int count = testClassNames.size(); log("Extracting testcases from " + count + " classnames..."); - final Vector suites = new Vector(count); + final Map suites = new HashMap(); for (int i = 0; i < count; i++) { - String classname = (String) testClassNames.elementAt(i); + String classname = (String) testClassNames.get(i); try { Test test = JUnitHelper.getTest(null, classname); if (test != null) { - suites.addElement(test); + suites.put(classname, test); } } catch (Exception e) { // notify log error instead ? @@ -283,9 +297,7 @@ public class TestRunner implements TestListener { } } log("Extracted " + suites.size() + " testcases."); - Test[] array = new Test[suites.size()]; - suites.copyInto(array); - return array; + return suites; } /** @@ -293,41 +305,75 @@ public class TestRunner implements TestListener { */ private void runTests() throws Exception { - Test[] suites = getSuites(); + Map suites = getSuites(); // count all testMethods and inform TestRunListeners - int count = countTests(suites); + int count = countTests(suites.values()); log("Total tests to run: " + count); - fireEvent(new TestRunEvent(id, TestRunEvent.RUN_STARTED)); - - long startTime = System.currentTimeMillis(); - for (int i = 0; i < suites.length; i++) { - String name = suites[i].getClass().getName(); - if (suites[i] instanceof TestCase) { - suites[i] = new TestSuite(name); + TestRunEvent evt = new TestRunEvent(id, TestRunEvent.RUN_STARTED); + if (debug){ + debugFormatter.onRunStarted(evt); + } + fireEvent(evt); + + TestSummary runSummary = new TestSummary(); + runSummary.start(testResult); + for (Iterator it = suites.entrySet().iterator(); it.hasNext(); ) { + Map.Entry entry = (Map.Entry)it.next(); + String name = (String)entry.getKey(); + Test test = (Test)entry.getValue(); + if (test instanceof TestCase) { + test = new TestSuite(name); } - log("running suite: " + suites[i]); - fireEvent(new TestRunEvent(id, TestRunEvent.SUITE_STARTED, name)); - suites[i].run(testResult); - fireEvent(new TestRunEvent(id, TestRunEvent.SUITE_ENDED, name)); + runTest(test, name); } + runSummary.stop(testResult); // inform TestRunListeners of test end - long elapsedTime = System.currentTimeMillis() - startTime; - if (testResult == null || testResult.shouldStop()) { - fireEvent(new TestRunEvent(id, TestRunEvent.RUN_STOPPED, System.getProperties())); - } else { - fireEvent(new TestRunEvent(id, TestRunEvent.RUN_ENDED, System.getProperties())); + int type = (testResult == null || testResult.shouldStop()) ? + TestRunEvent.RUN_STOPPED : TestRunEvent.RUN_ENDED; + evt = new TestRunEvent(id, type, System.getProperties(), runSummary); + if (debug){ + debugFormatter.onRunEnded(evt); } - log("Finished after " + elapsedTime + "ms"); + fireEvent(evt); + log("Finished after " + runSummary.elapsedTime() + "ms"); shutDown(); } - /** count the number of test methods in all tests */ - private final int countTests(Test[] tests) { + /** + * run a single suite and dispatch its results. + * @param test the instance of the testsuite to run. + * @param name the name of the testsuite (classname) + */ + private void runTest(Test test, String name){ + TestRunEvent evt = new TestRunEvent(id, TestRunEvent.SUITE_STARTED, name); + if (debug){ + debugFormatter.onSuiteStarted(evt); + } + fireEvent(evt); + TestSummary suiteSummary = new TestSummary(); + suiteSummary.start(testResult); + try { + test.run(testResult); + } finally { + suiteSummary.stop(testResult); + evt = new TestRunEvent(id, TestRunEvent.SUITE_ENDED, name, suiteSummary); + if (debug){ + debugFormatter.onSuiteEnded(evt); + } + fireEvent(evt); + } + } + + /** + * count the number of test methods in all tests + */ + private final int countTests(Collection tests) { int count = 0; - for (int i = 0; i < tests.length; i++) { - count = count + tests[i].countTestCases(); + for (Iterator it = tests.iterator(); it.hasNext(); ) { + Test test = (Test)it.next(); + count = count + test.countTestCases(); } return count; } @@ -383,14 +429,20 @@ public class TestRunner implements TestListener { public void startTest(Test test) { String testName = test.toString(); - log("starting test: " + test); - fireEvent(new TestRunEvent(id, TestRunEvent.TEST_STARTED, testName)); + TestRunEvent evt = new TestRunEvent(id, TestRunEvent.TEST_STARTED, testName); + if (debug){ + debugFormatter.onTestStarted(evt); + } + fireEvent(evt); } public void addError(Test test, Throwable t) { - log("Adding error for test: " + test); String testName = test.toString(); - fireEvent(new TestRunEvent(id, TestRunEvent.TEST_ERROR, testName, t)); + TestRunEvent evt = new TestRunEvent(id, TestRunEvent.TEST_ERROR, testName, t); + if (debug){ + debugFormatter.onTestError(evt); + } + fireEvent(evt); } /** @@ -406,15 +458,21 @@ public class TestRunner implements TestListener { * @see addFailure(Test, AssertionFailedError) */ public void addFailure(Test test, Throwable t) { - log("Adding failure for test: " + test); String testName = test.toString(); - fireEvent(new TestRunEvent(id, TestRunEvent.TEST_FAILURE, testName, t)); + TestRunEvent evt = new TestRunEvent(id, TestRunEvent.TEST_FAILURE, testName, t); + if (debug){ + debugFormatter.onTestFailure(evt); + } + fireEvent(evt); } public void endTest(Test test) { - log("Ending test: " + test); String testName = test.toString(); - fireEvent(new TestRunEvent(id, TestRunEvent.TEST_ENDED, testName)); + TestRunEvent evt = new TestRunEvent(id, TestRunEvent.TEST_ENDED, testName); + if (debug){ + debugFormatter.onTestEnded(evt); + } + fireEvent(evt); } public void log(String msg) { diff --git a/proposal/sandbox/junit/src/main/org/apache/tools/ant/taskdefs/optional/rjunit/remote/TestSummary.java b/proposal/sandbox/junit/src/main/org/apache/tools/ant/taskdefs/optional/rjunit/remote/TestSummary.java new file mode 100644 index 000000000..79d71e682 --- /dev/null +++ b/proposal/sandbox/junit/src/main/org/apache/tools/ant/taskdefs/optional/rjunit/remote/TestSummary.java @@ -0,0 +1,177 @@ +/* + * The Apache Software License, Version 1.1 + * + * Copyright (c) 2002 The Apache Software Foundation. All rights + * reserved. + * + * Redistribution and use in source and binary forms, with or without + * modification, are permitted provided that the following conditions + * are met: + * + * 1. Redistributions of source code must retain the above copyright + * notice, this list of conditions and the following disclaimer. + * + * 2. Redistributions in binary form must reproduce the above copyright + * notice, this list of conditions and the following disclaimer in + * the documentation and/or other materials provided with the + * distribution. + * + * 3. The end-user documentation included with the redistribution, if + * any, must include the following acknowlegement: + * "This product includes software developed by the + * Apache Software Foundation (http://www.apache.org/)." + * Alternately, this acknowlegement may appear in the software itself, + * if and wherever such third-party acknowlegements normally appear. + * + * 4. The names "The Jakarta Project", "Ant", and "Apache Software + * Foundation" must not be used to endorse or promote products derived + * from this software without prior written permission. For written + * permission, please contact apache@apache.org. + * + * 5. Products derived from this software may not be called "Apache" + * nor may "Apache" appear in their names without prior written + * permission of the Apache Group. + * + * THIS SOFTWARE IS PROVIDED ``AS IS'' AND ANY EXPRESSED OR IMPLIED + * WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES + * OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE + * DISCLAIMED. IN NO EVENT SHALL THE APACHE SOFTWARE FOUNDATION OR + * ITS CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, + * SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT + * LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF + * USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND + * ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, + * OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT + * OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF + * SUCH DAMAGE. + * ==================================================================== + * + * This software consists of voluntary contributions made by many + * individuals on behalf of the Apache Software Foundation. For more + * information on the Apache Software Foundation, please see + * . + */ +package org.apache.tools.ant.taskdefs.optional.rjunit.remote; + +import java.io.Serializable; + +import junit.framework.AssertionFailedError; +import junit.framework.Test; +import junit.framework.TestListener; +import junit.framework.TestResult; + +/** + * A helpful test summary that is somewhat similar to TestResult. + * Here the difference is that this test summary should register to + * the test result the time you wan to collect information. + * + * @author Stephane Bailliez + */ +public final class TestSummary implements Serializable, TestListener { + + /** time elapsed during tests run in ms */ + private long elapsedTime; + + /** number of errors */ + private int errorCount; + + /** number of successes */ + private int successCount; + + /** number of failures */ + private int failureCount; + + /** number of runs */ + private int runCount; + + private transient String toString; + + /** bean constructor */ + public TestSummary() { + } + + /** + * @return the number of errors that occurred in this test. + */ + public int errorCount() { + return errorCount; + } + + /** + * @return the number of successes that occurred in this test. + */ + public int successCount() { + return successCount; + } + + /** + * @return the number of failures that occurred in this test. + */ + public int failureCount() { + return failureCount; + } + + /** + * @return the number of runs that occurred in this test. + * a run is the sum of failures + errors + successes. + */ + public int runCount() { + return runCount; + } + + /** + * @return the elapsed time in ms + */ + public long elapsedTime() { + return elapsedTime; + } + +// + /** + * register to the TestResult and starts the time counter. + * @param result the instance to register to. + * @see #stop() + */ + public void start(TestResult result){ + elapsedTime = System.currentTimeMillis(); + result.addListener(this); + } + + /** + * unregister from the TestResult and stops the time counter. + * @param result the instance to unregister from. + * @see #start() + */ + public void stop(TestResult result){ + elapsedTime = System.currentTimeMillis() - elapsedTime; + result.removeListener(this); + } + +// test listener implementation + + public void addError(Test test, Throwable throwable) { + errorCount++; + } + + public void addFailure(Test test, AssertionFailedError error) { + failureCount++; + } + + public void endTest(Test test) { + successCount++; + } + + public void startTest(Test test) { + runCount++; + } + + public String toString(){ + StringBuffer buf = new StringBuffer(); + buf.append("run: ").append(runCount); + buf.append(" success: ").append(successCount); + buf.append(" failures: ").append(failureCount); + buf.append(" errors: ").append(errorCount); + buf.append(" elapsed: ").append(elapsedTime); + return buf.toString(); + } +}