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 109 110 111 112 113 114 115 116 117 118 119 120 121 122 123 124 125 126 127 128 129 130 131 132 133 134 135 136 137 138 139 140 141 142 143 144 145 146 147 148 149 150 151 152 153 154 155 156 157 158 159 160 161 162 163 164 165 166 167 168
|
# frozen_string_literal: true
module ProcessExecuter
# rubocop:disable Layout/LineLength
# Base class for all {ProcessExecuter} errors
#
# It is recommended to rescue {ProcessExecuter::Error} to catch any runtime error
# raised by this gem unless you need more specific error handling.
#
# Custom errors are arranged in the following class hierarchy:
#
# ```text
# ::StandardError
# └─> Error
# ├─> ArgumentError
# ├─> CommandError
# │ ├─> FailedError
# │ └─> SignaledError
# │ └─> TimeoutError
# ├─> ProcessIOError
# └─> SpawnError
# ```
#
# | Error Class | Description |
# | --- | --- |
# | `Error` | This catch-all error serves as the base class for other custom errors. |
# | `ArgumentError` | Raised when an invalid argument is passed to a method. |
# | `CommandError` | A subclass of this error is raised when there is a problem executing a command. |
# | `FailedError` | Raised when the command exits with a non-zero exit status. |
# | `SignaledError` | Raised when the command is terminated as a result of receiving a signal. This could happen if the process is forcibly terminated or if there is a serious system error. |
# | `TimeoutError` | This is a specific type of `SignaledError` that is raised when the command times out and is killed via the SIGKILL signal. |
# | `ProcessIOError` | Raised when an error was encountered reading or writing to the command's subprocess. |
# | `SpawnError` | Raised when the process could not execute. Check the `#cause` for the original exception from `Process.spawn`. |
#
# @example Rescuing any error
# begin
# ProcessExecuter.run('git', 'status')
# rescue ProcessExecuter::Error => e
# puts "An error occurred: #{e.message}"
# end
#
# @example Rescuing a timeout error
# begin
# timeout_after = 0.1 # seconds
# ProcessExecuter.run('sleep', '1', timeout_after:)
# rescue ProcessExecuter::TimeoutError => e # Catch the more specific error first!
# puts "Command took too long and timed out: #{e}"
# rescue ProcessExecuter::Error => e
# puts "Some other error occurred: #{e}"
# end
#
# @api public
#
class Error < ::StandardError; end
# rubocop:enable Layout/LineLength
# Raised when an invalid argument is passed to a method
#
# @example Raising ProcessExecuter::ArgumentError due to invalid option value
# begin
# ProcessExecuter.run('echo Hello', timeout_after: 'not_a_number')
# rescue ProcessExecuter::ArgumentError => e
# e.message #=> 'timeout_after must be nil or a non-negative real number but was "not_a_number"'
# end
#
# @api public
#
class ArgumentError < ProcessExecuter::Error; end
# Raised when a command fails or exits because of an uncaught signal
#
# The command executed and its result are available from this object.
#
# This gem will raise a more specific error for each type of failure:
#
# * {FailedError}: when the command exits with a non-zero status
# * {SignaledError}: when the command exits because of an uncaught signal
# * {TimeoutError}: when the command times out
#
# @api public
#
class CommandError < ProcessExecuter::Error
# Create a CommandError object
#
# @example
# `exit 1` # set $? appropriately for this example
# result_data = {
# command: ['exit 1'],
# options: ProcessExecuter::Options::RunOptions.new,
# timed_out: false,
# elapsed_time: 0.01
# }
# result = ProcessExecuter::Result.new($?, **result_data)
# error = ProcessExecuter::CommandError.new(result)
# error.to_s #=> '["exit 1"], status: pid 29686 exit 1'
#
# @param result [ProcessExecuter::Result] The result of the command including the
# command and exit status
#
def initialize(result)
@result = result
super(error_message)
end
# The human readable representation of this error
#
# @example
# error.error_message #=> '["git", "status"], status: pid 89784 exit 1'
#
# @return [String]
#
def error_message
"#{result.command}, status: #{result}"
end
# @attribute [r] result
#
# The result of the command including the command, its status and its output
#
# @example
# error.result #=> #<ProcessExecuter::Result:0x00007f9b1b8b3d20>
#
# @return [ProcessExecuter::Result]
#
attr_reader :result
end
# Raised when the command returns a non-zero exit status
#
# @api public
#
class FailedError < ProcessExecuter::CommandError; end
# Raised when the command exits because of an uncaught signal
#
# @api public
#
class SignaledError < ProcessExecuter::CommandError; end
# Raised when the command takes longer than the configured timeout_after
#
# @example
# begin
# ProcessExecuter.spawn_with_timeout('sleep 1', timeout_after: 0.1)
# rescue ProcessExecuter::TimeoutError => e
# puts "Command timed out: #{e.result.command}"
# end
#
# @api public
#
class TimeoutError < ProcessExecuter::SignaledError; end
# Raised if an exception occurred while processing subprocess output
#
# @api public
#
class ProcessIOError < ProcessExecuter::Error; end
# Raised when spawn could not execute the process
#
# See the `cause` for the exception that Process.spawn raised.
#
# @api public
#
class SpawnError < ProcessExecuter::Error; end
end
|