/******************************************************************************* * Copyright (c) 2000, 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 * Julien Ruaux: jruaux@octo.com * Vincent Massol: vmassol@octo.com ******************************************************************************/ package org.rubypeople.rdt.internal.testunit.ui; import java.io.BufferedReader; import java.io.IOException; import java.io.InputStreamReader; import java.io.OutputStreamWriter; import java.io.PrintWriter; import java.io.UnsupportedEncodingException; import java.net.ServerSocket; import java.net.Socket; import java.net.SocketException; import org.eclipse.core.runtime.ISafeRunnable; import org.eclipse.core.runtime.SafeRunner; import org.rubypeople.rdt.internal.testunit.runner.MessageIds; import org.rubypeople.rdt.testunit.ITestRunListener; /** * The client side of the RemoteTestRunner. Handles the marshaling of the * different messages. */ public class RemoteTestRunnerClient { public abstract class ListenerSafeRunnable implements ISafeRunnable { public void handleException(Throwable exception) { TestunitPlugin.log(exception); } } /** * A simple state machine to process requests from the RemoteTestRunner */ abstract class ProcessingState { abstract ProcessingState readMessage(String message); } class DefaultProcessingState extends ProcessingState { ProcessingState readMessage(String message) { if (message.startsWith(MessageIds.TRACE_START)) { fFailedTrace = ""; //$NON-NLS-1$ return fTraceState; } if (message.startsWith(MessageIds.EXPECTED_START)) { fExpectedResult = null; return fExpectedState; } if (message.startsWith(MessageIds.ACTUAL_START)) { fActualResult = null; return fActualState; } if (message.startsWith(MessageIds.RTRACE_START)) { fFailedRerunTrace = ""; //$NON-NLS-1$ return fRerunState; } String arg = message.substring(MessageIds.MSG_HEADER_LENGTH); if (message.startsWith(MessageIds.TEST_RUN_START)) { // version < 2 format: count // version >= 2 format: count+" "+version int count = 0; int v = arg.indexOf(' '); if (v == -1) { fVersion = "v1"; //$NON-NLS-1$ count = Integer.parseInt(arg); } else { fVersion = arg.substring(v + 1); String sc = arg.substring(0, v); count = Integer.parseInt(sc); } notifyTestRunStarted(count); return this; } if (message.startsWith(MessageIds.TEST_START)) { notifyTestStarted(arg); return this; } if (message.startsWith(MessageIds.TEST_END)) { notifyTestEnded(arg); return this; } if (message.startsWith(MessageIds.TEST_ERROR)) { extractFailure(arg, ITestRunListener.STATUS_ERROR); return this; } if (message.startsWith(MessageIds.TEST_FAILED)) { extractFailure(arg, ITestRunListener.STATUS_FAILURE); return this; } if (message.startsWith(MessageIds.TEST_RUN_END)) { long elapsedTime = Long.parseLong(arg); testRunEnded(elapsedTime); return this; } if (message.startsWith(MessageIds.TEST_STOPPED)) { long elapsedTime = Long.parseLong(arg); notifyTestRunStopped(elapsedTime); shutDown(); return this; } if (message.startsWith(MessageIds.TEST_TREE)) { notifyTestTreeEntry(arg); return this; } if (message.startsWith(MessageIds.TEST_RERAN)) { if (hasTestId()) scanReranMessage(arg); else scanOldReranMessage(arg); return this; } return this; } } class TraceProcessingState extends ProcessingState { ProcessingState readMessage(String message) { if (message.startsWith(MessageIds.TRACE_END)) { notifyTestFailed(); fFailedTrace = ""; //$NON-NLS-1$ fExpectedResult = null; fActualResult = null; return fDefaultState; } fFailedTrace += message + '\n'; return this; } } class ExpectedProcessingState extends ProcessingState { ProcessingState readMessage(String message) { if (message.startsWith(MessageIds.EXPECTED_END)) return fDefaultState; if (fExpectedResult == null) fExpectedResult = message + '\n'; else fExpectedResult += message + '\n'; return this; } } class ActualProcessingState extends ProcessingState { ProcessingState readMessage(String message) { if (message.startsWith(MessageIds.ACTUAL_END)) return fDefaultState; if (fActualResult == null) fActualResult = message + '\n'; else fActualResult += message + '\n'; return this; } } class RerunTraceProcessingState extends ProcessingState { ProcessingState readMessage(String message) { if (message.startsWith(MessageIds.RTRACE_END)) return fDefaultState; fFailedRerunTrace += message + '\n'; return this; } } ProcessingState fDefaultState = new DefaultProcessingState(); ProcessingState fTraceState = new TraceProcessingState(); ProcessingState fExpectedState = new ExpectedProcessingState(); ProcessingState fActualState = new ActualProcessingState(); ProcessingState fRerunState = new RerunTraceProcessingState(); ProcessingState fCurrentState = fDefaultState; /** * An array of listeners that are informed about test events. */ private ITestRunListener[] fListeners; /** * The server socket */ private ServerSocket fServerSocket; private Socket fSocket; private int fPort = -1; private PrintWriter fWriter; private BufferedReader fBufferedReader; /** * The protocol version */ private String fVersion; /** * The failed test that is currently reported from the RemoteTestRunner */ private String fFailedTest; /** * The Id of the failed test */ private String fFailedTestId; /** * The failed trace that is currently reported from the RemoteTestRunner */ private String fFailedTrace; /** * The expected test result */ private String fExpectedResult; /** * The actual test result */ private String fActualResult; /** * The failed trace of a reran test */ private String fFailedRerunTrace; /** * The kind of failure of the test that is currently reported as failed */ private int fFailureKind; private boolean fDebug = false; /** * Reads the message stream from the RemoteTestRunner */ private class ServerConnection extends Thread { int fServerPort; public ServerConnection(int port) { super("ServerConnection"); //$NON-NLS-1$ fServerPort = port; } public void run() { try { if (fDebug) System.out.println("Creating server socket " + fServerPort); //$NON-NLS-1$ fServerSocket = new ServerSocket(fServerPort); fSocket = fServerSocket.accept(); try { fBufferedReader = new BufferedReader(new InputStreamReader(fSocket.getInputStream(), "UTF-8")); //$NON-NLS-1$ } catch (UnsupportedEncodingException e) { fBufferedReader = new BufferedReader(new InputStreamReader(fSocket.getInputStream())); } try { fWriter = new PrintWriter(new OutputStreamWriter(fSocket.getOutputStream(), "UTF-8"), true); //$NON-NLS-1$ } catch (UnsupportedEncodingException e1) { fWriter = new PrintWriter(new OutputStreamWriter(fSocket.getOutputStream()), true); } String message; while (fBufferedReader != null && (message = readMessage(fBufferedReader)) != null) receiveMessage(message); } catch (SocketException e) { notifyTestRunTerminated(); } catch (IOException e) { System.out.println(e); // fall through } shutDown(); } } /** * Start listening to a test run. Start a server connection that the * RemoteTestRunner can connect to. */ public synchronized void startListening(ITestRunListener[] listeners, int port) { fListeners = listeners; fPort = port; ServerConnection connection = new ServerConnection(port); connection.start(); } /** * Requests to stop the remote test run. */ public synchronized void stopTest() { if (isRunning()) { fWriter.println(MessageIds.TEST_STOP); fWriter.flush(); } } private synchronized void shutDown() { if (fDebug) System.out.println("shutdown " + fPort); //$NON-NLS-1$ if (fWriter != null) { fWriter.close(); fWriter = null; } try { if (fBufferedReader != null) { fBufferedReader.close(); fBufferedReader = null; } } catch (IOException e) {} try { if (fSocket != null) { fSocket.close(); fSocket = null; } } catch (IOException e) {} try { if (fServerSocket != null) { fServerSocket.close(); fServerSocket = null; } } catch (IOException e) {} } public boolean isRunning() { return fSocket != null; } private String readMessage(BufferedReader in) throws IOException { return in.readLine(); } private void receiveMessage(String message) { fCurrentState = fCurrentState.readMessage(message); } private void scanOldReranMessage(String arg) { // OLD V1 format // format: className" "testName" "status // status: FAILURE, ERROR, OK int c = arg.indexOf(" "); //$NON-NLS-1$ int t = arg.indexOf(" ", c + 1); //$NON-NLS-1$ String className = arg.substring(0, c); String testName = arg.substring(c + 1, t); String status = arg.substring(t + 1); int statusCode = ITestRunListener.STATUS_OK; if (status.equals("FAILURE")) //$NON-NLS-1$ statusCode = ITestRunListener.STATUS_FAILURE; else if (status.equals("ERROR")) //$NON-NLS-1$ statusCode = ITestRunListener.STATUS_ERROR; String trace = ""; //$NON-NLS-1$ if (statusCode != ITestRunListener.STATUS_OK) trace = fFailedRerunTrace; // assumption a rerun trace was sent before notifyTestReran(className + testName, className, testName, statusCode, trace); } private void scanReranMessage(String arg) { // format: testId" "className" "testName" "status // status: FAILURE, ERROR, OK int i = arg.indexOf(' '); int c = arg.indexOf(' ', i + 1); //$NON-NLS-1$ int t = arg.indexOf(' ', c + 1); //$NON-NLS-1$ String testId = arg.substring(0, i); String className = arg.substring(i + 1, c); String testName = arg.substring(c + 1, t); String status = arg.substring(t + 1); int statusCode = ITestRunListener.STATUS_OK; if (status.equals("FAILURE")) //$NON-NLS-1$ statusCode = ITestRunListener.STATUS_FAILURE; else if (status.equals("ERROR")) //$NON-NLS-1$ statusCode = ITestRunListener.STATUS_ERROR; String trace = ""; //$NON-NLS-1$ if (statusCode != ITestRunListener.STATUS_OK) trace = fFailedRerunTrace; // assumption a rerun trace was sent before notifyTestReran(testId, className, testName, statusCode, trace); } private void extractFailure(String arg, int status) { String s[] = extractTestId(arg); fFailedTestId = s[0]; fFailedTest = s[1]; fFailureKind = status; } /** * Returns an array with two elements. The first one is the testId, the * second one the testName. */ String[] extractTestId(String arg) { String[] result = new String[2]; if (!hasTestId()) { result[0] = arg; // use the test name as the test Id result[1] = arg; return result; } int i = arg.indexOf(','); result[0] = arg.substring(0, i); result[1] = arg.substring(i + 1, arg.length()); return result; } private boolean hasTestId() { if (fVersion == null) // TODO fix me return true; return fVersion.equals("v2"); //$NON-NLS-1$ } private void notifyTestReran(final String testId, final String className, final String testName, final int statusCode, final String trace) { for (int i = 0; i < fListeners.length; i++) { final ITestRunListener listener = fListeners[i]; SafeRunner.run(new ListenerSafeRunnable() { public void run() { if (listener instanceof ITestRunListener3) ((ITestRunListener3) listener).testReran(testId, className, testName, statusCode, trace, fExpectedResult, fActualResult); else listener.testReran(testId, className, testName, statusCode, trace); } }); } } private void notifyTestTreeEntry(final String treeEntry) { for (int i = 0; i < fListeners.length; i++) { if (fListeners[i] instanceof ITestRunListener2) { ITestRunListener2 listener = (ITestRunListener2) fListeners[i]; if (!hasTestId()) listener.testTreeEntry(fakeTestId(treeEntry)); else listener.testTreeEntry(treeEntry); } } } private String fakeTestId(String treeEntry) { // extract the test name and add it as the testId int index0 = treeEntry.indexOf(','); String testName = treeEntry.substring(0, index0).trim(); return testName + "," + treeEntry; //$NON-NLS-1$ } private void notifyTestRunStopped(final long elapsedTime) { for (int i = 0; i < fListeners.length; i++) { final ITestRunListener listener = fListeners[i]; SafeRunner.run(new ListenerSafeRunnable() { public void run() { listener.testRunStopped(elapsedTime); } }); } } private void testRunEnded(final long elapsedTime) { for (int i = 0; i < fListeners.length; i++) { final ITestRunListener listener = fListeners[i]; SafeRunner.run(new ListenerSafeRunnable() { public void run() { listener.testRunEnded(elapsedTime); } }); } } private void notifyTestEnded(final String test) { for (int i = 0; i < fListeners.length; i++) { final ITestRunListener listener = fListeners[i]; SafeRunner.run(new ListenerSafeRunnable() { public void run() { String s[] = extractTestId(test); listener.testEnded(s[0], s[1]); } }); } } private void notifyTestStarted(final String test) { for (int i = 0; i < fListeners.length; i++) { final ITestRunListener listener = fListeners[i]; SafeRunner.run(new ListenerSafeRunnable() { public void run() { String s[] = extractTestId(test); listener.testStarted(s[0], s[1]); } }); } } private void notifyTestRunStarted(final int count) { for (int i = 0; i < fListeners.length; i++) { final ITestRunListener listener = fListeners[i]; SafeRunner.run(new ListenerSafeRunnable() { public void run() { listener.testRunStarted(count); } }); } } private void notifyTestFailed() { for (int i = 0; i < fListeners.length; i++) { final ITestRunListener listener = fListeners[i]; SafeRunner.run(new ListenerSafeRunnable() { public void run() { if (listener instanceof ITestRunListener3) ((ITestRunListener3) listener).testFailed(fFailureKind, fFailedTestId, fFailedTest, fFailedTrace, fExpectedResult, fActualResult); else listener.testFailed(fFailureKind, fFailedTestId, fFailedTest, fFailedTrace); } }); } } private void notifyTestRunTerminated() { for (int i = 0; i < fListeners.length; i++) { final ITestRunListener listener = fListeners[i]; SafeRunner.run(new ListenerSafeRunnable() { public void run() { listener.testRunTerminated(); } }); } } public void rerunTest(String testId, String className, String testName) { if (isRunning()) { fWriter.println(MessageIds.TEST_RERUN + testId + " " + className + " " + testName); //$NON-NLS-1$ //$NON-NLS-2$ fWriter.flush(); } } }