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-ffa450edef68master
@@ -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 "; | |||||
} |
@@ -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); | |||||
} | |||||
} | |||||
} | |||||
} |
@@ -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(); | |||||
} | |||||
} | |||||
} |
@@ -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(); | |||||
} | |||||
} | |||||
} | |||||
} |
@@ -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 < 3.4 | |||||
* @see addFailure(Test, Throwable) | |||||
*/ | |||||
public void addFailure(Test test, AssertionFailedError afe) { | |||||
addFailure(test, (Throwable) afe); | |||||
} | |||||
/** | |||||
* This implementation is for JUnit <= 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(); | |||||
} | |||||
} | |||||
} | |||||