process_executer 4.0.4 → 4.1.1
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/.editorconfig +10 -0
- data/.release-please-manifest.json +1 -1
- data/.rubocop.yml +13 -0
- data/.vscode/extensions.json +3 -0
- data/CHANGELOG.md +56 -0
- data/README.md +20 -0
- data/Rakefile +3 -0
- data/lib/process_executer/commands/run.rb +163 -9
- data/lib/process_executer/commands/run_with_capture.rb +89 -35
- data/lib/process_executer/commands/spawn_with_timeout.rb +215 -16
- data/lib/process_executer/destinations/monitored_pipe.rb +7 -4
- data/lib/process_executer/errors.rb +2 -2
- data/lib/process_executer/monitored_pipe.rb +385 -47
- data/lib/process_executer/options/base.rb +48 -7
- data/lib/process_executer/options/run_with_capture_options.rb +92 -61
- data/lib/process_executer/options/spawn_options.rb +13 -0
- data/lib/process_executer/result.rb +13 -2
- data/lib/process_executer/version.rb +1 -1
- data/lib/process_executer.rb +34 -2
- data/process_spawn_test/README.md +38 -1
- data/process_spawn_test/spec/spec_helper.rb +18 -0
- data/process_spawn_test/spec/test_spec.rb +21 -0
- metadata +6 -4
|
@@ -23,13 +23,15 @@ module ProcessExecuter
|
|
|
23
23
|
# `encoding`, and finally defaults to `DEFAULT_ENCODING` if neither
|
|
24
24
|
# is available.
|
|
25
25
|
#
|
|
26
|
-
#
|
|
26
|
+
# The value is canonicalized to an Encoding object, so equivalent
|
|
27
|
+
# representations (e.g. `Encoding::UTF_8`, `'UTF-8'`, or `:binary` for
|
|
28
|
+
# `Encoding::BINARY`) all resolve to the same Encoding.
|
|
29
|
+
#
|
|
30
|
+
# @return [Encoding, nil] nil if the value names an unknown encoding
|
|
27
31
|
#
|
|
28
32
|
# @api private
|
|
29
33
|
#
|
|
30
|
-
def effective_stdout_encoding
|
|
31
|
-
stdout_encoding || encoding || DEFAULT_ENCODING
|
|
32
|
-
end
|
|
34
|
+
def effective_stdout_encoding = effective_encoding(stdout_encoding)
|
|
33
35
|
|
|
34
36
|
# Determines the character encoding to use for stderr
|
|
35
37
|
#
|
|
@@ -37,16 +39,35 @@ module ProcessExecuter
|
|
|
37
39
|
# `encoding`, and finally defaults to `DEFAULT_ENCODING` if neither
|
|
38
40
|
# is available.
|
|
39
41
|
#
|
|
40
|
-
#
|
|
42
|
+
# The value is canonicalized to an Encoding object, so equivalent
|
|
43
|
+
# representations (e.g. `Encoding::UTF_8`, `'UTF-8'`, or `:binary` for
|
|
44
|
+
# `Encoding::BINARY`) all resolve to the same Encoding.
|
|
45
|
+
#
|
|
46
|
+
# @return [Encoding, nil] nil if the value names an unknown encoding
|
|
41
47
|
#
|
|
42
48
|
# @api private
|
|
43
49
|
#
|
|
44
|
-
def effective_stderr_encoding
|
|
45
|
-
stderr_encoding || encoding || DEFAULT_ENCODING
|
|
46
|
-
end
|
|
50
|
+
def effective_stderr_encoding = effective_encoding(stderr_encoding)
|
|
47
51
|
|
|
48
52
|
private
|
|
49
53
|
|
|
54
|
+
# The encoding to use for a stream given its stream-specific option value
|
|
55
|
+
#
|
|
56
|
+
# Prioritizes the stream-specific value if set, otherwise falls back to
|
|
57
|
+
# `encoding`, and finally defaults to `DEFAULT_ENCODING`. The result is
|
|
58
|
+
# canonicalized with {#canonical_encoding}.
|
|
59
|
+
#
|
|
60
|
+
# @param stream_encoding [Encoding, String, Symbol, nil] the value of
|
|
61
|
+
# `stdout_encoding` or `stderr_encoding`
|
|
62
|
+
#
|
|
63
|
+
# @return [Encoding, nil] nil if the value names an unknown encoding
|
|
64
|
+
#
|
|
65
|
+
# @api private
|
|
66
|
+
#
|
|
67
|
+
def effective_encoding(stream_encoding)
|
|
68
|
+
canonical_encoding(stream_encoding || encoding || DEFAULT_ENCODING)
|
|
69
|
+
end
|
|
70
|
+
|
|
50
71
|
# The options allowed for objects of this class
|
|
51
72
|
# @return [Array<OptionDefinition>]
|
|
52
73
|
# @api private
|
|
@@ -66,6 +87,8 @@ module ProcessExecuter
|
|
|
66
87
|
# - if the merge_output value is not a Boolean
|
|
67
88
|
# - if merge_output: true and a stderr redirection is given
|
|
68
89
|
# - if merge_output: true and stdout and stderr encodings are different
|
|
90
|
+
# - if a combined stdout/stderr redirection (e.g. `[:out, :err] =>
|
|
91
|
+
# destination`) is given and stdout and stderr encodings are different
|
|
69
92
|
#
|
|
70
93
|
# @param _key [Symbol] the option key (not used)
|
|
71
94
|
# @param _value [Object] the option value (not used)
|
|
@@ -76,80 +99,88 @@ module ProcessExecuter
|
|
|
76
99
|
errors << "merge_output must be true or false but was #{merge_output.inspect}"
|
|
77
100
|
end
|
|
78
101
|
|
|
79
|
-
|
|
80
|
-
|
|
81
|
-
|
|
82
|
-
|
|
83
|
-
|
|
84
|
-
|
|
85
|
-
errors << 'Cannot give merge_output: true AND give different encodings for stdout and stderr'
|
|
102
|
+
if merge_output == true
|
|
103
|
+
errors << 'Cannot give merge_output: true AND a stderr redirection' if stderr_redirection_source
|
|
104
|
+
validate_uniform_capture_encoding('merge_output: true')
|
|
105
|
+
elsif combined_stdout_and_stderr_redirection?
|
|
106
|
+
validate_uniform_capture_encoding('a redirection that combines stdout and stderr')
|
|
107
|
+
end
|
|
86
108
|
end
|
|
87
109
|
|
|
88
|
-
# Note an error if the
|
|
89
|
-
#
|
|
90
|
-
#
|
|
110
|
+
# Note an error if the stdout and stderr encodings are different
|
|
111
|
+
#
|
|
112
|
+
# Used when both streams are captured into the single stdout buffer (via
|
|
113
|
+
# `merge_output: true` or a combined stdout/stderr redirection), which
|
|
114
|
+
# requires a single encoding.
|
|
115
|
+
#
|
|
116
|
+
# Encodings are compared in their canonical form, so equivalent
|
|
117
|
+
# representations (e.g. `Encoding::UTF_8` and `'UTF-8'`) are not
|
|
118
|
+
# rejected. A value that does not canonicalize to an Encoding (an
|
|
119
|
+
# unknown encoding name or an invalid type) is skipped here; the
|
|
120
|
+
# encoding option's own validator reports it.
|
|
121
|
+
#
|
|
122
|
+
# @param description [String] describes the option that requires a single encoding
|
|
91
123
|
# @return [Void]
|
|
92
124
|
# @api private
|
|
93
|
-
def
|
|
94
|
-
|
|
95
|
-
|
|
96
|
-
return if value.nil? || value.is_a?(Encoding)
|
|
125
|
+
def validate_uniform_capture_encoding(description)
|
|
126
|
+
stdout_encoding = effective_stdout_encoding
|
|
127
|
+
stderr_encoding = effective_stderr_encoding
|
|
97
128
|
|
|
98
|
-
|
|
129
|
+
return unless stdout_encoding.is_a?(Encoding) && stderr_encoding.is_a?(Encoding)
|
|
130
|
+
return if stdout_encoding == stderr_encoding
|
|
99
131
|
|
|
100
|
-
|
|
132
|
+
errors << "Cannot give #{description} AND give different encodings for stdout and stderr"
|
|
101
133
|
end
|
|
102
134
|
|
|
103
|
-
#
|
|
135
|
+
# Convert an encoding option value to its canonical Encoding object
|
|
104
136
|
#
|
|
105
|
-
# @param
|
|
106
|
-
#
|
|
107
|
-
# @param value [Object] the option value
|
|
137
|
+
# @param value [Encoding, String, Symbol, Object] the encoding option value
|
|
108
138
|
#
|
|
109
|
-
# @return [
|
|
139
|
+
# @return [Encoding, Object, nil] the Encoding for a recognized value, nil
|
|
140
|
+
# for a String naming an unknown encoding, otherwise the value unchanged
|
|
110
141
|
#
|
|
111
142
|
# @api private
|
|
112
|
-
|
|
113
|
-
|
|
114
|
-
|
|
115
|
-
|
|
116
|
-
|
|
117
|
-
|
|
118
|
-
|
|
119
|
-
false
|
|
143
|
+
def canonical_encoding(value)
|
|
144
|
+
case value
|
|
145
|
+
when :binary then Encoding::BINARY
|
|
146
|
+
when :default_external then Encoding.default_external
|
|
147
|
+
when String then find_encoding(value)
|
|
148
|
+
else value
|
|
149
|
+
end
|
|
120
150
|
end
|
|
121
151
|
|
|
122
|
-
#
|
|
123
|
-
#
|
|
124
|
-
# @param key [Symbol] the option key
|
|
125
|
-
#
|
|
126
|
-
# @param value [Symbol] the option value
|
|
127
|
-
#
|
|
128
|
-
# @return [Void]
|
|
152
|
+
# Find an encoding by name, returning nil if the name is unknown
|
|
129
153
|
#
|
|
154
|
+
# @param name [String] the encoding name
|
|
155
|
+
# @return [Encoding, nil]
|
|
130
156
|
# @api private
|
|
131
|
-
|
|
132
|
-
|
|
133
|
-
|
|
134
|
-
|
|
135
|
-
errors << "#{key} when given as a symbol must be :binary or :default_external, " \
|
|
136
|
-
"but was #{value.inspect}"
|
|
157
|
+
def find_encoding(name)
|
|
158
|
+
Encoding.find(name)
|
|
159
|
+
rescue ::ArgumentError
|
|
160
|
+
nil
|
|
137
161
|
end
|
|
138
162
|
|
|
139
|
-
# Note an error if the encoding
|
|
140
|
-
#
|
|
141
|
-
# @param key [Symbol] the option key
|
|
142
|
-
#
|
|
143
|
-
# @param value [String] the option value
|
|
163
|
+
# Note an error if the encoding option is not valid
|
|
144
164
|
#
|
|
145
|
-
#
|
|
165
|
+
# `nil`, an Encoding object, `:binary`, and `:default_external` are
|
|
166
|
+
# valid as given. A String is valid if {#canonical_encoding} recognizes
|
|
167
|
+
# it as an encoding name. Any other value is invalid.
|
|
146
168
|
#
|
|
169
|
+
# @param key [Symbol] the option key
|
|
170
|
+
# @param value [Object] the option value
|
|
171
|
+
# @return [Void]
|
|
147
172
|
# @api private
|
|
148
|
-
|
|
149
|
-
|
|
150
|
-
Encoding
|
|
151
|
-
|
|
152
|
-
|
|
173
|
+
def validate_encoding_option(key, value)
|
|
174
|
+
case value
|
|
175
|
+
when nil, Encoding, :binary, :default_external then nil
|
|
176
|
+
when Symbol
|
|
177
|
+
errors << "#{key} when given as a symbol must be :binary or :default_external, but was #{value.inspect}"
|
|
178
|
+
when String
|
|
179
|
+
errors << "#{key} specifies an unknown encoding name: #{value.inspect}" if canonical_encoding(value).nil?
|
|
180
|
+
else
|
|
181
|
+
errors << "#{key} must be an Encoding object, String, Symbol (:binary, :default_external), " \
|
|
182
|
+
"or nil, but was #{value.inspect}"
|
|
183
|
+
end
|
|
153
184
|
end
|
|
154
185
|
end
|
|
155
186
|
end
|
|
@@ -110,6 +110,19 @@ module ProcessExecuter
|
|
|
110
110
|
(key = stderr_redirection_source) ? options_hash[key] : nil
|
|
111
111
|
end
|
|
112
112
|
|
|
113
|
+
# Determine if a single redirection option key covers both stdout and stderr
|
|
114
|
+
#
|
|
115
|
+
# This is the case for a combined redirection such as `[:out, :err] =>
|
|
116
|
+
# destination`, where both {#stdout_redirection_source} and
|
|
117
|
+
# {#stderr_redirection_source} resolve to the same key.
|
|
118
|
+
#
|
|
119
|
+
# @return [Boolean]
|
|
120
|
+
# @api private
|
|
121
|
+
def combined_stdout_and_stderr_redirection?
|
|
122
|
+
key = stdout_redirection_source
|
|
123
|
+
!key.nil? && key.equal?(stderr_redirection_source)
|
|
124
|
+
end
|
|
125
|
+
|
|
113
126
|
private
|
|
114
127
|
|
|
115
128
|
# Define the allowed options
|
|
@@ -10,6 +10,12 @@ module ProcessExecuter
|
|
|
10
10
|
# * `elapsed_time`: the seconds the command ran
|
|
11
11
|
# * `timed_out?`: true if the process timed out
|
|
12
12
|
#
|
|
13
|
+
# In the rare case that the timeout fires after the process was already
|
|
14
|
+
# reaped, its status is lost: the result delegates to a nil status and
|
|
15
|
+
# `timed_out?` is true. {#success?} and {#to_s} still work, but methods
|
|
16
|
+
# forwarded to the status (like `exitstatus` or `signaled?`) will raise
|
|
17
|
+
# NoMethodError.
|
|
18
|
+
#
|
|
13
19
|
# @api public
|
|
14
20
|
#
|
|
15
21
|
class Result < SimpleDelegator
|
|
@@ -25,7 +31,8 @@ module ProcessExecuter
|
|
|
25
31
|
#
|
|
26
32
|
# ProcessExecuter::Result.new(status, command:, options:, timed_out:, elapsed_time:)
|
|
27
33
|
#
|
|
28
|
-
# @param status [Process::Status] the status to delegate to
|
|
34
|
+
# @param status [Process::Status, nil] the status to delegate to (nil when the
|
|
35
|
+
# timeout raced the wait for the process and the status was lost)
|
|
29
36
|
#
|
|
30
37
|
# @param command [Array] the command that was used to spawn the process
|
|
31
38
|
#
|
|
@@ -70,7 +77,11 @@ module ProcessExecuter
|
|
|
70
77
|
attr_reader :elapsed_time
|
|
71
78
|
|
|
72
79
|
# @!attribute [r] timed_out?
|
|
73
|
-
# True if the
|
|
80
|
+
# True if the wait for the process was cut short by `timeout_after` elapsing
|
|
81
|
+
#
|
|
82
|
+
# A timed out process is sent the SIGKILL signal, unless it had already
|
|
83
|
+
# exited in the moment between the timeout firing and the kill (the
|
|
84
|
+
# raced-timeout corner case described above).
|
|
74
85
|
# @example
|
|
75
86
|
# result = ProcessExecuter.spawn_with_timeout('sleep 10', timeout_after: 0.01)
|
|
76
87
|
# result.timed_out? # => true
|
data/lib/process_executer.rb
CHANGED
|
@@ -32,6 +32,26 @@ module ProcessExecuter
|
|
|
32
32
|
# * `timeout_after: <Numeric, nil>`: the amount of time (in seconds) to wait before
|
|
33
33
|
# signaling the process with SIGKILL. 0 or nil means no timeout.
|
|
34
34
|
#
|
|
35
|
+
# When a timeout is given, the command is spawned into its own process group
|
|
36
|
+
# (`pgroup: true` on POSIX, `new_pgroup: true` on Windows) unless the caller
|
|
37
|
+
# passes a `pgroup`/`new_pgroup` option themselves. Note that a new process
|
|
38
|
+
# group is a background group for any terminal the command inherits, so an
|
|
39
|
+
# interactive command that reads the terminal is stopped by `SIGTTIN` and
|
|
40
|
+
# then killed when the timeout fires. A caller who needs an interactive
|
|
41
|
+
# command to stay in the foreground process group can pass their own
|
|
42
|
+
# `pgroup` option (for example, `pgroup: Process.getpgrp`).
|
|
43
|
+
#
|
|
44
|
+
# On timeout, the whole process group is killed so that descendant
|
|
45
|
+
# processes still in that group do not outlive the timeout. A descendant
|
|
46
|
+
# that started its own session or moved to another process group (a
|
|
47
|
+
# daemon, for example) is not killed. If the group can not be killed (for
|
|
48
|
+
# instance, the caller placed the command in an existing process group),
|
|
49
|
+
# only the direct child is killed.
|
|
50
|
+
#
|
|
51
|
+
# Killing the process group is only supported on POSIX platforms. On
|
|
52
|
+
# Windows, Ruby's `Process.kill` cannot signal a process group, so only the
|
|
53
|
+
# direct child is killed and descendant processes may survive the timeout.
|
|
54
|
+
#
|
|
35
55
|
# Returns a {Result} object. The {Result} class is a decorator for
|
|
36
56
|
# [Process::Status](https://docs.ruby-lang.org/en/3.4/Process/Status.html) that
|
|
37
57
|
# provides additional attributes about the command's status. This includes the
|
|
@@ -55,7 +75,10 @@ module ProcessExecuter
|
|
|
55
75
|
# the following options are supported: `:timeout_after`
|
|
56
76
|
#
|
|
57
77
|
# @option options_hash [Numeric] :timeout_after the amount of time (in seconds)
|
|
58
|
-
# to wait before
|
|
78
|
+
# to wait before killing the process -- and, on POSIX platforms when the
|
|
79
|
+
# effective spawn options make the command a new process group leader
|
|
80
|
+
# (set automatically, or by an explicit `pgroup: true` or `pgroup: 0`),
|
|
81
|
+
# its whole process group
|
|
59
82
|
#
|
|
60
83
|
# @overload spawn_with_timeout(*command, options)
|
|
61
84
|
#
|
|
@@ -209,7 +232,10 @@ module ProcessExecuter
|
|
|
209
232
|
# and its result at the info level
|
|
210
233
|
#
|
|
211
234
|
# @option options_hash [Numeric] :timeout_after the amount of time (in seconds)
|
|
212
|
-
# to wait before
|
|
235
|
+
# to wait before killing the process -- and, on POSIX platforms when the
|
|
236
|
+
# effective spawn options make the command a new process group leader
|
|
237
|
+
# (set automatically, or by an explicit `pgroup: true` or `pgroup: 0`),
|
|
238
|
+
# its whole process group
|
|
213
239
|
#
|
|
214
240
|
# @overload run(*command, options)
|
|
215
241
|
#
|
|
@@ -279,6 +305,12 @@ module ProcessExecuter
|
|
|
279
305
|
# <destination>` or `err: <destination>`). These redirections will receive the
|
|
280
306
|
# output in addition to the internal capture.
|
|
281
307
|
#
|
|
308
|
+
# A combined redirection whose key covers both stdout and stderr (e.g. `[:out,
|
|
309
|
+
# :err] => <destination>`) behaves like `merge_output: true`: both streams are
|
|
310
|
+
# interleaved into the `#stdout` capture and `#stderr` is empty. As with
|
|
311
|
+
# `merge_output: true`, a `ProcessExecuter::ArgumentError` is raised if
|
|
312
|
+
# different encodings are given for stdout and stderr.
|
|
313
|
+
#
|
|
282
314
|
# Unless told otherwise, the internally captured output is assumed to be in UTF-8
|
|
283
315
|
# encoding. This assumption can be changed with the `encoding`,
|
|
284
316
|
# `stdout_encoding`, or `stderr_encoding` options. These options accept any
|
|
@@ -4,14 +4,43 @@ These tests verify that `Process.spawn`, `Process.wait`, and `Process.wait2` wor
|
|
|
4
4
|
correctly across different Ruby implementations and operating systems.
|
|
5
5
|
|
|
6
6
|
This test suite is particularly important for verifying JRuby behavior on Windows,
|
|
7
|
-
where historically there have been issues with subprocess status reporting.
|
|
7
|
+
where historically there have been issues with subprocess status reporting. See
|
|
8
|
+
[jruby/jruby#7515](https://github.com/jruby/jruby/issues/7515).
|
|
8
9
|
|
|
9
10
|
## Tests
|
|
10
11
|
|
|
11
12
|
The test suite includes:
|
|
12
13
|
|
|
13
14
|
* Test that `Process#wait` sets the global `$CHILD_STATUS` variable
|
|
15
|
+
* Test that `Process#wait` reports the exit status of a child that failed
|
|
16
|
+
* Test that `Process#wait` blocks until the child exits
|
|
14
17
|
* Test that `Process#wait2` returns a non-nil status
|
|
18
|
+
* Test that `Process#wait2` returns the pid and status of a child that failed
|
|
19
|
+
|
|
20
|
+
## Spawn backends
|
|
21
|
+
|
|
22
|
+
Set `SPAWN_BACKEND` to choose which spawn implementation the examples exercise:
|
|
23
|
+
|
|
24
|
+
| Value | Meaning |
|
|
25
|
+
| --- | --- |
|
|
26
|
+
| `stock` (default) | Whatever the running Ruby provides |
|
|
27
|
+
| `subspawn` | [byteit101/subspawn](https://github.com/byteit101/subspawn) replaces `Process.spawn` and the `wait` family |
|
|
28
|
+
|
|
29
|
+
JRuby has a built-in opt-in for SubSpawn, `USE_SUBSPAWN=true`, but it refuses to
|
|
30
|
+
honor it on the one platform this suite cares about: `Ruby.java` logs
|
|
31
|
+
`env USE_SUBSPAWN=true is unsupported on Windows at this time` and loads nothing.
|
|
32
|
+
So `spec_helper.rb` requires `subspawn/replace-builtin` itself instead.
|
|
33
|
+
|
|
34
|
+
Two things keep the `subspawn` backend from being a plain `gem install subspawn --pre`:
|
|
35
|
+
|
|
36
|
+
* The published `subspawn` 0.2.0.pre1 cannot be installed on JRuby at all. Its
|
|
37
|
+
`engine-hacks` dependency was only ever pushed as a `ruby` platform gem carrying
|
|
38
|
+
a C extension; the `java` platform build that its gemspec provides for was never
|
|
39
|
+
published, so RubyGems tries to compile the C extension under JRuby.
|
|
40
|
+
* master is newer than the RC and carries fixes the RC does not have.
|
|
41
|
+
|
|
42
|
+
So the workflow clones the repo and builds the gemspecs with the active Ruby, which
|
|
43
|
+
produces the java-platform `engine-hacks` gem.
|
|
15
44
|
|
|
16
45
|
## Running the Tests
|
|
17
46
|
|
|
@@ -27,10 +56,18 @@ Alternatively, you can stay at the root and run:
|
|
|
27
56
|
bundle exec rspec process_spawn_test/spec/test_spec.rb
|
|
28
57
|
```
|
|
29
58
|
|
|
59
|
+
SubSpawn is not in that Gemfile. To run the `subspawn` backend, install it as a
|
|
60
|
+
system gem and run `rspec` directly, outside the bundle:
|
|
61
|
+
|
|
62
|
+
```bash
|
|
63
|
+
SPAWN_BACKEND=subspawn rspec spec/test_spec.rb
|
|
64
|
+
```
|
|
65
|
+
|
|
30
66
|
## GitHub Actions Workflow
|
|
31
67
|
|
|
32
68
|
The workflow file `.github/workflows/process-spawn-test.yml` can be manually triggered to run these tests on:
|
|
33
69
|
- MRI Ruby and JRuby
|
|
34
70
|
- Ubuntu (Linux) and Windows
|
|
71
|
+
- the `stock` and `subspawn` spawn backends (`subspawn` on JRuby only)
|
|
35
72
|
|
|
36
73
|
To run the workflow, go to the Actions tab in GitHub and select "Process.spawn Test" from the workflow list.
|
|
@@ -2,6 +2,17 @@
|
|
|
2
2
|
|
|
3
3
|
require 'rspec'
|
|
4
4
|
|
|
5
|
+
# Which spawn implementation these examples exercise: 'stock' (whatever the
|
|
6
|
+
# running Ruby provides) or 'subspawn' (byteit101/subspawn).
|
|
7
|
+
SPAWN_BACKEND = ENV.fetch('SPAWN_BACKEND', 'stock')
|
|
8
|
+
|
|
9
|
+
# JRuby has a built-in opt-in for SubSpawn, `USE_SUBSPAWN=true`, but it refuses
|
|
10
|
+
# to honor it on the one platform jruby/jruby#7515 is about: Ruby.java logs
|
|
11
|
+
# "env USE_SUBSPAWN=true is unsupported on Windows at this time" and loads
|
|
12
|
+
# nothing. Require the replacement shim directly so the Windows job actually
|
|
13
|
+
# runs against SubSpawn.
|
|
14
|
+
require 'subspawn/replace-builtin' if SPAWN_BACKEND == 'subspawn'
|
|
15
|
+
|
|
5
16
|
RSpec.configure do |config|
|
|
6
17
|
# Enable flags like --only-failures and --next-failure
|
|
7
18
|
config.example_status_persistence_file_path = '.rspec_status'
|
|
@@ -12,4 +23,11 @@ RSpec.configure do |config|
|
|
|
12
23
|
config.expect_with :rspec do |c|
|
|
13
24
|
c.syntax = :expect
|
|
14
25
|
end
|
|
26
|
+
|
|
27
|
+
config.before(:suite) do
|
|
28
|
+
warn "Ruby: #{RUBY_DESCRIPTION}"
|
|
29
|
+
warn "spawn backend: #{SPAWN_BACKEND}"
|
|
30
|
+
# Records whether the shim really took effect, and which backend it picked.
|
|
31
|
+
warn "SubSpawn: #{defined?(SubSpawn) ? SubSpawn::Platform : '(not loaded)'}"
|
|
32
|
+
end
|
|
15
33
|
end
|
|
@@ -9,6 +9,20 @@ RSpec.describe 'Process#wait' do
|
|
|
9
9
|
expect($CHILD_STATUS).not_to be_nil
|
|
10
10
|
expect($CHILD_STATUS.pid).to eq(pid)
|
|
11
11
|
end
|
|
12
|
+
|
|
13
|
+
it 'reports the exit status of a child that failed' do
|
|
14
|
+
pid = Process.spawn('ruby', '-e', 'exit 42')
|
|
15
|
+
Process.wait(pid)
|
|
16
|
+
expect($CHILD_STATUS&.exitstatus).to eq(42)
|
|
17
|
+
end
|
|
18
|
+
|
|
19
|
+
it 'blocks until the child exits' do
|
|
20
|
+
pid = Process.spawn('ruby', '-e', 'sleep 2')
|
|
21
|
+
started = Process.clock_gettime(Process::CLOCK_MONOTONIC)
|
|
22
|
+
Process.wait(pid)
|
|
23
|
+
elapsed = Process.clock_gettime(Process::CLOCK_MONOTONIC) - started
|
|
24
|
+
expect(elapsed).to be >= 1.5
|
|
25
|
+
end
|
|
12
26
|
end
|
|
13
27
|
|
|
14
28
|
RSpec.describe 'Process#wait2' do
|
|
@@ -18,4 +32,11 @@ RSpec.describe 'Process#wait2' do
|
|
|
18
32
|
expect(status).not_to be_nil
|
|
19
33
|
expect(status.pid).to eq(pid)
|
|
20
34
|
end
|
|
35
|
+
|
|
36
|
+
it 'returns the pid and status of a child that failed' do
|
|
37
|
+
pid = Process.spawn('ruby', '-e', 'exit 42')
|
|
38
|
+
reaped_pid, status = Process.wait2(pid)
|
|
39
|
+
expect(reaped_pid).to eq(pid)
|
|
40
|
+
expect(status&.exitstatus).to eq(42)
|
|
41
|
+
end
|
|
21
42
|
end
|
metadata
CHANGED
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
--- !ruby/object:Gem::Specification
|
|
2
2
|
name: process_executer
|
|
3
3
|
version: !ruby/object:Gem::Version
|
|
4
|
-
version: 4.
|
|
4
|
+
version: 4.1.1
|
|
5
5
|
platform: ruby
|
|
6
6
|
authors:
|
|
7
7
|
- James Couball
|
|
@@ -237,11 +237,13 @@ extensions: []
|
|
|
237
237
|
extra_rdoc_files: []
|
|
238
238
|
files:
|
|
239
239
|
- ".commitlintrc.yml"
|
|
240
|
+
- ".editorconfig"
|
|
240
241
|
- ".husky/commit-msg"
|
|
241
242
|
- ".markdownlint.yml"
|
|
242
243
|
- ".release-please-manifest.json"
|
|
243
244
|
- ".rspec"
|
|
244
245
|
- ".rubocop.yml"
|
|
246
|
+
- ".vscode/extensions.json"
|
|
245
247
|
- ".yardopts"
|
|
246
248
|
- CHANGELOG.md
|
|
247
249
|
- Gemfile
|
|
@@ -293,8 +295,8 @@ metadata:
|
|
|
293
295
|
allowed_push_host: https://rubygems.org
|
|
294
296
|
homepage_uri: https://github.com/main-branch/process_executer
|
|
295
297
|
source_code_uri: https://github.com/main-branch/process_executer
|
|
296
|
-
documentation_uri: https://rubydoc.info/gems/process_executer/4.
|
|
297
|
-
changelog_uri: https://rubydoc.info/gems/process_executer/4.
|
|
298
|
+
documentation_uri: https://rubydoc.info/gems/process_executer/4.1.1
|
|
299
|
+
changelog_uri: https://rubydoc.info/gems/process_executer/4.1.1/file/CHANGELOG.md
|
|
298
300
|
rubygems_mfa_required: 'true'
|
|
299
301
|
rdoc_options: []
|
|
300
302
|
require_paths:
|
|
@@ -312,7 +314,7 @@ required_rubygems_version: !ruby/object:Gem::Requirement
|
|
|
312
314
|
requirements:
|
|
313
315
|
- 'Platform: Mac, Linux, or Windows'
|
|
314
316
|
- 'Ruby: MRI 3.1 or later, TruffleRuby 24 or later, or JRuby 9.4 or later'
|
|
315
|
-
rubygems_version: 4.0.
|
|
317
|
+
rubygems_version: 4.0.16
|
|
316
318
|
specification_version: 4
|
|
317
319
|
summary: Enhanced subprocess execution with timeouts, output capture, and flexible
|
|
318
320
|
redirection
|