childprocess 0.8.0 → 2.0.0

Sign up to get free protection for your applications and to get access to all the features.
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