1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 72 73 74 75 76 77 78 79 80 81 82 83 84 85 86 87 88 89 90 91 92 93 94 95 96 97 98 99 100 101 102 103 104 105 106 107 108
|
# frozen_string_literal: true
require 'delegate'
module ProcessExecuter
# A decorator for Process::Status that adds the following attributes:
#
# * `command`: the command that was used to spawn the process
# * `options`: the options that were used to spawn the process
# * `elapsed_time`: the seconds the command ran
# * `timed_out?`: true if the process timed out
#
# @api public
#
class Result < SimpleDelegator
# Create a new Result object
#
# @example
# command = ['sleep 1']
# options = ProcessExecuter::Options::SpawnOptions.new
# pid = Process.spawn(*command, **options.spawn_options)
# _pid, status = Process.wait2(pid)
# timed_out = false
# elapsed_time = 0.01
#
# ProcessExecuter::Result.new(status, command:, options:, timed_out:, elapsed_time:)
#
# @param status [Process::Status] the status to delegate to
#
# @param command [Array] the command that was used to spawn the process
#
# @param options [ProcessExecuter::Options::Base] the options that were used to spawn the process
#
# @param timed_out [Boolean] true if the process timed out
#
# @param elapsed_time [Numeric] the seconds the command ran
#
def initialize(status, command:, options:, timed_out:, elapsed_time:)
super(status)
@command = command
@options = options
@timed_out = timed_out
@elapsed_time = elapsed_time
end
# The command that was used to spawn the process
# @see Process.spawn
# @example
# result.command #=> [{ 'GIT_DIR' => '/path/to/repo' }, 'git', 'status']
# @return [Array]
attr_reader :command
# The options that were used to spawn the process
#
# @see Process.spawn
#
# @example
# # Looks like a hash, but is actually an object that derives from
# # ProcessExecuter::Options::Base
# result.options #=> { chdir: '/path/to/repo', timeout_after: 0.5 }
#
# @return [ProcessExecuter::Options::Base]
#
attr_reader :options
# The seconds the command ran
# @example
# result.elapsed_time #=> 10.0
# @return [Numeric]
attr_reader :elapsed_time
# @!attribute [r] timed_out?
# True if the process timed out and was sent the SIGKILL signal
# @example
# result = ProcessExecuter.spawn_with_timeout('sleep 10', timeout_after: 0.01)
# result.timed_out? # => true
# @return [Boolean]
#
attr_reader :timed_out
alias timed_out? timed_out
# Overrides the default `success?` method to return `nil` if the process timed out
#
# This is because when a timeout occurs, Windows will still return true.
#
# @example
# result = ProcessExecuter.spawn_with_timeout('sleep 10', timeout_after: 0.01)
# result.success? # => nil
# @return [true, false, nil]
#
def success?
return nil if timed_out? # rubocop:disable Style/ReturnNilInPredicateMethodDefinition
super
end
# Return a string representation of the result
# @example
# result = ProcessExecuter.spawn_with_timeout('sleep 10', timeout_after: 1)
# # This message is platform dependent, but will look like this on Linux:
# result.to_s #=> "pid 70144 SIGKILL (signal 9) timed out after 1s"
# @return [String]
#
def to_s
"#{super}#{" timed out after #{options.timeout_after}s" if timed_out?}"
end
end
end
|