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 CHANGED
@@ -1,7 +1,7 @@
1
1
  ---
2
2
  SHA256:
3
- metadata.gz: ae4d5881afe09475a15692bf7110746ff4dfd76a70958775351c6fb510a7fb2f
4
- data.tar.gz: 683508220b6137f2129bf094b823862ae155a7a5b418aced87dd0056bb0eda7f
3
+ metadata.gz: fc6b432be7a421bc53561149ce4718a41fd7ff61b1b2213498000891c388a0c9
4
+ data.tar.gz: ce582e04a3500b0b6c0e31af16d2d735e87f9c99c5d82279c591c9e5abc0a619
5
5
  SHA512:
6
- metadata.gz: 506898e021e1bf3b923d3900fb83005836e729daf064885c0d8172a35690fc9f7865d716197ce60cfb6d6a5153817bde385f6b5b38165296b7f1c213a7a6cfe3
7
- data.tar.gz: 3111bfbaf30068de540fb1463a8f862a1d55d6c26f3c47cc90776b2d111a3330601927d4c7e70dcc719ea7c6e60da447c6a2b9b6bddeb89749f44e106a71847e
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
- task default: %w[spec rubocop yard yard:audit yard:coverage bundle:audit build]
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
- # YARD
61
+ unless RUBY_PLATFORM == 'java'
62
+ # YARD
57
63
 
58
- require 'yard'
59
- YARD::Rake::YardocTask.new do |t|
60
- t.files = %w[lib/**/*.rb examples/**/*]
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
- sh "yardstick 'lib/**/*.rb'"
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
- verify.threshold = 100
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 the pipe is written one or more writers passed to
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
- @thread = Thread.new { monitor_pipe }
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
- # Kill the monitoring thread, read remaining data, and close the pipe
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
- thread.kill
76
- thread.join
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
- # Reads data from the pipe forever until the monitoring thread is killed
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 monitor_pipe
221
- loop do
222
- read_pipe_output if pipe_reader.wait_readable
223
- end
246
+ def monitor
247
+ monitor_pipe until state == :closing
248
+ close_pipe
249
+ @state = :closed
224
250
  end
225
251
 
226
- # Read a chunk of data from the pipe and write it to the writers
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 read_pipe_output
258
+ def monitor_pipe
230
259
  new_data = pipe_reader.read_nonblock(chunk_size)
231
- # puts "Received new data: #{new_data.inspect} from #{pipe_reader.inspect}"
260
+ rescue IO::WaitReadable
261
+ pipe_reader.wait_readable(0.01)
262
+ else
232
263
  writers.each { |w| w.write(new_data) }
233
- rescue EOFError, IO::EAGAINWaitReadable
234
- # No output to read at this time
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
@@ -1,5 +1,6 @@
1
1
  # frozen_string_literal: true
2
2
 
3
3
  module ProcessExecuter
4
- VERSION = '0.3.0'
4
+ # The current Gem version
5
+ VERSION = '0.4.0'
5
6
  end
@@ -2,8 +2,6 @@
2
2
 
3
3
  require 'process_executer/monitored_pipe'
4
4
  require 'process_executer/options'
5
- require 'process_executer/process'
6
- require 'process_executer/status'
7
5
 
8
6
  require 'timeout'
9
7
 
@@ -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/main_branch/process_executer'
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/main_branch/process_executer'
21
- spec.metadata['changelog_uri'] = 'https://github.com/main_branch/process_executer'
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
- spec.add_development_dependency 'yard', '~> 0.9'
47
- spec.add_development_dependency 'yardstick', '~> 0.9'
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.3.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-02 00:00:00.000000000 Z
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/main_branch/process_executer
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/main_branch/process_executer
210
- source_code_uri: https://github.com/main_branch/process_executer
211
- changelog_uri: https://github.com/main_branch/process_executer
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