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(); | |||
| } | |||
| } | |||
| } | |||