// Copyright (C) 2003-2009 by Object Mentor, Inc. All rights reserved. // Released under the terms of the CPL Common Public License version 1.0. package fitnesse.components; import fit.FitServer; import fitnesse.responders.run.SocketDealer; import fitnesse.responders.run.SocketDoner; import fitnesse.responders.run.SocketSeeker; import fitnesse.responders.run.TestSystemListener; import fitnesse.testutil.MockCommandRunner; import java.io.IOException; import java.net.UnknownHostException; import java.util.Map; public class CommandRunningFitClient extends FitClient implements SocketSeeker { public static int TIMEOUT = 60000; private static final String SPACE = " "; private int ticketNumber; public CommandRunner commandRunner; private SocketDoner donor; private boolean connectionEstablished = false; private Thread timeoutThread; private Thread earlyTerminationThread; private boolean fastTest = false; private Thread fastFitServer; public CommandRunningFitClient(TestSystemListener listener, String command, int port, SocketDealer dealer, boolean fastTest) throws Exception { this(listener, command, port, null, dealer, fastTest); } public CommandRunningFitClient(TestSystemListener listener, String command, int port, Map<String, String> environmentVariables, SocketDealer dealer, boolean fastTest) { super(listener); this.fastTest = fastTest; ticketNumber = dealer.seekingSocket(this); String hostName; try { hostName = java.net.InetAddress.getLocalHost().getHostName(); } catch (UnknownHostException e) { throw new RuntimeException(e.getMessage(), e); } String fitArguments = hostName + SPACE + port + SPACE + ticketNumber; String commandLine = command + SPACE + fitArguments; if (fastTest) { commandRunner = new MockCommandRunner(); createFitServer("-x " + fitArguments); } else commandRunner = new CommandRunner(commandLine, "", environmentVariables); } public CommandRunningFitClient(TestSystemListener listener, String command, int port, SocketDealer dealer) throws Exception { this(listener, command, port, null, dealer); } public CommandRunningFitClient(TestSystemListener listener, String command, int port, Map<String, String> environmentVariables, SocketDealer dealer) throws Exception { this(listener, command, port, environmentVariables, dealer, false); } //For testing only. Makes responder faster. void createFitServer(String args) { final String fitArgs = args; Runnable fastFitServerRunnable = new Runnable() { public void run() { try { while (!tryCreateFitServer(fitArgs)) Thread.sleep(10); } catch (Exception e) { } } }; fastFitServer = new Thread(fastFitServerRunnable); fastFitServer.start(); } private boolean tryCreateFitServer(String args) { try { FitServer.main(args.trim().split(" ")); return true; } catch (Exception e) { return false; } } public void start() { try { commandRunner.asynchronousStart(); if (!fastTest) { timeoutThread = new Thread(new TimeoutRunnable(), "FitClient timeout"); timeoutThread.start(); earlyTerminationThread = new Thread(new EarlyTerminationRunnable(), "FitClient early termination"); earlyTerminationThread.start(); } waitForConnection(); } catch (Exception e) { listener.exceptionOccurred(e); } } public void acceptSocketFrom(SocketDoner donor) throws IOException, InterruptedException { this.donor = donor; acceptSocket(donor.donateSocket()); connectionEstablished = true; synchronized (this) { notify(); } } void setTicketNumber(int ticketNumber) { this.ticketNumber = ticketNumber; } public boolean isSuccessfullyStarted() { return fitSocket != null; } private void waitForConnection() throws InterruptedException { while (fitSocket == null) { Thread.sleep(100); checkForPulse(); } } public void join() { try { if (fastTest) { fastFitServer.join(); } else { commandRunner.join(); } super.join(); if (donor != null) donor.finishedWithSocket(); killVigilantThreads(); } catch (InterruptedException e) { } } public void kill() { super.kill(); killVigilantThreads(); commandRunner.kill(); } private void killVigilantThreads() { if (timeoutThread != null) timeoutThread.interrupt(); if (earlyTerminationThread != null) earlyTerminationThread.interrupt(); } public void exceptionOccurred(Exception e) { commandRunner.exceptionOccurred(e); super.exceptionOccurred(e); } private class TimeoutRunnable implements Runnable { long timeSlept = 0; public void run() { try { Thread.sleep(TIMEOUT); synchronized (CommandRunningFitClient.this) { if (fitSocket == null) { CommandRunningFitClient.this.notify(); listener.exceptionOccurred(new Exception( "FitClient: communication socket was not received on time.")); } } } catch (InterruptedException e) { // ok } } } private class EarlyTerminationRunnable implements Runnable { public void run() { try { Thread.sleep(1000); // next waitFor() can finish too quickly on Linux! commandRunner.process.waitFor(); synchronized (CommandRunningFitClient.this) { if (!connectionEstablished) { CommandRunningFitClient.this.notify(); listener.exceptionOccurred(new Exception( "FitClient: external process terminated before a connection could be established.")); } } } catch (InterruptedException e) { // ok } } } }