blob: 86b8978fc519163b1b41f859d90be342c3891db4 [file] [log] [blame]
/*******************************************************************************
* Copyright (c) 2004 IBM Corporation and others.
* All rights reserved. This program and the accompanying materials
* are made available under the terms of the Common Public License v1.0
* which accompanies this distribution, and is available at
* http://www.eclipse.org/legal/cpl-v10.html
*
* Contributors:
* IBM Corporation - initial API and implementation
*******************************************************************************/
package org.eclipse.core.tests.session;
import java.io.*;
import java.net.*;
import junit.framework.TestResult;
import org.eclipse.core.runtime.*;
import org.eclipse.core.tests.harness.CoreTest;
public class SessionTestRunner {
class Result {
final static int ERROR = 2;
final static int FAILURE = 1;
final static int SUCCESS = 0;
String message;
String stackTrace;
int type;
}
/**
* Collectors can be used a single time only.
*/
class ResultCollector implements Runnable {
private boolean finished;
private Result newResult = new Result();
private Result result;
ServerSocket serverSocket;
private boolean shouldRun = true;
private StringBuffer stack;
ResultCollector() throws IOException {
serverSocket = new ServerSocket(0);
}
public int getPort() {
return serverSocket.getLocalPort();
}
public Result getResult() {
return result;
}
public synchronized boolean isFinished() {
return finished;
}
private synchronized void markAsFinished() {
finished = true;
notifyAll();
}
private void processAvailableMessages(BufferedReader messageReader) throws IOException {
while (messageReader.ready()) {
String message = messageReader.readLine();
processMessage(message);
}
}
private void processMessage(String message) {
if (message.startsWith("%ERROR")) {
newResult.type = Result.ERROR;
newResult.message = "";
return;
}
if (message.startsWith("%FAILED")) {
newResult.type = Result.FAILURE;
newResult.message = "";
return;
}
if (message.startsWith("%TRACES")) {
stack = new StringBuffer();
return;
}
if (message.startsWith("%TRACEE")) {
newResult.stackTrace = stack.toString();
stack = null;
return;
}
if (stack != null) {
stack.append(message);
stack.append(System.getProperty("line.separator"));
return;
}
if (message.startsWith("%RUNTIME")) {
result = newResult;
return;
}
}
public void run() {
Socket connection = null;
try {
// someone asked us to stop before we could do anything
if (!shouldRun())
return;
try {
connection = serverSocket.accept();
} catch (SocketException se) {
if (!shouldRun())
// we have been finished without ever getting any connections
// no need to throw exception
return;
// something else stopped us
throw se;
}
BufferedReader messageReader = new BufferedReader(new InputStreamReader(connection.getInputStream(), "UTF-8"));
try {
// main loop
while (true) {
synchronized (this) {
processAvailableMessages(messageReader);
if (!shouldRun())
return;
this.wait(150);
}
}
} catch (InterruptedException e) {
// not expected
}
} catch (IOException e) {
CoreTest.log(CoreTest.PI_HARNESS, e);
} finally {
// remember we are already finished
markAsFinished();
// cleanup
try {
if (connection != null && !connection.isClosed())
connection.close();
} catch (IOException e) {
CoreTest.log(CoreTest.PI_HARNESS, e);
}
try {
if (serverSocket != null && !serverSocket.isClosed())
serverSocket.close();
} catch (IOException e) {
CoreTest.log(CoreTest.PI_HARNESS, e);
}
}
}
private synchronized boolean shouldRun() {
return shouldRun;
}
/*
* Politely asks the collector thread to stop and wait until it is finished.
*/
public void shutdown() {
// ask the collector to stop
synchronized (this) {
if (isFinished())
return;
shouldRun = false;
try {
serverSocket.close();
} catch (IOException e) {
CoreTest.log(CoreTest.PI_HARNESS, e);
}
notifyAll();
}
// wait until the collector is done
synchronized (this) {
while (!isFinished())
try {
wait(100);
} catch (InterruptedException e) {
// we don't care
}
}
}
}
/**
* Creates a brand new setup object to be used for this session only, based
* on the setup provided by the test descriptor.
*
* @param descriptor a test descriptor for the session test to run
* @param port the port used by the result collector
* @return a brand new setup
*/
private Setup createSetup(TestDescriptor descriptor, int port) {
Setup setup = (Setup) descriptor.getSetup().clone();
setup.setEclipseArgument(Setup.APPLICATION, descriptor.getApplicationId());
setup.setEclipseArgument("testpluginname", descriptor.getPluginId());
setup.setEclipseArgument("test", descriptor.getTestClass() + ':' + descriptor.getTestMethod());
setup.setEclipseArgument("port", Integer.toString(port));
return setup;
}
/**
* Runs the setup. Returns a status object indicating the outcome of the operation.
* @param timeout
* @return a status object indicating the outcome
*/
private IStatus launch(Setup setup) {
// to prevent changes in the protocol from breaking us,
// force the version we know we can work with
setup.setEclipseArgument("version", "3");
if (SetupManager.inDebugMode()) {
System.out.print("Command line: ");
System.out.println(setup.toCommandLineString());
}
IStatus outcome = Status.OK_STATUS;
try {
ProcessController process = new ProcessController(setup.getTimeout(), setup.getCommandLine());
process.forwardErrorOutput(System.err);
process.forwardOutput(System.out);
//if necessary to interact with the spawned process, this would have
// to be done
//process.forwardInput(System.in);
int returnCode = process.execute();
if (returnCode != 0)
outcome = new Status(IStatus.WARNING, Platform.PI_RUNTIME, returnCode, "Process returned non-zero code: " + returnCode + "\n\tCommand: " + setup, null);
} catch (Exception e) {
outcome = new Status(IStatus.ERROR, Platform.PI_RUNTIME, -1, "Error running process\n\tCommand: " + setup, e);
}
return outcome;
}
/**
* Runs the test described in a separate session.
*
* @param descriptor
* @param result
*/
public final void run(TestDescriptor descriptor, TestResult result) {
result.startTest(descriptor.getTest());
try {
ResultCollector collector = null;
try {
collector = new ResultCollector();
} catch (IOException e) {
result.addError(descriptor.getTest(), e);
return;
}
Setup setup = createSetup(descriptor, collector.getPort());
new Thread(collector, "Test result collector").start();
IStatus status = launch(setup);
collector.shutdown();
// ensure the session ran without any errors
if (!status.isOK()) {
CoreTest.log(CoreTest.PI_HARNESS, status);
if (status.getSeverity() == IStatus.ERROR) {
result.addError(descriptor.getTest(), new CoreException(status));
return;
}
}
Result collected = collector.getResult();
if (collected == null) {
if (!descriptor.isCrashTest())
result.addError(descriptor.getTest(), new Exception("Test did not run"));
} else if (collected.type == Result.FAILURE)
result.addFailure(descriptor.getTest(), new RemoteAssertionFailedError(collected.message, collected.stackTrace));
else if (collected.type == Result.ERROR)
result.addError(descriptor.getTest(), new RemoteTestException(collected.message, collected.stackTrace));
else if (descriptor.isCrashTest())
result.addError(descriptor.getTest(), new Exception("Crash test failed to cause crash"));
} finally {
result.endTest(descriptor.getTest());
}
}
}