childprocess 0.8.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 +7 -0
- data/.document +6 -0
- data/.gitignore +28 -0
- data/.rspec +1 -0
- data/.travis.yml +36 -0
- data/CHANGELOG.md +44 -0
- data/Gemfile +15 -0
- data/LICENSE +20 -0
- data/README.md +192 -0
- data/Rakefile +61 -0
- data/appveyor.yml +43 -0
- data/childprocess.gemspec +30 -0
- data/lib/childprocess.rb +205 -0
- data/lib/childprocess/abstract_io.rb +36 -0
- data/lib/childprocess/abstract_process.rb +192 -0
- data/lib/childprocess/errors.rb +26 -0
- data/lib/childprocess/jruby.rb +56 -0
- data/lib/childprocess/jruby/io.rb +16 -0
- data/lib/childprocess/jruby/process.rb +159 -0
- data/lib/childprocess/jruby/pump.rb +53 -0
- data/lib/childprocess/tools/generator.rb +146 -0
- data/lib/childprocess/unix.rb +9 -0
- data/lib/childprocess/unix/fork_exec_process.rb +70 -0
- data/lib/childprocess/unix/io.rb +21 -0
- data/lib/childprocess/unix/lib.rb +186 -0
- data/lib/childprocess/unix/platform/i386-linux.rb +12 -0
- data/lib/childprocess/unix/platform/i386-solaris.rb +11 -0
- data/lib/childprocess/unix/platform/x86_64-linux.rb +12 -0
- data/lib/childprocess/unix/platform/x86_64-macosx.rb +11 -0
- data/lib/childprocess/unix/posix_spawn_process.rb +134 -0
- data/lib/childprocess/unix/process.rb +89 -0
- data/lib/childprocess/version.rb +3 -0
- data/lib/childprocess/windows.rb +33 -0
- data/lib/childprocess/windows/handle.rb +91 -0
- data/lib/childprocess/windows/io.rb +25 -0
- data/lib/childprocess/windows/lib.rb +416 -0
- data/lib/childprocess/windows/process.rb +130 -0
- data/lib/childprocess/windows/process_builder.rb +175 -0
- data/lib/childprocess/windows/structs.rb +149 -0
- data/spec/abstract_io_spec.rb +12 -0
- data/spec/childprocess_spec.rb +391 -0
- data/spec/io_spec.rb +228 -0
- data/spec/jruby_spec.rb +24 -0
- data/spec/pid_behavior.rb +12 -0
- data/spec/platform_detection_spec.rb +86 -0
- data/spec/spec_helper.rb +261 -0
- data/spec/unix_spec.rb +57 -0
- data/spec/windows_spec.rb +23 -0
- metadata +179 -0
@@ -0,0 +1,12 @@
|
|
1
|
+
require File.expand_path('../spec_helper', __FILE__)
|
2
|
+
|
3
|
+
describe ChildProcess::AbstractIO do
|
4
|
+
let(:io) { ChildProcess::AbstractIO.new }
|
5
|
+
|
6
|
+
it "inherits the parent's IO streams" do
|
7
|
+
io.inherit!
|
8
|
+
|
9
|
+
expect(io.stdout).to eq STDOUT
|
10
|
+
expect(io.stderr).to eq STDERR
|
11
|
+
end
|
12
|
+
end
|
@@ -0,0 +1,391 @@
|
|
1
|
+
# encoding: utf-8
|
2
|
+
|
3
|
+
require File.expand_path('../spec_helper', __FILE__)
|
4
|
+
require 'rubygems/mock_gem_ui'
|
5
|
+
|
6
|
+
|
7
|
+
describe ChildProcess do
|
8
|
+
|
9
|
+
here = File.dirname(__FILE__)
|
10
|
+
|
11
|
+
let(:gemspec) { eval(File.read "#{here}/../childprocess.gemspec") }
|
12
|
+
|
13
|
+
it 'validates cleanly' do
|
14
|
+
mock_ui = Gem::MockGemUi.new
|
15
|
+
Gem::DefaultUserInteraction.use_ui(mock_ui) { gemspec.validate }
|
16
|
+
|
17
|
+
expect(mock_ui.error).to_not match(/warn/i)
|
18
|
+
end
|
19
|
+
|
20
|
+
|
21
|
+
it "returns self when started" do
|
22
|
+
process = sleeping_ruby
|
23
|
+
|
24
|
+
expect(process.start).to eq process
|
25
|
+
expect(process).to be_alive
|
26
|
+
end
|
27
|
+
|
28
|
+
# We can't detect failure to execve() when using posix_spawn() on Linux
|
29
|
+
# without waiting for the child to exit with code 127.
|
30
|
+
#
|
31
|
+
# See e.g. http://repo.or.cz/w/glibc.git/blob/669704fd:/sysdeps/posix/spawni.c#l34
|
32
|
+
#
|
33
|
+
# We could work around this by doing the PATH search ourselves, but not sure
|
34
|
+
# it's worth it.
|
35
|
+
it "raises ChildProcess::LaunchError if the process can't be started", :posix_spawn_on_linux => false do
|
36
|
+
expect { invalid_process.start }.to raise_error(ChildProcess::LaunchError)
|
37
|
+
end
|
38
|
+
|
39
|
+
it 'raises ArgumentError if given a non-string argument' do
|
40
|
+
expect { ChildProcess.build(nil, "unlikelytoexist") }.to raise_error(ArgumentError)
|
41
|
+
expect { ChildProcess.build("foo", 1) }.to raise_error(ArgumentError)
|
42
|
+
end
|
43
|
+
|
44
|
+
it "knows if the process crashed" do
|
45
|
+
process = exit_with(1).start
|
46
|
+
process.wait
|
47
|
+
|
48
|
+
expect(process).to be_crashed
|
49
|
+
end
|
50
|
+
|
51
|
+
it "knows if the process didn't crash" do
|
52
|
+
process = exit_with(0).start
|
53
|
+
process.wait
|
54
|
+
|
55
|
+
expect(process).to_not be_crashed
|
56
|
+
end
|
57
|
+
|
58
|
+
it "can wait for a process to finish" do
|
59
|
+
process = exit_with(0).start
|
60
|
+
return_value = process.wait
|
61
|
+
|
62
|
+
expect(process).to_not be_alive
|
63
|
+
expect(return_value).to eq 0
|
64
|
+
end
|
65
|
+
|
66
|
+
it 'ignores #wait if process already finished' do
|
67
|
+
process = exit_with(0).start
|
68
|
+
sleep 0.01 until process.exited?
|
69
|
+
|
70
|
+
expect(process.wait).to eql 0
|
71
|
+
end
|
72
|
+
|
73
|
+
it "escalates if TERM is ignored" do
|
74
|
+
process = ignored('TERM').start
|
75
|
+
process.stop
|
76
|
+
expect(process).to be_exited
|
77
|
+
end
|
78
|
+
|
79
|
+
it "accepts a timeout argument to #stop" do
|
80
|
+
process = sleeping_ruby.start
|
81
|
+
process.stop(exit_timeout)
|
82
|
+
end
|
83
|
+
|
84
|
+
it "lets child process inherit the environment of the current process" do
|
85
|
+
Tempfile.open("env-spec") do |file|
|
86
|
+
with_env('INHERITED' => 'yes') do
|
87
|
+
process = write_env(file.path).start
|
88
|
+
process.wait
|
89
|
+
end
|
90
|
+
|
91
|
+
child_env = eval rewind_and_read(file)
|
92
|
+
expect(child_env['INHERITED']).to eql 'yes'
|
93
|
+
end
|
94
|
+
end
|
95
|
+
|
96
|
+
it "can override env vars only for the current process" do
|
97
|
+
Tempfile.open("env-spec") do |file|
|
98
|
+
process = write_env(file.path)
|
99
|
+
process.environment['CHILD_ONLY'] = '1'
|
100
|
+
process.start
|
101
|
+
|
102
|
+
expect(ENV['CHILD_ONLY']).to be_nil
|
103
|
+
|
104
|
+
process.wait
|
105
|
+
|
106
|
+
child_env = eval rewind_and_read(file)
|
107
|
+
expect(child_env['CHILD_ONLY']).to eql '1'
|
108
|
+
end
|
109
|
+
end
|
110
|
+
|
111
|
+
it "inherits the parent's env vars also when some are overridden" do
|
112
|
+
Tempfile.open("env-spec") do |file|
|
113
|
+
with_env('INHERITED' => 'yes', 'CHILD_ONLY' => 'no') do
|
114
|
+
process = write_env(file.path)
|
115
|
+
process.environment['CHILD_ONLY'] = 'yes'
|
116
|
+
|
117
|
+
process.start
|
118
|
+
process.wait
|
119
|
+
|
120
|
+
child_env = eval rewind_and_read(file)
|
121
|
+
|
122
|
+
expect(child_env['INHERITED']).to eq 'yes'
|
123
|
+
expect(child_env['CHILD_ONLY']).to eq 'yes'
|
124
|
+
end
|
125
|
+
end
|
126
|
+
end
|
127
|
+
|
128
|
+
it "can unset env vars" do
|
129
|
+
Tempfile.open("env-spec") do |file|
|
130
|
+
ENV['CHILDPROCESS_UNSET'] = '1'
|
131
|
+
process = write_env(file.path)
|
132
|
+
process.environment['CHILDPROCESS_UNSET'] = nil
|
133
|
+
process.start
|
134
|
+
|
135
|
+
process.wait
|
136
|
+
|
137
|
+
child_env = eval rewind_and_read(file)
|
138
|
+
expect(child_env).to_not have_key('CHILDPROCESS_UNSET')
|
139
|
+
end
|
140
|
+
end
|
141
|
+
|
142
|
+
it 'does not see env vars unset in parent' do
|
143
|
+
Tempfile.open('env-spec') do |file|
|
144
|
+
ENV['CHILDPROCESS_UNSET'] = nil
|
145
|
+
process = write_env(file.path)
|
146
|
+
process.start
|
147
|
+
|
148
|
+
process.wait
|
149
|
+
|
150
|
+
child_env = eval rewind_and_read(file)
|
151
|
+
expect(child_env).to_not have_key('CHILDPROCESS_UNSET')
|
152
|
+
end
|
153
|
+
end
|
154
|
+
|
155
|
+
|
156
|
+
it "passes arguments to the child" do
|
157
|
+
args = ["foo", "bar"]
|
158
|
+
|
159
|
+
Tempfile.open("argv-spec") do |file|
|
160
|
+
process = write_argv(file.path, *args).start
|
161
|
+
process.wait
|
162
|
+
|
163
|
+
expect(rewind_and_read(file)).to eql args.inspect
|
164
|
+
end
|
165
|
+
end
|
166
|
+
|
167
|
+
it "lets a detached child live on" do
|
168
|
+
p_pid = nil
|
169
|
+
c_pid = nil
|
170
|
+
|
171
|
+
Tempfile.open('grandparent_out') do |gp_file|
|
172
|
+
# Create a parent and detached child process that will spit out their PID. Make sure that the child process lasts longer than the parent.
|
173
|
+
p_process = ruby("require 'childprocess' ; c_process = ChildProcess.build('ruby', '-e', 'puts \\\"Child PID: \#{Process.pid}\\\" ; sleep 5') ; c_process.io.inherit! ; c_process.detach = true ; c_process.start ; puts \"Child PID: \#{c_process.pid}\" ; puts \"Parent PID: \#{Process.pid}\"")
|
174
|
+
p_process.io.stdout = p_process.io.stderr = gp_file
|
175
|
+
|
176
|
+
# Let the parent process die
|
177
|
+
p_process.start
|
178
|
+
p_process.wait
|
179
|
+
|
180
|
+
|
181
|
+
# Gather parent and child PIDs
|
182
|
+
pids = rewind_and_read(gp_file).split("\n")
|
183
|
+
pids.collect! { |pid| pid[/\d+/].to_i }
|
184
|
+
c_pid, p_pid = pids
|
185
|
+
end
|
186
|
+
|
187
|
+
# Check that the parent process has dies but the child process is still alive
|
188
|
+
expect(alive?(p_pid)).to_not be true
|
189
|
+
expect(alive?(c_pid)).to be true
|
190
|
+
end
|
191
|
+
|
192
|
+
it "preserves Dir.pwd in the child" do
|
193
|
+
Tempfile.open("dir-spec-out") do |file|
|
194
|
+
process = ruby("print Dir.pwd")
|
195
|
+
process.io.stdout = process.io.stderr = file
|
196
|
+
|
197
|
+
expected_dir = nil
|
198
|
+
Dir.chdir(Dir.tmpdir) do
|
199
|
+
expected_dir = Dir.pwd
|
200
|
+
process.start
|
201
|
+
end
|
202
|
+
|
203
|
+
process.wait
|
204
|
+
|
205
|
+
expect(rewind_and_read(file)).to eq expected_dir
|
206
|
+
end
|
207
|
+
end
|
208
|
+
|
209
|
+
it "can handle whitespace, special characters and quotes in arguments" do
|
210
|
+
args = ["foo bar", 'foo\bar', "'i-am-quoted'", '"i am double quoted"']
|
211
|
+
|
212
|
+
Tempfile.open("argv-spec") do |file|
|
213
|
+
process = write_argv(file.path, *args).start
|
214
|
+
process.wait
|
215
|
+
|
216
|
+
expect(rewind_and_read(file)).to eq args.inspect
|
217
|
+
end
|
218
|
+
end
|
219
|
+
|
220
|
+
it 'handles whitespace in the executable name' do
|
221
|
+
path = File.expand_path('foo bar')
|
222
|
+
|
223
|
+
with_executable_at(path) do |proc|
|
224
|
+
expect(proc.start).to eq proc
|
225
|
+
expect(proc).to be_alive
|
226
|
+
end
|
227
|
+
end
|
228
|
+
|
229
|
+
it "times out when polling for exit" do
|
230
|
+
process = sleeping_ruby.start
|
231
|
+
expect { process.poll_for_exit(0.1) }.to raise_error(ChildProcess::TimeoutError)
|
232
|
+
end
|
233
|
+
|
234
|
+
it "can change working directory" do
|
235
|
+
process = ruby "print Dir.pwd"
|
236
|
+
|
237
|
+
with_tmpdir { |dir|
|
238
|
+
process.cwd = dir
|
239
|
+
|
240
|
+
orig_pwd = Dir.pwd
|
241
|
+
|
242
|
+
Tempfile.open('cwd') do |file|
|
243
|
+
process.io.stdout = file
|
244
|
+
|
245
|
+
process.start
|
246
|
+
process.wait
|
247
|
+
|
248
|
+
expect(rewind_and_read(file)).to eq dir
|
249
|
+
end
|
250
|
+
|
251
|
+
expect(Dir.pwd).to eq orig_pwd
|
252
|
+
}
|
253
|
+
end
|
254
|
+
|
255
|
+
it 'kills the full process tree', :process_builder => false do
|
256
|
+
Tempfile.open('kill-process-tree') do |file|
|
257
|
+
process = write_pid_in_sleepy_grand_child(file.path)
|
258
|
+
process.leader = true
|
259
|
+
process.start
|
260
|
+
|
261
|
+
pid = wait_until(30) do
|
262
|
+
Integer(rewind_and_read(file)) rescue nil
|
263
|
+
end
|
264
|
+
|
265
|
+
process.stop
|
266
|
+
wait_until(3) { expect(alive?(pid)).to eql(false) }
|
267
|
+
end
|
268
|
+
end
|
269
|
+
|
270
|
+
it 'releases the GIL while waiting for the process' do
|
271
|
+
time = Time.now
|
272
|
+
threads = []
|
273
|
+
|
274
|
+
threads << Thread.new { sleeping_ruby(1).start.wait }
|
275
|
+
threads << Thread.new(time) { expect(Time.now - time).to be < 0.5 }
|
276
|
+
|
277
|
+
threads.each { |t| t.join }
|
278
|
+
end
|
279
|
+
|
280
|
+
it 'can check if a detached child is alive' do
|
281
|
+
proc = ruby_process("-e", "sleep")
|
282
|
+
proc.detach = true
|
283
|
+
|
284
|
+
proc.start
|
285
|
+
|
286
|
+
expect(proc).to be_alive
|
287
|
+
proc.stop(0)
|
288
|
+
|
289
|
+
expect(proc).to be_exited
|
290
|
+
end
|
291
|
+
|
292
|
+
|
293
|
+
it 'has a logger' do
|
294
|
+
expect(ChildProcess).to respond_to(:logger)
|
295
|
+
end
|
296
|
+
|
297
|
+
it 'can change its logger' do
|
298
|
+
expect(ChildProcess).to respond_to(:logger=)
|
299
|
+
|
300
|
+
original_logger = ChildProcess.logger
|
301
|
+
begin
|
302
|
+
ChildProcess.logger = :some_other_logger
|
303
|
+
expect(ChildProcess.logger).to eq(:some_other_logger)
|
304
|
+
ensure
|
305
|
+
ChildProcess.logger = original_logger
|
306
|
+
end
|
307
|
+
end
|
308
|
+
|
309
|
+
|
310
|
+
describe 'logger' do
|
311
|
+
|
312
|
+
before(:each) do
|
313
|
+
ChildProcess.logger = logger
|
314
|
+
end
|
315
|
+
|
316
|
+
after(:all) do
|
317
|
+
ChildProcess.logger = nil
|
318
|
+
end
|
319
|
+
|
320
|
+
|
321
|
+
context 'with the default logger' do
|
322
|
+
|
323
|
+
let(:logger) { nil }
|
324
|
+
|
325
|
+
|
326
|
+
it 'logs at INFO level by default' do
|
327
|
+
expect(ChildProcess.logger.level).to eq(Logger::INFO)
|
328
|
+
end
|
329
|
+
|
330
|
+
it 'logs at DEBUG level by default if $DEBUG is on' do
|
331
|
+
original_debug = $DEBUG
|
332
|
+
|
333
|
+
begin
|
334
|
+
$DEBUG = true
|
335
|
+
|
336
|
+
expect(ChildProcess.logger.level).to eq(Logger::DEBUG)
|
337
|
+
ensure
|
338
|
+
$DEBUG = original_debug
|
339
|
+
end
|
340
|
+
end
|
341
|
+
|
342
|
+
it "logs to stderr by default" do
|
343
|
+
cap = capture_std { generate_log_messages }
|
344
|
+
|
345
|
+
expect(cap.stdout).to be_empty
|
346
|
+
expect(cap.stderr).to_not be_empty
|
347
|
+
end
|
348
|
+
|
349
|
+
end
|
350
|
+
|
351
|
+
context 'with a custom logger' do
|
352
|
+
|
353
|
+
let(:logger) { Logger.new($stdout) }
|
354
|
+
|
355
|
+
it "logs to configured logger" do
|
356
|
+
cap = capture_std { generate_log_messages }
|
357
|
+
|
358
|
+
expect(cap.stdout).to_not be_empty
|
359
|
+
expect(cap.stderr).to be_empty
|
360
|
+
end
|
361
|
+
|
362
|
+
end
|
363
|
+
|
364
|
+
end
|
365
|
+
|
366
|
+
describe '#started?' do
|
367
|
+
subject { process.started? }
|
368
|
+
|
369
|
+
context 'when not started' do
|
370
|
+
let(:process) { sleeping_ruby(1) }
|
371
|
+
|
372
|
+
it { is_expected.to be false }
|
373
|
+
end
|
374
|
+
|
375
|
+
context 'when started' do
|
376
|
+
let(:process) { sleeping_ruby(1).start }
|
377
|
+
|
378
|
+
it { is_expected.to be true }
|
379
|
+
end
|
380
|
+
|
381
|
+
context 'when finished' do
|
382
|
+
before(:each) { process.wait }
|
383
|
+
|
384
|
+
let(:process) { sleeping_ruby(0).start }
|
385
|
+
|
386
|
+
it { is_expected.to be true }
|
387
|
+
end
|
388
|
+
|
389
|
+
end
|
390
|
+
|
391
|
+
end
|
data/spec/io_spec.rb
ADDED
@@ -0,0 +1,228 @@
|
|
1
|
+
require File.expand_path('../spec_helper', __FILE__)
|
2
|
+
|
3
|
+
describe ChildProcess do
|
4
|
+
it "can run even when $stdout is a StringIO" do
|
5
|
+
begin
|
6
|
+
stdout = $stdout
|
7
|
+
$stdout = StringIO.new
|
8
|
+
expect { sleeping_ruby.start }.to_not raise_error
|
9
|
+
ensure
|
10
|
+
$stdout = stdout
|
11
|
+
end
|
12
|
+
end
|
13
|
+
|
14
|
+
it "can redirect stdout, stderr" do
|
15
|
+
process = ruby(<<-CODE)
|
16
|
+
[STDOUT, STDERR].each_with_index do |io, idx|
|
17
|
+
io.sync = true
|
18
|
+
io.puts idx
|
19
|
+
end
|
20
|
+
CODE
|
21
|
+
|
22
|
+
out = Tempfile.new("stdout-spec")
|
23
|
+
err = Tempfile.new("stderr-spec")
|
24
|
+
|
25
|
+
begin
|
26
|
+
process.io.stdout = out
|
27
|
+
process.io.stderr = err
|
28
|
+
|
29
|
+
process.start
|
30
|
+
expect(process.io.stdin).to be_nil
|
31
|
+
process.wait
|
32
|
+
|
33
|
+
expect(rewind_and_read(out)).to eq "0\n"
|
34
|
+
expect(rewind_and_read(err)).to eq "1\n"
|
35
|
+
ensure
|
36
|
+
out.close
|
37
|
+
err.close
|
38
|
+
end
|
39
|
+
end
|
40
|
+
|
41
|
+
it "can redirect stdout only" do
|
42
|
+
process = ruby(<<-CODE)
|
43
|
+
[STDOUT, STDERR].each_with_index do |io, idx|
|
44
|
+
io.sync = true
|
45
|
+
io.puts idx
|
46
|
+
end
|
47
|
+
CODE
|
48
|
+
|
49
|
+
out = Tempfile.new("stdout-spec")
|
50
|
+
|
51
|
+
begin
|
52
|
+
process.io.stdout = out
|
53
|
+
|
54
|
+
process.start
|
55
|
+
process.wait
|
56
|
+
|
57
|
+
expect(rewind_and_read(out)).to eq "0\n"
|
58
|
+
ensure
|
59
|
+
out.close
|
60
|
+
end
|
61
|
+
end
|
62
|
+
|
63
|
+
it "pumps all output" do
|
64
|
+
process = echo
|
65
|
+
|
66
|
+
out = Tempfile.new("pump")
|
67
|
+
|
68
|
+
begin
|
69
|
+
process.io.stdout = out
|
70
|
+
|
71
|
+
process.start
|
72
|
+
process.wait
|
73
|
+
|
74
|
+
expect(rewind_and_read(out)).to eq "hello\n"
|
75
|
+
ensure
|
76
|
+
out.close
|
77
|
+
end
|
78
|
+
end
|
79
|
+
|
80
|
+
it "can write to stdin if duplex = true" do
|
81
|
+
process = cat
|
82
|
+
|
83
|
+
out = Tempfile.new("duplex")
|
84
|
+
out.sync = true
|
85
|
+
|
86
|
+
begin
|
87
|
+
process.io.stdout = out
|
88
|
+
process.io.stderr = out
|
89
|
+
process.duplex = true
|
90
|
+
|
91
|
+
process.start
|
92
|
+
process.io.stdin.puts "hello world"
|
93
|
+
process.io.stdin.close
|
94
|
+
|
95
|
+
process.poll_for_exit(exit_timeout)
|
96
|
+
|
97
|
+
expect(rewind_and_read(out)).to eq "hello world\n"
|
98
|
+
ensure
|
99
|
+
out.close
|
100
|
+
end
|
101
|
+
end
|
102
|
+
|
103
|
+
it "can write to stdin interactively if duplex = true" do
|
104
|
+
process = cat
|
105
|
+
|
106
|
+
out = Tempfile.new("duplex")
|
107
|
+
out.sync = true
|
108
|
+
|
109
|
+
out_receiver = File.open(out.path, "rb")
|
110
|
+
begin
|
111
|
+
process.io.stdout = out
|
112
|
+
process.io.stderr = out
|
113
|
+
process.duplex = true
|
114
|
+
|
115
|
+
process.start
|
116
|
+
|
117
|
+
stdin = process.io.stdin
|
118
|
+
|
119
|
+
stdin.puts "hello"
|
120
|
+
stdin.flush
|
121
|
+
wait_until { expect(rewind_and_read(out_receiver)).to match(/\Ahello\r?\n\z/m) }
|
122
|
+
|
123
|
+
stdin.putc "n"
|
124
|
+
stdin.flush
|
125
|
+
wait_until { expect(rewind_and_read(out_receiver)).to match(/\Ahello\r?\nn\z/m) }
|
126
|
+
|
127
|
+
stdin.print "e"
|
128
|
+
stdin.flush
|
129
|
+
wait_until { expect(rewind_and_read(out_receiver)).to match(/\Ahello\r?\nne\z/m) }
|
130
|
+
|
131
|
+
stdin.printf "w"
|
132
|
+
stdin.flush
|
133
|
+
wait_until { expect(rewind_and_read(out_receiver)).to match(/\Ahello\r?\nnew\z/m) }
|
134
|
+
|
135
|
+
stdin.write "\nworld\n"
|
136
|
+
stdin.flush
|
137
|
+
wait_until { expect(rewind_and_read(out_receiver)).to match(/\Ahello\r?\nnew\r?\nworld\r?\n\z/m) }
|
138
|
+
|
139
|
+
stdin.close
|
140
|
+
process.poll_for_exit(exit_timeout)
|
141
|
+
ensure
|
142
|
+
out_receiver.close
|
143
|
+
out.close
|
144
|
+
end
|
145
|
+
end
|
146
|
+
|
147
|
+
#
|
148
|
+
# this works on JRuby 1.6.5 on my Mac, but for some reason
|
149
|
+
# hangs on Travis (running 1.6.5.1 + OpenJDK).
|
150
|
+
#
|
151
|
+
# http://travis-ci.org/#!/enkessler/childprocess/jobs/487331
|
152
|
+
#
|
153
|
+
|
154
|
+
it "works with pipes", :process_builder => false do
|
155
|
+
process = ruby(<<-CODE)
|
156
|
+
STDOUT.print "stdout"
|
157
|
+
STDERR.print "stderr"
|
158
|
+
CODE
|
159
|
+
|
160
|
+
stdout, stdout_w = IO.pipe
|
161
|
+
stderr, stderr_w = IO.pipe
|
162
|
+
|
163
|
+
process.io.stdout = stdout_w
|
164
|
+
process.io.stderr = stderr_w
|
165
|
+
|
166
|
+
process.duplex = true
|
167
|
+
|
168
|
+
process.start
|
169
|
+
process.wait
|
170
|
+
|
171
|
+
# write streams are closed *after* the process
|
172
|
+
# has exited - otherwise it won't work on JRuby
|
173
|
+
# with the current Process implementation
|
174
|
+
|
175
|
+
stdout_w.close
|
176
|
+
stderr_w.close
|
177
|
+
|
178
|
+
out = stdout.read
|
179
|
+
err = stderr.read
|
180
|
+
|
181
|
+
expect([out, err]).to eq %w[stdout stderr]
|
182
|
+
end
|
183
|
+
|
184
|
+
it "can set close-on-exec when IO is inherited" do
|
185
|
+
port = random_free_port
|
186
|
+
server = TCPServer.new("127.0.0.1", port)
|
187
|
+
ChildProcess.close_on_exec server
|
188
|
+
|
189
|
+
process = sleeping_ruby
|
190
|
+
process.io.inherit!
|
191
|
+
|
192
|
+
process.start
|
193
|
+
server.close
|
194
|
+
|
195
|
+
wait_until { can_bind? "127.0.0.1", port }
|
196
|
+
end
|
197
|
+
|
198
|
+
it "handles long output" do
|
199
|
+
process = ruby <<-CODE
|
200
|
+
print 'a'*3000
|
201
|
+
CODE
|
202
|
+
|
203
|
+
out = Tempfile.new("long-output")
|
204
|
+
out.sync = true
|
205
|
+
|
206
|
+
begin
|
207
|
+
process.io.stdout = out
|
208
|
+
|
209
|
+
process.start
|
210
|
+
process.wait
|
211
|
+
|
212
|
+
expect(rewind_and_read(out).size).to eq 3000
|
213
|
+
ensure
|
214
|
+
out.close
|
215
|
+
end
|
216
|
+
end
|
217
|
+
|
218
|
+
it 'should not inherit stdout and stderr by default' do
|
219
|
+
cap = capture_std do
|
220
|
+
process = echo
|
221
|
+
process.start
|
222
|
+
process.wait
|
223
|
+
end
|
224
|
+
|
225
|
+
expect(cap.stdout).to eq ''
|
226
|
+
expect(cap.stderr).to eq ''
|
227
|
+
end
|
228
|
+
end
|