process_executer 1.1.2 → 1.3.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- checksums.yaml +4 -4
- data/.commitlintrc.yml +16 -0
- data/.husky/commit-msg +1 -0
- data/.rubocop.yml +6 -22
- data/.tool-versions +1 -1
- data/.yardopts +1 -1
- data/CHANGELOG.md +72 -16
- data/{LICENSE.md → LICENSE.txt} +1 -1
- data/README.md +54 -44
- data/Rakefile +13 -13
- data/lib/process_executer/command/errors.rb +170 -0
- data/lib/process_executer/command/result.rb +77 -0
- data/lib/process_executer/command/runner.rb +167 -0
- data/lib/process_executer/command.rb +12 -0
- data/lib/process_executer/monitored_pipe.rb +46 -9
- data/lib/process_executer/status.rb +31 -6
- data/lib/process_executer/version.rb +1 -1
- data/lib/process_executer.rb +217 -13
- data/package.json +11 -0
- data/process_executer.gemspec +15 -8
- metadata +52 -15
checksums.yaml
CHANGED
@@ -1,7 +1,7 @@
|
|
1
1
|
---
|
2
2
|
SHA256:
|
3
|
-
metadata.gz:
|
4
|
-
data.tar.gz:
|
3
|
+
metadata.gz: d2bd50be1f3683bc2c8210451c45e270e7072992ec48897a8bbb16cf10790c8b
|
4
|
+
data.tar.gz: e0d1ba5a7c5571b0059db55537726b68baf6017a66941883953ece63f5a58b53
|
5
5
|
SHA512:
|
6
|
-
metadata.gz:
|
7
|
-
data.tar.gz:
|
6
|
+
metadata.gz: bdaab34abbd99de650933f367eedcb40e7a2538d1f48ab8eda6f5df5da3d5f1748543eb28f54f21cc3557c64d34575001da2158caf2f30cc768bec88f657e16c
|
7
|
+
data.tar.gz: 920227450b1da0c56aa3987a2ff1bbeb2c73cd093d04c1318ebb39d02302a408682d0d40c1668eb1f9b37d71f58fe47a6c4b101b75e701fdbaf3db2cdb845b26
|
data/.commitlintrc.yml
ADDED
@@ -0,0 +1,16 @@
|
|
1
|
+
---
|
2
|
+
extends: '@commitlint/config-conventional'
|
3
|
+
|
4
|
+
rules:
|
5
|
+
# See: https://commitlint.js.org/reference/rules.html
|
6
|
+
#
|
7
|
+
# Rules are made up by a name and a configuration array. The configuration array contains:
|
8
|
+
#
|
9
|
+
# * Severity [0..2]: 0 disable rule, 1 warning if violated, or 2 error if violated
|
10
|
+
# * Applicability [always|never]: never inverts the rule
|
11
|
+
# * Value: value to use for this rule
|
12
|
+
#
|
13
|
+
# Run `npx commitlint --print-config` to see the current setting for all rules.
|
14
|
+
#
|
15
|
+
body-leading-blank: [2, 'always']
|
16
|
+
footer-leading-blank: [2, 'always']
|
data/.husky/commit-msg
ADDED
@@ -0,0 +1 @@
|
|
1
|
+
npx --no-install commitlint --edit "$1"
|
data/.rubocop.yml
CHANGED
@@ -1,23 +1,7 @@
|
|
1
|
-
|
2
|
-
|
3
|
-
# Output extra information for each offense to make it easier to diagnose:
|
4
|
-
DisplayCopNames: true
|
5
|
-
DisplayStyleGuide: true
|
6
|
-
ExtraDetails: true
|
7
|
-
SuggestExtensions: false
|
8
|
-
# RuboCop enforces rules depending on the oldest version of Ruby which
|
9
|
-
# your project supports:
|
10
|
-
TargetRubyVersion: 3.0
|
11
|
-
|
12
|
-
# The default max line length is 80 characters
|
13
|
-
Layout/LineLength:
|
14
|
-
Max: 120
|
1
|
+
inherit_gem:
|
2
|
+
main_branch_shared_rubocop_config: config/rubocop.yml
|
15
3
|
|
16
|
-
|
17
|
-
|
18
|
-
|
19
|
-
|
20
|
-
- "*.gemspec"
|
21
|
-
|
22
|
-
Gemspec/DevelopmentDependencies:
|
23
|
-
Enabled: false
|
4
|
+
AllCops:
|
5
|
+
# Pin this project to Ruby 3.1 in case the shared config above is upgraded to 3.2
|
6
|
+
# or later.
|
7
|
+
TargetRubyVersion: 3.1
|
data/.tool-versions
CHANGED
@@ -1 +1 @@
|
|
1
|
-
ruby 3.3.5
|
1
|
+
ruby 3.3.5
|
data/.yardopts
CHANGED
data/CHANGELOG.md
CHANGED
@@ -5,25 +5,81 @@ All notable changes to the process_executer gem will be documented in this file.
|
|
5
5
|
The format is based on [Keep a Changelog](https://keepachangelog.com/en/1.0.0/)
|
6
6
|
and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0.html).
|
7
7
|
|
8
|
-
## v1.
|
9
|
-
|
10
|
-
[Full Changelog](https://github.com/main-branch/process_executer/compare/v1.
|
11
|
-
|
12
|
-
Changes since v1.
|
13
|
-
|
14
|
-
*
|
15
|
-
|
16
|
-
|
17
|
-
|
18
|
-
|
8
|
+
## v1.3.0 (2025-02-26)
|
9
|
+
|
10
|
+
[Full Changelog](https://github.com/main-branch/process_executer/compare/v1.2.0..v1.3.0)
|
11
|
+
|
12
|
+
Changes since v1.2.0:
|
13
|
+
|
14
|
+
* d1e189b build: add Ruby 3.4 to the CI workflow
|
15
|
+
* e805dfc feat: implement ProcessExecuter.run_command
|
16
|
+
* bad822f fix: update the yard build in the rake file and update included files
|
17
|
+
* 6fbdc5e feat: allow #spawn to accept file descriptors for redirection destination
|
18
|
+
* d745685 test: make it so that tests do not give unnecessary output
|
19
|
+
|
20
|
+
## v1.2.0 (2024-10-10)
|
21
|
+
|
22
|
+
[Full Changelog](https://github.com/main-branch/process_executer/compare/v1.1.2..v1.2.0)
|
23
|
+
|
24
|
+
Changes since v1.1.2:
|
25
|
+
|
26
|
+
* 35663c9 chore: reset main branch to 1.x
|
27
|
+
* 39913bc build: remove semver pr label check
|
28
|
+
* 8ae8e34 build: enforce conventional commit message formatting
|
29
|
+
* f5b8c51 Release v2.0.0.pre1
|
30
|
+
* 8e15c39 Re-add require for 'forwardable'
|
31
|
+
* 4bba06e Fix flakey test that checks for thread to die
|
32
|
+
* 83bfd78 Remove unused require for 'forwardable' and 'ostruct'
|
33
|
+
* ea3ea3c Use shared Rubocop config
|
34
|
+
* ecd2cb5 Update copyright notice in this project
|
35
|
+
* 7d5bfe1 Update links in gemspec
|
36
|
+
* 797de91 Add Slack badge for this project in README
|
37
|
+
* 591b716 Update “Build Status” link the README
|
38
|
+
* 2fcd001 Update yardopts with new standard options
|
39
|
+
* 4e1de47 Standardize YARD and Markdown Lint configurations
|
40
|
+
* 929c680 Set JRuby --debug option when running tests in GitHub Actions workflows
|
41
|
+
* 71049cb Finish Integration of simplecov-rspec into the project
|
42
|
+
* 4fb44bb Update continuous integration and experimental ruby builds
|
43
|
+
* 289645c Depend on v1 of semver_pr_label_check
|
44
|
+
* 3c4d988 Update code climate test coverage reporter version
|
45
|
+
* 04103b4 Simplify how the experimental ruby builds are triggered
|
46
|
+
* 35840a4 Use a reusable workflow for the Semver PR label check
|
47
|
+
* 0d887f0 Update code climate test coverage reporter version
|
48
|
+
* bb7f73b Rename the experimental build workflow
|
49
|
+
* 035ce8a Fix the experimental CI Build workflow
|
50
|
+
* 3d739f4 Move CI builds using experimental Rubies to a different workflow
|
51
|
+
* c5ef6b0 Integrate simplecov-rspec to ensure code covage in CI builds
|
52
|
+
* f33707e Update development dependencies and examples (#45)
|
53
|
+
|
54
|
+
## v2.0.0.pre1 (2024-09-26)
|
55
|
+
|
56
|
+
[Full Changelog](https://github.com/main-branch/process_executer/compare/v1.1.0..v2.0.0.pre1)
|
19
57
|
|
20
58
|
Changes since v1.1.0:
|
21
59
|
|
22
|
-
*
|
23
|
-
*
|
24
|
-
*
|
25
|
-
*
|
26
|
-
*
|
60
|
+
* 8e15c39 Re-add require for 'forwardable'
|
61
|
+
* 4bba06e Fix flakey test that checks for thread to die
|
62
|
+
* 83bfd78 Remove unused require for 'forwardable' and 'ostruct'
|
63
|
+
* ea3ea3c Use shared Rubocop config
|
64
|
+
* ecd2cb5 Update copyright notice in this project
|
65
|
+
* 7d5bfe1 Update links in gemspec
|
66
|
+
* 797de91 Add Slack badge for this project in README
|
67
|
+
* 591b716 Update “Build Status” link the README
|
68
|
+
* 2fcd001 Update yardopts with new standard options
|
69
|
+
* 4e1de47 Standardize YARD and Markdown Lint configurations
|
70
|
+
* 929c680 Set JRuby --debug option when running tests in GitHub Actions workflows
|
71
|
+
* 71049cb Finish Integration of simplecov-rspec into the project
|
72
|
+
* 4fb44bb Update continuous integration and experimental ruby builds
|
73
|
+
* 289645c Depend on v1 of semver_pr_label_check
|
74
|
+
* 3c4d988 Update code climate test coverage reporter version
|
75
|
+
* 04103b4 Simplify how the experimental ruby builds are triggered
|
76
|
+
* 35840a4 Use a reusable workflow for the Semver PR label check
|
77
|
+
* 0d887f0 Update code climate test coverage reporter version
|
78
|
+
* bb7f73b Rename the experimental build workflow
|
79
|
+
* 035ce8a Fix the experimental CI Build workflow
|
80
|
+
* 3d739f4 Move CI builds using experimental Rubies to a different workflow
|
81
|
+
* c5ef6b0 Integrate simplecov-rspec to ensure code covage in CI builds
|
82
|
+
* f33707e Update development dependencies and examples (#45)
|
27
83
|
|
28
84
|
## v1.1.0 (2024-02-02)
|
29
85
|
|
data/{LICENSE.md → LICENSE.txt}
RENAMED
data/README.md
CHANGED
@@ -3,18 +3,23 @@
|
|
3
3
|
[](https://badge.fury.io/rb/process_executer)
|
4
4
|
[](https://rubydoc.info/gems/process_executer/)
|
5
5
|
[](https://rubydoc.info/gems/process_executer/file/CHANGELOG.md)
|
6
|
-
[](https://github.com/main-branch/process_executer/actions/workflows/continuous-integration.yml)
|
7
7
|
[](https://codeclimate.com/github/main-branch/process_executer/maintainability)
|
8
8
|
[](https://codeclimate.com/github/main-branch/process_executer/test_coverage)
|
9
|
+
[](https://conventionalcommits.org)
|
11
|
+
[](https://main-branch.slack.com/archives/C07NG2BPG8Y)
|
9
12
|
|
10
13
|
* [Usage](#usage)
|
14
|
+
* [ProcessExecuter.run](#processexecuterrun)
|
11
15
|
* [ProcessExecuter::MonitoredPipe](#processexecutermonitoredpipe)
|
12
16
|
* [ProcessExecuter.spawn](#processexecuterspawn)
|
13
17
|
* [Installation](#installation)
|
14
18
|
* [Contributing](#contributing)
|
15
19
|
* [Reporting Issues](#reporting-issues)
|
16
20
|
* [Developing](#developing)
|
17
|
-
* [
|
21
|
+
* [Commit message guidelines](#commit-message-guidelines)
|
22
|
+
* [Pull request guidelines](#pull-request-guidelines)
|
18
23
|
* [Releasing](#releasing)
|
19
24
|
* [License](#license)
|
20
25
|
|
@@ -25,6 +30,36 @@ gem is hosted on RubyGems.org. Read below of an overview and several examples.
|
|
25
30
|
|
26
31
|
This gem contains the following important classes:
|
27
32
|
|
33
|
+
### ProcessExecuter.run
|
34
|
+
|
35
|
+
`ProcessExecuter.run` execute the given command as a subprocess blocking until it is finished.
|
36
|
+
|
37
|
+
A Result object is returned which includes the process's status and output.
|
38
|
+
|
39
|
+
Supports the same features as
|
40
|
+
[Process.spawn](https://docs.ruby-lang.org/en/3.3/Process.html#method-c-spawn).
|
41
|
+
In addition, it (1) blocks until the command has exited, (2) captures stdout and
|
42
|
+
stderr to a buffer or file, and (3) can optionally kill the command if it exceeds
|
43
|
+
an timeout.
|
44
|
+
|
45
|
+
This command takes two forms:
|
46
|
+
|
47
|
+
1. When passing a single string the command is passed to a shell:
|
48
|
+
|
49
|
+
`ProcessExecuter.run([env, ] command_line, options = {}) ->` {ProcessExecuter::Command::Result}
|
50
|
+
|
51
|
+
2. When passing an array of strings the command is run directly (bypassing the shell):
|
52
|
+
|
53
|
+
`ProcessExecuter.run([env, ] exe_path, *args, options = {}) ->` {ProcessExecuter::Command::Result}
|
54
|
+
|
55
|
+
Argument env, if given, is a hash that affects ENV for the new process; see
|
56
|
+
[Execution
|
57
|
+
Environment](https://docs.ruby-lang.org/en/3.3/Process.html#module-Process-label-Execution+Environment).
|
58
|
+
|
59
|
+
Argument options is a hash of options for the new process; see the options listed below.
|
60
|
+
|
61
|
+
See comprehensive examples in the YARD documentation for this method.
|
62
|
+
|
28
63
|
### ProcessExecuter::MonitoredPipe
|
29
64
|
|
30
65
|
`ProcessExecuter::MonitoredPipe` streams data sent through a pipe to one or more writers.
|
@@ -116,59 +151,34 @@ allow you to experiment.
|
|
116
151
|
|
117
152
|
To install this gem onto your local machine, run `bundle exec rake install`.
|
118
153
|
|
119
|
-
###
|
120
|
-
|
121
|
-
In order for a pull request to be merged, it must be approved by a code owner and
|
122
|
-
include a semver label.
|
123
|
-
|
124
|
-
The approval must be done using the Github PR Review process by a code owner defined
|
125
|
-
in the project's CODEOWNERS file.
|
126
|
-
|
127
|
-
The semver label indicates the type of change so that the gem version can be
|
128
|
-
increments according to semver rules prior to release. One and only one of the
|
129
|
-
following labels must added to the PR:
|
130
|
-
|
131
|
-
* **`major-change`**
|
154
|
+
### Commit message guidelines
|
132
155
|
|
133
|
-
|
156
|
+
All commit messages must follow the [Conventional Commits
|
157
|
+
standard](https://www.conventionalcommits.org/en/v1.0.0/). This helps us maintain a
|
158
|
+
clear and structured commit history, automate versioning, and generate changelogs
|
159
|
+
effectively.
|
134
160
|
|
135
|
-
|
136
|
-
depends on this gem. For example, removing a public method, changing a method's
|
137
|
-
signature, or altering the expected behavior of a method in a way that would
|
138
|
-
require changes in the dependent code.
|
161
|
+
To ensure compliance, this project includes:
|
139
162
|
|
140
|
-
*
|
163
|
+
* A git commit-msg hook that validates your commit messages before they are accepted.
|
141
164
|
|
142
|
-
|
165
|
+
To activate the hook, you must have node installed and run `npm install`.
|
143
166
|
|
144
|
-
|
145
|
-
|
167
|
+
* A GitHub Actions workflow that will enforce the Conventional Commit standard as
|
168
|
+
part of the continuous integration pipeline.
|
146
169
|
|
147
|
-
|
148
|
-
|
170
|
+
Any commit message that does not conform to the Conventional Commits standard will
|
171
|
+
cause the workflow to fail and not allow the PR to be merged.
|
149
172
|
|
150
|
-
|
173
|
+
### Pull request guidelines
|
151
174
|
|
152
|
-
|
153
|
-
|
154
|
-
|
155
|
-
This includes bug fixes or other internal changes that do not affect the API such
|
156
|
-
as refactoring code, improving performance, or updating user documentation.
|
157
|
-
|
158
|
-
* **`internal-change`**
|
159
|
-
|
160
|
-
Use when the PR includes changes that are NOT user facing and will NOT require a
|
161
|
-
release.
|
162
|
-
|
163
|
-
This includes updates to developer documentation, comments, GitHub Actions, minor
|
164
|
-
refactorings, and fixing Rubocop offenses.
|
175
|
+
All pull requests must be merged using rebase merges. This ensures that commit
|
176
|
+
messages from the feature branch are preserved in the release branch, keeping the
|
177
|
+
history clean and meaningful.
|
165
178
|
|
166
179
|
### Releasing
|
167
180
|
|
168
|
-
|
169
|
-
PRs included in the release.
|
170
|
-
|
171
|
-
Then in the root directory of this project with the `main` branch checked out, run
|
181
|
+
In the root directory of this project with the `main` branch checked out, run
|
172
182
|
the following command:
|
173
183
|
|
174
184
|
```shell
|
data/Rakefile
CHANGED
@@ -7,7 +7,7 @@ desc 'Run the same tasks that the CI build will run'
|
|
7
7
|
if RUBY_PLATFORM == 'java'
|
8
8
|
task default: %w[spec rubocop bundle:audit build]
|
9
9
|
else
|
10
|
-
task default: %w[spec rubocop yard
|
10
|
+
task default: %w[spec rubocop yard bundle:audit build]
|
11
11
|
end
|
12
12
|
|
13
13
|
# Bundler Audit
|
@@ -45,38 +45,38 @@ CLEAN << 'rspec-report.xml'
|
|
45
45
|
|
46
46
|
require 'rubocop/rake_task'
|
47
47
|
|
48
|
-
RuboCop::RakeTask.new
|
49
|
-
t.options = %w[
|
50
|
-
--format progress
|
51
|
-
--format json --out rubocop-report.json
|
52
|
-
]
|
53
|
-
end
|
48
|
+
RuboCop::RakeTask.new
|
54
49
|
|
55
|
-
|
50
|
+
# YARD
|
56
51
|
|
57
52
|
unless RUBY_PLATFORM == 'java'
|
58
|
-
#
|
53
|
+
# yard:build
|
59
54
|
|
60
55
|
require 'yard'
|
61
|
-
|
62
|
-
|
56
|
+
|
57
|
+
YARD::Rake::YardocTask.new('yard:build') do |t|
|
58
|
+
t.files = %w[lib/**/*.rb]
|
59
|
+
t.stats_options = ['--list-undoc']
|
63
60
|
end
|
64
61
|
|
65
62
|
CLEAN << '.yardoc'
|
66
63
|
CLEAN << 'doc'
|
67
64
|
|
68
|
-
#
|
65
|
+
# yard:audit
|
69
66
|
|
70
67
|
desc 'Run yardstick to show missing YARD doc elements'
|
71
68
|
task :'yard:audit' do
|
72
69
|
sh "yardstick 'lib/**/*.rb'"
|
73
70
|
end
|
74
71
|
|
75
|
-
#
|
72
|
+
# yard:coverage
|
76
73
|
|
77
74
|
require 'yardstick/rake/verify'
|
78
75
|
|
79
76
|
Yardstick::Rake::Verify.new(:'yard:coverage') do |verify|
|
80
77
|
verify.threshold = 100
|
78
|
+
verify.require_exact_threshold = false
|
81
79
|
end
|
80
|
+
|
81
|
+
task yard: %i[yard:build yard:audit yard:coverage]
|
82
82
|
end
|
@@ -0,0 +1,170 @@
|
|
1
|
+
# frozen_string_literal: true
|
2
|
+
|
3
|
+
# rubocop:disable Layout/LineLength
|
4
|
+
|
5
|
+
module ProcessExecuter
|
6
|
+
module Command
|
7
|
+
# Base class for all ProcessExecuter::Command errors
|
8
|
+
#
|
9
|
+
# It is recommended to rescue `ProcessExecuter::Command::Error` to catch any
|
10
|
+
# runtime error raised by this gem unless you need more specific error handling.
|
11
|
+
#
|
12
|
+
# Custom errors are arranged in the following class hierarchy:
|
13
|
+
#
|
14
|
+
# ```text
|
15
|
+
# ::StandardError
|
16
|
+
# └─> Error
|
17
|
+
# ├─> CommandError
|
18
|
+
# │ ├─> FailedError
|
19
|
+
# │ └─> SignaledError
|
20
|
+
# │ └─> TimeoutError
|
21
|
+
# └─> ProcessIOError
|
22
|
+
# ```
|
23
|
+
#
|
24
|
+
# | Error Class | Description |
|
25
|
+
# | --- | --- |
|
26
|
+
# | `Error` | This catch-all error serves as the base class for other custom errors. |
|
27
|
+
# | `CommandError` | A subclass of this error is raised when there is a problem executing a command. |
|
28
|
+
# | `FailedError` | Raised when the command exits with a non-zero status code. |
|
29
|
+
# | `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. |
|
30
|
+
# | `TimeoutError` | This is a specific type of `SignaledError` that is raised when the command times out and is killed via the SIGKILL signal. Raised when the operation takes longer than the specified timeout duration (if provided). |
|
31
|
+
# | `ProcessIOError` | Raised when an error was encountered reading or writing to the command's subprocess. |
|
32
|
+
#
|
33
|
+
# @example Rescuing any error
|
34
|
+
# begin
|
35
|
+
# ProcessExecuter.run_command('git', 'status')
|
36
|
+
# rescue ProcessExecuter::Command::Error => e
|
37
|
+
# puts "An error occurred: #{e.message}"
|
38
|
+
# end
|
39
|
+
#
|
40
|
+
# @example Rescuing a timeout error
|
41
|
+
# begin
|
42
|
+
# timeout_duration = 0.1 # seconds
|
43
|
+
# ProcessExecuter.run_command('sleep', '1', timeout: timeout_duration)
|
44
|
+
# rescue ProcessExecuter::TimeoutError => e # Catch the more specific error first!
|
45
|
+
# puts "Command took too long and timed out: #{e}"
|
46
|
+
# rescue ProcessExecuter::Error => e
|
47
|
+
# puts "Some other error occured: #{e}"
|
48
|
+
# end
|
49
|
+
#
|
50
|
+
# @api public
|
51
|
+
#
|
52
|
+
class Error < ::StandardError; end
|
53
|
+
|
54
|
+
# Raised when a command fails or exits because of an uncaught signal
|
55
|
+
#
|
56
|
+
# The command executed, status, stdout, and stderr are available from this
|
57
|
+
# object.
|
58
|
+
#
|
59
|
+
# The Gem will raise a more specific error for each type of failure:
|
60
|
+
#
|
61
|
+
# * {FailedError}: when the command exits with a non-zero status
|
62
|
+
# * {SignaledError}: when the command exits because of an uncaught signal
|
63
|
+
# * {TimeoutError}: when the command times out
|
64
|
+
#
|
65
|
+
# @api public
|
66
|
+
#
|
67
|
+
class CommandError < ProcessExecuter::Command::Error
|
68
|
+
# Create a CommandError object
|
69
|
+
#
|
70
|
+
# @example
|
71
|
+
# `exit 1` # set $? appropriately for this example
|
72
|
+
# result = ProcessExecuter::Command::Result.new(%w[git status], $?, 'stdout', 'stderr')
|
73
|
+
# error = ProcessExecuter::Command::CommandError.new(result)
|
74
|
+
# error.to_s #=> '["git", "status"], status: pid 89784 exit 1, stderr: "stderr"'
|
75
|
+
#
|
76
|
+
# @param result [Result] The result of the command including the command,
|
77
|
+
# status, stdout, and stderr
|
78
|
+
#
|
79
|
+
def initialize(result)
|
80
|
+
@result = result
|
81
|
+
super(error_message)
|
82
|
+
end
|
83
|
+
|
84
|
+
# The human readable representation of this error
|
85
|
+
#
|
86
|
+
# @example
|
87
|
+
# error.error_message #=> '["git", "status"], status: pid 89784 exit 1, stderr: "stderr"'
|
88
|
+
#
|
89
|
+
# @return [String]
|
90
|
+
#
|
91
|
+
def error_message
|
92
|
+
"#{result.command}, status: #{result}, stderr: #{result.stderr_to_s.inspect}"
|
93
|
+
end
|
94
|
+
|
95
|
+
# @attribute [r] result
|
96
|
+
#
|
97
|
+
# The result of the command including the command, its status and its output
|
98
|
+
#
|
99
|
+
# @example
|
100
|
+
# error.result #=> #<ProcessExecuter::Command::Result:0x00007f9b1b8b3d20>
|
101
|
+
#
|
102
|
+
# @return [Result]
|
103
|
+
#
|
104
|
+
attr_reader :result
|
105
|
+
end
|
106
|
+
|
107
|
+
# Raised when the command returns a non-zero exitstatus
|
108
|
+
#
|
109
|
+
# @api public
|
110
|
+
#
|
111
|
+
class FailedError < ProcessExecuter::Command::CommandError; end
|
112
|
+
|
113
|
+
# Raised when the command exits because of an uncaught signal
|
114
|
+
#
|
115
|
+
# @api public
|
116
|
+
#
|
117
|
+
class SignaledError < ProcessExecuter::Command::CommandError; end
|
118
|
+
|
119
|
+
# Raised when the command takes longer than the configured timeout
|
120
|
+
#
|
121
|
+
# @example
|
122
|
+
# result.status.timeout? #=> true
|
123
|
+
#
|
124
|
+
# @api public
|
125
|
+
#
|
126
|
+
class TimeoutError < ProcessExecuter::Command::SignaledError
|
127
|
+
# Create a TimeoutError object
|
128
|
+
#
|
129
|
+
# @example
|
130
|
+
# command = %w[sleep 10]
|
131
|
+
# timeout_duration = 1
|
132
|
+
# status = ProcessExecuter.spawn(*command, timeout: timeout_duration)
|
133
|
+
# result = Result.new(command, status, 'stdout', 'err output')
|
134
|
+
# error = TimeoutError.new(result, timeout_duration)
|
135
|
+
# error.error_message
|
136
|
+
# #=> '["sleep", "10"], status: pid 70144 SIGKILL (signal 9), stderr: "err output", timed out after 1s'
|
137
|
+
#
|
138
|
+
# @param result [Result] The result of the command including the git command,
|
139
|
+
# status, stdout, and stderr
|
140
|
+
#
|
141
|
+
# @param timeout_duration [Numeric] The duration the subprocess was allowed
|
142
|
+
# to run before being terminated
|
143
|
+
#
|
144
|
+
def initialize(result, timeout_duration)
|
145
|
+
@timeout_duration = timeout_duration
|
146
|
+
super(result)
|
147
|
+
end
|
148
|
+
|
149
|
+
# The amount of time the subprocess was allowed to run before being killed
|
150
|
+
#
|
151
|
+
# @example
|
152
|
+
# `kill -9 $$` # set $? appropriately for this example
|
153
|
+
# result = Result.new(%w[git status], $?, '', "killed")
|
154
|
+
# error = TimeoutError.new(result, 10)
|
155
|
+
# error.timeout_duration #=> 10
|
156
|
+
#
|
157
|
+
# @return [Numeric]
|
158
|
+
#
|
159
|
+
attr_reader :timeout_duration
|
160
|
+
end
|
161
|
+
|
162
|
+
# Raised when the output of a command can not be read
|
163
|
+
#
|
164
|
+
# @api public
|
165
|
+
#
|
166
|
+
class ProcessIOError < ProcessExecuter::Command::Error; end
|
167
|
+
end
|
168
|
+
end
|
169
|
+
|
170
|
+
# rubocop:enable Layout/LineLength
|
@@ -0,0 +1,77 @@
|
|
1
|
+
# frozen_string_literal: true
|
2
|
+
|
3
|
+
require 'delegate'
|
4
|
+
|
5
|
+
module ProcessExecuter
|
6
|
+
module Command
|
7
|
+
# A wrapper around {ProcessExecuter::Status} which adds captured command output
|
8
|
+
#
|
9
|
+
# This class is used to represent the result of a subprocess execution, combining
|
10
|
+
# the process status with the captured output for easier access and manipulation.
|
11
|
+
#
|
12
|
+
# Features:
|
13
|
+
# * Provides access to the process's status, stdout, and stderr.
|
14
|
+
# * Allows conversion of stdout and stderr buffers to strings.
|
15
|
+
#
|
16
|
+
# @example Create a Result object
|
17
|
+
# status = ProcessExecuter.spawn(*command, timeout:, out:, err:)
|
18
|
+
# result = ProcessExecuter::Command::Result.new(command, status, out_buffer.string, err_buffer.string)
|
19
|
+
#
|
20
|
+
# @api public
|
21
|
+
#
|
22
|
+
class Result < SimpleDelegator
|
23
|
+
# Create a new Result object
|
24
|
+
# @example
|
25
|
+
# status = ProcessExecuter.spawn(*command, timeout:, out:, err:)
|
26
|
+
# Result.new(command, status, out_buffer.string, err_buffer.string)
|
27
|
+
# @param command [Array<String>] The command that was executed
|
28
|
+
# @param status [ProcessExecuter::Status] The status of the process
|
29
|
+
# @param stdout [String] The stdout output from the process
|
30
|
+
# @param stderr [String] The stderr output from the process
|
31
|
+
def initialize(command, status, stdout, stderr)
|
32
|
+
super(status)
|
33
|
+
@command = command
|
34
|
+
@stdout = stdout
|
35
|
+
@stderr = stderr
|
36
|
+
end
|
37
|
+
|
38
|
+
# The command that was run
|
39
|
+
# @example
|
40
|
+
# result.command #=> %w[git status]
|
41
|
+
# @return [Array<String>]
|
42
|
+
attr_reader :command
|
43
|
+
|
44
|
+
# The captured stdout output from the process
|
45
|
+
# @example
|
46
|
+
# result.stdout #=> "On branch master\nnothing to commit, working tree clean\n"
|
47
|
+
# @return [String]
|
48
|
+
attr_reader :stdout
|
49
|
+
|
50
|
+
# The captured stderr output from the process
|
51
|
+
# @example
|
52
|
+
# result.stderr #=> "ERROR: file not found"
|
53
|
+
# @return [String]
|
54
|
+
attr_reader :stderr
|
55
|
+
|
56
|
+
# Return the stdout output as a string
|
57
|
+
# @example When stdout is a StringIO containing "Hello World"
|
58
|
+
# result.stdout_to_s #=> "Hello World"
|
59
|
+
# @example When stdout is a File object
|
60
|
+
# result.stdout_to_s #=> #<File:/tmp/output.txt>
|
61
|
+
# @return [String, Object] Returns a String if stdout is a StringIO; otherwise, returns the stdout object
|
62
|
+
def stdout_to_s
|
63
|
+
stdout.respond_to?(:string) ? stdout.string : stdout
|
64
|
+
end
|
65
|
+
|
66
|
+
# Return the stderr output as a string
|
67
|
+
# @example When stderr is a StringIO containing "Hello World"
|
68
|
+
# result.stderr_to_s #=> "Hello World"
|
69
|
+
# @example When stderr is a File object
|
70
|
+
# result.stderr_to_s #=> #<File:/tmp/output.txt>
|
71
|
+
# @return [String, Object] Returns a String if stderr is a StringIO; otherwise, returns the stderr object
|
72
|
+
def stderr_to_s
|
73
|
+
stderr.respond_to?(:string) ? stderr.string : stderr
|
74
|
+
end
|
75
|
+
end
|
76
|
+
end
|
77
|
+
end
|