Browse Source

- Refactored the Server and TestRunner to deal with events.

- Added TestSummary (TestResult was better but is conflicting with JUnit)
which sole purpose is to provide a helpful summary of the sequence
to listeners.


git-svn-id: https://svn.apache.org/repos/asf/ant/core/trunk@271124 13f79535-47bb-0310-9956-ffa450edef68
master
Stephane Bailliez 23 years ago
parent
commit
c2b10718db
5 changed files with 313 additions and 173 deletions
  1. +18
    -35
      proposal/sandbox/junit/src/main/org/apache/tools/ant/taskdefs/optional/rjunit/remote/Server.java
  2. +0
    -95
      proposal/sandbox/junit/src/main/org/apache/tools/ant/taskdefs/optional/rjunit/remote/SocketUtil.java
  3. +20
    -3
      proposal/sandbox/junit/src/main/org/apache/tools/ant/taskdefs/optional/rjunit/remote/TestRunEvent.java
  4. +98
    -40
      proposal/sandbox/junit/src/main/org/apache/tools/ant/taskdefs/optional/rjunit/remote/TestRunner.java
  5. +177
    -0
      proposal/sandbox/junit/src/main/org/apache/tools/ant/taskdefs/optional/rjunit/remote/TestSummary.java

+ 18
- 35
proposal/sandbox/junit/src/main/org/apache/tools/ant/taskdefs/optional/rjunit/remote/Server.java View File

@@ -116,19 +116,24 @@ public class Server {
} }


/** start a server to the specified port */ /** 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) { } 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();
}
}
}
} }

+ 0
- 95
proposal/sandbox/junit/src/main/org/apache/tools/ant/taskdefs/optional/rjunit/remote/SocketUtil.java View File

@@ -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
* <http://www.apache.org/>.
*/
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 <a href="mailto:sbailliez@apache.org">Stephane Bailliez</a>
*/
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();
}
}

+ 20
- 3
proposal/sandbox/junit/src/main/org/apache/tools/ant/taskdefs/optional/rjunit/remote/TestRunEvent.java View File

@@ -59,6 +59,9 @@ import java.util.Properties;
import org.apache.tools.ant.util.StringUtils; 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 <a href="mailto:sbailliez@apache.org">Stephane Bailliez</a> * @author <a href="mailto:sbailliez@apache.org">Stephane Bailliez</a>
*/ */
@@ -81,7 +84,7 @@ public class TestRunEvent extends EventObject {
/** the type of event */ /** the type of event */
private int type = -1; private int type = -1;


/** timestamp for all tests */
/** timestamp for all events */
private long timestamp = System.currentTimeMillis(); private long timestamp = System.currentTimeMillis();


/** name of testcase(method name) or testsuite (classname) */ /** name of testcase(method name) or testsuite (classname) */
@@ -93,19 +96,28 @@ public class TestRunEvent extends EventObject {
/** properties for end of testrun */ /** properties for end of testrun */
private Properties props; private Properties props;


/** handy result for each end of sequence */
private TestSummary result;

public TestRunEvent(Integer id, int type){ public TestRunEvent(Integer id, int type){
super(id); super(id);
this.type = type; 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){ public TestRunEvent(Integer id, int type, String name){
this(id, type); this(id, type);
this.name = name; 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(id, type);
this.props = props; this.props = props;
this.result = result;
} }


public TestRunEvent(Integer id, int type, String name, Throwable t){ public TestRunEvent(Integer id, int type, String name, Throwable t){
@@ -145,6 +157,10 @@ public class TestRunEvent extends EventObject {
return name; return name;
} }


public TestSummary getSummary(){
return result;
}

public String getStackTrace(){ public String getStackTrace(){
return stacktrace; return stacktrace;
} }
@@ -160,7 +176,8 @@ public class TestRunEvent extends EventObject {
(timestamp == other.timestamp) && (timestamp == other.timestamp) &&
( name == null ? other.name == null : name.equals(other.name) ) && ( name == null ? other.name == null : name.equals(other.name) ) &&
( stacktrace == null ? other.stacktrace == null : stacktrace.equals(other.stacktrace) ) && ( 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; return false;
} }


+ 98
- 40
proposal/sandbox/junit/src/main/org/apache/tools/ant/taskdefs/optional/rjunit/remote/TestRunner.java View File

@@ -62,6 +62,11 @@ import java.util.Properties;
import java.util.StringTokenizer; import java.util.StringTokenizer;
import java.util.Vector; import java.util.Vector;
import java.util.Random; 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.AssertionFailedError;
import junit.framework.Test; import junit.framework.Test;
@@ -71,6 +76,8 @@ import junit.framework.TestResult;
import junit.framework.TestSuite; import junit.framework.TestSuite;


import org.apache.tools.ant.taskdefs.optional.rjunit.JUnitHelper; 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; import org.apache.tools.ant.util.StringUtils;


/** /**
@@ -95,10 +102,11 @@ public class TestRunner implements TestListener {
/** port to connect to */ /** port to connect to */
private int port = -1; private int port = -1;


/** handy debug flag */
private boolean debug = false; private boolean debug = false;


/** the list of test class names to run */ /** the list of test class names to run */
private Vector testClassNames = new Vector();
private final ArrayList testClassNames = new ArrayList();


/** result of the current test */ /** result of the current test */
private TestResult testResult; private TestResult testResult;
@@ -109,8 +117,14 @@ public class TestRunner implements TestListener {
/** writer to send message to the server */ /** writer to send message to the server */
private Messenger messenger; private Messenger messenger;


/** helpful formatter to debug events directly here */
private final Formatter debugFormatter = new PlainFormatter();

/** bean constructor */ /** bean constructor */
public TestRunner() { 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. * @param classname the class name of the test to run.
*/ */
public void addTestClassName(String classname) { 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 * @throws Exception a generic exception that can be thrown while
* instantiating a test case. * instantiating a test case.
*/ */
protected Test[] getSuites() throws Exception {
protected Map getSuites() throws Exception {
final int count = testClassNames.size(); final int count = testClassNames.size();
log("Extracting testcases from " + count + " classnames..."); log("Extracting testcases from " + count + " classnames...");
final Vector suites = new Vector(count);
final Map suites = new HashMap();
for (int i = 0; i < count; i++) { for (int i = 0; i < count; i++) {
String classname = (String) testClassNames.elementAt(i);
String classname = (String) testClassNames.get(i);
try { try {
Test test = JUnitHelper.getTest(null, classname); Test test = JUnitHelper.getTest(null, classname);
if (test != null) { if (test != null) {
suites.addElement(test);
suites.put(classname, test);
} }
} catch (Exception e) { } catch (Exception e) {
// notify log error instead ? // notify log error instead ?
@@ -283,9 +297,7 @@ public class TestRunner implements TestListener {
} }
} }
log("Extracted " + suites.size() + " testcases."); 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 { private void runTests() throws Exception {


Test[] suites = getSuites();
Map suites = getSuites();


// count all testMethods and inform TestRunListeners // count all testMethods and inform TestRunListeners
int count = countTests(suites);
int count = countTests(suites.values());
log("Total tests to run: " + count); 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 // 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(); 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; 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; return count;
} }
@@ -383,14 +429,20 @@ public class TestRunner implements TestListener {


public void startTest(Test test) { public void startTest(Test test) {
String testName = test.toString(); 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) { public void addError(Test test, Throwable t) {
log("Adding error for test: " + test);
String testName = test.toString(); 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) * @see addFailure(Test, AssertionFailedError)
*/ */
public void addFailure(Test test, Throwable t) { public void addFailure(Test test, Throwable t) {
log("Adding failure for test: " + test);
String testName = test.toString(); 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) { public void endTest(Test test) {
log("Ending test: " + test);
String testName = test.toString(); 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) { public void log(String msg) {


+ 177
- 0
proposal/sandbox/junit/src/main/org/apache/tools/ant/taskdefs/optional/rjunit/remote/TestSummary.java View File

@@ -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
* <http://www.apache.org/>.
*/
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 <tt>TestResult</tt>.
* Here the difference is that this test summary should register to
* the test result the time you wan to collect information.
*
* @author <a href="mailto:sbailliez@apache.org">Stephane Bailliez</a>
*/
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 <tt>TestResult</tt> 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 <tt>TestResult</tt> 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();
}
}

Loading…
Cancel
Save