right_popen 1.1.3 → 3.0.1
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/README.rdoc +2 -0
- data/lib/right_popen.rb +35 -31
- data/lib/right_popen/linux/popen3_async.rb +26 -10
- data/lib/right_popen/linux/process.rb +21 -25
- data/lib/right_popen/{linux/popen3_sync.rb → popen3_sync.rb} +2 -1
- data/lib/right_popen/process_base.rb +20 -16
- data/lib/right_popen/process_status.rb +3 -0
- data/lib/right_popen/safe_output_buffer.rb +3 -0
- data/lib/right_popen/target_proxy.rb +12 -8
- data/right_popen.gemspec +57 -31
- metadata +39 -128
- data/Rakefile +0 -127
- data/lib/right_popen/linux/accumulator.rb +0 -127
- data/lib/right_popen/linux/utilities.rb +0 -94
- data/lib/right_popen/version.rb +0 -28
- data/spec/background.rb +0 -28
- data/spec/increment.rb +0 -2
- data/spec/print_env.rb +0 -2
- data/spec/produce_mixed_output.rb +0 -12
- data/spec/produce_output.rb +0 -2
- data/spec/produce_status.rb +0 -1
- data/spec/produce_stderr_only.rb +0 -5
- data/spec/produce_stdout_only.rb +0 -5
- data/spec/right_popen/linux/accumulator_spec.rb +0 -267
- data/spec/right_popen/linux/utilities_spec.rb +0 -178
- data/spec/right_popen/safe_output_buffer_spec.rb +0 -26
- data/spec/right_popen_spec.rb +0 -331
- data/spec/runner.rb +0 -217
- data/spec/sleeper.rb +0 -35
- data/spec/spec_helper.rb +0 -26
- data/spec/stdout.rb +0 -28
- data/spec/writer.rb +0 -34
@@ -1,127 +0,0 @@
|
|
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
|
-
|
27
|
-
# @deprecated this seems like test harness code smell, not production code
|
28
|
-
class Accumulator
|
29
|
-
READ_CHUNK_SIZE = 4096
|
30
|
-
|
31
|
-
def initialize(process, inputs, read_callbacks, outputs, write_callbacks)
|
32
|
-
warn 'WARNING: RightScale::RightPopen::Accumulator is deprecated and will be removed.'
|
33
|
-
@process = process
|
34
|
-
@inputs = inputs
|
35
|
-
@outputs = outputs
|
36
|
-
null = Proc.new {}
|
37
|
-
@reads = {}
|
38
|
-
@writes = {}
|
39
|
-
inputs.zip(read_callbacks).each do |pair|
|
40
|
-
input, callback = pair
|
41
|
-
@reads[input] = callback
|
42
|
-
end
|
43
|
-
outputs.zip(write_callbacks).each do |pair|
|
44
|
-
output, callback = pair
|
45
|
-
@writes[output] = callback
|
46
|
-
end
|
47
|
-
@writebuffers = {}
|
48
|
-
@status = nil
|
49
|
-
end
|
50
|
-
|
51
|
-
def status
|
52
|
-
unless @status
|
53
|
-
@status = ::Process.waitpid2(@process.pid, ::Process::WNOHANG)
|
54
|
-
end
|
55
|
-
@status
|
56
|
-
end
|
57
|
-
|
58
|
-
def tick(sleep_time = 0.1)
|
59
|
-
return true unless @status.nil?
|
60
|
-
|
61
|
-
status
|
62
|
-
|
63
|
-
inputs = @inputs.dup
|
64
|
-
outputs = @outputs.dup
|
65
|
-
ready = nil
|
66
|
-
while ready.nil?
|
67
|
-
begin
|
68
|
-
# in theory, we should note "exceptional conditions" and
|
69
|
-
# permit procs for those, too. In practice there are only
|
70
|
-
# two times when exceptional conditions occur: out of band
|
71
|
-
# data in TCP connections and "packet mode" for
|
72
|
-
# pseudoterminals. We care about neither of these,
|
73
|
-
# therefore ignore exceptional conditions.
|
74
|
-
ready = IO.select(inputs, outputs, nil, sleep_time)
|
75
|
-
rescue Errno::EAGAIN, Errno::EINTR
|
76
|
-
end
|
77
|
-
end unless inputs.empty? && outputs.empty?
|
78
|
-
|
79
|
-
ready[0].each do |fdes|
|
80
|
-
if fdes.eof?
|
81
|
-
fdes.close
|
82
|
-
@inputs.delete(fdes)
|
83
|
-
else
|
84
|
-
chunk = fdes.readpartial(READ_CHUNK_SIZE)
|
85
|
-
@reads[fdes].call(chunk) if @reads[fdes]
|
86
|
-
end
|
87
|
-
end unless ready.nil? || ready[0].nil?
|
88
|
-
ready[1].each do |fdes|
|
89
|
-
buffered = @writebuffers[fdes]
|
90
|
-
buffered = @writes[fdes].call if @writes[fdes] if buffered.nil? || buffered.empty?
|
91
|
-
if buffered.nil?
|
92
|
-
fdes.close
|
93
|
-
@outputs.delete(fdes)
|
94
|
-
elsif !buffered.empty?
|
95
|
-
begin
|
96
|
-
amount = fdes.write_nonblock buffered
|
97
|
-
@writebuffers[fdes] = buffered[amount..-1]
|
98
|
-
rescue Errno::EPIPE
|
99
|
-
# subprocess closed the pipe; fine.
|
100
|
-
fdes.close
|
101
|
-
@outputs.delete(fdes)
|
102
|
-
end
|
103
|
-
end
|
104
|
-
end unless ready.nil? || ready[1].nil?
|
105
|
-
|
106
|
-
return !@status.nil?
|
107
|
-
end
|
108
|
-
|
109
|
-
def number_waiting_on
|
110
|
-
@inputs.size + @outputs.size
|
111
|
-
end
|
112
|
-
|
113
|
-
def cleanup
|
114
|
-
@inputs.each {|p| p.close unless p.closed? }
|
115
|
-
@outputs.each {|p| p.close unless p.closed? }
|
116
|
-
@status = ::Process.waitpid2(@process.pid) if @status.nil?
|
117
|
-
end
|
118
|
-
|
119
|
-
def run_to_completion(sleep_time=0.1)
|
120
|
-
until tick(sleep_time)
|
121
|
-
break if number_waiting_on == 0
|
122
|
-
end
|
123
|
-
cleanup
|
124
|
-
end
|
125
|
-
end
|
126
|
-
end
|
127
|
-
end
|
@@ -1,94 +0,0 @@
|
|
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
|
-
|
30
|
-
# @deprecated this seems like test harness code smell, not production code
|
31
|
-
module Utilities
|
32
|
-
module_function
|
33
|
-
|
34
|
-
SIGNAL_LOOKUP = Signal.list.invert
|
35
|
-
|
36
|
-
def reason(status)
|
37
|
-
if status.exitstatus
|
38
|
-
"with exit status #{status.exitstatus}"
|
39
|
-
else
|
40
|
-
"due to SIG#{SIGNAL_LOOKUP[status.termsig]}"
|
41
|
-
end
|
42
|
-
end
|
43
|
-
private :reason
|
44
|
-
|
45
|
-
def run(cmd, parameters={})
|
46
|
-
status, out, err = run_collecting_output(cmd, parameters)
|
47
|
-
unless status.success?
|
48
|
-
raise "Command \"#{cmd}\" failed #{reason(status)}: " +
|
49
|
-
"stdout #{out}, stderr #{err}"
|
50
|
-
end
|
51
|
-
[out, err]
|
52
|
-
end
|
53
|
-
|
54
|
-
def run_with_stdin_collecting_output(cmd, input, parameters={})
|
55
|
-
out = StringIO.new
|
56
|
-
err = StringIO.new
|
57
|
-
first = true
|
58
|
-
status = run_with_blocks(cmd,
|
59
|
-
Proc.new {
|
60
|
-
if (first)
|
61
|
-
first = false
|
62
|
-
input
|
63
|
-
else
|
64
|
-
nil
|
65
|
-
end},
|
66
|
-
Proc.new {|s| out.write(s)},
|
67
|
-
Proc.new {|s| err.write(s)})
|
68
|
-
[status, out.string, err.string]
|
69
|
-
end
|
70
|
-
alias_method :run_input, :run_with_stdin_collecting_output
|
71
|
-
|
72
|
-
def run_collecting_output(cmd, parameters={})
|
73
|
-
out = StringIO.new
|
74
|
-
err = StringIO.new
|
75
|
-
status = run_with_blocks(cmd, nil, Proc.new {|s| out.write(s)},
|
76
|
-
Proc.new {|s| err.write(s)})
|
77
|
-
[status, out.string, err.string]
|
78
|
-
end
|
79
|
-
alias_method :spawn, :run_collecting_output
|
80
|
-
|
81
|
-
def run_with_blocks(cmd, stdin_block, stdout_block, stderr_block, parameters={})
|
82
|
-
warn 'WARNING: RightScale::RightPopen::Utilities are deprecated and will be removed.'
|
83
|
-
process = Process.new(parameters)
|
84
|
-
process.spawn(cmd, ::RightScale::RightPopen::TargetProxy.new(parameters))
|
85
|
-
process.wait_for_exec
|
86
|
-
a = Accumulator.new(process,
|
87
|
-
[process.stdout, process.stderr], [stdout_block, stderr_block],
|
88
|
-
[process.stdin], [stdin_block])
|
89
|
-
a.run_to_completion
|
90
|
-
a.status[1]
|
91
|
-
end
|
92
|
-
end
|
93
|
-
end
|
94
|
-
end
|
data/lib/right_popen/version.rb
DELETED
@@ -1,28 +0,0 @@
|
|
1
|
-
#-- -*- mode: ruby; encoding: utf-8 -*-
|
2
|
-
# Copyright: Copyright (c) 2011-2013 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.1.3"
|
27
|
-
end
|
28
|
-
end
|
data/spec/background.rb
DELETED
@@ -1,28 +0,0 @@
|
|
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
|
data/spec/increment.rb
DELETED
data/spec/print_env.rb
DELETED
data/spec/produce_output.rb
DELETED
data/spec/produce_status.rb
DELETED
@@ -1 +0,0 @@
|
|
1
|
-
exit ARGV[0].to_i
|
data/spec/produce_stderr_only.rb
DELETED
data/spec/produce_stdout_only.rb
DELETED
@@ -1,267 +0,0 @@
|
|
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
|
-
@input = flexmock("input")
|
37
|
-
@output = flexmock("output")
|
38
|
-
@read = flexmock("read")
|
39
|
-
@write = flexmock("write")
|
40
|
-
end
|
41
|
-
|
42
|
-
it 'should skip calling select if no pipes are given' do
|
43
|
-
a = Accumulator.new(@process, [], [], [], [])
|
44
|
-
flexmock(::Process).should_receive(:waitpid2).with(42, ::Process::WNOHANG).once.and_return(nil)
|
45
|
-
a.tick.should be_false
|
46
|
-
end
|
47
|
-
|
48
|
-
it 'should just check waitpid if the select times out' do
|
49
|
-
a = Accumulator.new(@process, [@input], [@read], [@output], [@write])
|
50
|
-
flexmock(::IO).should_receive(:select).with([@input], [@output], nil, 0.1).once.and_return([[], [], []])
|
51
|
-
flexmock(::Process).should_receive(:waitpid2).with(42, ::Process::WNOHANG).once.and_return(nil)
|
52
|
-
a.tick.should be_false
|
53
|
-
end
|
54
|
-
|
55
|
-
it 'should use the timeout value in the select' do
|
56
|
-
value = flexmock("value")
|
57
|
-
a = Accumulator.new(@process, [@input], [@read], [@output], [@write])
|
58
|
-
flexmock(::IO).should_receive(:select).with([@input], [@output], nil, value).once.and_return([[], [], []])
|
59
|
-
flexmock(::Process).should_receive(:waitpid2).with(42, ::Process::WNOHANG).once.and_return(nil)
|
60
|
-
a.tick(value).should be_false
|
61
|
-
end
|
62
|
-
|
63
|
-
it 'should retry the select when seeing Errno::EAGAIN or Errno::EINTR' do
|
64
|
-
a = Accumulator.new(@process, [@input], [@read], [@output], [@write])
|
65
|
-
flexmock(::IO).should_receive(:select).with([@input], [@output], nil, 0.1).times(3).and_raise(Errno::EAGAIN).and_raise(Errno::EINTR).and_return([[], [], []])
|
66
|
-
flexmock(::Process).should_receive(:waitpid2).with(42, ::Process::WNOHANG).once.and_return(nil)
|
67
|
-
a.tick.should be_false
|
68
|
-
end
|
69
|
-
|
70
|
-
it 'should read data from the pipe and call the reader if it is ready' do
|
71
|
-
value = flexmock("value")
|
72
|
-
a = Accumulator.new(@process, [@input], [@read], [@output], [@write])
|
73
|
-
flexmock(::IO).should_receive(:select).with([@input], [@output], nil, 0.1).once.and_return([[@input], [], []])
|
74
|
-
@input.should_receive(:eof?).once.and_return(false)
|
75
|
-
@input.should_receive(:readpartial).with(Accumulator::READ_CHUNK_SIZE).once.and_return(value)
|
76
|
-
@read.should_receive(:call).with(value).once
|
77
|
-
flexmock(::Process).should_receive(:waitpid2).with(42, ::Process::WNOHANG).once.and_return(nil)
|
78
|
-
a.tick.should be_false
|
79
|
-
end
|
80
|
-
|
81
|
-
it 'should read data from the pipe and throw it away if no reader' do
|
82
|
-
value = flexmock("value")
|
83
|
-
a = Accumulator.new(@process, [@input], [], [@output], [@write])
|
84
|
-
flexmock(::IO).should_receive(:select).with([@input], [@output], nil, 0.1).once.and_return([[@input], [], []])
|
85
|
-
@input.should_receive(:eof?).once.and_return(false)
|
86
|
-
@input.should_receive(:readpartial).with(Accumulator::READ_CHUNK_SIZE).once.and_return(value)
|
87
|
-
flexmock(::Process).should_receive(:waitpid2).with(42, ::Process::WNOHANG).once.and_return(nil)
|
88
|
-
a.tick.should be_false
|
89
|
-
end
|
90
|
-
|
91
|
-
it 'should call the writer and then write data to the pipe if it is ready' do
|
92
|
-
value = flexmock("value")
|
93
|
-
a = Accumulator.new(@process, [@input], [@read], [@output], [@write])
|
94
|
-
flexmock(::IO).should_receive(:select).with([@input], [@output], nil, 0.1).once.and_return([[], [@output], []])
|
95
|
-
@write.should_receive(:call).with().once.and_return(value)
|
96
|
-
value.should_receive(:[]).with(30..-1).and_return("")
|
97
|
-
value.should_receive("empty?").and_return(false)
|
98
|
-
@output.should_receive(:write_nonblock).with(value).once.and_return(30)
|
99
|
-
flexmock(::Process).should_receive(:waitpid2).with(42, ::Process::WNOHANG).once.and_return(nil)
|
100
|
-
a.tick.should be_false
|
101
|
-
end
|
102
|
-
|
103
|
-
it 'should only call the writer when it is stalling' do
|
104
|
-
value = flexmock("value")
|
105
|
-
other = flexmock("other value")
|
106
|
-
a = Accumulator.new(@process, [@input], [@read], [@output], [@write])
|
107
|
-
flexmock(::IO).should_receive(:select).with([@input], [@output], nil, 0.1).and_return([[], [@output], []])
|
108
|
-
@write.should_receive(:call).with().once.and_return(value)
|
109
|
-
value.should_receive(:[]).with(30..-1).and_return(other)
|
110
|
-
other.should_receive(:[]).with(20..-1).and_return("")
|
111
|
-
value.should_receive("empty?").and_return(false)
|
112
|
-
other.should_receive("empty?").and_return(false)
|
113
|
-
@output.should_receive(:write_nonblock).with(value).once.and_return(30)
|
114
|
-
@output.should_receive(:write_nonblock).with(other).once.and_return(20)
|
115
|
-
flexmock(::Process).should_receive(:waitpid2).with(42, ::Process::WNOHANG).and_return(nil)
|
116
|
-
a.tick.should be_false
|
117
|
-
a.tick.should be_false
|
118
|
-
end
|
119
|
-
|
120
|
-
it 'should not read data from the pipe any more if EOF has been reached' do
|
121
|
-
value = flexmock("value")
|
122
|
-
a = Accumulator.new(@process, [@input], [@read], [], [])
|
123
|
-
flexmock(::IO).should_receive(:select).with([@input], [], nil, 0.1).once.and_return([[@input], [], []])
|
124
|
-
@input.should_receive(:eof?).once.and_return(true)
|
125
|
-
@input.should_receive(:close).once
|
126
|
-
flexmock(::Process).should_receive(:waitpid2).with(42, ::Process::WNOHANG).twice.and_return(nil)
|
127
|
-
a.tick.should be_false
|
128
|
-
a.tick.should be_false
|
129
|
-
end
|
130
|
-
|
131
|
-
it 'should not write data to the pipe any more if the caller has no more data' do
|
132
|
-
value = flexmock("value")
|
133
|
-
a = Accumulator.new(@process, [], [], [@output], [@write])
|
134
|
-
flexmock(::IO).should_receive(:select).with([], [@output], nil, 0.1).once.and_return([[], [@output], []])
|
135
|
-
@write.should_receive(:call).once.and_return(nil)
|
136
|
-
@output.should_receive(:close).once
|
137
|
-
flexmock(::Process).should_receive(:waitpid2).with(42, ::Process::WNOHANG).twice.and_return(nil)
|
138
|
-
a.tick.should be_false
|
139
|
-
a.tick.should be_false
|
140
|
-
end
|
141
|
-
|
142
|
-
it 'should not write data to the pipe any more if the caller is nil' do
|
143
|
-
a = Accumulator.new(@process, [], [], [@output], [nil])
|
144
|
-
flexmock(::IO).should_receive(:select).with([], [@output], nil, 0.1).once.and_return([[], [@output], []])
|
145
|
-
@output.should_receive(:close).once
|
146
|
-
flexmock(::Process).should_receive(:waitpid2).with(42, ::Process::WNOHANG).twice.and_return(nil)
|
147
|
-
a.tick.should be_false
|
148
|
-
a.tick.should be_false
|
149
|
-
end
|
150
|
-
end
|
151
|
-
|
152
|
-
it 'should update the status if waitpid is successful' do
|
153
|
-
a = Accumulator.new(@process, [], [], [], [])
|
154
|
-
status = flexmock("status")
|
155
|
-
flexmock(::Process).should_receive(:waitpid2).with(42, ::Process::WNOHANG).once.and_return(status)
|
156
|
-
a.tick.should be_true
|
157
|
-
end
|
158
|
-
|
159
|
-
it 'should return true if the process has already been waited on' do
|
160
|
-
a = Accumulator.new(@process, [], [], [], [])
|
161
|
-
status = flexmock("status")
|
162
|
-
flexmock(::Process).should_receive(:waitpid2).with(42, ::Process::WNOHANG).once.and_return(status)
|
163
|
-
a.tick.should be_true
|
164
|
-
a.tick.should be_true
|
165
|
-
end
|
166
|
-
end
|
167
|
-
|
168
|
-
describe "#number_waiting_on" do
|
169
|
-
it 'should return 0 when no pipes are left' do
|
170
|
-
a = Accumulator.new(@process, [], [], [], [])
|
171
|
-
a.number_waiting_on.should == 0
|
172
|
-
end
|
173
|
-
|
174
|
-
it 'should return 1 when one pipe is left' do
|
175
|
-
pipe = flexmock("pipe")
|
176
|
-
a = Accumulator.new(@process, [pipe], [nil], [], [])
|
177
|
-
a.number_waiting_on.should == 1
|
178
|
-
end
|
179
|
-
|
180
|
-
it 'should return add readers and writers' do
|
181
|
-
pipe = flexmock("pipe")
|
182
|
-
a = Accumulator.new(@process, [pipe, pipe, pipe], [], [pipe], [])
|
183
|
-
a.number_waiting_on.should == 4
|
184
|
-
end
|
185
|
-
|
186
|
-
it 'should transition from 0 to 1 as pipes are removed' do
|
187
|
-
pipe = flexmock("pipe")
|
188
|
-
read = flexmock("read")
|
189
|
-
a = Accumulator.new(@process, [pipe], [read], [], [])
|
190
|
-
flexmock(::IO).should_receive(:select).with([pipe], [], nil, 0.1).once.and_return([[pipe], [], []])
|
191
|
-
pipe.should_receive(:eof?).and_return(true)
|
192
|
-
pipe.should_receive(:close)
|
193
|
-
flexmock(::Process).should_receive(:waitpid2).with(42, ::Process::WNOHANG).once.and_return(nil)
|
194
|
-
a.number_waiting_on.should == 1
|
195
|
-
a.tick.should be_false
|
196
|
-
a.number_waiting_on.should == 0
|
197
|
-
end
|
198
|
-
end
|
199
|
-
|
200
|
-
describe "#cleanup" do
|
201
|
-
it 'should do nothing if no pipes are left open and the process is reaped' do
|
202
|
-
pending 'needs refactoring if actually in use'
|
203
|
-
a = Accumulator.new(@process, [], [], [], [])
|
204
|
-
flexmock(::Process).should_receive(:waitpid2).never
|
205
|
-
a.cleanup
|
206
|
-
end
|
207
|
-
|
208
|
-
it 'should just call waitpid if no pipes are left open' do
|
209
|
-
value = flexmock("value")
|
210
|
-
a = Accumulator.new(@process, [], [], [], [])
|
211
|
-
flexmock(::Process).should_receive(:waitpid2).with(42).once.and_return(value)
|
212
|
-
a.cleanup
|
213
|
-
end
|
214
|
-
|
215
|
-
it 'should close all open pipes' do
|
216
|
-
pending 'needs refactoring if actually in use'
|
217
|
-
a, b, c = flexmock("a"), flexmock("b"), flexmock("c")
|
218
|
-
acc = Accumulator.new(@process, [a, b], [], [c], [])
|
219
|
-
[a, b].each {|fdes| fdes.should_receive(:close).with().once }
|
220
|
-
[a, b].each {|fdes| fdes.should_receive(:closed?).and_return(false) }
|
221
|
-
[c].each {|fdes| fdes.should_receive(:closed?).and_return(true) }
|
222
|
-
flexmock(::Process).should_receive(:waitpid2).never
|
223
|
-
acc.cleanup
|
224
|
-
end
|
225
|
-
|
226
|
-
it 'should close all open pipes and reap zombies if needed' do
|
227
|
-
value = flexmock("value")
|
228
|
-
a, b, c = flexmock("a"), flexmock("b"), flexmock("c")
|
229
|
-
acc = Accumulator.new(@process, [b, c], [], [a], [])
|
230
|
-
[a, b].each {|fdes| fdes.should_receive(:close).with().once }
|
231
|
-
[a, b].each {|fdes| fdes.should_receive(:closed?).and_return(false) }
|
232
|
-
[c].each {|fdes| fdes.should_receive(:closed?).and_return(true) }
|
233
|
-
flexmock(::Process).should_receive(:waitpid2).with(42).once.and_return(value)
|
234
|
-
acc.cleanup
|
235
|
-
end
|
236
|
-
end
|
237
|
-
|
238
|
-
describe "#run_to_completion" do
|
239
|
-
it 'should run ticks until it is true' do
|
240
|
-
value = flexmock("value")
|
241
|
-
acc = flexmock(Accumulator.new(@process, [], [], [], []))
|
242
|
-
acc.should_receive(:tick).with(value).times(3).and_return(false).and_return(false).and_return(true)
|
243
|
-
acc.should_receive(:cleanup).once
|
244
|
-
acc.should_receive(:number_waiting_on).and_return(1)
|
245
|
-
acc.run_to_completion(value)
|
246
|
-
end
|
247
|
-
|
248
|
-
it 'should abort the loop early if there are no remaining pipes' do
|
249
|
-
value = flexmock("value")
|
250
|
-
acc = flexmock(Accumulator.new(@process, [], [], [], []))
|
251
|
-
acc.should_receive(:tick).with(value).twice.and_return(false)
|
252
|
-
acc.should_receive(:cleanup).once
|
253
|
-
acc.should_receive(:number_waiting_on).and_return(1).and_return(0)
|
254
|
-
acc.run_to_completion(value)
|
255
|
-
end
|
256
|
-
|
257
|
-
it 'should abort the loop after one iteration if there never were any pipes' do
|
258
|
-
value = flexmock("value")
|
259
|
-
acc = flexmock(Accumulator.new(@process, [], [], [], []))
|
260
|
-
acc.should_receive(:tick).with(value).once.and_return(false)
|
261
|
-
acc.should_receive(:cleanup).once
|
262
|
-
acc.should_receive(:number_waiting_on).and_return(0)
|
263
|
-
acc.run_to_completion(value)
|
264
|
-
end
|
265
|
-
end
|
266
|
-
end
|
267
|
-
end
|