process_executer 0.3.0 → 0.4.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/CHANGELOG.md +13 -0
- data/Rakefile +24 -17
- data/lib/process_executer/monitored_pipe.rb +66 -21
- data/lib/process_executer/options.rb +6 -0
- data/lib/process_executer/version.rb +2 -1
- data/lib/process_executer.rb +0 -2
- data/process_executer.gemspec +9 -6
- metadata +26 -22
- data/lib/process_executer/process.rb +0 -72
- data/lib/process_executer/status.rb +0 -345
checksums.yaml
CHANGED
@@ -1,7 +1,7 @@
|
|
1
1
|
---
|
2
2
|
SHA256:
|
3
|
-
metadata.gz:
|
4
|
-
data.tar.gz:
|
3
|
+
metadata.gz: fc6b432be7a421bc53561149ce4718a41fd7ff61b1b2213498000891c388a0c9
|
4
|
+
data.tar.gz: ce582e04a3500b0b6c0e31af16d2d735e87f9c99c5d82279c591c9e5abc0a619
|
5
5
|
SHA512:
|
6
|
-
metadata.gz:
|
7
|
-
data.tar.gz:
|
6
|
+
metadata.gz: 312c65d62033f2e3a13ed5094de32940b9923d775d45f3d90dc6cbe4f0a8d0dcf3e5e772e6add2999c97e2b2980b39d4fa3d4f7ddf0172e9877d7e94a1770d94
|
7
|
+
data.tar.gz: 267da77563194721787fee28ff41cdf19159f083f5a0cb04d6d912e0271f2e2062b50da8d1655b36bea2ebb5327f3f3d618aa90453ad60ba61d72e4c779afe84
|
data/CHANGELOG.md
CHANGED
@@ -5,6 +5,19 @@ 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
|
+
## v0.4.0 (2022-12-06)
|
9
|
+
|
10
|
+
[Full Changelog](https://github.com/main-branch/process_executer/compare/v0.3.0...v0.4.0)
|
11
|
+
|
12
|
+
* 9ac17a4 Remove build using jruby-head on windows
|
13
|
+
* d36d131 Work around a SimpleCov problem when using JRuby
|
14
|
+
* b6b3a19 Remove unused Status and Process classes
|
15
|
+
* a5cdf04 Allow 100% coverage check to be skipped
|
16
|
+
* a3fa1f5 Output coverage details when coverage is below 100%
|
17
|
+
* 6a9a417 Refactor monitor so that closing the pipe is on the monitoring thread
|
18
|
+
* 65ee9a2 Add JRuby and Windows builds
|
19
|
+
* 2e713e3 Release v0.3.0
|
20
|
+
|
8
21
|
## v0.3.0 (2022-12-01)
|
9
22
|
|
10
23
|
[Full Changelog](https://github.com/main-branch/process_executer/compare/v0.2.0...v0.3.0)
|
data/Rakefile
CHANGED
@@ -3,7 +3,12 @@
|
|
3
3
|
# The default task
|
4
4
|
|
5
5
|
desc 'Run the same tasks that the CI build will run'
|
6
|
-
|
6
|
+
|
7
|
+
if RUBY_PLATFORM == 'java'
|
8
|
+
task default: %w[spec rubocop bundle:audit build]
|
9
|
+
else
|
10
|
+
task default: %w[spec rubocop yard yard:audit yard:coverage bundle:audit build]
|
11
|
+
end
|
7
12
|
|
8
13
|
# Bundler Audit
|
9
14
|
|
@@ -53,27 +58,29 @@ end
|
|
53
58
|
|
54
59
|
CLEAN << 'rubocop-report.json'
|
55
60
|
|
56
|
-
|
61
|
+
unless RUBY_PLATFORM == 'java'
|
62
|
+
# YARD
|
57
63
|
|
58
|
-
require 'yard'
|
59
|
-
YARD::Rake::YardocTask.new do |t|
|
60
|
-
|
61
|
-
end
|
64
|
+
require 'yard'
|
65
|
+
YARD::Rake::YardocTask.new do |t|
|
66
|
+
t.files = %w[lib/**/*.rb examples/**/*]
|
67
|
+
end
|
62
68
|
|
63
|
-
CLEAN << '.yardoc'
|
64
|
-
CLEAN << 'doc'
|
69
|
+
CLEAN << '.yardoc'
|
70
|
+
CLEAN << 'doc'
|
65
71
|
|
66
|
-
# Yardstick
|
72
|
+
# Yardstick
|
67
73
|
|
68
|
-
desc 'Run yardstick to show missing YARD doc elements'
|
69
|
-
task :'yard:audit' do
|
70
|
-
|
71
|
-
end
|
74
|
+
desc 'Run yardstick to show missing YARD doc elements'
|
75
|
+
task :'yard:audit' do
|
76
|
+
sh "yardstick 'lib/**/*.rb'"
|
77
|
+
end
|
72
78
|
|
73
|
-
# Yardstick coverage
|
79
|
+
# Yardstick coverage
|
74
80
|
|
75
|
-
require 'yardstick/rake/verify'
|
81
|
+
require 'yardstick/rake/verify'
|
76
82
|
|
77
|
-
Yardstick::Rake::Verify.new(:'yard:coverage') do |verify|
|
78
|
-
|
83
|
+
Yardstick::Rake::Verify.new(:'yard:coverage') do |verify|
|
84
|
+
verify.threshold = 100
|
85
|
+
end
|
79
86
|
end
|
@@ -9,7 +9,7 @@ module ProcessExecuter
|
|
9
9
|
# When a new MonitoredPipe is created, a pipe is created (via IO.pipe) and
|
10
10
|
# a thread is created to read data written to the pipe.
|
11
11
|
#
|
12
|
-
# Data that is read from
|
12
|
+
# Data that is read from that pipe is written one or more writers passed to
|
13
13
|
# `#initialize`.
|
14
14
|
#
|
15
15
|
# `#close` must be called to ensure that (1) the pipe is closed, (2) all data is
|
@@ -53,30 +53,31 @@ module ProcessExecuter
|
|
53
53
|
# @param chunk_size [Integer] the size of the chunks to read from the pipe
|
54
54
|
#
|
55
55
|
def initialize(*writers, chunk_size: 1000)
|
56
|
-
@pipe_reader, @pipe_writer = IO.pipe
|
57
|
-
@chunk_size = chunk_size
|
58
56
|
@writers = writers
|
59
|
-
@
|
57
|
+
@chunk_size = chunk_size
|
58
|
+
@pipe_reader, @pipe_writer = IO.pipe
|
59
|
+
@state = :open
|
60
|
+
@thread = Thread.new { monitor }
|
60
61
|
end
|
61
62
|
|
62
|
-
#
|
63
|
+
# Set the state to `:closing` and wait for the state to be set to `:closed`
|
64
|
+
#
|
65
|
+
# The monitoring thread will see that the state has changed and will close the pipe.
|
63
66
|
#
|
64
67
|
# @example
|
65
|
-
# require 'stringio'
|
66
68
|
# data_collector = StringIO.new
|
67
69
|
# pipe = ProcessExecuter::MonitoredPipe.new(data_collector)
|
70
|
+
# pipe.state #=> :open
|
68
71
|
# pipe.write('Hello World')
|
69
72
|
# pipe.close
|
73
|
+
# pipe.state #=> :closed
|
70
74
|
# data_collector.string #=> "Hello World"
|
71
75
|
#
|
72
76
|
# @return [void]
|
73
77
|
#
|
74
78
|
def close
|
75
|
-
|
76
|
-
|
77
|
-
pipe_writer.close
|
78
|
-
read_pipe_output if pipe_reader.wait_readable(0)
|
79
|
-
pipe_reader.close
|
79
|
+
@state = :closing
|
80
|
+
sleep 0.01 until state == :closed
|
80
81
|
end
|
81
82
|
|
82
83
|
# Return the write end of the pipe so that data can be written to it
|
@@ -212,26 +213,70 @@ module ProcessExecuter
|
|
212
213
|
# @return [IO] the write end of the pipe
|
213
214
|
attr_reader :pipe_writer
|
214
215
|
|
216
|
+
# @!attribute [r]
|
217
|
+
#
|
218
|
+
# The state of the pipe
|
219
|
+
#
|
220
|
+
# Must be either `:open`, `:closing`, or `:closed`
|
221
|
+
#
|
222
|
+
# * `:open` - the pipe is open and data can be written to it
|
223
|
+
# * `:closing` - the pipe is being closed and data can no longer be written to it
|
224
|
+
# * `:closed` - the pipe is closed and data can no longer be written to it
|
225
|
+
#
|
226
|
+
# @example
|
227
|
+
# pipe = ProcessExecuter::MonitoredPipe.new($stdout)
|
228
|
+
# pipe.state #=> :open
|
229
|
+
# pipe.close
|
230
|
+
# pipe.state #=> :closed
|
231
|
+
#
|
232
|
+
# @return [Symbol] the state of the pipe
|
233
|
+
#
|
234
|
+
attr_reader :state
|
235
|
+
|
215
236
|
private
|
216
237
|
|
217
|
-
#
|
238
|
+
# Read data from the pipe until `#state` is changed to `:closing`
|
239
|
+
#
|
240
|
+
# The state is changed to `:closed` by calling `#close`.
|
241
|
+
#
|
242
|
+
# Before this method returns, state is set to `:closed`
|
243
|
+
#
|
218
244
|
# @return [void]
|
219
245
|
# @api private
|
220
|
-
def
|
221
|
-
|
222
|
-
|
223
|
-
|
246
|
+
def monitor
|
247
|
+
monitor_pipe until state == :closing
|
248
|
+
close_pipe
|
249
|
+
@state = :closed
|
224
250
|
end
|
225
251
|
|
226
|
-
# Read
|
252
|
+
# Read data from the pipe until `#state` is changed to `:closing`
|
253
|
+
#
|
254
|
+
# Data read from the pipe is written to the writers given to the constructor.
|
255
|
+
#
|
227
256
|
# @return [void]
|
228
257
|
# @api private
|
229
|
-
def
|
258
|
+
def monitor_pipe
|
230
259
|
new_data = pipe_reader.read_nonblock(chunk_size)
|
231
|
-
|
260
|
+
rescue IO::WaitReadable
|
261
|
+
pipe_reader.wait_readable(0.01)
|
262
|
+
else
|
232
263
|
writers.each { |w| w.write(new_data) }
|
233
|
-
|
234
|
-
|
264
|
+
end
|
265
|
+
|
266
|
+
# Read any remaining data from the pipe and close it
|
267
|
+
#
|
268
|
+
# @return [void]
|
269
|
+
# @api private
|
270
|
+
def close_pipe
|
271
|
+
# Close the write end of the pipe so no more data can be written to it
|
272
|
+
pipe_writer.close
|
273
|
+
# Read remaining data from pipe_reader (if any)
|
274
|
+
if pipe_reader.wait_readable(0.01)
|
275
|
+
new_data = pipe_reader.read(chunk_size)
|
276
|
+
writers.each { |w| w.write(new_data) }
|
277
|
+
end
|
278
|
+
# Close the read end of the pipe
|
279
|
+
pipe_reader.close
|
235
280
|
end
|
236
281
|
end
|
237
282
|
end
|
@@ -13,6 +13,10 @@ module ProcessExecuter
|
|
13
13
|
# @api public
|
14
14
|
#
|
15
15
|
class Options
|
16
|
+
# :nocov:
|
17
|
+
# SimpleCov on JRuby seems to hav a bug that causes hashes declared on multiple lines
|
18
|
+
# to not be counted as covered.
|
19
|
+
|
16
20
|
# These options should be passed to `Process.spawn`
|
17
21
|
#
|
18
22
|
# Additionally, any options whose key is an Integer or an IO object will
|
@@ -49,6 +53,8 @@ module ProcessExecuter
|
|
49
53
|
timeout: nil
|
50
54
|
}.freeze
|
51
55
|
|
56
|
+
# :nocov:
|
57
|
+
|
52
58
|
# All options allowed by this class
|
53
59
|
#
|
54
60
|
ALL_OPTIONS = (SPAWN_OPTIONS + NON_SPAWN_OPTIONS).freeze
|
data/lib/process_executer.rb
CHANGED
data/process_executer.gemspec
CHANGED
@@ -10,15 +10,15 @@ Gem::Specification.new do |spec|
|
|
10
10
|
|
11
11
|
spec.summary = 'An API for executing commands in a subprocess'
|
12
12
|
spec.description = 'An API for executing commands in a subprocess'
|
13
|
-
spec.homepage = 'https://github.com/
|
13
|
+
spec.homepage = 'https://github.com/main-branch/process_executer'
|
14
14
|
spec.license = 'MIT'
|
15
15
|
spec.required_ruby_version = '>= 2.7.0'
|
16
16
|
|
17
17
|
spec.metadata['allowed_push_host'] = 'https://rubygems.org'
|
18
18
|
|
19
19
|
spec.metadata['homepage_uri'] = spec.homepage
|
20
|
-
spec.metadata['source_code_uri'] = 'https://github.com/
|
21
|
-
spec.metadata['changelog_uri'] = 'https://github.com/
|
20
|
+
spec.metadata['source_code_uri'] = 'https://github.com/main-branch/process_executer'
|
21
|
+
spec.metadata['changelog_uri'] = 'https://github.com/main-branch/process_executer'
|
22
22
|
|
23
23
|
# Specify which files should be added to the gem when it is released.
|
24
24
|
# The `git ls-files -z` loads the files in the RubyGem that have been added into git.
|
@@ -37,14 +37,17 @@ Gem::Specification.new do |spec|
|
|
37
37
|
spec.add_development_dependency 'bundler-audit', '~> 0.9'
|
38
38
|
spec.add_development_dependency 'create_github_release', '~> 0.2'
|
39
39
|
spec.add_development_dependency 'rake', '~> 13.0'
|
40
|
-
spec.add_development_dependency 'redcarpet', '~> 3.5'
|
41
40
|
spec.add_development_dependency 'rspec', '~> 3.10'
|
42
41
|
spec.add_development_dependency 'rubocop', '~> 1.36'
|
43
42
|
spec.add_development_dependency 'simplecov', '~> 0.21'
|
44
43
|
spec.add_development_dependency 'simplecov-lcov', '~> 0.8'
|
45
44
|
spec.add_development_dependency 'solargraph', '~> 0.47'
|
46
|
-
|
47
|
-
|
45
|
+
|
46
|
+
unless RUBY_PLATFORM == 'java'
|
47
|
+
spec.add_development_dependency 'redcarpet', '~> 3.5'
|
48
|
+
spec.add_development_dependency 'yard', '~> 0.9', '>= 0.9.28'
|
49
|
+
spec.add_development_dependency 'yardstick', '~> 0.9'
|
50
|
+
end
|
48
51
|
|
49
52
|
# For more information and examples about making a new gem, check out our
|
50
53
|
# guide at: https://bundler.io/guides/creating_gem.html
|
metadata
CHANGED
@@ -1,14 +1,14 @@
|
|
1
1
|
--- !ruby/object:Gem::Specification
|
2
2
|
name: process_executer
|
3
3
|
version: !ruby/object:Gem::Version
|
4
|
-
version: 0.
|
4
|
+
version: 0.4.0
|
5
5
|
platform: ruby
|
6
6
|
authors:
|
7
7
|
- James Couball
|
8
8
|
autorequire:
|
9
9
|
bindir: exe
|
10
10
|
cert_chain: []
|
11
|
-
date: 2022-12-
|
11
|
+
date: 2022-12-07 00:00:00.000000000 Z
|
12
12
|
dependencies:
|
13
13
|
- !ruby/object:Gem::Dependency
|
14
14
|
name: bump
|
@@ -66,20 +66,6 @@ dependencies:
|
|
66
66
|
- - "~>"
|
67
67
|
- !ruby/object:Gem::Version
|
68
68
|
version: '13.0'
|
69
|
-
- !ruby/object:Gem::Dependency
|
70
|
-
name: redcarpet
|
71
|
-
requirement: !ruby/object:Gem::Requirement
|
72
|
-
requirements:
|
73
|
-
- - "~>"
|
74
|
-
- !ruby/object:Gem::Version
|
75
|
-
version: '3.5'
|
76
|
-
type: :development
|
77
|
-
prerelease: false
|
78
|
-
version_requirements: !ruby/object:Gem::Requirement
|
79
|
-
requirements:
|
80
|
-
- - "~>"
|
81
|
-
- !ruby/object:Gem::Version
|
82
|
-
version: '3.5'
|
83
69
|
- !ruby/object:Gem::Dependency
|
84
70
|
name: rspec
|
85
71
|
requirement: !ruby/object:Gem::Requirement
|
@@ -150,6 +136,20 @@ dependencies:
|
|
150
136
|
- - "~>"
|
151
137
|
- !ruby/object:Gem::Version
|
152
138
|
version: '0.47'
|
139
|
+
- !ruby/object:Gem::Dependency
|
140
|
+
name: redcarpet
|
141
|
+
requirement: !ruby/object:Gem::Requirement
|
142
|
+
requirements:
|
143
|
+
- - "~>"
|
144
|
+
- !ruby/object:Gem::Version
|
145
|
+
version: '3.5'
|
146
|
+
type: :development
|
147
|
+
prerelease: false
|
148
|
+
version_requirements: !ruby/object:Gem::Requirement
|
149
|
+
requirements:
|
150
|
+
- - "~>"
|
151
|
+
- !ruby/object:Gem::Version
|
152
|
+
version: '3.5'
|
153
153
|
- !ruby/object:Gem::Dependency
|
154
154
|
name: yard
|
155
155
|
requirement: !ruby/object:Gem::Requirement
|
@@ -157,6 +157,9 @@ dependencies:
|
|
157
157
|
- - "~>"
|
158
158
|
- !ruby/object:Gem::Version
|
159
159
|
version: '0.9'
|
160
|
+
- - ">="
|
161
|
+
- !ruby/object:Gem::Version
|
162
|
+
version: 0.9.28
|
160
163
|
type: :development
|
161
164
|
prerelease: false
|
162
165
|
version_requirements: !ruby/object:Gem::Requirement
|
@@ -164,6 +167,9 @@ dependencies:
|
|
164
167
|
- - "~>"
|
165
168
|
- !ruby/object:Gem::Version
|
166
169
|
version: '0.9'
|
170
|
+
- - ">="
|
171
|
+
- !ruby/object:Gem::Version
|
172
|
+
version: 0.9.28
|
167
173
|
- !ruby/object:Gem::Dependency
|
168
174
|
name: yardstick
|
169
175
|
requirement: !ruby/object:Gem::Requirement
|
@@ -197,18 +203,16 @@ files:
|
|
197
203
|
- lib/process_executer.rb
|
198
204
|
- lib/process_executer/monitored_pipe.rb
|
199
205
|
- lib/process_executer/options.rb
|
200
|
-
- lib/process_executer/process.rb
|
201
|
-
- lib/process_executer/status.rb
|
202
206
|
- lib/process_executer/version.rb
|
203
207
|
- process_executer.gemspec
|
204
|
-
homepage: https://github.com/
|
208
|
+
homepage: https://github.com/main-branch/process_executer
|
205
209
|
licenses:
|
206
210
|
- MIT
|
207
211
|
metadata:
|
208
212
|
allowed_push_host: https://rubygems.org
|
209
|
-
homepage_uri: https://github.com/
|
210
|
-
source_code_uri: https://github.com/
|
211
|
-
changelog_uri: https://github.com/
|
213
|
+
homepage_uri: https://github.com/main-branch/process_executer
|
214
|
+
source_code_uri: https://github.com/main-branch/process_executer
|
215
|
+
changelog_uri: https://github.com/main-branch/process_executer
|
212
216
|
rubygems_mfa_required: 'true'
|
213
217
|
post_install_message:
|
214
218
|
rdoc_options: []
|
@@ -1,72 +0,0 @@
|
|
1
|
-
# frozen_string_literal: true
|
2
|
-
|
3
|
-
module ProcessExecuter
|
4
|
-
# Spawns a process and knows how to check if the process is terminated
|
5
|
-
#
|
6
|
-
# This class is not currently used in this Gem.
|
7
|
-
#
|
8
|
-
# @api public
|
9
|
-
#
|
10
|
-
class Process
|
11
|
-
# Spawns a new process using Process.spawn
|
12
|
-
#
|
13
|
-
# @example
|
14
|
-
# command = ['echo', 'hello world']
|
15
|
-
# options = { chdir: '/tmp' }
|
16
|
-
# process = ProcessExecuter::Process.new(*command, **options)
|
17
|
-
# process.pid # => 12345
|
18
|
-
# process.terminated? # => true
|
19
|
-
# process.status # => #<Process::Status: pid 12345 exit 0>
|
20
|
-
#
|
21
|
-
# @see https://ruby-doc.org/core/Process.html#method-c-spawn Process.spawn documentation
|
22
|
-
#
|
23
|
-
# @param command [Array] the command to execute
|
24
|
-
# @param spawn_options [Hash] the options to pass to Process.spawn
|
25
|
-
#
|
26
|
-
def initialize(*command, **spawn_options)
|
27
|
-
@pid = ::Process.spawn(*command, **spawn_options)
|
28
|
-
end
|
29
|
-
|
30
|
-
# @!attribute [r]
|
31
|
-
#
|
32
|
-
# The id of the process
|
33
|
-
#
|
34
|
-
# @example
|
35
|
-
# ProcessExecuter::Process.new('echo', 'hello world').pid # => 12345
|
36
|
-
#
|
37
|
-
# @return [Integer] The id of the process
|
38
|
-
#
|
39
|
-
attr_reader :pid
|
40
|
-
|
41
|
-
# @!attribute [r]
|
42
|
-
#
|
43
|
-
# The exit status of the process or `nil` if the process has not terminated
|
44
|
-
#
|
45
|
-
# @example
|
46
|
-
# ProcessExecuter::Process.new('echo', 'hello world').status # => #<Process::Status: pid 12345 exit 0>
|
47
|
-
#
|
48
|
-
# @return [::Process::Status, nil]
|
49
|
-
#
|
50
|
-
# The status is set only when `terminated?` is called and returns `true`.
|
51
|
-
#
|
52
|
-
attr_reader :status
|
53
|
-
|
54
|
-
# Return true if the process has terminated
|
55
|
-
#
|
56
|
-
# If the proces has terminated, `#status` is set to the exit status of the process.
|
57
|
-
#
|
58
|
-
# @example
|
59
|
-
# process = ProcessExecuter::Process.new('echo', 'hello world')
|
60
|
-
# sleep 1
|
61
|
-
# process.terminated? # => true
|
62
|
-
#
|
63
|
-
# @return [Boolean] true if the process has terminated
|
64
|
-
#
|
65
|
-
def terminated?
|
66
|
-
return true if @status
|
67
|
-
|
68
|
-
_pid, @status = ::Process.wait2(pid, ::Process::WNOHANG)
|
69
|
-
!@status.nil?
|
70
|
-
end
|
71
|
-
end
|
72
|
-
end
|
@@ -1,345 +0,0 @@
|
|
1
|
-
# frozen_string_literal: true
|
2
|
-
|
3
|
-
module ProcessExecuter
|
4
|
-
# A replacement for Process::Status that can be used to mock the exit status of a process
|
5
|
-
#
|
6
|
-
# This class is not currently used in this Gem.
|
7
|
-
#
|
8
|
-
# Process::Status encapsulates the information on the status of a running or
|
9
|
-
# terminated system process. The built-in variable $? is either nil or a
|
10
|
-
# Process::Status object.
|
11
|
-
#
|
12
|
-
# ```ruby
|
13
|
-
# fork { exit 99 } #=> 26557
|
14
|
-
# Process.wait #=> 26557
|
15
|
-
# $?.class #=> Process::Status
|
16
|
-
# $?.to_i #=> 25344
|
17
|
-
# $? >> 8 #=> 99
|
18
|
-
# $?.stopped? #=> false
|
19
|
-
# $?.exited? #=> true
|
20
|
-
# $?.exitstatus #=> 99
|
21
|
-
# ```
|
22
|
-
#
|
23
|
-
# Posix systems record information on processes using a 16-bit integer. The
|
24
|
-
# lower bits record the process status (stopped, exited, signaled) and the
|
25
|
-
# upper bits possibly contain additional information (for example the program's
|
26
|
-
# return code in the case of exited processes). Pre Ruby 1.8, these bits were
|
27
|
-
# exposed directly to the Ruby program. Ruby now encapsulates these in a
|
28
|
-
# Process::Status object. To maximize compatibility, however, these objects
|
29
|
-
# retain a bit-oriented interface. In the descriptions that follow, when we
|
30
|
-
# talk about the integer value of stat, we're referring to this 16 bit value.
|
31
|
-
#
|
32
|
-
# @api public
|
33
|
-
#
|
34
|
-
class Status
|
35
|
-
# Create a new Status object
|
36
|
-
#
|
37
|
-
# @example
|
38
|
-
# status = ProcessExecuter::Status.new(999, 0)
|
39
|
-
# status.exited? # => true
|
40
|
-
# status.success? # => true
|
41
|
-
# status.exitstatus # => 0
|
42
|
-
#
|
43
|
-
def initialize(pid, stat)
|
44
|
-
@pid = pid
|
45
|
-
@stat = stat
|
46
|
-
end
|
47
|
-
|
48
|
-
# @!attribute
|
49
|
-
#
|
50
|
-
# The pid of the process
|
51
|
-
#
|
52
|
-
# @example
|
53
|
-
# status = ProcessExecuter::Status.new(999, 0)
|
54
|
-
# status.pid # => 999
|
55
|
-
#
|
56
|
-
# @return [Integer]
|
57
|
-
#
|
58
|
-
# @api public
|
59
|
-
#
|
60
|
-
attr_reader :pid
|
61
|
-
|
62
|
-
# @!attribute
|
63
|
-
#
|
64
|
-
# The status code of the process
|
65
|
-
#
|
66
|
-
# @example
|
67
|
-
# status = ProcessExecuter::Status.new(999, 123)
|
68
|
-
# status.stat # => 123
|
69
|
-
#
|
70
|
-
# @return [Integer]
|
71
|
-
#
|
72
|
-
# @api public
|
73
|
-
#
|
74
|
-
attr_reader :stat
|
75
|
-
|
76
|
-
# Logical AND of the bits in stat with `other`
|
77
|
-
#
|
78
|
-
# @example Process ended due to an uncaught signal 11 with a core dump
|
79
|
-
# status = ProcessExecuter::Status.new(999, 139)
|
80
|
-
# status & 127 # => 11 => the uncaught signal
|
81
|
-
# !(status & 128).zero? # => true => indicating a core dump
|
82
|
-
#
|
83
|
-
# @param other [Integer] the value to AND with stat
|
84
|
-
#
|
85
|
-
# @return [Integer] the result of the AND operation
|
86
|
-
#
|
87
|
-
def &(other)
|
88
|
-
stat & other
|
89
|
-
end
|
90
|
-
|
91
|
-
# Compare stat to `other`
|
92
|
-
#
|
93
|
-
# @example Process exited normally with exitstatus 99
|
94
|
-
# status = ProcessExecuter::Status.new(999, 25_344)
|
95
|
-
# status == 25_344 # => true
|
96
|
-
#
|
97
|
-
# @param other [Integer] the value to compare stat to
|
98
|
-
#
|
99
|
-
# @return [Boolean] true if stat == other, false otherwise
|
100
|
-
#
|
101
|
-
def ==(other)
|
102
|
-
stat == other
|
103
|
-
end
|
104
|
-
|
105
|
-
# rubocop:disable Naming/BinaryOperatorParameterName
|
106
|
-
|
107
|
-
# Shift the bits in stat right `num` places
|
108
|
-
#
|
109
|
-
# @example Process exited normally with exitstatus 99
|
110
|
-
# status = ProcessExecuter::Status.new(999, 25_344)
|
111
|
-
# status >> 8 # => 99
|
112
|
-
#
|
113
|
-
# @param num [Integer] the number of places to shift stat
|
114
|
-
#
|
115
|
-
# @return [Integer] the result of the shift operation
|
116
|
-
#
|
117
|
-
def >>(num)
|
118
|
-
stat >> num
|
119
|
-
end
|
120
|
-
|
121
|
-
# rubocop:enable Naming/BinaryOperatorParameterName
|
122
|
-
|
123
|
-
# Returns true if the process generated a coredump upon termination
|
124
|
-
#
|
125
|
-
# Not available on all platforms.
|
126
|
-
#
|
127
|
-
# @example process exited normally with exitstatus 99
|
128
|
-
# status = ProcessExecuter::Status.new(999, 25_344)
|
129
|
-
# status.coredump? # => false
|
130
|
-
#
|
131
|
-
# @example process ended due to an uncaught signal 11 with a core dump
|
132
|
-
# status = ProcessExecuter::Status.new(999, 139)
|
133
|
-
# status.coredump? # => true
|
134
|
-
#
|
135
|
-
# @return [Boolean] true if stat generated a coredump when it terminated
|
136
|
-
#
|
137
|
-
def coredump?
|
138
|
-
!(stat & 128).zero?
|
139
|
-
end
|
140
|
-
|
141
|
-
# Returns true if the process exited normally
|
142
|
-
#
|
143
|
-
# This happens when the process uses an exit() call or runs to the end of the program.
|
144
|
-
#
|
145
|
-
# @example process exited normally with exitstatus 0
|
146
|
-
# status = ProcessExecuter::Status.new(999, 0)
|
147
|
-
# status.exited? # => true
|
148
|
-
#
|
149
|
-
# @example process exited normally with exitstatus 99
|
150
|
-
# status = ProcessExecuter::Status.new(999, 25_344)
|
151
|
-
# status.exited? # => true
|
152
|
-
#
|
153
|
-
# @example process ended due to an uncaught signal 11 with a core dump
|
154
|
-
# status = ProcessExecuter::Status.new(999, 139)
|
155
|
-
# status.exited? # => false
|
156
|
-
#
|
157
|
-
# @return [Boolean] true if the process exited normally
|
158
|
-
#
|
159
|
-
def exited?
|
160
|
-
(stat & 127).zero?
|
161
|
-
end
|
162
|
-
|
163
|
-
# Returns the exit status of the process
|
164
|
-
#
|
165
|
-
# Returns nil if the process did not exit normally (when `#exited?` is false).
|
166
|
-
#
|
167
|
-
# @example process exited normally with exitstatus 99
|
168
|
-
# status = ProcessExecuter::Status.new(999, 25_344)
|
169
|
-
# status.exitstatus # => 99
|
170
|
-
#
|
171
|
-
# @return [Integer, nil] the exit status of the process
|
172
|
-
#
|
173
|
-
def exitstatus
|
174
|
-
stat >> 8 if exited?
|
175
|
-
end
|
176
|
-
|
177
|
-
# Returns true if the process was successful
|
178
|
-
#
|
179
|
-
# This means that `exited?` is true and `#exitstatus` is 0.
|
180
|
-
#
|
181
|
-
# Returns nil if the process did not exit normally (when `#exited?` is false).
|
182
|
-
#
|
183
|
-
# @example process exited normally with exitstatus 0
|
184
|
-
# status = ProcessExecuter::Status.new(999, 0)
|
185
|
-
# status.success? # => true
|
186
|
-
#
|
187
|
-
# @example process exited normally with exitstatus 99
|
188
|
-
# status = ProcessExecuter::Status.new(999, 25_344)
|
189
|
-
# status.success? # => false
|
190
|
-
#
|
191
|
-
# @example process ended due to an uncaught signal 11 with a core dump
|
192
|
-
# status = ProcessExecuter::Status.new(999, 139)
|
193
|
-
# status.success? # => nil
|
194
|
-
#
|
195
|
-
# @return [Boolean, nil] true if successful, false if unsuccessful, nil if the process did not exit normally
|
196
|
-
#
|
197
|
-
def success?
|
198
|
-
exitstatus.zero? if exited?
|
199
|
-
end
|
200
|
-
|
201
|
-
# Returns true if the process was stopped
|
202
|
-
#
|
203
|
-
# @example with a stopped process with signal 17
|
204
|
-
# status = ProcessExecuter::Status.new(999, 4_479)
|
205
|
-
# status.stopped? # => true
|
206
|
-
#
|
207
|
-
# @example process exited normally with exitstatus 99
|
208
|
-
# status = ProcessExecuter::Status.new(999, 25_344)
|
209
|
-
# status.stopped? # => false
|
210
|
-
#
|
211
|
-
# @example process ended due to an uncaught signal 11 with a core dump
|
212
|
-
# status = ProcessExecuter::Status.new(999, 139)
|
213
|
-
# status.stopped? # => false
|
214
|
-
#
|
215
|
-
# @return [Boolean] true if the process was stopped, false otherwise
|
216
|
-
#
|
217
|
-
def stopped?
|
218
|
-
(stat & 127) == 127
|
219
|
-
end
|
220
|
-
|
221
|
-
# The signal number that casused the process to stop
|
222
|
-
#
|
223
|
-
# Returns nil if the process is not stopped.
|
224
|
-
#
|
225
|
-
# @example with a stopped process with signal 17
|
226
|
-
# status = ProcessExecuter::Status.new(999, 4_479)
|
227
|
-
# status.stopsig # => 17
|
228
|
-
#
|
229
|
-
# @example process exited normally with exitstatus 99
|
230
|
-
# status = ProcessExecuter::Status.new(999, 25_344)
|
231
|
-
# status.stopsig # => nil
|
232
|
-
#
|
233
|
-
# @return [Integer, nil] the signal number that caused the process to stop or nil
|
234
|
-
#
|
235
|
-
def stopsig
|
236
|
-
stat >> 8 if stopped?
|
237
|
-
end
|
238
|
-
|
239
|
-
# Returns true if stat terminated because of an uncaught signal
|
240
|
-
#
|
241
|
-
# @example process ended due to an uncaught signal 9
|
242
|
-
# status = ProcessExecuter::Status.new(999, 9)
|
243
|
-
# status.signaled? # => true
|
244
|
-
#
|
245
|
-
# @example process exited normally with exitstatus 0
|
246
|
-
# status = ProcessExecuter::Status.new(999, 0)
|
247
|
-
# status.signaled? # => false
|
248
|
-
#
|
249
|
-
# @return [Boolean] true if stat terminated because of an uncaught signal, false otherwise
|
250
|
-
#
|
251
|
-
def signaled?
|
252
|
-
![0, 127].include?(stat & 127)
|
253
|
-
end
|
254
|
-
|
255
|
-
# Returns the number of the signal that caused the process to terminate
|
256
|
-
#
|
257
|
-
# Returns nil if the process exited normally or is stopped.
|
258
|
-
#
|
259
|
-
# @example process ended due to an uncaught signal 9
|
260
|
-
# status = ProcessExecuter::Status.new(999, 9)
|
261
|
-
# status.termsig # => 9
|
262
|
-
#
|
263
|
-
# @example process exited normally with exitstatus 0
|
264
|
-
# status = ProcessExecuter::Status.new(999, 0)
|
265
|
-
# status.termsig # => nil
|
266
|
-
#
|
267
|
-
# @return [Integer, nil] the signal number that caused the process to terminate or nil
|
268
|
-
#
|
269
|
-
def termsig
|
270
|
-
stat & 127 if signaled?
|
271
|
-
end
|
272
|
-
|
273
|
-
# Returns the bits in stat as an Integer
|
274
|
-
#
|
275
|
-
# @example with a stopped process with signal 17
|
276
|
-
# status = ProcessExecuter::Status.new(999, 4_479)
|
277
|
-
# status.to_i # => 4_479
|
278
|
-
#
|
279
|
-
# @return [Integer] the bits in stat
|
280
|
-
#
|
281
|
-
def to_i
|
282
|
-
stat
|
283
|
-
end
|
284
|
-
|
285
|
-
# Show the status type, pid, and exit status as a string
|
286
|
-
#
|
287
|
-
# @example with a stopped process with signal 17
|
288
|
-
# status = ProcessExecuter::Status.new(999, 4_479)
|
289
|
-
# status.to_s # => "pid 999 stopped SIGSTOP (signal 17)"
|
290
|
-
#
|
291
|
-
# @return [String] the status type, pid, and exit status as a string
|
292
|
-
#
|
293
|
-
def to_s
|
294
|
-
type_to_s + (coredump? ? ' (core dumped)' : '')
|
295
|
-
end
|
296
|
-
|
297
|
-
# Show the status type, pid, and exit status as a string
|
298
|
-
#
|
299
|
-
# @example with a stopped process with signal 17
|
300
|
-
# status = ProcessExecuter::Status.new(999, 4_479)
|
301
|
-
# status.inspect # => "#<ProcessExecuter::Status pid 999 stopped SIGSTOP (signal 17)>"
|
302
|
-
#
|
303
|
-
# @return [String] the status type, pid, and exit status as a string
|
304
|
-
#
|
305
|
-
def inspect
|
306
|
-
"#<#{self.class} #{self}>"
|
307
|
-
end
|
308
|
-
|
309
|
-
private
|
310
|
-
|
311
|
-
# The string representation of a status based on how it was terminated
|
312
|
-
# @return [String] the string representation
|
313
|
-
# @api private
|
314
|
-
def type_to_s
|
315
|
-
if signaled?
|
316
|
-
signaled_to_s
|
317
|
-
elsif exited?
|
318
|
-
exited_to_s
|
319
|
-
elsif stopped?
|
320
|
-
stopped_to_s
|
321
|
-
end
|
322
|
-
end
|
323
|
-
|
324
|
-
# The string representation of a signaled process
|
325
|
-
# @return [String] the string representation of a signaled process
|
326
|
-
# @api private
|
327
|
-
def signaled_to_s
|
328
|
-
"pid #{pid} SIG#{Signal.signame(termsig)} (signal #{termsig})"
|
329
|
-
end
|
330
|
-
|
331
|
-
# The string representation of an exited process
|
332
|
-
# @return [String] the string representation of an exited process
|
333
|
-
# @api private
|
334
|
-
def exited_to_s
|
335
|
-
"pid #{pid} exit #{exitstatus}"
|
336
|
-
end
|
337
|
-
|
338
|
-
# The string representation of a stopped process
|
339
|
-
# @return [String] the string representation of a stopped process
|
340
|
-
# @api private
|
341
|
-
def stopped_to_s
|
342
|
-
"pid #{pid} stopped SIG#{Signal.signame(stopsig)} (signal #{stopsig})"
|
343
|
-
end
|
344
|
-
end
|
345
|
-
end
|