blob: 36551e03e925ef329effed212e4a72b2c9869759 [file] [log] [blame]
/*
* (c) Copyright IBM Corp. 2000, 2001.
* All Rights Reserved.
*/
package org.eclipse.jdt.internal.junit.runner;
import java.io.BufferedReader;
import java.io.IOException;
import java.io.InputStreamReader;
import java.io.PrintWriter;
import java.io.StringWriter;
import java.lang.reflect.Constructor;
import java.lang.reflect.InvocationTargetException;
import java.lang.reflect.Method;
import java.net.Socket;
import java.util.ArrayList;
import java.util.List;
import junit.extensions.TestDecorator;
import junit.framework.AssertionFailedError;
import junit.framework.Test;
import junit.framework.TestFailure;
import junit.framework.TestListener;
import junit.framework.TestResult;
import junit.framework.TestSuite;
import org.eclipse.jdt.internal.junit.ui.JUnitMessages;
/**
* A TestRunner that reports results via a socket connection.
* See MessageIds for more information about the protocl.
*/
public class RemoteTestRunner implements TestListener {
/**
* Holder for information for a rerun request
*/
private static class RerunRequest {
String fClassName;
String fTestName;
public RerunRequest(String className, String testName) {
fClassName= className;
fTestName= testName;
}
}
private static final String SUITE_METHODNAME= "suite"; //$NON-NLS-1$
/**
* The name of the test classes to be executed
*/
private String[] fTestClassNames;
/**
* The current test result
*/
private TestResult fTestResult;
/**
* The client socket.
*/
private Socket fClientSocket;
/**
* Print writer for sending messages
*/
private PrintWriter fWriter;
/**
* Reader for incoming messages
*/
private BufferedReader fReader;
/**
* Host to connect to, default is the localhost
*/
private String fHost= ""; //$NON-NLS-1$
/**
* Port to connect to.
*/
private int fPort= -1;
/**
* Is the debug mode enabled?
*/
private boolean fDebugMode= false;
/**
* Keep the test run server alive after a test run has finished.
* This allows to rerun tests.
*/
private boolean fKeepAlive= false;
/**
* Has the server been stopped
*/
private boolean fStopped= false;
/**
* Queue of rerun requests.
*/
private List fRerunRequests= new ArrayList(10);
/**
* Reader thread that processes messages from the client.
*/
private class ReaderThread extends Thread {
public ReaderThread() {
super("ReaderThread"); //$NON-NLS-1$
}
public void run(){
try {
String message= null;
while (true) {
if ((message= fReader.readLine()) != null) {
if (message.startsWith(MessageIds.TEST_STOP)){
fStopped= true;
RemoteTestRunner.this.stop();
synchronized(RemoteTestRunner.this) {
RemoteTestRunner.this.notifyAll();
}
break;
}
else if (message.startsWith(MessageIds.TEST_RERUN)) {
String arg= message.substring(MessageIds.MSG_HEADER_LENGTH);
int c= arg.indexOf(" "); //$NON-NLS-1$
synchronized(RemoteTestRunner.this) {
fRerunRequests.add(new RerunRequest(arg.substring(0, c), arg.substring(c+1)));
RemoteTestRunner.this.notifyAll();
}
}
}
}
} catch (Exception e) {
RemoteTestRunner.this.stop();
}
}
}
/**
* The main entry point.
* Parameters<pre>
* -classnames: the name of the test suite class
* -host: the host to connect to - default local host
* -port: the port to connect to, mandatory argument
* -keepalive: keep the process alive after a test run
* </pre>
*/
public static void main(String[] args) {
// hack to pass the AllTests of JUnit
// force static initialization of BaseTestRunner
// by creating a junit.textui.TestRunner and free
// it immediately.
RemoteTestRunner testRunServer= new RemoteTestRunner();
testRunServer.init(args);
testRunServer.run();
// fix for 14434
System.exit(0);
}
/**
* Parse command line arguments. Hook for subclasses to process
* additional arguments.
*/
protected void init(String[] args) {
defaultInit(args);
}
/**
* The class loader to be used for loading tests.
* Subclasses may override to use another class loader.
*/
protected ClassLoader getClassLoader() {
return getClass().getClassLoader();
}
/**
* Process the default arguments.
*/
protected final void defaultInit(String[] args) {
for(int i= 0; i < args.length; i++) {
if(args[i].toLowerCase().equals("-classnames") || args[i].toLowerCase().equals("-classname")){ //$NON-NLS-1$ //$NON-NLS-2$
ArrayList list= new ArrayList();
for (int j= i+1; j < args.length; j++) {
if (args[j].startsWith("-")) //$NON-NLS-1$
break;
list.add(args[j]);
}
fTestClassNames= (String[]) list.toArray(new String[list.size()]);
}
else if(args[i].toLowerCase().equals("-port")) { //$NON-NLS-1$
fPort= Integer.parseInt(args[i+1]);
}
else if(args[i].toLowerCase().equals("-host")) { //$NON-NLS-1$
fHost= args[i+1];
}
else if(args[i].toLowerCase().equals("-keepalive")) { //$NON-NLS-1$
fKeepAlive= true;
}
else if(args[i].toLowerCase().equals("-debugging") || args[i].toLowerCase().equals("-debug")){ //$NON-NLS-1$ //$NON-NLS-2$
fDebugMode= true;
}
}
if(fTestClassNames == null || fTestClassNames.length == 0)
throw new IllegalArgumentException(JUnitMessages.getString("RemoteTestRunner.error.classnamemissing")); //$NON-NLS-1$
if (fPort == -1)
throw new IllegalArgumentException(JUnitMessages.getString("RemoteTestRunner.error.portmissing")); //$NON-NLS-1$
if (fDebugMode)
System.out.println("keepalive "+fKeepAlive); //$NON-NLS-1$
}
/**
* Connects to the remote ports and runs the tests.
*/
protected void run() {
if (!connect()) {
return;
}
fTestResult= new TestResult();
fTestResult.addListener(this);
runTests(fTestClassNames);
fTestResult.removeListener(this);
if (fTestResult != null) {
fTestResult.stop();
fTestResult= null;
}
if (fKeepAlive)
waitForReruns();
shutDown();
}
/**
* Waits for rerun requests until an explicit stop request
*/
private synchronized void waitForReruns() {
while (!fStopped) {
try {
wait();
if (!fStopped && fRerunRequests.size() > 0) {
RerunRequest r= (RerunRequest)fRerunRequests.remove(0);
rerunTest(r.fClassName, r.fTestName);
}
} catch (InterruptedException e) {
}
}
}
/**
* Returns the Test corresponding to the given suite.
*/
private Test getTest(String suiteClassName) {
Class testClass= null;
try {
testClass= loadSuiteClass(suiteClassName);
} catch (ClassNotFoundException e) {
String clazz= e.getMessage();
if (clazz == null)
clazz= suiteClassName;
runFailed(JUnitMessages.getFormattedString("RemoteTestRunner.error.classnotfound", clazz)); //$NON-NLS-1$
return null;
} catch(Exception e) {
runFailed(JUnitMessages.getFormattedString("RemoteTestRunner.error.exception", e.toString() )); //$NON-NLS-1$
return null;
}
Method suiteMethod= null;
try {
suiteMethod= testClass.getMethod(SUITE_METHODNAME, new Class[0]);
} catch(Exception e) {
// try to extract a test suite automatically
return new TestSuite(testClass);
}
Test test= null;
try {
test= (Test)suiteMethod.invoke(null, new Class[0]); // static method
}
catch (InvocationTargetException e) {
runFailed(JUnitMessages.getFormattedString("RemoteTestRunner.error.invoke", e.getTargetException().toString() )); //$NON-NLS-1$
return null;
}
catch (IllegalAccessException e) {
runFailed(JUnitMessages.getFormattedString("RemoteTestRunner.error.invoke", e.toString() )); //$NON-NLS-1$
return null;
}
return test;
}
protected void runFailed(String message) {
System.err.println(message);
}
/**
* Loads the test suite class.
*/
private Class loadSuiteClass(String className) throws ClassNotFoundException {
if (className == null)
return null;
return getClassLoader().loadClass(className);
}
/**
* Runs a set of tests.
*/
private void runTests(String[] testClassNames) {
// instantiate all tests
Test[] suites= new Test[testClassNames.length];
for (int i= 0; i < suites.length; i++) {
Test test= getTest(testClassNames[i]);
suites[i]= test;
}
// count all testMethods and inform ITestRunListeners
int count= countTests(suites);
notifyTestRunStarted(count);
if (count == 0) {
notifyTestRunEnded(0);
return;
}
long startTime= System.currentTimeMillis();
if (fDebugMode)
System.out.println("start send tree"); //$NON-NLS-1$
for (int i= 0; i < suites.length; i++) {
sendTree(suites[i]);
}
if (fDebugMode)
System.out.println("done send tree"+(System.currentTimeMillis()-startTime)); //$NON-NLS-1$
long testStartTime= System.currentTimeMillis();
for (int i= 0; i < suites.length; i++) {
suites[i].run(fTestResult);
}
// inform ITestRunListeners of test end
if (fTestResult == null || fTestResult.shouldStop())
notifyTestRunStopped(System.currentTimeMillis() - testStartTime);
else
notifyTestRunEnded(System.currentTimeMillis() - testStartTime);
}
private int countTests(Test[] tests) {
int count= 0;
for (int i= 0; i < tests.length; i++) {
if (tests[i] != null)
count= count + tests[i].countTestCases();
}
return count;
}
/**
* Reruns a test as defined by the fully qualified class name and
* the name of the test.
*/
public void rerunTest(String className, String testName) {
Test reloadedTest= null;
try {
Class reloadedTestClass= getClassLoader().loadClass(className);
Class[] classArgs= { String.class };
Constructor constructor= reloadedTestClass.getConstructor(classArgs);
Object[] args= new Object[]{testName};
reloadedTest=(Test)constructor.newInstance(args);
} catch(Exception e) {
runFailed(JUnitMessages.getFormattedString("RemoteTestRunner.error.load", reloadedTest )); //$NON-NLS-1$
return;
}
TestResult result= new TestResult();
reloadedTest.run(result);
notifyTestReran(result, className, testName);
}
/*
* @see TestListener#addError(Test, Throwable)
*/
public final void addError(Test test, Throwable throwable) {
notifyTestFailed(MessageIds.TEST_ERROR, test.toString(), getTrace(throwable));
}
/*
* @see TestListener#addFailure(Test, AssertionFailedError)
*/
public final void addFailure(Test test, AssertionFailedError assertionFailedError) {
notifyTestFailed(MessageIds.TEST_FAILED, test.toString(), getTrace(assertionFailedError));
}
/*
* @see TestListener#endTest(Test)
*/
public void endTest(Test test) {
notifyTestEnded(test.toString());
}
/*
* @see TestListener#startTest(Test)
*/
public void startTest(Test test) {
notifyTestStarted(test.toString());
}
private void sendTree(Test test){
if(test instanceof TestDecorator){
TestDecorator decorator= (TestDecorator) test;
sendTree(decorator.getTest());
}
else if(test instanceof TestSuite){
TestSuite suite= (TestSuite) test;
notifyTestTreeEntry(suite.toString().trim() + ',' + true + ',' + suite.testCount());
for(int i=0; i < suite.testCount(); i++){
sendTree(suite.testAt(i));
}
}
else {
notifyTestTreeEntry(test.toString().trim() + ',' + false + ',' + test.countTestCases());
}
}
/**
* Returns the stack trace for the given throwable.
*/
private String getTrace(Throwable t) {
StringWriter stringWriter= new StringWriter();
PrintWriter writer= new PrintWriter(stringWriter);
t.printStackTrace(writer);
StringBuffer buffer= stringWriter.getBuffer();
return buffer.toString();
}
/**
* Stop the current test run.
*/
protected void stop() {
if (fTestResult != null) {
fTestResult.stop();
}
}
/**
* Connect to the remote test listener.
*/
private boolean connect() {
if (fDebugMode)
System.out.println("RemoteTestRunner: trying to connect" + fHost + ":" + fPort); //$NON-NLS-1$ //$NON-NLS-2$
Exception exception= null;
for (int i= 1; i < 10; i++) {
try{
fClientSocket= new Socket(fHost, fPort);
fWriter= new PrintWriter(fClientSocket.getOutputStream(), false/*true*/);
fReader= new BufferedReader(new InputStreamReader(fClientSocket.getInputStream()));
new ReaderThread().start();
return true;
} catch(IOException e){
exception= e;
}
try {
Thread.currentThread().sleep(2000);
} catch(InterruptedException e) {
}
}
runFailed(JUnitMessages.getFormattedString("RemoteTestRunner.error.connect", new String[]{fHost, Integer.toString(fPort)} )); //$NON-NLS-1$
exception.printStackTrace();
return false;
}
/**
* Shutsdown the connection to the remote test listener.
*/
private void shutDown() {
if (fWriter != null) {
fWriter.close();
fWriter= null;
}
try {
if (fReader != null) {
fReader.close();
fReader= null;
}
} catch(IOException e) {
if (fDebugMode)
e.printStackTrace();
}
try {
if (fClientSocket != null) {
fClientSocket.close();
fClientSocket= null;
}
} catch(IOException e) {
if (fDebugMode)
e.printStackTrace();
}
}
private void sendMessage(String msg) {
if(fWriter == null)
return;
fWriter.println(msg);
}
private void notifyTestRunStarted(int testCount) {
sendMessage(MessageIds.TEST_RUN_START + testCount);
}
private void notifyTestRunEnded(long elapsedTime) {
sendMessage(MessageIds.TEST_RUN_END + elapsedTime);
fWriter.flush();
//shutDown();
}
private void notifyTestRunStopped(long elapsedTime) {
sendMessage(MessageIds.TEST_STOPPED + elapsedTime );
fWriter.flush();
//shutDown();
}
private void notifyTestStarted(String testName) {
sendMessage(MessageIds.TEST_START + testName);
fWriter.flush();
}
private void notifyTestEnded(String testName) {
sendMessage(MessageIds.TEST_END + testName);
}
private void notifyTestFailed(String status, String testName, String trace) {
sendMessage(status + testName);
sendMessage(MessageIds.TRACE_START);
sendMessage(trace);
sendMessage(MessageIds.TRACE_END);
fWriter.flush();
}
private void notifyTestTreeEntry(String treeEntry) {
sendMessage(MessageIds.TEST_TREE + treeEntry);
}
private void notifyTestReran(TestResult result, String testClass, String testName) {
TestFailure failure= null;
if (result.errorCount() > 0) {
failure= (TestFailure)result.errors().nextElement();
}
if (result.failureCount() > 0) {
failure= (TestFailure)result.failures().nextElement();
}
if (failure != null) {
Throwable t= failure.thrownException();
String trace= getTrace(t);
sendMessage(MessageIds.RTRACE_START);
sendMessage(trace);
sendMessage(MessageIds.RTRACE_END);
fWriter.flush();
}
String status= "OK"; //$NON-NLS-1$
if (result.errorCount() > 0)
status= "ERROR"; //$NON-NLS-1$
else if (result.failureCount() > 0)
status= "FAILURE"; //$NON-NLS-1$
if (fPort != -1) {
sendMessage(MessageIds.TEST_RERAN + testClass+" "+testName+" "+status); //$NON-NLS-1$ //$NON-NLS-2$
fWriter.flush();
}
}
}