Browse Source

initial commit.

Code based from Erich Gamma's plugin for Eclipse.
It has been heavily changed so I believe that not much is in
common now except the content of the message identifiers. :-)

I use Base64 encoding for transferring serialized objects and stacktrace.
This can be greatly simplified but it is ok for now.


git-svn-id: https://svn.apache.org/repos/asf/ant/core/trunk@270537 13f79535-47bb-0310-9956-ffa450edef68
master
Stephane Bailliez 23 years ago
parent
commit
f7d1565cfd
5 changed files with 1089 additions and 0 deletions
  1. +101
    -0
      proposal/sandbox/junit/src/main/org/apache/tools/ant/taskdefs/optional/junit/remote/MessageIds.java
  2. +246
    -0
      proposal/sandbox/junit/src/main/org/apache/tools/ant/taskdefs/optional/junit/remote/MessageReader.java
  3. +155
    -0
      proposal/sandbox/junit/src/main/org/apache/tools/ant/taskdefs/optional/junit/remote/MessageWriter.java
  4. +159
    -0
      proposal/sandbox/junit/src/main/org/apache/tools/ant/taskdefs/optional/junit/remote/Server.java
  5. +428
    -0
      proposal/sandbox/junit/src/main/org/apache/tools/ant/taskdefs/optional/junit/remote/TestRunner.java

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

@@ -0,0 +1,101 @@
/*
* The Apache Software License, Version 1.1
*
* Copyright (c) 2001 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.junit.remote;


/**
* A set of messages identifiers to be used for communication
* between server/client(TestRunner).
*
* <i>
* This code is based on the code from Erich Gamma made for the
* JUnit plugin for Eclipse. {@link http://www.eclipse.org} and is merged
* with code originating from Ant 1.4.x.
* </i>
*
* @author <a href="mailto:sbailliez@apache.org">Stephane Bailliez</a>
*/
public interface MessageIds {
int MSG_HEADER_LENGTH = 8;

// messages send by TestRunServer
String TRACE_START = "%TRACES ";
String TRACE_END = "%TRACEE ";

// a line printed on stdout
String STDOUT_START = "%STDOUTS";
String STDOUT_END = "%STDOUTE";

// a line printed on stderr
String STDERR_START = "%STDERRS";
String STDERR_END = "%STDERRE";

// JVM system properties used in the VM
String PROPS_START = "%SYSPROS";
String PROPS_END = "%SYSPROE";

// test run started...
String TEST_COUNT = "%TESTC ";
// a test just started
String TEST_START = "%TESTS ";
// a test is finished
String TEST_END = "%TESTE ";
String TEST_ERROR = "%ERROR ";
String TEST_FAILED = "%FAILED ";
String TEST_ELAPSED_TIME = "%RUNTIME";
String TEST_STOPPED = "%TSTSTP ";

// messages understood by the Server
String TEST_STOP = ">STOP ";
}

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

@@ -0,0 +1,246 @@
/*
* The Apache Software License, Version 1.1
*
* Copyright (c) 2001 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.junit.remote;

import java.io.BufferedReader;
import java.io.IOException;
import java.io.InputStream;
import java.io.InputStreamReader;
import java.io.ObjectInputStream;
import java.io.ByteArrayInputStream;
import java.util.Vector;
import java.util.Properties;

import org.apache.tools.ant.taskdefs.optional.junit.TestRunListener;

/**
* Read and dispatch messages received via an input stream.
* The inputstream should be the connection to the remote client.
* <p>
* All messages are dispatched to the registered listeners.
* </p>
* <i>
* This code is based on the code from Erich Gamma made for the
* JUnit plugin for Eclipse. {@link http://www.eclipse.org} and is merged
* with code originating from Ant 1.4.x.
* </i>
*
* @author <a href="mailto:sbailliez@apache.org">Stephane Bailliez</a>
*/
public class MessageReader {

/** the set of registered listeners */
protected Vector listeners = new Vector();

// communication states with client
protected boolean inReadTrace = false;
protected boolean inFailedMessage = false;
protected String failedTest;
protected String failedMessage;
protected String failedTrace;
protected int failureKind;
protected long elapsedTime;
protected Properties sysprops;

public MessageReader() {
}

/**
* Add a new listener.
* @param listener a listener that will receive events from the client.
*/
public void addListener(TestRunListener listener) {
listeners.addElement(listener);
}

public void removeListener(TestRunListener listener) {
listeners.removeElement(listener);
}

/**
* Read a complete stream from a client, it will only return
* once the connection is stopped. You'd better not reuse
* an instance of this class since there are instance variables used
* to keep track of the client state.
* @param in the inputstream to the client.
*/
public void process(InputStream in) throws IOException {
BufferedReader reader = new BufferedReader(new InputStreamReader(in, "UTF8"));
String line;
while ((line = reader.readLine()) != null) {
processMessage(line);
}
}

/**
* Process a message from the client and dispatch the
* appropriate message to the listeners.
*/
protected void processMessage(String message) {
if (message == null){
return;
}

String arg = message.substring(MessageIds.MSG_HEADER_LENGTH);
if (message.startsWith(MessageIds.TRACE_START)) {
failedTrace = arg.substring(0, arg.indexOf(MessageIds.TRACE_END));
failedTrace = new String(Base64.decode(failedTrace.getBytes()));
notifyTestFailed(failureKind, failedTest, failedTrace);
return;
}

if (message.startsWith(MessageIds.TEST_COUNT)) {
int count = Integer.parseInt(arg);
notifyTestSuiteStarted(count);
return;
}
if (message.startsWith(MessageIds.TEST_START)) {
notifyTestStarted(arg);
return;
}
if (message.startsWith(MessageIds.TEST_END)) {
notifyTestEnded(arg);
return;
}
if (message.startsWith(MessageIds.TEST_ERROR)) {
failedTest = arg;
failureKind = TestRunListener.STATUS_ERROR;
return;
}
if (message.startsWith(MessageIds.TEST_FAILED)) {
failedTest = arg;
failureKind = TestRunListener.STATUS_FAILURE;
return;
}
if (message.startsWith(MessageIds.TEST_ELAPSED_TIME)) {
elapsedTime = Long.parseLong(arg);
notifyTestSuiteEnded(elapsedTime);
return;
}
if (message.startsWith(MessageIds.TEST_STOPPED)) {
elapsedTime = Long.parseLong(arg);
notifyTestSuiteStopped(elapsedTime);
return;
}
if (message.startsWith(MessageIds.PROPS_START)){
try {
byte[] bytes = arg.substring(0, arg.indexOf(MessageIds.PROPS_END)).getBytes();
bytes = Base64.decode(bytes);
ObjectInputStream ois = new ObjectInputStream(new ByteArrayInputStream(bytes));
sysprops = (Properties)ois.readObject();
} catch (Exception e){
// ignore now
e.printStackTrace();
}
notifyTestSystemProperties(sysprops);
}
}

protected void notifyTestStarted(String testname) {
synchronized (listeners) {
for (int i = 0; i < listeners.size(); i++) {
((TestRunListener) listeners.elementAt(i)).testStarted(testname);
}
}
}

protected void notifyTestEnded(String testname) {
synchronized (listeners) {
for (int i = 0; i < listeners.size(); i++) {
((TestRunListener) listeners.elementAt(i)).testEnded(testname);
}
}
}

protected void notifyTestFailed(int kind, String testname, String trace) {
synchronized (listeners) {
for (int i = 0; i < listeners.size(); i++) {
((TestRunListener) listeners.elementAt(i)).testFailed(kind, testname, trace);
}
}
}

protected void notifyTestSuiteStarted(int count) {
synchronized (listeners) {
for (int i = 0; i < listeners.size(); i++) {
((TestRunListener) listeners.elementAt(i)).testRunStarted(count);
}
}
}

protected void notifyTestSuiteEnded(long elapsedtime) {
synchronized (listeners) {
for (int i = 0; i < listeners.size(); i++) {
((TestRunListener) listeners.elementAt(i)).testRunEnded(elapsedtime);
}
}
}

protected void notifyTestSuiteStopped(long elapsedtime) {
synchronized (listeners) {
for (int i = 0; i < listeners.size(); i++) {
((TestRunListener) listeners.elementAt(i)).testRunStopped(elapsedtime);
}
}
}

protected void notifyTestSystemProperties(Properties props) {
synchronized (listeners) {
for (int i = 0; i < listeners.size(); i++) {
((TestRunListener) listeners.elementAt(i)).testRunSystemProperties(props);
}
}
}

}

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

@@ -0,0 +1,155 @@
/*
* The Apache Software License, Version 1.1
*
* Copyright (c) 2001 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.junit.remote;

import java.io.OutputStream;
import java.io.PrintWriter;
import java.io.ObjectOutputStream;
import java.io.ByteArrayOutputStream;
import java.io.IOException;

import org.apache.tools.ant.taskdefs.optional.junit.TestRunListener;

/**
* A wrapper that sends string messages to a given stream.
*
* <i>
* This code is based on the code from Erich Gamma made for the
* JUnit plugin for Eclipse. {@link http://www.eclipse.org} and is merged
* with code originating from Ant 1.4.x.
* </i>
*
* @author <a href="mailto:sbailliez@apache.org">Stephane Bailliez</a>
*/
public class MessageWriter implements MessageIds {

private PrintWriter pw;

public MessageWriter(OutputStream out) {
this.pw = new PrintWriter(out, true);
}

protected void finalize(){
close();
}

public void close() {
if (pw != null){
pw.close();
pw = null;
}
}

public void sendMessage(String msg) {
pw.println(msg);
}

// -------- notifier helper methods

public void notifyTestRunStarted(int testCount) {
sendMessage(MessageIds.TEST_COUNT + testCount);
}

public void notifyTestRunEnded(long elapsedTime) {
sendMessage(MessageIds.TEST_ELAPSED_TIME + elapsedTime);
}

public void notifyTestRunStopped(long elapsedTime) {
sendMessage(MessageIds.TEST_STOPPED + elapsedTime);
}

public void notifyTestStarted(String testName) {
sendMessage(MessageIds.TEST_START + testName);
}

public void notifyTestEnded(String testName) {
sendMessage(MessageIds.TEST_END + testName);
}

public void notifyTestFailed(int status, String testName, String trace) {
if (status == TestRunListener.STATUS_FAILURE) {
sendMessage(MessageIds.TEST_FAILED + testName);
} else {
sendMessage(MessageIds.TEST_ERROR + testName);
}
sendMessage(MessageIds.TRACE_START + new String(Base64.encode(trace.getBytes())) + MessageIds.TRACE_END);
}

public void notifyStdOutLine(String testname, String line) {
sendMessage(MessageIds.STDOUT_START);
sendMessage(line);
sendMessage(MessageIds.STDOUT_END);
}

public void notifyStdErrLine(String testname, String line) {
sendMessage(MessageIds.STDERR_START);
sendMessage(line);
sendMessage(MessageIds.STDERR_END);
}

public void notifySystemProperties() {
try {
ByteArrayOutputStream out = new ByteArrayOutputStream();
ObjectOutputStream oos = new ObjectOutputStream(out);
oos.writeObject(System.getProperties());
oos.close();
String msg = new String(Base64.encode(out.toByteArray()));
sendMessage(MessageIds.PROPS_START + msg + MessageIds.PROPS_END);
} catch (IOException e){
// ignore
e.printStackTrace();
}
}

}

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

@@ -0,0 +1,159 @@
/*
* The Apache Software License, Version 1.1
*
* Copyright (c) 2001 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.junit.remote;

import java.io.IOException;
import java.io.PrintWriter;
import java.net.ServerSocket;
import java.net.Socket;

import org.apache.tools.ant.taskdefs.optional.junit.TestRunListener;

/**
* The server that will receive events from a remote client.
*
* <i>
* This code is based on the code from Erich Gamma made for the
* JUnit plugin for Eclipse. {@link http://www.eclipse.org} and is merged
* with code originating from Ant 1.4.x.
* </i>
*
* @see TestRunner
* @author <a href="mailto:sbailliez@apache.org">Stephane Bailliez</a>
*/
public class Server {

/** the port where the server is listening */
private int port = -1;

/** the server socket */
private ServerSocket server;

/** the client that is connected to the server */
private Socket client;

/** the reader in charge of interpreting messages from the client */
private MessageReader reader = new MessageReader();

/** writer used to send message to clients */
private PrintWriter writer;

public Server(int port) {
this.port = port;
}

/**
* add a new listener
* @param listener a instance of a listener.
*/
public void addListener(TestRunListener listener) {
reader.addListener(listener);
}

/**
* remove an existing listener
* @param listener a instance of a listener.
*/
public void removeListener(TestRunListener listener) {
reader.removeListener(listener);
}

/** return whether there is a client running or not */
public boolean isRunning() {
return client != null;
}

/** start a server to the specified port */
public void start() {
Worker worker = new Worker();
worker.start();
}

/** cancel the connection to the client */
public void cancel() {
if (isRunning()) {
//@fixme
}
}

/** shutdown the server and any running client */
public void shutdown() {
try {
if (client != null) {
client.shutdownInput();
client.shutdownOutput();
}
server.close();
} catch (IOException e) {
}
}

//-----

private class Worker extends Thread {
public void run() {
try {
server = new ServerSocket(port);
client = server.accept();
writer = new PrintWriter(client.getOutputStream(), true);
reader.process(client.getInputStream());
} catch (IOException e) {
e.printStackTrace();
} finally {
stop();
shutdown();
}
}
}
}

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

@@ -0,0 +1,428 @@
/*
* The Apache Software License, Version 1.1
*
* Copyright (c) 2001 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.junit.remote;

import java.io.BufferedReader;
import java.io.IOException;
import java.io.InputStreamReader;
import java.io.FileInputStream;
import java.net.Socket;
import java.util.Vector;
import java.util.Properties;
import java.util.StringTokenizer;

import junit.framework.AssertionFailedError;
import junit.framework.Test;
import junit.framework.TestCase;
import junit.framework.TestListener;
import junit.framework.TestResult;
import junit.framework.TestSuite;

import org.apache.tools.ant.util.StringUtils;
import org.apache.tools.ant.taskdefs.optional.junit.JUnitHelper;
import org.apache.tools.ant.taskdefs.optional.junit.TestRunListener;

/**
* TestRunner for running tests and send results to a remote server.
*
* <i>
* This code is originally based on the code from Erich Gamma made for the
* JUnit plugin for Eclipse. {@link http://www.eclipse.org} and is merged
* with code ideas originating from Ant 1.4.x.
* </i>
*
* @author <a href="mailto:sbailliez@apache.org">Stephane Bailliez</a>
*/
public class TestRunner implements TestListener {

/** host to connect to */
private String host = "127.0.0.1";

/** port to connect to */
private int port = -1;

private boolean debug = false;

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

/** result of the current test */
private TestResult testResult;

/** client socket to communicate with the server */
private Socket clientSocket;

/** writer to send message to the server */
private MessageWriter writer;

/** reader to listen for a shutdown from the server */
private BufferedReader reader;

/** bean constructor */
public TestRunner(){
}

/**
* Set the debug mode.
* @param debug true to set to debug mode otherwise false.
*/
public void setDebug(boolean debug) {
this.debug = debug;
}

/**
* Set the port to connect to the server
* @param port a valid port number.
*/
public void setPort(int port) {
this.port = port;
}

/**
* Set the hostname of the server
* @param host the hostname or ip of the server
*/
public void setHost(String host) {
this.host = host;
}

/**
* Add a test class name to be executed by this runner.
* @param classname the class name of the test to run.
*/
public void addTestClassName(String classname) {
testClassNames.addElement(classname);
}

/**
* Thread listener for a shutdown from the server
* Note that it will stop any running test.
*/
private class StopThread extends Thread {
public void run() {
try {
String line = null;
if ((line = reader.readLine()) != null) {
if (line.startsWith(MessageIds.TEST_STOP)) {
TestRunner.this.stop();
}
}
} catch (Exception e) {
TestRunner.this.stop();
}
}
}

/**
* Entry point for command line.
* Usage:
* <pre>
* TestRunner -classnames <classnames> -port <port> -host <host> -debug
* -file
* -classnames <list of whitespace separated classnames to run>
* -port <port to connect to>
* -host <host to connect to>
* -debug to run in debug mode
* </pre>
*/
public static void main(String[] args) throws Exception {
TestRunner testRunServer = new TestRunner();
testRunServer.init(args);
testRunServer.run();
}

/**
* Parses the arguments of command line.
* testClassNames, host, port, listeners and debug mode are set
* @see #main(String[])
*/
protected void init(String[] args) throws Exception {
for (int i = 0; i < args.length; i++) {
if ("-file".equalsIgnoreCase(args[i])) {
// @fixme if you mix file and other options it will be a mess,
// not important right now.
FileInputStream fis = new FileInputStream(args[i + 1]);
Properties props = new Properties();
props.load(fis);
fis.close();
init(props);
}
if ("-classnames".equalsIgnoreCase(args[i])) {
for (int j = i + 1; j < args.length; j++) {
if (args[j].startsWith("-"))
break;
addTestClassName(args[j]);
}
}
if ("-port".equalsIgnoreCase(args[i])) {
setPort(Integer.parseInt(args[i + 1]));
}
if ("-host".equalsIgnoreCase(args[i])) {
setHost(args[i + 1]);
}
if ("-debug".equalsIgnoreCase(args[i])) {
setDebug(true);
}
}
}
/**
* Initialize the TestRunner from properties.
* @param the properties containing configuration data.
* @see #init(String[])
*/
protected void init(Properties props){
if ( props.getProperty("debug") != null ){
setDebug(true);
}
String port = props.getProperty("port");
if (port != null){
setPort(Integer.parseInt(port));
}
String host = props.getProperty("host");
if (host != null){
setHost(host);
}
String classnames = props.getProperty("classnames");
if (classnames != null){
StringTokenizer st = new StringTokenizer(classnames);
while (st.hasMoreTokens()){
addTestClassName( st.nextToken() );
}
}
}

public final void run() throws Exception {
if (testClassNames.size() == 0) {
throw new IllegalArgumentException("No TestCase specified");
}
connect();

testResult = new TestResult();
testResult.addListener(this);
runTests();

testResult.removeListener(this);
if (testResult != null) {
testResult.stop();
testResult = null;
}
}

/**
* Transform all classnames into instantiated <tt>Test</tt>.
* @throws Exception a generic exception that can be thrown while
* instantiating a test case.
*/
protected Test[] getSuites() throws Exception {
final int count = testClassNames.size();
log("Extracting testcases from " + count + " classnames...");
final Vector suites = new Vector(count);
for (int i = 0; i < count; i++) {
String classname = (String) testClassNames.elementAt(i);
try {
Test test = JUnitHelper.getTest(null, classname);
if (test != null){
suites.addElement(test);
}
} catch (Exception e){
// notify log error instead ?
log("Could not get Test instance from " + classname);
log(e);
}
}
log("Extracted " + suites.size() + " testcases.");
Test[] array = new Test[suites.size()];
suites.copyInto(array);
return array;
}

/**
* @param testClassNames String array of full qualified class names of test classes
*/
private void runTests() throws Exception {

Test[] suites = getSuites();

// count all testMethods and inform TestRunListeners
int count = countTests(suites);
log("Total tests to run: " + count);
writer.notifyTestRunStarted(count);

// send system properties to know for the JVM status
writer.notifySystemProperties();

long startTime = System.currentTimeMillis();
for (int i = 0; i < suites.length; i++) {
if (suites[i] instanceof TestCase){
suites[i] = new TestSuite(suites[i].getClass().getName());
}
suites[i].run(testResult);
}

// inform TestRunListeners of test end
long elapsedTime = System.currentTimeMillis() - startTime;
if (testResult == null || testResult.shouldStop()) {
writer.notifyTestRunStopped(elapsedTime);
} else {
writer.notifyTestRunEnded(elapsedTime);
}
log("Finished after " + elapsedTime + "ms");
shutDown();
}

/** count the number of test methods in all tests */
private final int countTests(Test[] tests) {
int count = 0;
for (int i = 0; i < tests.length; i++) {
count = count + tests[i].countTestCases();
}
return count;
}

protected void stop() {
if (testResult != null) {
testResult.stop();
}
}

/**
* connect to the specified host and port.
* @throws IOException if any error occurs during connection.
*/
protected void connect() throws IOException {
log("Connecting to " + host + " on port " + port + "...");
clientSocket = new Socket(host, port);
writer = new MessageWriter(clientSocket.getOutputStream());
reader = new BufferedReader(new InputStreamReader(clientSocket.getInputStream()));
new StopThread().start();
}


protected void shutDown() {

if (writer != null) {
writer.close();
writer = null;
}

try {
if (reader != null) {
reader.close();
reader = null;
}
} catch (IOException e) {
log(e);
}

try {
if (clientSocket != null) {
clientSocket.close();
clientSocket = null;
}
} catch (IOException e) {
log(e);
}
}


// -------- JUnit TestListener implementation


public void startTest(Test test) {
String testName = test.toString();
writer.notifyTestStarted(testName);
}

public void addError(Test test, Throwable t) {
String testName = test.toString();
String trace = StringUtils.getStackTrace(t);
writer.notifyTestFailed(TestRunListener.STATUS_ERROR, testName, trace);
}

/**
* this implementation is for JUnit &lt; 3.4
* @see addFailure(Test, Throwable)
*/
public void addFailure(Test test, AssertionFailedError afe) {
addFailure(test, (Throwable) afe);
}

/**
* This implementation is for JUnit &lt;= 3.4
* @see addFailure(Test, AssertionFailedError)
*/
public void addFailure(Test test, Throwable t) {
String testName = test.toString();
String trace = StringUtils.getStackTrace(t);
writer.notifyTestFailed(TestRunListener.STATUS_FAILURE, testName, trace);
}

public void endTest(Test test) {
String testName = test.toString();
writer.notifyTestEnded(testName);
}

public void log(String msg){
if (debug){
System.out.println(msg);
}
}

public void log(Throwable t){
if (debug){
t.printStackTrace();
}
}
}


Loading…
Cancel
Save