All Downloads are FREE. Search and download functionalities are using the official Maven repository.

fitnesse.testsystems.CommandRunner Maven / Gradle / Ivy

// 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.testsystems;

import org.apache.commons.lang3.StringUtils;

import java.io.*;
import java.util.Collections;
import java.util.HashMap;
import java.util.List;
import java.util.Map;
import java.util.logging.Level;
import java.util.logging.Logger;

import static java.util.Arrays.asList;
import static util.FileUtil.CHARENCODING;


public class CommandRunner {
  private static final Logger LOG = Logger.getLogger(CommandRunner.class.getName());

  private Process process;
//  protected int exitCode = -1;
  private String[] command;
  private Map environmentVariables;
  private final int timeout;
  private final ExecutionLogListener executionLogListener;
  private String commandErrorMessage = "";

  /**
   *  @param command Commands to run
   * @param environmentVariables Map of environment variables
   * @param executionLogListener Execution Log Listener
   * @param timeout Time-out in seconds.
   */
  public CommandRunner(String[] command, Map environmentVariables, ExecutionLogListener executionLogListener, int timeout) {
    if (executionLogListener == null) {
      throw new IllegalArgumentException("executionLogListener may not be null");
    }
    this.command = command;
    this.environmentVariables = environmentVariables;
    this.executionLogListener = executionLogListener;
    this.timeout = timeout;
  }

  public CommandRunner(String[] command, Map environmentVariables, ExecutionLogListener executionLogListener) {
    this(command, environmentVariables, executionLogListener, 2);
  }

  public void asynchronousStart() throws IOException {
    ProcessBuilder processBuilder = new ProcessBuilder(command);
    processBuilder.environment().putAll(determineEnvironment());
    if (LOG.isLoggable(Level.FINE)) {
      LOG.fine("Starting process " + asList(command));
    }
    process = processBuilder.start();

    sendCommandStartedEvent();
    redirectOutputs(process, executionLogListener);
  }

  // Note: for pipe-based connection, this method is overridden in SlimClientBuilder
  protected void redirectOutputs(Process process, final ExecutionLogListener executionLogListener) throws IOException {
    InputStream stdout = process.getInputStream();
    InputStream stderr = process.getErrorStream();

    // Fit and SlimService
    new Thread(new OutputReadingRunnable(stdout, new OutputWriter() {
      @Override
      public void write(String output) {
        executionLogListener.stdOut(output);
      }
    }), "CommandRunner stdOut").start();
    new Thread(new OutputReadingRunnable(stderr, new OutputWriter() {
      @Override
      public void write(String output) {
        executionLogListener.stdErr(output);
        setCommandErrorMessage(output);
      }
    }), "CommandRunner stdErr").start();

    // Close stdin
    process.getOutputStream().close();
  }

  protected void sendCommandStartedEvent() {
    executionLogListener.commandStarted(new ExecutionLogListener.ExecutionContext() {
      @Override
      public String getCommand() {
        return StringUtils.join(asList(command), " ");
      }

      @Override
      public String getTestSystemName() {
        // What to do with this? Need an identifier?
        return "command";
      }
    });
  }

  private Map determineEnvironment() {
    if (environmentVariables == null) {
      return Collections.emptyMap();
    }
    Map systemVariables = new HashMap<>(System.getenv());
    systemVariables.putAll(environmentVariables);
    return systemVariables;
  }

  public void join() {
    if (process != null) {
      waitForDeathOf(process);
      if (isDead(process)) {
        int exitCode = process.exitValue();
        executionLogListener.exitCode(exitCode);
      }
    }
  }

  private void waitForDeathOf(Process process) {
    int timeStep = 100;
    int maxDelay = timeout * 1000;
    try {
      for (int delayed = 0; delayed < maxDelay; delayed += timeStep) {
        if (isDead(process)) {
          return;
        }
        Thread.sleep(timeStep);
      }
    } catch (InterruptedException e) {
      LOG.log(Level.WARNING, "Wait for death of process " + process + " interrupted", e);
      Thread.currentThread().interrupt();
    }
    LOG.warning("Could not detect death of command line test runner.");
  }

  private boolean isDead(Process process) {
    try {
      process.exitValue();
      return true;
    } catch (IllegalThreadStateException e) {
      return false;
    }
  }

  public boolean isDead() {
	  if (process !=null) return isDead(process);
	  //if there is or was never a process due to a remote / manual start then it is alive!
	  return false;
  }

  public void kill() {
    if (process != null) {
      process.destroy();
      join();
    }
  }

  public String[] getCommand() {
    return command;
  }

  public String getOutput() {
    return "";
  }

  public String getError() {
    return "";
  }

  public List getExceptions() {
    return Collections.emptyList();
  }

  // Used to catch exceptions thrown from the read and write threads.
  public void exceptionOccurred(Exception e) {
    executionLogListener.exceptionOccurred(e);
  }

  protected class OutputReadingRunnable implements Runnable {
    public OutputWriter writer;
    private BufferedReader reader;

    public OutputReadingRunnable(InputStream input, OutputWriter writer) {
      try {
        reader = new BufferedReader(new InputStreamReader(input, CHARENCODING));
      } catch (UnsupportedEncodingException e) {
        exceptionOccurred(e);
      }
      this.writer = writer;
    }

    @Override
    public void run() {
      try {
        String s;
        while ((s = reader.readLine()) != null) {
          writer.write(s);
        }
      } catch (Exception e) {
        exceptionOccurred(e);
      }
    }

  }

  public int waitForCommandToFinish() throws InterruptedException {
    return process.waitFor();
  }

  protected interface OutputWriter {
    void write(String output);
  }

  protected void setCommandErrorMessage(String commandErrorMessage) {
    this.commandErrorMessage = commandErrorMessage;
  }

  public String getCommandErrorMessage() {
	return commandErrorMessage;
  }

  // TODO: Those should go, since the data is sent to the ExecutionListener already
  public InputStream getInputStream() {
    return process.getInputStream();
  }

  public OutputStream getOutputStream() {
    return process.getOutputStream();
  }
}




© 2015 - 2024 Weber Informatics LLC | Privacy Policy