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