right_popen 1.0.11 → 1.0.16
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.
- data/Rakefile +71 -11
- data/lib/right_popen.rb +5 -5
- data/lib/right_popen/linux/accumulator.rb +117 -0
- data/lib/right_popen/linux/process.rb +148 -0
- data/lib/right_popen/linux/right_popen.rb +170 -0
- data/lib/right_popen/linux/utilities.rb +91 -0
- data/lib/right_popen/version.rb +28 -0
- data/right_popen.gemspec +12 -15
- data/spec/background.rb +28 -0
- data/spec/right_popen/linux/accumulator_spec.rb +272 -0
- data/spec/right_popen/linux/utilities_spec.rb +178 -0
- data/spec/right_popen_spec.rb +185 -253
- data/spec/runner.rb +112 -0
- data/spec/spec_helper.rb +6 -0
- data/spec/stdout.rb +28 -0
- metadata +84 -12
- data/lib/linux/right_popen.rb +0 -118
@@ -0,0 +1,91 @@
|
|
1
|
+
#-- -*- mode: ruby; encoding: utf-8 -*-
|
2
|
+
# Copyright: Copyright (c) 2011 RightScale, Inc.
|
3
|
+
#
|
4
|
+
# Permission is hereby granted, free of charge, to any person obtaining
|
5
|
+
# a copy of this software and associated documentation files (the
|
6
|
+
# 'Software'), to deal in the Software without restriction, including
|
7
|
+
# without limitation the rights to use, copy, modify, merge, publish,
|
8
|
+
# distribute, sublicense, and/or sell copies of the Software, and to
|
9
|
+
# permit persons to whom the Software is furnished to do so, subject to
|
10
|
+
# the following conditions:
|
11
|
+
#
|
12
|
+
# The above copyright notice and this permission notice shall be
|
13
|
+
# included in all copies or substantial portions of the Software.
|
14
|
+
#
|
15
|
+
# THE SOFTWARE IS PROVIDED 'AS IS', WITHOUT WARRANTY OF ANY KIND,
|
16
|
+
# EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF
|
17
|
+
# MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT.
|
18
|
+
# IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY
|
19
|
+
# CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT,
|
20
|
+
# TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE
|
21
|
+
# SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
|
22
|
+
#++
|
23
|
+
|
24
|
+
require File.expand_path(File.join(File.dirname(__FILE__), "process"))
|
25
|
+
require File.expand_path(File.join(File.dirname(__FILE__), "accumulator"))
|
26
|
+
|
27
|
+
module RightScale
|
28
|
+
module RightPopen
|
29
|
+
module Utilities
|
30
|
+
module_function
|
31
|
+
|
32
|
+
SIGNAL_LOOKUP = Signal.list.invert
|
33
|
+
|
34
|
+
def reason(status)
|
35
|
+
if status.exitstatus
|
36
|
+
"with exit status #{status.exitstatus}"
|
37
|
+
else
|
38
|
+
"due to SIG#{SIGNAL_LOOKUP[status.termsig]}"
|
39
|
+
end
|
40
|
+
end
|
41
|
+
private :reason
|
42
|
+
|
43
|
+
def run(cmd, parameters={})
|
44
|
+
status, out, err = run_collecting_output(cmd, parameters)
|
45
|
+
unless status.success?
|
46
|
+
raise "Command \"#{cmd}\" failed #{reason(status)}: " +
|
47
|
+
"stdout #{out}, stderr #{err}"
|
48
|
+
end
|
49
|
+
[out, err]
|
50
|
+
end
|
51
|
+
|
52
|
+
def run_with_stdin_collecting_output(cmd, input, parameters={})
|
53
|
+
out = StringIO.new
|
54
|
+
err = StringIO.new
|
55
|
+
first = true
|
56
|
+
status = run_with_blocks(cmd,
|
57
|
+
Proc.new {
|
58
|
+
if (first)
|
59
|
+
first = false
|
60
|
+
input
|
61
|
+
else
|
62
|
+
nil
|
63
|
+
end},
|
64
|
+
Proc.new {|s| out.write(s)},
|
65
|
+
Proc.new {|s| err.write(s)})
|
66
|
+
[status, out.string, err.string]
|
67
|
+
end
|
68
|
+
alias_method :run_input, :run_with_stdin_collecting_output
|
69
|
+
|
70
|
+
def run_collecting_output(cmd, parameters={})
|
71
|
+
out = StringIO.new
|
72
|
+
err = StringIO.new
|
73
|
+
status = run_with_blocks(cmd, nil, Proc.new {|s| out.write(s)},
|
74
|
+
Proc.new {|s| err.write(s)})
|
75
|
+
[status, out.string, err.string]
|
76
|
+
end
|
77
|
+
alias_method :spawn, :run_collecting_output
|
78
|
+
|
79
|
+
def run_with_blocks(cmd, stdin_block, stdout_block, stderr_block, parameters={})
|
80
|
+
process = Process.new(parameters)
|
81
|
+
process.fork(cmd)
|
82
|
+
process.wait_for_exec
|
83
|
+
a = Accumulator.new(process,
|
84
|
+
[process.stdout, process.stderr], [stdout_block, stderr_block],
|
85
|
+
[process.stdin], [stdin_block])
|
86
|
+
a.run_to_completion
|
87
|
+
process.status[1]
|
88
|
+
end
|
89
|
+
end
|
90
|
+
end
|
91
|
+
end
|
@@ -0,0 +1,28 @@
|
|
1
|
+
#-- -*- mode: ruby; encoding: utf-8 -*-
|
2
|
+
# Copyright: Copyright (c) 2011 RightScale, Inc.
|
3
|
+
#
|
4
|
+
# Permission is hereby granted, free of charge, to any person obtaining
|
5
|
+
# a copy of this software and associated documentation files (the
|
6
|
+
# 'Software'), to deal in the Software without restriction, including
|
7
|
+
# without limitation the rights to use, copy, modify, merge, publish,
|
8
|
+
# distribute, sublicense, and/or sell copies of the Software, and to
|
9
|
+
# permit persons to whom the Software is furnished to do so, subject to
|
10
|
+
# the following conditions:
|
11
|
+
#
|
12
|
+
# The above copyright notice and this permission notice shall be
|
13
|
+
# included in all copies or substantial portions of the Software.
|
14
|
+
#
|
15
|
+
# THE SOFTWARE IS PROVIDED 'AS IS', WITHOUT WARRANTY OF ANY KIND,
|
16
|
+
# EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF
|
17
|
+
# MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT.
|
18
|
+
# IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY
|
19
|
+
# CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT,
|
20
|
+
# TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE
|
21
|
+
# SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
|
22
|
+
#++
|
23
|
+
|
24
|
+
module RightScale
|
25
|
+
module RightPopen
|
26
|
+
VERSION = "1.0.16"
|
27
|
+
end
|
28
|
+
end
|
data/right_popen.gemspec
CHANGED
@@ -1,16 +1,15 @@
|
|
1
|
-
|
2
|
-
|
3
|
-
def is_windows?
|
4
|
-
return RUBY_PLATFORM =~ /mswin/
|
5
|
-
end
|
1
|
+
$:.push File.expand_path("../lib", __FILE__)
|
2
|
+
require "right_popen/version"
|
6
3
|
|
7
4
|
spec = Gem::Specification.new do |spec|
|
5
|
+
is_windows = RUBY_PLATFORM =~ /mswin/
|
6
|
+
|
8
7
|
spec.name = 'right_popen'
|
9
|
-
spec.version =
|
8
|
+
spec.version = RightScale::RightPopen::VERSION
|
10
9
|
spec.authors = ['Scott Messier', 'Raphael Simon', 'Graham Hughes']
|
11
10
|
spec.email = 'scott@rightscale.com'
|
12
11
|
spec.homepage = 'https://github.com/rightscale/right_popen'
|
13
|
-
if is_windows
|
12
|
+
if is_windows
|
14
13
|
spec.platform = 'x86-mswin32-60'
|
15
14
|
else
|
16
15
|
spec.platform = Gem::Platform::RUBY
|
@@ -29,7 +28,7 @@ of its internal mechanisms. The Linux implementation is valid for any Linux
|
|
29
28
|
platform but there is also a native implementation for Windows platforms.
|
30
29
|
EOF
|
31
30
|
|
32
|
-
if is_windows
|
31
|
+
if is_windows
|
33
32
|
extension_dir = "ext,"
|
34
33
|
else
|
35
34
|
extension_dir = ""
|
@@ -40,7 +39,7 @@ EOF
|
|
40
39
|
item.include?("Makefile") || item.include?(".obj") || item.include?(".pdb") || item.include?(".def") || item.include?(".exp") || item.include?(".lib")
|
41
40
|
end
|
42
41
|
candidates = candidates.delete_if do |item|
|
43
|
-
if is_windows
|
42
|
+
if is_windows
|
44
43
|
item.include?("/linux/")
|
45
44
|
else
|
46
45
|
item.include?("/win32/")
|
@@ -50,12 +49,10 @@ EOF
|
|
50
49
|
|
51
50
|
# Current implementation supports >= 0.12.10
|
52
51
|
spec.add_runtime_dependency(%q<eventmachine>, [">= 0.12.10"])
|
53
|
-
if is_windows
|
52
|
+
if is_windows
|
54
53
|
spec.add_runtime_dependency(%q<win32-process>, [">= 0.6.1"])
|
55
54
|
end
|
56
|
-
|
57
|
-
|
58
|
-
|
59
|
-
Gem.manage_gems if Gem::RubyGemsVersion.to_f < 1.0
|
60
|
-
Gem::Builder.new(spec).build
|
55
|
+
spec.add_development_dependency('rspec', "~> 1.3")
|
56
|
+
spec.add_development_dependency('rake', "~> 0.8.7")
|
57
|
+
spec.add_development_dependency('flexmock')
|
61
58
|
end
|
data/spec/background.rb
ADDED
@@ -0,0 +1,28 @@
|
|
1
|
+
#-- -*- mode: ruby; encoding: utf-8 -*-
|
2
|
+
# Copyright: Copyright (c) 2011 RightScale, Inc.
|
3
|
+
#
|
4
|
+
# Permission is hereby granted, free of charge, to any person obtaining
|
5
|
+
# a copy of this software and associated documentation files (the
|
6
|
+
# 'Software'), to deal in the Software without restriction, including
|
7
|
+
# without limitation the rights to use, copy, modify, merge, publish,
|
8
|
+
# distribute, sublicense, and/or sell copies of the Software, and to
|
9
|
+
# permit persons to whom the Software is furnished to do so, subject to
|
10
|
+
# the following conditions:
|
11
|
+
#
|
12
|
+
# The above copyright notice and this permission notice shall be
|
13
|
+
# included in all copies or substantial portions of the Software.
|
14
|
+
#
|
15
|
+
# THE SOFTWARE IS PROVIDED 'AS IS', WITHOUT WARRANTY OF ANY KIND,
|
16
|
+
# EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF
|
17
|
+
# MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT.
|
18
|
+
# IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY
|
19
|
+
# CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT,
|
20
|
+
# TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE
|
21
|
+
# SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
|
22
|
+
#++
|
23
|
+
|
24
|
+
fork {
|
25
|
+
sleep 30
|
26
|
+
puts "Done!"
|
27
|
+
}
|
28
|
+
exit 0
|
@@ -0,0 +1,272 @@
|
|
1
|
+
#-- -*- mode: ruby; encoding: utf-8 -*-
|
2
|
+
# Copyright: Copyright (c) 2011 RightScale, Inc.
|
3
|
+
#
|
4
|
+
# Permission is hereby granted, free of charge, to any person obtaining
|
5
|
+
# a copy of this software and associated documentation files (the
|
6
|
+
# 'Software'), to deal in the Software without restriction, including
|
7
|
+
# without limitation the rights to use, copy, modify, merge, publish,
|
8
|
+
# distribute, sublicense, and/or sell copies of the Software, and to
|
9
|
+
# permit persons to whom the Software is furnished to do so, subject to
|
10
|
+
# the following conditions:
|
11
|
+
#
|
12
|
+
# The above copyright notice and this permission notice shall be
|
13
|
+
# included in all copies or substantial portions of the Software.
|
14
|
+
#
|
15
|
+
# THE SOFTWARE IS PROVIDED 'AS IS', WITHOUT WARRANTY OF ANY KIND,
|
16
|
+
# EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF
|
17
|
+
# MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT.
|
18
|
+
# IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY
|
19
|
+
# CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT,
|
20
|
+
# TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE
|
21
|
+
# SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
|
22
|
+
#++
|
23
|
+
|
24
|
+
require File.expand_path(File.join(File.dirname(__FILE__), '..', '..', 'spec_helper'))
|
25
|
+
|
26
|
+
module RightScale::RightPopen
|
27
|
+
describe Accumulator do
|
28
|
+
before(:each) do
|
29
|
+
@process = flexmock("process")
|
30
|
+
@process.should_receive(:pid).and_return(42)
|
31
|
+
end
|
32
|
+
|
33
|
+
describe "#tick" do
|
34
|
+
context 'with a live child' do
|
35
|
+
before(:each) do
|
36
|
+
@process.should_receive(:status).and_return(nil)
|
37
|
+
@process.should_receive(:status=)
|
38
|
+
@input = flexmock("input")
|
39
|
+
@output = flexmock("output")
|
40
|
+
@read = flexmock("read")
|
41
|
+
@write = flexmock("write")
|
42
|
+
end
|
43
|
+
|
44
|
+
it 'should skip calling select if no pipes are given' do
|
45
|
+
a = Accumulator.new(@process, [], [], [], [])
|
46
|
+
flexmock(::Process).should_receive(:waitpid2).with(42, ::Process::WNOHANG).once.and_return(nil)
|
47
|
+
a.tick.should be_false
|
48
|
+
end
|
49
|
+
|
50
|
+
it 'should just check waitpid if the select times out' do
|
51
|
+
a = Accumulator.new(@process, [@input], [@read], [@output], [@write])
|
52
|
+
flexmock(::IO).should_receive(:select).with([@input], [@output], nil, 0.1).once.and_return([[], [], []])
|
53
|
+
flexmock(::Process).should_receive(:waitpid2).with(42, ::Process::WNOHANG).once.and_return(nil)
|
54
|
+
a.tick.should be_false
|
55
|
+
end
|
56
|
+
|
57
|
+
it 'should use the timeout value in the select' do
|
58
|
+
value = flexmock("value")
|
59
|
+
a = Accumulator.new(@process, [@input], [@read], [@output], [@write])
|
60
|
+
flexmock(::IO).should_receive(:select).with([@input], [@output], nil, value).once.and_return([[], [], []])
|
61
|
+
flexmock(::Process).should_receive(:waitpid2).with(42, ::Process::WNOHANG).once.and_return(nil)
|
62
|
+
a.tick(value).should be_false
|
63
|
+
end
|
64
|
+
|
65
|
+
it 'should retry the select when seeing Errno::EAGAIN or Errno::EINTR' do
|
66
|
+
a = Accumulator.new(@process, [@input], [@read], [@output], [@write])
|
67
|
+
flexmock(::IO).should_receive(:select).with([@input], [@output], nil, 0.1).times(3).and_raise(Errno::EAGAIN).and_raise(Errno::EINTR).and_return([[], [], []])
|
68
|
+
flexmock(::Process).should_receive(:waitpid2).with(42, ::Process::WNOHANG).once.and_return(nil)
|
69
|
+
a.tick.should be_false
|
70
|
+
end
|
71
|
+
|
72
|
+
it 'should read data from the pipe and call the reader if it is ready' do
|
73
|
+
value = flexmock("value")
|
74
|
+
a = Accumulator.new(@process, [@input], [@read], [@output], [@write])
|
75
|
+
flexmock(::IO).should_receive(:select).with([@input], [@output], nil, 0.1).once.and_return([[@input], [], []])
|
76
|
+
@input.should_receive(:eof?).once.and_return(false)
|
77
|
+
@input.should_receive(:readpartial).with(Accumulator::READ_CHUNK_SIZE).once.and_return(value)
|
78
|
+
@read.should_receive(:call).with(value).once
|
79
|
+
flexmock(::Process).should_receive(:waitpid2).with(42, ::Process::WNOHANG).once.and_return(nil)
|
80
|
+
a.tick.should be_false
|
81
|
+
end
|
82
|
+
|
83
|
+
it 'should read data from the pipe and throw it away if no reader' do
|
84
|
+
value = flexmock("value")
|
85
|
+
a = Accumulator.new(@process, [@input], [], [@output], [@write])
|
86
|
+
flexmock(::IO).should_receive(:select).with([@input], [@output], nil, 0.1).once.and_return([[@input], [], []])
|
87
|
+
@input.should_receive(:eof?).once.and_return(false)
|
88
|
+
@input.should_receive(:readpartial).with(Accumulator::READ_CHUNK_SIZE).once.and_return(value)
|
89
|
+
flexmock(::Process).should_receive(:waitpid2).with(42, ::Process::WNOHANG).once.and_return(nil)
|
90
|
+
a.tick.should be_false
|
91
|
+
end
|
92
|
+
|
93
|
+
it 'should call the writer and then write data to the pipe if it is ready' do
|
94
|
+
value = flexmock("value")
|
95
|
+
a = Accumulator.new(@process, [@input], [@read], [@output], [@write])
|
96
|
+
flexmock(::IO).should_receive(:select).with([@input], [@output], nil, 0.1).once.and_return([[], [@output], []])
|
97
|
+
@write.should_receive(:call).with().once.and_return(value)
|
98
|
+
value.should_receive(:[]).with(30..-1).and_return("")
|
99
|
+
@output.should_receive(:write_nonblock).with(value).once.and_return(30)
|
100
|
+
flexmock(::Process).should_receive(:waitpid2).with(42, ::Process::WNOHANG).once.and_return(nil)
|
101
|
+
a.tick.should be_false
|
102
|
+
end
|
103
|
+
|
104
|
+
it 'should only call the writer when it is stalling' do
|
105
|
+
value = flexmock("value")
|
106
|
+
other = flexmock("other value")
|
107
|
+
a = Accumulator.new(@process, [@input], [@read], [@output], [@write])
|
108
|
+
flexmock(::IO).should_receive(:select).with([@input], [@output], nil, 0.1).and_return([[], [@output], []])
|
109
|
+
@write.should_receive(:call).with().once.and_return(value)
|
110
|
+
value.should_receive(:[]).with(30..-1).and_return(other)
|
111
|
+
other.should_receive(:[]).with(20..-1).and_return("")
|
112
|
+
@output.should_receive(:write_nonblock).with(value).once.and_return(30)
|
113
|
+
@output.should_receive(:write_nonblock).with(other).once.and_return(20)
|
114
|
+
flexmock(::Process).should_receive(:waitpid2).with(42, ::Process::WNOHANG).and_return(nil)
|
115
|
+
a.tick.should be_false
|
116
|
+
a.tick.should be_false
|
117
|
+
end
|
118
|
+
|
119
|
+
it 'should not read data from the pipe any more if EOF has been reached' do
|
120
|
+
value = flexmock("value")
|
121
|
+
a = Accumulator.new(@process, [@input], [@read], [], [])
|
122
|
+
flexmock(::IO).should_receive(:select).with([@input], [], nil, 0.1).once.and_return([[@input], [], []])
|
123
|
+
@input.should_receive(:eof?).once.and_return(true)
|
124
|
+
@input.should_receive(:close).once
|
125
|
+
flexmock(::Process).should_receive(:waitpid2).with(42, ::Process::WNOHANG).twice.and_return(nil)
|
126
|
+
a.tick.should be_false
|
127
|
+
a.tick.should be_false
|
128
|
+
end
|
129
|
+
|
130
|
+
it 'should not write data to the pipe any more if the caller has no more data' do
|
131
|
+
value = flexmock("value")
|
132
|
+
a = Accumulator.new(@process, [], [], [@output], [@write])
|
133
|
+
flexmock(::IO).should_receive(:select).with([], [@output], nil, 0.1).once.and_return([[], [@output], []])
|
134
|
+
@write.should_receive(:call).once.and_return(nil)
|
135
|
+
@output.should_receive(:close).once
|
136
|
+
flexmock(::Process).should_receive(:waitpid2).with(42, ::Process::WNOHANG).twice.and_return(nil)
|
137
|
+
a.tick.should be_false
|
138
|
+
a.tick.should be_false
|
139
|
+
end
|
140
|
+
|
141
|
+
it 'should not write data to the pipe any more if the caller is nil' do
|
142
|
+
a = Accumulator.new(@process, [], [], [@output], [nil])
|
143
|
+
flexmock(::IO).should_receive(:select).with([], [@output], nil, 0.1).once.and_return([[], [@output], []])
|
144
|
+
@output.should_receive(:close).once
|
145
|
+
flexmock(::Process).should_receive(:waitpid2).with(42, ::Process::WNOHANG).twice.and_return(nil)
|
146
|
+
a.tick.should be_false
|
147
|
+
a.tick.should be_false
|
148
|
+
end
|
149
|
+
end
|
150
|
+
|
151
|
+
it 'should update the status if waitpid is successful' do
|
152
|
+
a = Accumulator.new(@process, [], [], [], [])
|
153
|
+
status = flexmock("status")
|
154
|
+
flexmock(::Process).should_receive(:waitpid2).with(42, ::Process::WNOHANG).once.and_return(status)
|
155
|
+
@process.should_receive(:status).twice.and_return(nil).and_return(status)
|
156
|
+
@process.should_receive(:status=).with(status).once
|
157
|
+
a.tick.should be_true
|
158
|
+
end
|
159
|
+
|
160
|
+
it 'should return true if the process has already been waited on' do
|
161
|
+
@process.should_receive(:status).and_return(true)
|
162
|
+
a = Accumulator.new(@process, [], [], [], [])
|
163
|
+
a.tick.should be_true
|
164
|
+
end
|
165
|
+
end
|
166
|
+
|
167
|
+
describe "#number_waiting_on" do
|
168
|
+
it 'should return 0 when no pipes are left' do
|
169
|
+
a = Accumulator.new(@process, [], [], [], [])
|
170
|
+
a.number_waiting_on.should == 0
|
171
|
+
end
|
172
|
+
|
173
|
+
it 'should return 1 when one pipe is left' do
|
174
|
+
pipe = flexmock("pipe")
|
175
|
+
a = Accumulator.new(@process, [pipe], [nil], [], [])
|
176
|
+
a.number_waiting_on.should == 1
|
177
|
+
end
|
178
|
+
|
179
|
+
it 'should return add readers and writers' do
|
180
|
+
pipe = flexmock("pipe")
|
181
|
+
a = Accumulator.new(@process, [pipe, pipe, pipe], [], [pipe], [])
|
182
|
+
a.number_waiting_on.should == 4
|
183
|
+
end
|
184
|
+
|
185
|
+
it 'should transition from 0 to 1 as pipes are removed' do
|
186
|
+
pipe = flexmock("pipe")
|
187
|
+
read = flexmock("read")
|
188
|
+
@process.should_receive(:status).and_return(nil)
|
189
|
+
@process.should_receive(:status=)
|
190
|
+
a = Accumulator.new(@process, [pipe], [read], [], [])
|
191
|
+
flexmock(::IO).should_receive(:select).with([pipe], [], nil, 0.1).once.and_return([[pipe], [], []])
|
192
|
+
pipe.should_receive(:eof?).and_return(true)
|
193
|
+
pipe.should_receive(:close)
|
194
|
+
flexmock(::Process).should_receive(:waitpid2).with(42, ::Process::WNOHANG).once.and_return(nil)
|
195
|
+
a.number_waiting_on.should == 1
|
196
|
+
a.tick.should be_false
|
197
|
+
a.number_waiting_on.should == 0
|
198
|
+
end
|
199
|
+
end
|
200
|
+
|
201
|
+
describe "#cleanup" do
|
202
|
+
it 'should do nothing if no pipes are left open and the process is reaped' do
|
203
|
+
@process.should_receive(:status).and_return(true)
|
204
|
+
a = Accumulator.new(@process, [], [], [], [])
|
205
|
+
flexmock(::Process).should_receive(:waitpid2).never
|
206
|
+
a.cleanup
|
207
|
+
end
|
208
|
+
|
209
|
+
it 'should just call waitpid if no pipes are left open' do
|
210
|
+
value = flexmock("value")
|
211
|
+
a = Accumulator.new(@process, [], [], [], [])
|
212
|
+
@process.should_receive(:status).and_return(nil)
|
213
|
+
flexmock(::Process).should_receive(:waitpid2).with(42).once.and_return(value)
|
214
|
+
@process.should_receive(:status=).with(value).once
|
215
|
+
a.cleanup
|
216
|
+
end
|
217
|
+
|
218
|
+
it 'should close all open pipes' do
|
219
|
+
a, b, c = flexmock("a"), flexmock("b"), flexmock("c")
|
220
|
+
acc = Accumulator.new(@process, [a, b], [], [c], [])
|
221
|
+
@process.should_receive(:status).and_return(true)
|
222
|
+
[a, b].each {|fdes| fdes.should_receive(:close).with().once }
|
223
|
+
[a, b].each {|fdes| fdes.should_receive(:closed?).and_return(false) }
|
224
|
+
[c].each {|fdes| fdes.should_receive(:closed?).and_return(true) }
|
225
|
+
flexmock(::Process).should_receive(:waitpid2).never
|
226
|
+
acc.cleanup
|
227
|
+
end
|
228
|
+
|
229
|
+
it 'should close all open pipes and reap zombies if needed' do
|
230
|
+
value = flexmock("value")
|
231
|
+
a, b, c = flexmock("a"), flexmock("b"), flexmock("c")
|
232
|
+
acc = Accumulator.new(@process, [b, c], [], [a], [])
|
233
|
+
@process.should_receive(:status).and_return(nil)
|
234
|
+
[a, b].each {|fdes| fdes.should_receive(:close).with().once }
|
235
|
+
[a, b].each {|fdes| fdes.should_receive(:closed?).and_return(false) }
|
236
|
+
[c].each {|fdes| fdes.should_receive(:closed?).and_return(true) }
|
237
|
+
flexmock(::Process).should_receive(:waitpid2).with(42).once.and_return(value)
|
238
|
+
@process.should_receive(:status=).with(value).once
|
239
|
+
acc.cleanup
|
240
|
+
end
|
241
|
+
end
|
242
|
+
|
243
|
+
describe "#run_to_completion" do
|
244
|
+
it 'should run ticks until it is true' do
|
245
|
+
value = flexmock("value")
|
246
|
+
acc = flexmock(Accumulator.new(@process, [], [], [], []))
|
247
|
+
acc.should_receive(:tick).with(value).times(3).and_return(false).and_return(false).and_return(true)
|
248
|
+
acc.should_receive(:cleanup).once
|
249
|
+
acc.should_receive(:number_waiting_on).and_return(1)
|
250
|
+
acc.run_to_completion(value)
|
251
|
+
end
|
252
|
+
|
253
|
+
it 'should abort the loop early if there are no remaining pipes' do
|
254
|
+
value = flexmock("value")
|
255
|
+
acc = flexmock(Accumulator.new(@process, [], [], [], []))
|
256
|
+
acc.should_receive(:tick).with(value).twice.and_return(false)
|
257
|
+
acc.should_receive(:cleanup).once
|
258
|
+
acc.should_receive(:number_waiting_on).and_return(1).and_return(0)
|
259
|
+
acc.run_to_completion(value)
|
260
|
+
end
|
261
|
+
|
262
|
+
it 'should abort the loop after one iteration if there never were any pipes' do
|
263
|
+
value = flexmock("value")
|
264
|
+
acc = flexmock(Accumulator.new(@process, [], [], [], []))
|
265
|
+
acc.should_receive(:tick).with(value).once.and_return(false)
|
266
|
+
acc.should_receive(:cleanup).once
|
267
|
+
acc.should_receive(:number_waiting_on).and_return(0)
|
268
|
+
acc.run_to_completion(value)
|
269
|
+
end
|
270
|
+
end
|
271
|
+
end
|
272
|
+
end
|