gorgon 0.2.0 → 0.3.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.
- data/.gitignore +3 -1
- data/Gemfile.lock +9 -9
- data/README.md +33 -9
- data/bin/gorgon +9 -0
- data/gorgon.json.sample +29 -0
- data/gorgon_listener.json.sample +8 -0
- data/lib/gorgon/colors.rb +1 -0
- data/lib/gorgon/crash_reporter.rb +19 -0
- data/lib/gorgon/gem_command_handler.rb +46 -0
- data/lib/gorgon/gem_service.rb +77 -0
- data/lib/gorgon/job_state.rb +4 -4
- data/lib/gorgon/listener.rb +26 -21
- data/lib/gorgon/originator_protocol.rb +9 -8
- data/lib/gorgon/ping_service.rb +2 -2
- data/lib/gorgon/pipe_forker.rb +22 -0
- data/lib/gorgon/source_tree_syncer.rb +2 -1
- data/lib/gorgon/version.rb +1 -1
- data/lib/gorgon/worker.rb +64 -30
- data/lib/gorgon/worker_manager.rb +42 -12
- data/spec/crash_reporter_spec.rb +46 -0
- data/spec/gem_command_handler_spec.rb +84 -0
- data/spec/gem_service_spec.rb +74 -0
- data/spec/job_state_spec.rb +0 -7
- data/spec/listener_spec.rb +37 -19
- data/spec/originator_protocol_spec.rb +12 -9
- data/spec/ping_service_spec.rb +3 -3
- data/spec/pipe_forker_spec.rb +53 -0
- data/spec/worker_manager_spec.rb +34 -5
- data/spec/worker_spec.rb +63 -1
- metadata +12 -4
- data/lib/gorgon/pipe_manager.rb +0 -55
- data/lib/gorgon/worker_watcher.rb +0 -22
@@ -0,0 +1,74 @@
|
|
1
|
+
require 'gorgon/gem_service'
|
2
|
+
|
3
|
+
describe GemService do
|
4
|
+
let(:configuration){ {:connection => {:host => "host"}, :originator_log_file => "file.log"}}
|
5
|
+
let(:protocol) { stub("OriginatorProtocol", :connect => nil,
|
6
|
+
:receive_payloads => nil, :disconnect => nil,
|
7
|
+
:send_message_to_listeners => nil)}
|
8
|
+
let(:logger){ stub("Originator Logger", :log => nil, :log_message => nil)}
|
9
|
+
|
10
|
+
before do
|
11
|
+
$stdout.stub!(:write)
|
12
|
+
GemService.any_instance.stub(:load_configuration_from_file).and_return configuration
|
13
|
+
EM.stub!(:run).and_yield
|
14
|
+
EM.stub!(:add_periodic_timer).and_yield
|
15
|
+
OriginatorLogger.stub!(:new).and_return logger
|
16
|
+
OriginatorProtocol.stub!(:new).and_return(protocol)
|
17
|
+
@service = GemService.new
|
18
|
+
@command = "install"
|
19
|
+
end
|
20
|
+
|
21
|
+
describe "#run" do
|
22
|
+
it "runs EventMachine loop and connect using configuration[:connection]" do
|
23
|
+
EM.should_receive(:run)
|
24
|
+
protocol.should_receive(:connect).once.ordered.with({:host => "host"}, anything)
|
25
|
+
@service.run @command
|
26
|
+
end
|
27
|
+
|
28
|
+
it "calls Protocol#send_message_to_listeners with version number" do
|
29
|
+
protocol.should_receive(:send_message_to_listeners).with(:gem_command, :gem_command => @command)
|
30
|
+
@service.run @command
|
31
|
+
end
|
32
|
+
|
33
|
+
it "adds a periodic timer that checks if there is any listener running command" do
|
34
|
+
EM.should_receive(:add_periodic_timer).with(GemService::TIMEOUT)
|
35
|
+
@service.run @command
|
36
|
+
end
|
37
|
+
|
38
|
+
context "when it receives an running_command message" do
|
39
|
+
before do
|
40
|
+
payload = {:type => "running_command", :hostname => "host"}
|
41
|
+
protocol.stub!(:receive_payloads).and_yield Yajl::Encoder.encode(payload)
|
42
|
+
end
|
43
|
+
|
44
|
+
it "writes to console" do
|
45
|
+
$stdout.should_receive(:write).with(/host/)
|
46
|
+
@service.run @command
|
47
|
+
end
|
48
|
+
|
49
|
+
it "won't diconnect as long as there is a host running_command" do
|
50
|
+
protocol.should_not_receive(:disconnect)
|
51
|
+
@service.run @command
|
52
|
+
end
|
53
|
+
end
|
54
|
+
|
55
|
+
context "when it receives an command_completed message" do
|
56
|
+
before do
|
57
|
+
running_command_payload = {:type => "running_command", :hostname => "host"}
|
58
|
+
complete_payload = {:type => "command_completed", :hostname => "host"}
|
59
|
+
protocol.stub!(:receive_payloads).and_yield(Yajl::Encoder.encode(running_command_payload))
|
60
|
+
.and_yield(Yajl::Encoder.encode(complete_payload))
|
61
|
+
end
|
62
|
+
|
63
|
+
it "writes to console" do
|
64
|
+
$stdout.should_receive(:write).at_least(:twice).with(/host/)
|
65
|
+
@service.run @command
|
66
|
+
end
|
67
|
+
|
68
|
+
it "disconnect since there is no host running command anymore" do
|
69
|
+
protocol.should_receive(:disconnect)
|
70
|
+
@service.run @command
|
71
|
+
end
|
72
|
+
end
|
73
|
+
end
|
74
|
+
end
|
data/spec/job_state_spec.rb
CHANGED
@@ -123,13 +123,6 @@ describe JobState do
|
|
123
123
|
end.should raise_error
|
124
124
|
end
|
125
125
|
|
126
|
-
it "raises if job was cancelled" do
|
127
|
-
@job_state.cancel
|
128
|
-
lambda do
|
129
|
-
@job_state.file_finished payload
|
130
|
-
end.should raise_error
|
131
|
-
end
|
132
|
-
|
133
126
|
it "tells to the proper HostState object that a file finished in that host" do
|
134
127
|
HostState.stub!(:new).and_return host_state
|
135
128
|
@job_state.file_started({:hostname => "hostname",
|
data/spec/listener_spec.rb
CHANGED
@@ -136,21 +136,45 @@ describe Listener do
|
|
136
136
|
|
137
137
|
context "ping message pending on queue" do
|
138
138
|
let(:ping_payload) {{
|
139
|
-
:payload => Yajl::Encoder.encode({:type => "ping", :reply_exchange_name => "name"
|
139
|
+
:payload => Yajl::Encoder.encode({:type => "ping", :reply_exchange_name => "name",
|
140
|
+
:body => {}}) }}
|
140
141
|
|
141
142
|
before do
|
142
143
|
queue.stub!(:pop => ping_payload)
|
143
|
-
|
144
|
+
listener.stub(:configuration).and_return({:worker_slots => 3})
|
145
|
+
end
|
144
146
|
|
145
147
|
it "publishes ping_response message with Gorgon's version" do
|
146
148
|
listener.should_not_receive(:run_job)
|
147
149
|
bunny.should_receive(:exchange).with("name", anything).and_return(exchange)
|
148
150
|
response = {:type => "ping_response", :hostname => Socket.gethostname,
|
149
|
-
:version => Gorgon::VERSION}
|
151
|
+
:version => Gorgon::VERSION, :worker_slots => 3}
|
150
152
|
exchange.should_receive(:publish).with(Yajl::Encoder.encode(response))
|
151
153
|
listener.poll
|
152
154
|
end
|
153
155
|
end
|
156
|
+
|
157
|
+
context "gem_command message pending on queue" do
|
158
|
+
let(:command) { "install" }
|
159
|
+
|
160
|
+
let(:payload) {
|
161
|
+
{:type => "gem_command", :reply_exchange_name => "name",
|
162
|
+
:body => {:command => command}}
|
163
|
+
}
|
164
|
+
|
165
|
+
let(:gem_command_handler) { stub("GemCommandHandler", :handle => nil) }
|
166
|
+
let(:configuration) { {:worker_slots => 3} }
|
167
|
+
before do
|
168
|
+
queue.stub!(:pop => {:payload => Yajl::Encoder.encode(payload)})
|
169
|
+
listener.stub(:configuration).and_return(configuration)
|
170
|
+
end
|
171
|
+
|
172
|
+
it "calls GemCommandHandler#handle and pass payload" do
|
173
|
+
GemCommandHandler.should_receive(:new).with(bunny).and_return gem_command_handler
|
174
|
+
gem_command_handler.should_receive(:handle).with payload, configuration
|
175
|
+
listener.poll
|
176
|
+
end
|
177
|
+
end
|
154
178
|
end
|
155
179
|
|
156
180
|
describe "#run_job" do
|
@@ -194,23 +218,22 @@ describe Listener do
|
|
194
218
|
end
|
195
219
|
|
196
220
|
it "sends message to originator with output and errors from syncer" do
|
197
|
-
|
198
|
-
|
199
|
-
@listener.run_job(reply)
|
221
|
+
@listener.should_receive(:send_crash_message).with exchange, "some output", "some errors"
|
222
|
+
@listener.run_job(payload)
|
200
223
|
end
|
201
224
|
end
|
202
225
|
|
203
|
-
context "Worker Manager
|
226
|
+
context "Worker Manager crashes" do
|
204
227
|
before do
|
205
|
-
process_status.should_receive(:exitstatus).and_return
|
228
|
+
process_status.should_receive(:exitstatus).and_return 2, 2
|
206
229
|
end
|
207
230
|
|
208
|
-
it "
|
209
|
-
|
210
|
-
|
211
|
-
|
212
|
-
|
213
|
-
@listener.run_job(
|
231
|
+
it "report_crash with pid, exitstatus, stdout and stderr outputs" do
|
232
|
+
@listener.should_receive(:report_crash).with(exchange,
|
233
|
+
:out_file => WorkerManager::STDOUT_FILE,
|
234
|
+
:err_file => WorkerManager::STDERR_FILE,
|
235
|
+
:footer_text => Listener::ERROR_FOOTER_TEXT)
|
236
|
+
@listener.run_job(payload)
|
214
237
|
end
|
215
238
|
end
|
216
239
|
|
@@ -228,11 +251,6 @@ describe Listener do
|
|
228
251
|
callback_handler.should_receive(:after_sync).once
|
229
252
|
@listener.run_job(payload)
|
230
253
|
end
|
231
|
-
|
232
|
-
it "uses Bundler#with_clean_env so the workers load new gems that could have been installed in after_sync" do
|
233
|
-
Bundler.should_receive(:with_clean_env).and_yield
|
234
|
-
@listener.run_job(payload)
|
235
|
-
end
|
236
254
|
end
|
237
255
|
|
238
256
|
private
|
@@ -4,7 +4,7 @@ describe OriginatorProtocol do
|
|
4
4
|
let(:connection) { stub("Connection", :disconnect => nil, :on_closed => nil)}
|
5
5
|
let(:queue) { stub("Queue", :bind => nil, :subscribe => nil, :name => "queue", :purge => nil,
|
6
6
|
:delete => nil) }
|
7
|
-
let(:exchange) { stub("Exchange", :publish => nil, :name => "exchange") }
|
7
|
+
let(:exchange) { stub("Exchange", :publish => nil, :name => "exchange", :delete => nil) }
|
8
8
|
let(:channel) { stub("Channel", :queue => queue, :direct => exchange, :fanout => exchange,
|
9
9
|
:default_exchange => exchange) }
|
10
10
|
let(:logger){ stub("Logger", :log => nil)}
|
@@ -39,13 +39,13 @@ describe OriginatorProtocol do
|
|
39
39
|
|
40
40
|
it "opens a reply and exchange queue" do
|
41
41
|
UUIDTools::UUID.stub!(:timestamp_create).and_return 1
|
42
|
-
channel.should_receive(:queue).once.with("
|
42
|
+
channel.should_receive(:queue).once.with("reply_queue_1")
|
43
43
|
@originator_p.connect @conn_information
|
44
44
|
end
|
45
45
|
|
46
46
|
it "opens a reply exchange and binds reply queue to it" do
|
47
47
|
UUIDTools::UUID.stub!(:timestamp_create).and_return 1
|
48
|
-
channel.should_receive(:direct).with("
|
48
|
+
channel.should_receive(:direct).with("reply_exchange_1")
|
49
49
|
queue.should_receive(:bind).with(exchange)
|
50
50
|
@originator_p.connect @conn_information
|
51
51
|
end
|
@@ -82,16 +82,18 @@ describe OriginatorProtocol do
|
|
82
82
|
end
|
83
83
|
end
|
84
84
|
|
85
|
-
describe "#
|
85
|
+
describe "#send_message_to_listeners" do
|
86
86
|
before do
|
87
87
|
@originator_p.connect @conn_information
|
88
88
|
end
|
89
89
|
|
90
|
-
it "adds reply_exchange_name to
|
91
|
-
expected_msg = {:type =>
|
90
|
+
it "adds type and reply_exchange_name to message and fanouts it using 'gorgon.jobs' exchange" do
|
91
|
+
expected_msg = {:type => :msg_type, :reply_exchange_name => "exchange",
|
92
|
+
:body => {:data => 'data'}}
|
93
|
+
Yajl::Encoder.should_receive(:encode).with(expected_msg).and_return :msg
|
92
94
|
channel.should_receive(:fanout).once.ordered.with("gorgon.jobs")
|
93
|
-
exchange.should_receive(:publish).once.ordered.with(
|
94
|
-
@originator_p.
|
95
|
+
exchange.should_receive(:publish).once.ordered.with(:msg)
|
96
|
+
@originator_p.send_message_to_listeners :msg_type, :data => 'data'
|
95
97
|
end
|
96
98
|
end
|
97
99
|
|
@@ -136,9 +138,10 @@ describe OriginatorProtocol do
|
|
136
138
|
@originator_p.connect @conn_information
|
137
139
|
end
|
138
140
|
|
139
|
-
it "deletes reply and file
|
141
|
+
it "deletes reply_exchange and reply and file queues" do
|
140
142
|
@originator_p.publish_files []
|
141
143
|
queue.should_receive(:delete).twice
|
144
|
+
exchange.should_receive(:delete)
|
142
145
|
@originator_p.disconnect
|
143
146
|
end
|
144
147
|
|
data/spec/ping_service_spec.rb
CHANGED
@@ -5,7 +5,7 @@ describe "PingService" do
|
|
5
5
|
let(:configuration){ {:connection => {:host => "host"}, :originator_log_file => "file.log"}}
|
6
6
|
let(:protocol) { stub("OriginatorProtocol", :connect => nil, :ping => nil,
|
7
7
|
:receive_payloads => nil, :disconnect => nil,
|
8
|
-
:
|
8
|
+
:send_message_to_listeners => nil)}
|
9
9
|
let(:logger){ stub("Originator Logger", :log => nil, :log_message => nil)}
|
10
10
|
|
11
11
|
before do
|
@@ -16,10 +16,10 @@ describe "PingService" do
|
|
16
16
|
OriginatorLogger.stub!(:new).and_return logger
|
17
17
|
end
|
18
18
|
|
19
|
-
it "connnects and calls OriginatorProtocol#
|
19
|
+
it "connnects and calls OriginatorProtocol#send_message_to_listeners" do
|
20
20
|
OriginatorProtocol.should_receive(:new).once.ordered.and_return(protocol)
|
21
21
|
protocol.should_receive(:connect).once.ordered.with({:host => "host"}, anything)
|
22
|
-
protocol.should_receive(:
|
22
|
+
protocol.should_receive(:send_message_to_listeners).once.ordered
|
23
23
|
PingService.new.ping_listeners
|
24
24
|
end
|
25
25
|
|
@@ -0,0 +1,53 @@
|
|
1
|
+
require 'gorgon/pipe_forker'
|
2
|
+
|
3
|
+
describe PipeForker do
|
4
|
+
let(:io_pipe) { stub("IO object", :close => nil)}
|
5
|
+
let(:pipe) {stub("Pipe", :write => io_pipe)}
|
6
|
+
|
7
|
+
let(:container_class) do
|
8
|
+
Class.new do
|
9
|
+
extend(PipeForker)
|
10
|
+
end
|
11
|
+
end
|
12
|
+
|
13
|
+
before do
|
14
|
+
IO.stub!(:pipe).and_return([io_pipe, io_pipe])
|
15
|
+
STDIN.stub!(:reopen)
|
16
|
+
container_class.stub!(:fork).and_yield.and_return(1)
|
17
|
+
container_class.stub!(:exit)
|
18
|
+
end
|
19
|
+
|
20
|
+
describe ".pipe_fork" do
|
21
|
+
it "creates a new pipe" do
|
22
|
+
IO.should_receive(:pipe).once.and_return ([io_pipe,io_pipe])
|
23
|
+
container_class.pipe_fork { }
|
24
|
+
end
|
25
|
+
|
26
|
+
it "forks once" do
|
27
|
+
container_class.should_receive(:fork).and_yield
|
28
|
+
container_class.pipe_fork { }
|
29
|
+
end
|
30
|
+
|
31
|
+
it "closes both side of pipe inside child and read in parent" do
|
32
|
+
io_pipe.should_receive(:close).exactly(3).times
|
33
|
+
container_class.pipe_fork { }
|
34
|
+
end
|
35
|
+
|
36
|
+
it "reopens stdin with a pipe" do
|
37
|
+
STDIN.should_receive(:reopen).with(io_pipe)
|
38
|
+
container_class.pipe_fork { }
|
39
|
+
end
|
40
|
+
|
41
|
+
it "yields" do
|
42
|
+
has_yielded = false
|
43
|
+
container_class.pipe_fork { has_yielded = true }
|
44
|
+
has_yielded.should be_true
|
45
|
+
end
|
46
|
+
|
47
|
+
it "returns pid of new process and a pipe" do
|
48
|
+
pid, stdin = container_class.pipe_fork { }
|
49
|
+
pid.should be 1
|
50
|
+
stdin.should == io_pipe
|
51
|
+
end
|
52
|
+
end
|
53
|
+
end
|
data/spec/worker_manager_spec.rb
CHANGED
@@ -7,17 +7,46 @@ describe WorkerManager do
|
|
7
7
|
let(:bunny) { stub("Bunny", :start => nil, :exchange => exchange,
|
8
8
|
:queue => queue, :stop => nil) }
|
9
9
|
before do
|
10
|
-
|
11
|
-
|
12
|
-
|
10
|
+
STDIN.stub!(:read).and_return "{}"
|
11
|
+
STDOUT.stub!(:reopen)
|
12
|
+
STDERR.stub!(:reopen)
|
13
|
+
STDOUT.stub!(:sync)
|
14
|
+
STDERR.stub!(:sync)
|
15
|
+
Bunny.stub!(:new).and_return(bunny)
|
16
|
+
Configuration.stub!(:load_configuration_from_file).and_return({})
|
17
|
+
EventMachine.stub!(:run).and_yield
|
13
18
|
end
|
14
19
|
|
15
20
|
describe ".build" do
|
16
21
|
it "should load_configuration_from_file" do
|
17
|
-
|
18
|
-
|
22
|
+
STDIN.should_receive(:read).and_return '{"source_tree_path":"path/to/source",
|
23
|
+
"sync_exclude":["log"]}'
|
24
|
+
|
25
|
+
Configuration.should_receive(:load_configuration_from_file).with("file.json").and_return({})
|
19
26
|
|
20
27
|
WorkerManager.build "file.json"
|
21
28
|
end
|
29
|
+
|
30
|
+
it "redirect output to a file since writing to a pipe may block when pipe is full" do
|
31
|
+
File.should_receive(:open).with(WorkerManager::STDOUT_FILE, 'w').and_return(:file1)
|
32
|
+
STDOUT.should_receive(:reopen).with(:file1)
|
33
|
+
File.should_receive(:open).with(WorkerManager::STDERR_FILE, 'w').and_return(:file2)
|
34
|
+
STDERR.should_receive(:reopen).with(:file2)
|
35
|
+
WorkerManager.build ""
|
36
|
+
end
|
37
|
+
|
38
|
+
it "use STDOUT#sync to flush output immediately so if an exception happens, we can grab the last\
|
39
|
+
few lines of output and send it to originator. Order matters" do
|
40
|
+
STDOUT.should_receive(:reopen).once.ordered
|
41
|
+
STDOUT.should_receive(:sync=).with(true).once.ordered
|
42
|
+
WorkerManager.build ""
|
43
|
+
end
|
44
|
+
|
45
|
+
it "use STDERR#sync to flush output immediately so if an exception happens, we can grab the last\
|
46
|
+
few lines of output and send it to originator. Order matters" do
|
47
|
+
STDERR.should_receive(:reopen).once.ordered
|
48
|
+
STDERR.should_receive(:sync=).with(true).once.ordered
|
49
|
+
WorkerManager.build ""
|
50
|
+
end
|
22
51
|
end
|
23
52
|
end
|
data/spec/worker_spec.rb
CHANGED
@@ -18,6 +18,9 @@ describe Worker do
|
|
18
18
|
let(:fake_amqp) { fake_amqp = FakeAmqp.new file_queue, reply_exchange }
|
19
19
|
let(:test_runner) { double("Test Runner") }
|
20
20
|
let(:callback_handler) { stub("Callback Handler", :before_start => nil, :after_complete => nil) }
|
21
|
+
let(:job_definition) {stub("JobDefinition", :callbacks => ["/path/to/callback"],
|
22
|
+
:file_queue_name => "queue",
|
23
|
+
:reply_exchange_name => "exchange")}
|
21
24
|
|
22
25
|
let(:params) {
|
23
26
|
{
|
@@ -26,11 +29,61 @@ describe Worker do
|
|
26
29
|
:reply_exchange_name => "exchange",
|
27
30
|
:worker_id => WORKER_ID,
|
28
31
|
:test_runner => test_runner,
|
29
|
-
:callback_handler => callback_handler
|
32
|
+
:callback_handler => callback_handler,
|
33
|
+
:log_file => "path/to/log_file"
|
30
34
|
}
|
31
35
|
}
|
32
36
|
|
37
|
+
describe ".build" do
|
38
|
+
let(:config) { {:connection => "", :log_file => "path/to/log_file"} }
|
39
|
+
before do
|
40
|
+
stub_streams
|
41
|
+
AmqpService.stub!(:new).and_return fake_amqp
|
42
|
+
CallbackHandler.stub!(:new).and_return callback_handler
|
43
|
+
Worker.stub!(:new)
|
44
|
+
end
|
45
|
+
|
46
|
+
it "redirects output to a file since writing to a pipe may block when pipe is full" do
|
47
|
+
File.should_receive(:open).with(Worker.output_file(1, :out), 'w').and_return(:file1)
|
48
|
+
STDOUT.should_receive(:reopen).with(:file1)
|
49
|
+
File.should_receive(:open).with(Worker.output_file(1, :err), 'w').and_return(:file2)
|
50
|
+
STDERR.should_receive(:reopen).with(:file2)
|
51
|
+
Worker.build 1, config
|
52
|
+
end
|
53
|
+
|
54
|
+
it "use STDOUT#sync to flush output immediately so if an exception happens, we can grab the last\
|
55
|
+
few lines of output and send it to originator. Order matters" do
|
56
|
+
STDOUT.should_receive(:reopen).once.ordered
|
57
|
+
STDOUT.should_receive(:sync=).with(true).once.ordered
|
58
|
+
Worker.build 1, config
|
59
|
+
end
|
60
|
+
|
61
|
+
it "use STDERR#sync to flush output immediately so if an exception happens, we can grab the last\
|
62
|
+
few lines of output and send it to originator. Order matters" do
|
63
|
+
STDERR.should_receive(:reopen).once.ordered
|
64
|
+
STDERR.should_receive(:sync=).with(true).once.ordered
|
65
|
+
Worker.build 1, config
|
66
|
+
end
|
67
|
+
|
68
|
+
it "creates a JobDefinition using a payload written to stdin" do
|
69
|
+
STDIN.should_receive(:read).and_return '{ "key": "value" }'
|
70
|
+
JobDefinition.should_receive(:new).with({:key => "value"}).and_return job_definition
|
71
|
+
Worker.build 1, config
|
72
|
+
end
|
73
|
+
|
74
|
+
it "creates a new worker" do
|
75
|
+
JobDefinition.stub!(:new).and_return job_definition
|
76
|
+
stub_const("WorkUnit", test_runner)
|
77
|
+
Worker.should_receive(:new).with(params)
|
78
|
+
Worker.build 1, config
|
79
|
+
end
|
80
|
+
end
|
81
|
+
|
33
82
|
describe '#work' do
|
83
|
+
before do
|
84
|
+
Worker.any_instance.stub(:initialize_logger)
|
85
|
+
end
|
86
|
+
|
34
87
|
it 'should do nothing if the file queue is empty' do
|
35
88
|
file_queue.should_receive(:pop).and_return(nil)
|
36
89
|
|
@@ -111,4 +164,13 @@ describe Worker do
|
|
111
164
|
|
112
165
|
end
|
113
166
|
|
167
|
+
private
|
168
|
+
|
169
|
+
def stub_streams
|
170
|
+
STDIN.stub!(:read).and_return "{}"
|
171
|
+
STDOUT.stub!(:reopen)
|
172
|
+
STDERR.stub!(:reopen)
|
173
|
+
STDOUT.stub!(:sync)
|
174
|
+
STDERR.stub!(:sync)
|
175
|
+
end
|
114
176
|
end
|