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.
@@ -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
- require 'rubygems'
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 = '1.0.11'
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
- end
57
-
58
- if $PROGRAM_NAME == __FILE__
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
@@ -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