childprocess 0.8.0 → 2.0.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.
Files changed (51) hide show
  1. checksums.yaml +5 -5
  2. data/.document +6 -6
  3. data/.gitignore +28 -28
  4. data/.rspec +1 -1
  5. data/.travis.yml +42 -36
  6. data/CHANGELOG.md +67 -44
  7. data/Gemfile +18 -15
  8. data/LICENSE +20 -20
  9. data/README.md +216 -192
  10. data/Rakefile +61 -61
  11. data/appveyor.yml +42 -43
  12. data/childprocess.gemspec +32 -30
  13. data/ext/mkrf_conf.rb +24 -0
  14. data/lib/childprocess/abstract_io.rb +36 -36
  15. data/lib/childprocess/abstract_process.rb +192 -192
  16. data/lib/childprocess/errors.rb +37 -26
  17. data/lib/childprocess/jruby/io.rb +16 -16
  18. data/lib/childprocess/jruby/process.rb +184 -159
  19. data/lib/childprocess/jruby/pump.rb +53 -53
  20. data/lib/childprocess/jruby.rb +56 -56
  21. data/lib/childprocess/tools/generator.rb +145 -145
  22. data/lib/childprocess/unix/fork_exec_process.rb +78 -70
  23. data/lib/childprocess/unix/io.rb +21 -21
  24. data/lib/childprocess/unix/lib.rb +186 -186
  25. data/lib/childprocess/unix/platform/i386-linux.rb +12 -12
  26. data/lib/childprocess/unix/platform/i386-solaris.rb +11 -11
  27. data/lib/childprocess/unix/platform/x86_64-linux.rb +12 -12
  28. data/lib/childprocess/unix/platform/x86_64-macosx.rb +11 -11
  29. data/lib/childprocess/unix/posix_spawn_process.rb +134 -134
  30. data/lib/childprocess/unix/process.rb +90 -89
  31. data/lib/childprocess/unix.rb +9 -9
  32. data/lib/childprocess/version.rb +3 -3
  33. data/lib/childprocess/windows/handle.rb +91 -91
  34. data/lib/childprocess/windows/io.rb +25 -25
  35. data/lib/childprocess/windows/lib.rb +416 -416
  36. data/lib/childprocess/windows/process.rb +130 -130
  37. data/lib/childprocess/windows/process_builder.rb +178 -175
  38. data/lib/childprocess/windows/structs.rb +148 -148
  39. data/lib/childprocess/windows.rb +33 -33
  40. data/lib/childprocess.rb +210 -205
  41. data/spec/abstract_io_spec.rb +12 -12
  42. data/spec/childprocess_spec.rb +447 -391
  43. data/spec/get_env.ps1 +13 -0
  44. data/spec/io_spec.rb +228 -228
  45. data/spec/jruby_spec.rb +24 -24
  46. data/spec/pid_behavior.rb +12 -12
  47. data/spec/platform_detection_spec.rb +86 -86
  48. data/spec/spec_helper.rb +270 -261
  49. data/spec/unix_spec.rb +57 -57
  50. data/spec/windows_spec.rb +23 -23
  51. metadata +18 -33
data/spec/spec_helper.rb CHANGED
@@ -1,261 +1,270 @@
1
- $LOAD_PATH.unshift(File.dirname(__FILE__))
2
- $LOAD_PATH.unshift(File.join(File.dirname(__FILE__), '..', 'lib'))
3
-
4
- unless defined?(JRUBY_VERSION)
5
- require 'coveralls'
6
- Coveralls.wear!
7
- end
8
-
9
- require 'childprocess'
10
- require 'rspec'
11
- require 'tempfile'
12
- require 'socket'
13
- require 'stringio'
14
- require 'ostruct'
15
-
16
- module ChildProcessSpecHelper
17
- RUBY = defined?(Gem) ? Gem.ruby : 'ruby'
18
-
19
- def ruby_process(*args)
20
- @process = ChildProcess.build(RUBY , *args)
21
- end
22
-
23
- def sleeping_ruby(seconds = nil)
24
- if seconds
25
- ruby_process("-e", "sleep #{seconds}")
26
- else
27
- ruby_process("-e", "sleep")
28
- end
29
- end
30
-
31
- def invalid_process
32
- @process = ChildProcess.build("unlikelytoexist")
33
- end
34
-
35
- def ignored(signal)
36
- code = <<-RUBY
37
- trap(#{signal.inspect}, "IGNORE")
38
- sleep
39
- RUBY
40
-
41
- ruby_process tmp_script(code)
42
- end
43
-
44
- def write_env(path)
45
- code = <<-RUBY
46
- File.open(#{path.inspect}, "w") { |f| f << ENV.inspect }
47
- RUBY
48
-
49
- ruby_process tmp_script(code)
50
- end
51
-
52
- def write_argv(path, *args)
53
- code = <<-RUBY
54
- File.open(#{path.inspect}, "w") { |f| f << ARGV.inspect }
55
- RUBY
56
-
57
- ruby_process(tmp_script(code), *args)
58
- end
59
-
60
- def write_pid(path)
61
- code = <<-RUBY
62
- File.open(#{path.inspect}, "w") { |f| f << Process.pid }
63
- RUBY
64
-
65
- ruby_process tmp_script(code)
66
- end
67
-
68
- def write_pid_in_sleepy_grand_child(path)
69
- code = <<-RUBY
70
- system "ruby", "-e", 'File.open(#{path.inspect}, "w") { |f| f << Process.pid; f.flush }; sleep'
71
- RUBY
72
-
73
- ruby_process tmp_script(code)
74
- end
75
-
76
- def exit_with(exit_code)
77
- ruby_process(tmp_script("exit(#{exit_code})"))
78
- end
79
-
80
- def with_env(hash)
81
- hash.each { |k,v| ENV[k] = v }
82
- begin
83
- yield
84
- ensure
85
- hash.each_key { |k| ENV[k] = nil }
86
- end
87
- end
88
-
89
- def tmp_script(code)
90
- # use an ivar to avoid GC
91
- @tf = Tempfile.new("childprocess-temp")
92
- @tf << code
93
- @tf.close
94
-
95
- puts code if $DEBUG
96
-
97
- @tf.path
98
- end
99
-
100
- def cat
101
- if ChildProcess.os == :windows
102
- ruby(<<-CODE)
103
- STDIN.sync = STDOUT.sync = true
104
- IO.copy_stream(STDIN, STDOUT)
105
- CODE
106
- else
107
- ChildProcess.build("cat")
108
- end
109
- end
110
-
111
- def echo
112
- if ChildProcess.os == :windows
113
- ruby(<<-CODE)
114
- STDIN.sync = true
115
- STDOUT.sync = true
116
-
117
- puts "hello"
118
- CODE
119
- else
120
- ChildProcess.build("echo", "hello")
121
- end
122
- end
123
-
124
- def ruby(code)
125
- ruby_process(tmp_script(code))
126
- end
127
-
128
- def with_executable_at(path, &blk)
129
- if ChildProcess.os == :windows
130
- path << ".cmd"
131
- content = "#{RUBY} -e 'sleep 10' \n @echo foo"
132
- else
133
- content = "#!/bin/sh\nsleep 10\necho foo"
134
- end
135
-
136
- File.open(path, 'w', 0744) { |io| io << content }
137
- proc = ChildProcess.build(path)
138
-
139
- begin
140
- yield proc
141
- ensure
142
- proc.stop if proc.alive?
143
- File.delete path
144
- end
145
- end
146
-
147
- def exit_timeout
148
- 10
149
- end
150
-
151
- def random_free_port
152
- server = TCPServer.new('127.0.0.1', 0)
153
- port = server.addr[1]
154
- server.close
155
-
156
- port
157
- end
158
-
159
- def with_tmpdir(&blk)
160
- name = "#{Time.now.strftime("%Y%m%d")}-#{$$}-#{rand(0x100000000).to_s(36)}"
161
- FileUtils.mkdir_p(name)
162
-
163
- begin
164
- yield File.expand_path(name)
165
- ensure
166
- FileUtils.rm_rf name
167
- end
168
- end
169
-
170
- def wait_until(timeout = 10, &blk)
171
- end_time = Time.now + timeout
172
- last_exception = nil
173
-
174
- until Time.now >= end_time
175
- begin
176
- result = yield
177
- return result if result
178
- rescue RSpec::Expectations::ExpectationNotMetError => ex
179
- last_exception = ex
180
- end
181
-
182
- sleep 0.01
183
- end
184
-
185
- msg = "timed out after #{timeout} seconds"
186
- msg << ":\n#{last_exception.message}" if last_exception
187
-
188
- raise msg
189
- end
190
-
191
- def can_bind?(host, port)
192
- TCPServer.new(host, port).close
193
- true
194
- rescue
195
- false
196
- end
197
-
198
- def rewind_and_read(io)
199
- io.rewind
200
- io.read
201
- end
202
-
203
- def alive?(pid)
204
- if ChildProcess.windows?
205
- ChildProcess::Windows::Lib.alive?(pid)
206
- else
207
- begin
208
- Process.getpgid pid
209
- true
210
- rescue Errno::ESRCH
211
- false
212
- end
213
- end
214
- end
215
-
216
- def capture_std
217
- orig_out = STDOUT.clone
218
- orig_err = STDERR.clone
219
-
220
- out = Tempfile.new 'captured-stdout'
221
- err = Tempfile.new 'captured-stderr'
222
- out.sync = true
223
- err.sync = true
224
-
225
- STDOUT.reopen out
226
- STDERR.reopen err
227
-
228
- yield
229
-
230
- OpenStruct.new stdout: rewind_and_read(out), stderr: rewind_and_read(err)
231
- ensure
232
- STDOUT.reopen orig_out
233
- STDERR.reopen orig_err
234
- end
235
-
236
- def generate_log_messages
237
- ChildProcess.logger.level = Logger::DEBUG
238
-
239
- process = exit_with(0).start
240
- process.wait
241
- process.poll_for_exit(0.1)
242
- end
243
-
244
- end # ChildProcessSpecHelper
245
-
246
- Thread.abort_on_exception = true
247
-
248
- RSpec.configure do |c|
249
- c.include(ChildProcessSpecHelper)
250
- c.after(:each) {
251
- defined?(@process) && @process.alive? && @process.stop
252
- }
253
-
254
- if ChildProcess.jruby? && ChildProcess.new("true").instance_of?(ChildProcess::JRuby::Process)
255
- c.filter_run_excluding :process_builder => false
256
- end
257
-
258
- if ChildProcess.linux? && ChildProcess.posix_spawn?
259
- c.filter_run_excluding :posix_spawn_on_linux => false
260
- end
261
- end
1
+ $LOAD_PATH.unshift(File.dirname(__FILE__))
2
+ $LOAD_PATH.unshift(File.join(File.dirname(__FILE__), '..', 'lib'))
3
+
4
+ unless defined?(JRUBY_VERSION)
5
+ require 'coveralls'
6
+ Coveralls.wear!
7
+ end
8
+
9
+ require 'childprocess'
10
+ require 'rspec'
11
+ require 'tempfile'
12
+ require 'socket'
13
+ require 'stringio'
14
+ require 'ostruct'
15
+
16
+ module ChildProcessSpecHelper
17
+ RUBY = defined?(Gem) ? Gem.ruby : 'ruby'
18
+
19
+ def ruby_process(*args)
20
+ @process = ChildProcess.build(RUBY , *args)
21
+ end
22
+
23
+ def windows_process(*args)
24
+ @process = ChildProcess.build("powershell", *args)
25
+ end
26
+
27
+ def sleeping_ruby(seconds = nil)
28
+ if seconds
29
+ ruby_process("-e", "sleep #{seconds}")
30
+ else
31
+ ruby_process("-e", "sleep")
32
+ end
33
+ end
34
+
35
+ def invalid_process
36
+ @process = ChildProcess.build("unlikelytoexist")
37
+ end
38
+
39
+ def ignored(signal)
40
+ code = <<-RUBY
41
+ trap(#{signal.inspect}, "IGNORE")
42
+ sleep
43
+ RUBY
44
+
45
+ ruby_process tmp_script(code)
46
+ end
47
+
48
+ def write_env(path)
49
+ if ChildProcess.os == :windows
50
+ ps_env_file_path = File.expand_path(File.dirname(__FILE__))
51
+ args = ['-File', "#{ps_env_file_path}/get_env.ps1", path]
52
+ windows_process(*args)
53
+ else
54
+ code = <<-RUBY
55
+ File.open(#{path.inspect}, "w") { |f| f << ENV.inspect }
56
+ RUBY
57
+ ruby_process tmp_script(code)
58
+ end
59
+ end
60
+
61
+ def write_argv(path, *args)
62
+ code = <<-RUBY
63
+ File.open(#{path.inspect}, "w") { |f| f << ARGV.inspect }
64
+ RUBY
65
+
66
+ ruby_process(tmp_script(code), *args)
67
+ end
68
+
69
+ def write_pid(path)
70
+ code = <<-RUBY
71
+ File.open(#{path.inspect}, "w") { |f| f << Process.pid }
72
+ RUBY
73
+
74
+ ruby_process tmp_script(code)
75
+ end
76
+
77
+ def write_pid_in_sleepy_grand_child(path)
78
+ code = <<-RUBY
79
+ system "ruby", "-e", 'File.open(#{path.inspect}, "w") { |f| f << Process.pid; f.flush }; sleep'
80
+ RUBY
81
+
82
+ ruby_process tmp_script(code)
83
+ end
84
+
85
+ def exit_with(exit_code)
86
+ ruby_process(tmp_script("exit(#{exit_code})"))
87
+ end
88
+
89
+ def with_env(hash)
90
+ hash.each { |k,v| ENV[k] = v }
91
+ begin
92
+ yield
93
+ ensure
94
+ hash.each_key { |k| ENV[k] = nil }
95
+ end
96
+ end
97
+
98
+ def tmp_script(code)
99
+ # use an ivar to avoid GC
100
+ @tf = Tempfile.new("childprocess-temp")
101
+ @tf << code
102
+ @tf.close
103
+
104
+ puts code if $DEBUG
105
+
106
+ @tf.path
107
+ end
108
+
109
+ def cat
110
+ if ChildProcess.os == :windows
111
+ ruby(<<-CODE)
112
+ STDIN.sync = STDOUT.sync = true
113
+ IO.copy_stream(STDIN, STDOUT)
114
+ CODE
115
+ else
116
+ ChildProcess.build("cat")
117
+ end
118
+ end
119
+
120
+ def echo
121
+ if ChildProcess.os == :windows
122
+ ruby(<<-CODE)
123
+ STDIN.sync = true
124
+ STDOUT.sync = true
125
+
126
+ puts "hello"
127
+ CODE
128
+ else
129
+ ChildProcess.build("echo", "hello")
130
+ end
131
+ end
132
+
133
+ def ruby(code)
134
+ ruby_process(tmp_script(code))
135
+ end
136
+
137
+ def with_executable_at(path, &blk)
138
+ if ChildProcess.os == :windows
139
+ path << ".cmd"
140
+ content = "#{RUBY} -e 'sleep 10' \n @echo foo"
141
+ else
142
+ content = "#!/bin/sh\nsleep 10\necho foo"
143
+ end
144
+
145
+ File.open(path, 'w', 0744) { |io| io << content }
146
+ proc = ChildProcess.build(path)
147
+
148
+ begin
149
+ yield proc
150
+ ensure
151
+ proc.stop if proc.alive?
152
+ File.delete path
153
+ end
154
+ end
155
+
156
+ def exit_timeout
157
+ 10
158
+ end
159
+
160
+ def random_free_port
161
+ server = TCPServer.new('127.0.0.1', 0)
162
+ port = server.addr[1]
163
+ server.close
164
+
165
+ port
166
+ end
167
+
168
+ def with_tmpdir(&blk)
169
+ name = "#{Time.now.strftime("%Y%m%d")}-#{$$}-#{rand(0x100000000).to_s(36)}"
170
+ FileUtils.mkdir_p(name)
171
+
172
+ begin
173
+ yield File.expand_path(name)
174
+ ensure
175
+ FileUtils.rm_rf name
176
+ end
177
+ end
178
+
179
+ def wait_until(timeout = 10, &blk)
180
+ end_time = Time.now + timeout
181
+ last_exception = nil
182
+
183
+ until Time.now >= end_time
184
+ begin
185
+ result = yield
186
+ return result if result
187
+ rescue RSpec::Expectations::ExpectationNotMetError => ex
188
+ last_exception = ex
189
+ end
190
+
191
+ sleep 0.01
192
+ end
193
+
194
+ msg = "timed out after #{timeout} seconds"
195
+ msg << ":\n#{last_exception.message}" if last_exception
196
+
197
+ raise msg
198
+ end
199
+
200
+ def can_bind?(host, port)
201
+ TCPServer.new(host, port).close
202
+ true
203
+ rescue
204
+ false
205
+ end
206
+
207
+ def rewind_and_read(io)
208
+ io.rewind
209
+ io.read
210
+ end
211
+
212
+ def alive?(pid)
213
+ if ChildProcess.windows?
214
+ ChildProcess::Windows::Lib.alive?(pid)
215
+ else
216
+ begin
217
+ Process.getpgid pid
218
+ true
219
+ rescue Errno::ESRCH
220
+ false
221
+ end
222
+ end
223
+ end
224
+
225
+ def capture_std
226
+ orig_out = STDOUT.clone
227
+ orig_err = STDERR.clone
228
+
229
+ out = Tempfile.new 'captured-stdout'
230
+ err = Tempfile.new 'captured-stderr'
231
+ out.sync = true
232
+ err.sync = true
233
+
234
+ STDOUT.reopen out
235
+ STDERR.reopen err
236
+
237
+ yield
238
+
239
+ OpenStruct.new stdout: rewind_and_read(out), stderr: rewind_and_read(err)
240
+ ensure
241
+ STDOUT.reopen orig_out
242
+ STDERR.reopen orig_err
243
+ end
244
+
245
+ def generate_log_messages
246
+ ChildProcess.logger.level = Logger::DEBUG
247
+
248
+ process = exit_with(0).start
249
+ process.wait
250
+ process.poll_for_exit(0.1)
251
+ end
252
+
253
+ end # ChildProcessSpecHelper
254
+
255
+ Thread.abort_on_exception = true
256
+
257
+ RSpec.configure do |c|
258
+ c.include(ChildProcessSpecHelper)
259
+ c.after(:each) {
260
+ defined?(@process) && @process.alive? && @process.stop
261
+ }
262
+
263
+ if ChildProcess.jruby? && ChildProcess.new("true").instance_of?(ChildProcess::JRuby::Process)
264
+ c.filter_run_excluding :process_builder => false
265
+ end
266
+
267
+ if ChildProcess.linux? && ChildProcess.posix_spawn?
268
+ c.filter_run_excluding :posix_spawn_on_linux => false
269
+ end
270
+ end
data/spec/unix_spec.rb CHANGED
@@ -1,57 +1,57 @@
1
- require File.expand_path('../spec_helper', __FILE__)
2
- require "pid_behavior"
3
-
4
- if ChildProcess.unix? && !ChildProcess.jruby? && !ChildProcess.posix_spawn?
5
-
6
- describe ChildProcess::Unix::Process do
7
- it_behaves_like "a platform that provides the child's pid"
8
-
9
- it "handles ECHILD race condition where process dies between timeout and KILL" do
10
- process = sleeping_ruby
11
-
12
- allow(process).to receive(:fork).and_return('fakepid')
13
- allow(process).to receive(:send_term)
14
- allow(process).to receive(:poll_for_exit).and_raise(ChildProcess::TimeoutError)
15
- allow(process).to receive(:send_kill).and_raise(Errno::ECHILD.new)
16
-
17
- process.start
18
- expect { process.stop }.not_to raise_error
19
-
20
- allow(process).to receive(:alive?).and_return(false)
21
-
22
- process.send(:send_signal, 'TERM')
23
- end
24
-
25
- it "handles ESRCH race condition where process dies between timeout and KILL" do
26
- process = sleeping_ruby
27
-
28
- allow(process).to receive(:fork).and_return('fakepid')
29
- allow(process).to receive(:send_term)
30
- allow(process).to receive(:poll_for_exit).and_raise(ChildProcess::TimeoutError)
31
- allow(process).to receive(:send_kill).and_raise(Errno::ESRCH.new)
32
-
33
- process.start
34
- expect { process.stop }.not_to raise_error
35
-
36
- allow(process).to receive(:alive?).and_return(false)
37
-
38
- process.send(:send_signal, 'TERM')
39
- end
40
- end
41
-
42
- describe ChildProcess::Unix::IO do
43
- let(:io) { ChildProcess::Unix::IO.new }
44
-
45
- it "raises an ArgumentError if given IO does not respond to :to_io" do
46
- expect { io.stdout = nil }.to raise_error(ArgumentError, /to respond to :to_io/)
47
- end
48
-
49
- it "raises a TypeError if #to_io does not return an IO" do
50
- fake_io = Object.new
51
- def fake_io.to_io() StringIO.new end
52
-
53
- expect { io.stdout = fake_io }.to raise_error(TypeError, /expected IO, got/)
54
- end
55
- end
56
-
57
- end
1
+ require File.expand_path('../spec_helper', __FILE__)
2
+ require "pid_behavior"
3
+
4
+ if ChildProcess.unix? && !ChildProcess.jruby? && !ChildProcess.posix_spawn?
5
+
6
+ describe ChildProcess::Unix::Process do
7
+ it_behaves_like "a platform that provides the child's pid"
8
+
9
+ it "handles ECHILD race condition where process dies between timeout and KILL" do
10
+ process = sleeping_ruby
11
+
12
+ allow(process).to receive(:fork).and_return('fakepid')
13
+ allow(process).to receive(:send_term)
14
+ allow(process).to receive(:poll_for_exit).and_raise(ChildProcess::TimeoutError)
15
+ allow(process).to receive(:send_kill).and_raise(Errno::ECHILD.new)
16
+
17
+ process.start
18
+ expect { process.stop }.not_to raise_error
19
+
20
+ allow(process).to receive(:alive?).and_return(false)
21
+
22
+ process.send(:send_signal, 'TERM')
23
+ end
24
+
25
+ it "handles ESRCH race condition where process dies between timeout and KILL" do
26
+ process = sleeping_ruby
27
+
28
+ allow(process).to receive(:fork).and_return('fakepid')
29
+ allow(process).to receive(:send_term)
30
+ allow(process).to receive(:poll_for_exit).and_raise(ChildProcess::TimeoutError)
31
+ allow(process).to receive(:send_kill).and_raise(Errno::ESRCH.new)
32
+
33
+ process.start
34
+ expect { process.stop }.not_to raise_error
35
+
36
+ allow(process).to receive(:alive?).and_return(false)
37
+
38
+ process.send(:send_signal, 'TERM')
39
+ end
40
+ end
41
+
42
+ describe ChildProcess::Unix::IO do
43
+ let(:io) { ChildProcess::Unix::IO.new }
44
+
45
+ it "raises an ArgumentError if given IO does not respond to :to_io" do
46
+ expect { io.stdout = nil }.to raise_error(ArgumentError, /to respond to :to_io/)
47
+ end
48
+
49
+ it "raises a TypeError if #to_io does not return an IO" do
50
+ fake_io = Object.new
51
+ def fake_io.to_io() StringIO.new end
52
+
53
+ expect { io.stdout = fake_io }.to raise_error(TypeError, /expected IO, got/)
54
+ end
55
+ end
56
+
57
+ end