class ProcessExecuter::Commands::Run

Run a command and return the {ProcessExecuter::Result}

Extends {ProcessExecuter::Commands::SpawnWithTimeout} to provide the core functionality for {ProcessExecuter.run}.

It accepts all [Process.spawn execution options](docs.ruby-lang.org/en/3.4/Process.html#module-Process-label-Execution+Options) plus the additional options ‘timeout_after`, `raise_errors` and `logger`.

This class wraps any stdout or stderr redirection destinations in a {MonitoredPipe}. This allows any class that implements ‘#write` to be used as an output redirection destination. This means that you can redirect to a StringIO which is not possible with `Process.spawn`.

@api private

Public Instance Methods

call() click to toggle source

Run a command and return the result

Wrap the stdout and stderr redirection destinations in pipes and then execute the command.

@example

options = ProcessExecuter::Options::RunOptions.new(raise_errors: true)
result = ProcessExecuter::Commands::Run.new('echo hello', options).call
result.success? # => true
result.exitstatus # => 0

@raise [ProcessExecuter::SpawnError] ‘Process.spawn` raised an error before the

command was run

@raise [ProcessExecuter::FailedError] If the command ran and failed

@raise [ProcessExecuter::SignaledError] If the command ran and terminated due to

an unhandled signal

@raise [ProcessExecuter::TimeoutError] If the command timed out

@raise [ProcessExecuter::ProcessIOError] If there was an exception while

collecting subprocess output

@return [ProcessExecuter::Result] The result of the completed subprocess

Calls superclass method
# File lib/process_executer/commands/run.rb, line 51
def call
  opened_pipes = wrap_stdout_stderr
  super.tap do
    log_result
    raise_errors if options.raise_errors
  end
ensure
  opened_pipes.each_value(&:close)
  opened_pipes.each { |option_key, pipe| raise_pipe_error(option_key, pipe) }
end

Private Instance Methods

log_result() click to toggle source

Log the result of running the command @return [void]

# File lib/process_executer/commands/run.rb, line 100
def log_result
  options.logger.info { "PID #{pid}: #{command} exited with status #{result}" }
end
raise_errors() click to toggle source

Raise an error if the command failed @return [void] @raise [ProcessExecuter::FailedError] If the command ran and failed @raise [ProcessExecuter::SignaledError] If the command ran and terminated due to an unhandled signal @raise [ProcessExecuter::TimeoutError] If the command timed out

# File lib/process_executer/commands/run.rb, line 92
def raise_errors
  raise TimeoutError, result if result.timed_out?
  raise SignaledError, result if result.signaled?
  raise FailedError, result unless result.success?
end
raise_pipe_error(option_key, pipe) click to toggle source

Raises a ProcessIOError if the given pipe has a recorded exception

@param option_key [Object] The redirection option key

For example, `:out`, or an Array like `[:out, :err]` for merged streams.

@param pipe [ProcessExecuter::MonitoredPipe] The pipe that raised the exception

@raise [ProcessExecuter::ProcessIOError] If there was an exception while collecting subprocess output

@return [void]

# File lib/process_executer/commands/run.rb, line 116
def raise_pipe_error(option_key, pipe)
  return unless pipe.exception

  error = ProcessExecuter::ProcessIOError.new("Pipe Exception for #{command}: #{option_key.inspect}")
  raise(error, cause: pipe.exception)
end
should_wrap?(key, value) click to toggle source

Should the redirection option be wrapped by a MonitoredPipe @param key [Object] The option key @param value [Object] The option value @return [Boolean] Whether the option should be wrapped

# File lib/process_executer/commands/run.rb, line 82
def should_wrap?(key, value)
  (options.stdout_redirection?(key) || options.stderr_redirection?(key)) &&
    ProcessExecuter::Destinations.compatible_with_monitored_pipe?(value)
end
wrap_stdout_stderr() click to toggle source

Wrap the stdout and stderr redirection options with a MonitoredPipe @return [Hash<Object, ProcessExecuter::MonitoredPipe>] The opened pipes (the Object is the option key)

# File lib/process_executer/commands/run.rb, line 66
def wrap_stdout_stderr
  options.each_with_object({}) do |key_value, opened_pipes|
    key, value = key_value

    next unless should_wrap?(key, value)

    wrapped_destination = ProcessExecuter::MonitoredPipe.new(value)
    opened_pipes[key] = wrapped_destination
    options.merge!({ key => wrapped_destination })
  end
end