daemon_controller 1.2.0 → 3.0.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.
@@ -1,424 +1,701 @@
1
- require File.expand_path(File.join(File.dirname(__FILE__), "test_helper"))
2
- require 'daemon_controller'
3
- require 'benchmark'
4
- require 'socket'
1
+ # frozen_string_literal: true
2
+
3
+ require_relative "test_helper"
4
+ require "daemon_controller"
5
+ require "benchmark"
6
+ require "socket"
7
+ require "tmpdir"
8
+ require "shellwords"
9
+ require "stringio"
10
+ require "logger"
5
11
 
6
12
  describe DaemonController, "#start" do
7
- before :each do
8
- new_controller
9
- end
10
-
11
- include TestHelper
12
-
13
- it "works" do
14
- @controller.start
15
- ping_echo_server.should be_true
16
- @controller.stop
17
- end
18
-
19
- it "raises AlreadyStarted if the daemon is already running" do
20
- @controller.should_receive(:daemon_is_running?).and_return(true)
21
- lambda { @controller.start }.should raise_error(DaemonController::AlreadyStarted)
22
- end
23
-
24
- it "deletes existing PID file before starting the daemon" do
25
- write_file('spec/echo_server.pid', '1234')
26
- @controller.should_receive(:daemon_is_running?).and_return(false)
27
- @controller.should_receive(:spawn_daemon)
28
- @controller.should_receive(:pid_file_available?).and_return(true)
29
- @controller.should_receive(:run_ping_command).at_least(:once).and_return(true)
30
- @controller.start
31
- File.exist?('spec/echo_server.pid').should be_false
32
- end
33
-
34
- it "blocks until the daemon has written to its PID file" do
35
- thread = WaitingThread.new do
36
- sleep 0.15
37
- write_file('spec/echo_server.pid', '1234')
38
- end
39
- @controller.should_receive(:daemon_is_running?).and_return(false)
40
- @controller.should_receive(:spawn_daemon).and_return do
41
- thread.go!
42
- end
43
- @controller.should_receive(:run_ping_command).at_least(:once).and_return(true)
44
- begin
45
- result = Benchmark.measure do
46
- @controller.start
47
- end
48
- (0.15 .. 0.30).should === result.real
49
- ensure
50
- thread.join
51
- end
52
- end
53
-
54
- it "blocks until the daemon can be pinged" do
55
- ping_ok = false
56
- running = false
57
- thread = WaitingThread.new do
58
- sleep 0.15
59
- ping_ok = true
60
- end
61
- @controller.should_receive(:daemon_is_running?).at_least(:once).and_return do
62
- running
63
- end
64
- @controller.should_receive(:spawn_daemon).and_return do
65
- thread.go!
66
- running = true
67
- end
68
- @controller.should_receive(:pid_file_available?).and_return(true)
69
- @controller.should_receive(:run_ping_command).at_least(:once).and_return do
70
- ping_ok
71
- end
72
- begin
73
- result = Benchmark.measure do
74
- @controller.start
75
- end
76
- (0.15 .. 0.30).should === result.real
77
- ensure
78
- thread.join
79
- end
80
- end
81
-
82
- it "raises StartTimeout if the daemon doesn't start in time" do
83
- if exec_is_slow?
84
- start_timeout = 4
85
- min_start_timeout = 0
86
- max_start_timeout = 6
87
- else
88
- start_timeout = 0.15
89
- min_start_timeout = 0.15
90
- max_start_timeout = 0.30
91
- end
92
- new_controller(:start_command => 'sleep 2', :start_timeout => start_timeout)
93
- start_time = Time.now
94
- end_time = nil
95
- @controller.should_receive(:start_timed_out).and_return do
96
- end_time = Time.now
97
- end
98
- begin
99
- lambda { @controller.start }.should raise_error(DaemonController::StartTimeout)
100
- (min_start_timeout .. max_start_timeout).should === end_time - start_time
101
- ensure
102
- @controller.stop
103
- end
104
- end
105
-
106
- it "kills the daemon forcefully if the daemon has forked but doesn't " <<
107
- "become pingable in time, and there's a PID file" do
108
- new_controller(:wait2 => 3, :start_timeout => 1)
109
- pid = nil
110
- @controller.should_receive(:start_timed_out).and_return do
111
- @controller.send(:wait_until) do
112
- @controller.send(:pid_file_available?)
113
- end
114
- pid = @controller.send(:read_pid_file)
115
- end
116
- begin
117
- block = lambda { @controller.start }
118
- block.should raise_error(DaemonController::StartTimeout, /failed to start in time/)
119
- eventually(1) do
120
- !process_is_alive?(pid)
121
- end
122
-
123
- # The daemon should not be able to clean up its PID file since
124
- # it's killed with SIGKILL.
125
- File.exist?("spec/echo_server.pid").should be_true
126
- ensure
127
- File.unlink("spec/echo_server.pid") rescue nil
128
- end
129
- end
130
-
131
- if DaemonController.send(:fork_supported?) || DaemonController.send(:spawn_supported?)
132
- it "kills the daemon if it doesn't start in time and hasn't forked " <<
133
- "yet, on platforms where Ruby supports fork() or Process.spawn" do
134
- begin
135
- new_controller(:start_command => "./spec/unresponsive_daemon.rb",
136
- :start_timeout => 0.2)
137
- pid = nil
138
- @controller.should_receive(:daemonization_timed_out).and_return do
139
- @controller.send(:wait_until) do
140
- @controller.send(:pid_file_available?)
141
- end
142
- pid = @controller.send(:read_pid_file)
143
- end
144
- block = lambda { @controller.start }
145
- block.should raise_error(DaemonController::StartTimeout, /didn't daemonize in time/)
146
- eventually(1) do
147
- !process_is_alive?(pid)
148
- end
149
- ensure
150
- File.unlink("spec/echo_server.pid") rescue nil
151
- end
152
- end
153
- end
154
-
155
- it "raises an error if the daemon exits with an error before forking" do
156
- new_controller(:start_command => 'false')
157
- lambda { @controller.start }.should raise_error(DaemonController::Error)
158
- end
159
-
160
- it "raises an error if the daemon exits with an error after forking" do
161
- new_controller(:crash_before_bind => true, :log_file_activity_timeout => 0.2)
162
- lambda { @controller.start }.should raise_error(DaemonController::Error)
163
- end
164
-
165
- specify "the daemon's error output before forking is made available in the exception" do
166
- new_controller(:start_command => '(echo hello world; false)')
167
- begin
168
- @controller.start
169
- rescue DaemonController::Error => e
170
- e.message.should == "hello world"
171
- end
172
- end
173
-
174
- specify "the daemon's error output after forking is made available in the exception" do
175
- new_controller(:crash_before_bind => true, :log_file_activity_timeout => 0.1)
176
- begin
177
- @controller.start
178
- violated
179
- rescue DaemonController::StartTimeout => e
180
- e.message.should =~ /crashing, as instructed/
181
- end
182
- end
183
-
184
- specify "the start command may be a Proc" do
185
- called = true
186
- new_controller(:start_command => lambda { called = true; @start_command })
187
- begin
188
- @controller.start
189
- ensure
190
- @controller.stop
191
- end
192
- called.should be_true
193
- end
194
-
195
- specify "if the start command is a Proc then it is called after before_start" do
196
- log = []
197
- new_controller(
198
- :start_command => lambda {
199
- log << "start_command"
200
- @start_command
201
- },
202
- :before_start => lambda { log << "before_start" }
203
- )
204
- begin
205
- @controller.start
206
- ensure
207
- @controller.stop
208
- end
209
- log.should == ["before_start", "start_command"]
210
- end
211
-
212
- if DaemonController.send(:fork_supported?) || DaemonController.send(:spawn_supported?)
213
- it "keeps the file descriptors in 'keep_ios' open" do
214
- a, b = IO.pipe
215
- begin
216
- new_controller(:keep_ios => [b])
217
- begin
218
- @controller.start
219
- b.close
220
- select([a], nil, nil, 0).should be_nil
221
- ensure
222
- @controller.stop
223
- end
224
- ensure
225
- a.close if !a.closed?
226
- b.close if !b.closed?
227
- end
228
- end
229
-
230
- it "performs the daemonization on behalf of the daemon if 'daemonize_for_me' is set" do
231
- new_controller(:no_daemonize => true, :daemonize_for_me => true)
232
- @controller.start
233
- ping_echo_server.should be_true
234
- @controller.stop
235
- end
236
- end
237
-
238
- it "receives environment variables" do
239
- new_controller(:env => {'ENV_FILE' => 'spec/env_file.tmp'})
240
- File.unlink('spec/env_file.tmp') if File.exist?('spec/env_file.tmp')
241
- @controller.start
242
- File.exist?('spec/env_file.tmp').should be_true
243
- @controller.stop
244
- end
13
+ include TestHelper
14
+
15
+ around :each do |example|
16
+ error = example.run
17
+ @controller.stop if @controller
18
+ print_logs(example) if error
19
+ end
20
+
21
+ it "works" do
22
+ new_controller
23
+ @controller.start
24
+ expect(ping_echo_server).to be(true)
25
+ end
26
+
27
+ it "raises AlreadyStarted if the daemon is already running" do
28
+ new_controller
29
+ begin
30
+ expect(@controller).to receive(:daemon_is_running?).and_return(true)
31
+ expect { @controller.start }.to raise_error(DaemonController::AlreadyStarted)
32
+ ensure
33
+ @controller = nil # Don't invoke @controller.stop in after hook
34
+ end
35
+ end
36
+
37
+ it "deletes existing PID file before starting the daemon" do
38
+ write_file("spec/echo_server.pid", "1234")
39
+ new_controller
40
+ expect(@controller).to receive(:daemon_is_running?).and_return(false)
41
+ expect(@controller).to receive(:spawn_daemon).and_return(DaemonController::InternalCommandOkResult.new(1, ""))
42
+ expect(@controller).to receive(:pid_file_available?).and_return(true)
43
+ expect(@controller).to receive(:run_ping_command).at_least(:once).and_return(true)
44
+ @controller.start
45
+ expect(File.exist?("spec/echo_server.pid")).to be false
46
+ ensure
47
+ @controller = nil # Don't invoke @controller.stop in after hook
48
+ end
49
+
50
+ it "blocks until the daemon has written to its PID file" do
51
+ thread = WaitingThread.new do
52
+ sleep 0.15
53
+ write_file("spec/echo_server.pid", "1234")
54
+ end
55
+ new_controller
56
+ expect(@controller).to receive(:daemon_is_running?) { false }
57
+ expect(@controller).to receive(:spawn_daemon) {
58
+ thread.go!
59
+ DaemonController::InternalCommandOkResult.new(1, "")
60
+ }
61
+ expect(@controller).to receive(:run_ping_command).at_least(:once).and_return(true)
62
+ begin
63
+ result = Benchmark.measure do
64
+ @controller.start
65
+ end
66
+ expect(result.real).to be_between(0.15, 0.30)
67
+ ensure
68
+ thread.join
69
+ end
70
+ ensure
71
+ @controller = nil # Don't invoke @controller.stop in after hook
72
+ end
73
+
74
+ it "blocks until the daemon can be pinged" do
75
+ ping_ok = false
76
+ running = false
77
+ thread = WaitingThread.new do
78
+ sleep 0.15
79
+ ping_ok = true
80
+ end
81
+ new_controller
82
+ expect(@controller).to receive(:daemon_is_running?).at_least(:once) { running }
83
+ expect(@controller).to receive(:spawn_daemon) {
84
+ thread.go!
85
+ running = true
86
+ DaemonController::InternalCommandOkResult.new(1, "")
87
+ }
88
+ expect(@controller).to receive(:pid_file_available?).and_return(true)
89
+ expect(@controller).to receive(:run_ping_command).at_least(:once) { ping_ok }
90
+ begin
91
+ result = Benchmark.measure do
92
+ @controller.start
93
+ end
94
+ expect(result.real).to be_between(0.15, 0.30)
95
+ ensure
96
+ thread.join
97
+ end
98
+ ensure
99
+ @controller = nil # Don't invoke @controller.stop in after hook
100
+ end
101
+
102
+ it "works when the log file is not a regular file" do
103
+ new_controller(log_file: "/dev/stderr")
104
+ @controller.start
105
+ expect(ping_echo_server).to be(true)
106
+ end
107
+
108
+ context "if the daemon exits with an error" do
109
+ context "before forking" do
110
+ it "raises StartError" do
111
+ new_controller(start_command: "false")
112
+ expect { @controller.start }.to raise_error(DaemonController::StartError)
113
+ end
114
+
115
+ it "makes outputs available in the exception" do
116
+ new_controller(start_command: "(echo hello world; false)")
117
+ expect { @controller.start }.to raise_error(DaemonController::StartError, /hello world/)
118
+ end
119
+
120
+ it "doesn't make outputs available if the log file is not a regular file" do
121
+ new_controller(start_command: "false", log_file: "/dev/stderr")
122
+ expect { @controller.start }.to raise_error(DaemonController::StartError, /logs not available/)
123
+ end
124
+
125
+ it "makes file logs available in the exception" do
126
+ new_controller(start_command: "(echo hello world; false)")
127
+ begin
128
+ @controller.start
129
+ rescue DaemonController::StartError => e
130
+ expect(e.message).to eq("hello world\n(exited with status 1)")
131
+ end
132
+ end
133
+
134
+ specify "if file logs are available but empty, then the exception says so" do
135
+ new_controller(start_command: "exit 1")
136
+ begin
137
+ @controller.start
138
+ fail
139
+ rescue DaemonController::StartError => e
140
+ expect(e.message).to eq("(logs empty; exited with status 1)")
141
+ end
142
+ end
143
+
144
+ it "makes the exit signal available in the exception if the log file is a regular file" do
145
+ new_controller(crash_before_bind: true, crash_signal: "SIGXCPU", no_daemonize: true)
146
+ begin
147
+ @controller.start
148
+ fail
149
+ rescue DaemonController::StartError => e
150
+ expect(e.message).to include("crashing, as instructed")
151
+ expect(e.message).to include("SIGXCPU")
152
+ end
153
+ end
154
+
155
+ it "makes the exit signal available in the exception if the log file is not a regular file" do
156
+ new_controller(crash_before_bind: true,
157
+ crash_signal: "SIGXCPU",
158
+ no_daemonize: true,
159
+ log_file: "/dev/stderr")
160
+ begin
161
+ @controller.start
162
+ fail
163
+ rescue DaemonController::StartError => e
164
+ expect(e.message).to include("logs not available")
165
+ expect(e.message).to include("SIGXCPU")
166
+ end
167
+ end
168
+ end
169
+
170
+ context "after forking" do
171
+ it "raises StartError" do
172
+ new_controller(crash_before_bind: true)
173
+ expect { @controller.start }.to raise_error(DaemonController::StartError)
174
+ end
175
+
176
+ it "makes outputs available in the exception" do
177
+ new_controller(log_message1: "hello world", crash_before_bind: true)
178
+ expect { @controller.start }.to raise_error(DaemonController::StartError, /hello world/)
179
+ end
180
+
181
+ it "doesn't make outputs available if the log file is not a regular file" do
182
+ new_controller(crash_before_bind: true, log_file: "/dev/stderr")
183
+ expect { @controller.start }.to raise_error(DaemonController::StartError, "(logs not available)")
184
+ end
185
+
186
+ it "makes file logs available in the exception" do
187
+ new_controller(crash_before_bind: true)
188
+ expect { @controller.start }.to raise_error(DaemonController::StartError, /crashing, as instructed/)
189
+ end
190
+
191
+ specify "if the daemon didn't write a PID file, then the error is detected through log file inactivity" do
192
+ new_controller(log_message1: "hello",
193
+ log_message2: "world",
194
+ no_write_pid_file: true,
195
+ log_file_activity_timeout: 0.5)
196
+ begin
197
+ @controller.start
198
+ fail
199
+ rescue DaemonController::StartTimeout => e
200
+ expect(e.message).to include("hello")
201
+ expect(e.message).to include("world")
202
+ expect(e.message).to include("(timed out)")
203
+ ensure
204
+ # Kill echo_server without PID file
205
+ kill_and_wait_echo_server
206
+ end
207
+ end
208
+ end
209
+ end
210
+
211
+ context "if the daemon doesn't start in time" do
212
+ it "raises StartTimeout" do
213
+ start_timeout = exec_is_slow? ? 4 : 0.5
214
+ min_start_timeout = 0.5
215
+ max_start_timeout = exec_is_slow? ? 6 : 1
216
+
217
+ new_controller(start_command: "sleep 10", start_timeout: start_timeout)
218
+ start_time = monotonic_time
219
+ end_time = nil
220
+ expect(@controller).to receive(:daemonization_timed_out) { end_time = monotonic_time }
221
+ expect { @controller.start }.to raise_error(DaemonController::StartTimeout)
222
+ expect(end_time - start_time).to be_between(min_start_timeout, max_start_timeout)
223
+ end
224
+
225
+ it "doesn't terminate the fork if the daemon forked right before we perform termination" do
226
+ new_controller(log_message2: "hello world",
227
+ wait1: 0.5,
228
+ start_timeout: 0.1)
229
+ expect(@controller).to receive(:daemonization_timed_out) { |p|
230
+ wait_until_pid_file_available
231
+ }
232
+ expect { @controller.start }.to raise_error(DaemonController::StartTimeout)
233
+ expect(@controller).to be_running
234
+ end
235
+
236
+ context "if the daemon hasn't forked yet" do
237
+ it "terminates the daemon gracefully" do
238
+ new_controller(start_command: "./spec/run_in_mri_ruby unresponsive_daemon.rb",
239
+ start_timeout: 0.1)
240
+
241
+ pid = nil
242
+ expect(@controller).to receive(:daemonization_timed_out) { |p|
243
+ pid = p
244
+ wait_until_pid_file_available
245
+ }
246
+
247
+ allow(Process).to receive(:kill).and_call_original
248
+ expect { @controller.start }.to raise_error(DaemonController::StartTimeout, /logs empty/)
249
+ expect(Process).to have_received(:kill).with("SIGTERM", pid).once
250
+ expect(Process).not_to have_received(:kill).with("SIGKILL", pid)
251
+ ensure
252
+ @controller = nil
253
+ end
254
+
255
+ it "terminates the daemon forcefully if it doesn't gracefully terminate in time" do
256
+ new_controller(
257
+ wait2: 10,
258
+ start_timeout: 0.1,
259
+ start_abort_timeout: 0.1,
260
+ ignore_sigterm: true,
261
+ no_daemonize: true
262
+ )
263
+
264
+ pid = nil
265
+ expect(@controller).to receive(:daemonization_timed_out) { |p|
266
+ pid = p
267
+ wait_until_pid_file_available
268
+ }
269
+
270
+ allow(Process).to receive(:kill).and_call_original
271
+ expect { @controller.start }.to raise_error(DaemonController::StartTimeout)
272
+ expect(Process).to have_received(:kill).with("SIGTERM", pid).once
273
+ expect(Process).to have_received(:kill).with("SIGKILL", pid).once
274
+ end
275
+
276
+ it "deletes the PID file" do
277
+ new_controller(wait2: 10,
278
+ start_timeout: 0.1,
279
+ no_daemonize: true)
280
+ expect(@controller).to receive(:daemonization_timed_out) {
281
+ wait_until_pid_file_available
282
+ }
283
+ expect { @controller.start }.to raise_error(DaemonController::StartTimeout)
284
+ expect(File.exist?("spec/echo_server.pid")).to be(false)
285
+ end
286
+
287
+ it "reports logs written to standard I/O channels" do
288
+ new_controller(log_message2: "hello world",
289
+ wait2: 10,
290
+ start_timeout: 0.1,
291
+ no_daemonize: true)
292
+ expect(@controller).to receive(:daemonization_timed_out) { |p|
293
+ wait_until_pid_file_available
294
+ }
295
+ expect { @controller.start }.to raise_error(DaemonController::StartTimeout, /hello world/)
296
+ end
297
+
298
+ it "reports logs written to the log file" do
299
+ new_controller(log_message2: "hello world",
300
+ wait2: 10,
301
+ start_timeout: 0.1,
302
+ no_daemonize: true)
303
+ expect(@controller).to receive(:daemonization_timed_out) {
304
+ wait_until_pid_file_available
305
+ }
306
+ expect { @controller.start }.to raise_error(DaemonController::StartTimeout, /hello world/)
307
+ end
308
+
309
+ specify "if there are no logs, then the error says so" do
310
+ new_controller(wait2: 10,
311
+ start_timeout: 0.1,
312
+ no_daemonize: true)
313
+ expect(@controller).to receive(:daemonization_timed_out) {
314
+ wait_until_pid_file_available
315
+ }
316
+ expect { @controller.start }.to raise_error(DaemonController::StartTimeout, /\(logs empty; timed out\)/)
317
+ end
318
+
319
+ specify "if logs cannot be captured, then the error says so" do
320
+ new_controller(wait2: 10,
321
+ start_timeout: 0.1,
322
+ log_file: "/dev/stderr",
323
+ no_daemonize: true)
324
+ expect(@controller).to receive(:daemonization_timed_out) {
325
+ wait_until_pid_file_available
326
+ }
327
+ expect { @controller.start }.to raise_error(DaemonController::StartTimeout, /\(logs not available; timed out\)/)
328
+ end
329
+ end
330
+
331
+ context "if the daemon already forked" do
332
+ it "terminates the daemon gracefully" do
333
+ new_controller(wait2: 10)
334
+
335
+ expect(@controller).to receive(:daemon_spawned) {
336
+ wait_until_pid_file_available
337
+ raise Timeout::Error
338
+ }
339
+
340
+ pid = nil
341
+ expect(@controller).to receive(:start_timed_out) { |p| pid = p }
342
+
343
+ allow(Process).to receive(:kill).and_call_original
344
+ expect { @controller.start }.to raise_error(DaemonController::StartTimeout)
345
+ expect(Process).to have_received(:kill).with("SIGTERM", pid).once
346
+ expect(Process).not_to have_received(:kill).with("SIGKILL", pid)
347
+ end
348
+
349
+ it "terminates the daemon forcefully if it doesn't gracefully terminate in time" do
350
+ new_controller(wait2: 10,
351
+ start_abort_timeout: 0.5,
352
+ ignore_sigterm: true)
353
+
354
+ expect(@controller).to receive(:daemon_spawned) {
355
+ wait_until_pid_file_available
356
+ raise Timeout::Error
357
+ }
358
+
359
+ pid = nil
360
+ expect(@controller).to receive(:start_timed_out) { |p| pid = p }
361
+
362
+ allow(Process).to receive(:kill).and_call_original
363
+ expect { @controller.start }.to raise_error(DaemonController::StartTimeout)
364
+ expect(Process).to have_received(:kill).with("SIGTERM", pid).once
365
+ expect(Process).to have_received(:kill).with("SIGKILL", pid).once
366
+ end
367
+
368
+ it "deletes the PID file" do
369
+ new_controller(wait2: 10)
370
+ expect(@controller).to receive(:daemon_spawned) {
371
+ wait_until_pid_file_available
372
+ raise Timeout::Error
373
+ }
374
+ expect(@controller).to receive(:start_timed_out)
375
+ expect { @controller.start }.to raise_error(DaemonController::StartTimeout)
376
+ expect(File.exist?("spec/echo_server.pid")).to be(false)
377
+ end
378
+
379
+ it "reports logs written to standard I/O channels" do
380
+ new_controller(log_message1: "hello world", wait2: 10)
381
+ expect(@controller).to receive(:daemon_spawned) {
382
+ wait_until_pid_file_available
383
+ raise Timeout::Error
384
+ }
385
+ expect(@controller).to receive(:start_timed_out)
386
+ expect { @controller.start }.to raise_error(DaemonController::StartTimeout, /hello world/)
387
+ end
388
+
389
+ it "reports logs written to the log file" do
390
+ new_controller(log_message2: "hello world", wait2: 10)
391
+ expect(@controller).to receive(:daemon_spawned) {
392
+ wait_until_pid_file_available
393
+ raise Timeout::Error
394
+ }
395
+ expect(@controller).to receive(:start_timed_out)
396
+ expect { @controller.start }.to raise_error(DaemonController::StartTimeout, /hello world/)
397
+ end
398
+
399
+ specify "if there are no logs, then the error says so" do
400
+ new_controller(wait2: 10)
401
+ expect(@controller).to receive(:daemon_spawned) {
402
+ wait_until_pid_file_available
403
+ raise Timeout::Error
404
+ }
405
+ expect(@controller).to receive(:start_timed_out)
406
+ expect { @controller.start }.to raise_error(DaemonController::StartTimeout, /\(logs empty; timed out\)/)
407
+ end
408
+
409
+ specify "if logs cannot be captured, then the error says so" do
410
+ new_controller(log_message2: "hello world", wait2: 10, log_file: "/dev/stderr")
411
+ expect(@controller).to receive(:daemon_spawned) {
412
+ wait_until_pid_file_available
413
+ raise Timeout::Error
414
+ }
415
+ expect(@controller).to receive(:start_timed_out)
416
+ expect { @controller.start }.to raise_error(DaemonController::StartTimeout, /\(logs not available; timed out\)/)
417
+ end
418
+ end
419
+ end
420
+
421
+ specify "the start command may be a Proc" do
422
+ called = true
423
+ new_controller(start_command: lambda {
424
+ called = true
425
+ @start_command
426
+ })
427
+ @controller.start
428
+ expect(called).to be true
429
+ end
430
+
431
+ specify "if the start command is a Proc then it is called after before_start" do
432
+ log = []
433
+ new_controller(
434
+ start_command: lambda {
435
+ log << "start_command"
436
+ @start_command
437
+ },
438
+ before_start: lambda { log << "before_start" }
439
+ )
440
+ @controller.start
441
+ expect(log).to eq(["before_start", "start_command"])
442
+ end
443
+
444
+ it "keeps the file descriptors in 'keep_ios' open" do
445
+ a, b = IO.pipe
446
+ begin
447
+ new_controller(keep_ios: [b])
448
+ @controller.start
449
+ b.close
450
+ expect(select([a], nil, nil, 0)).to be_nil
451
+ ensure
452
+ a.close if !a.closed?
453
+ b.close if !b.closed?
454
+ end
455
+ end
456
+
457
+ it "performs the daemonization on behalf of the daemon if 'daemonize_for_me' is set" do
458
+ new_controller(no_daemonize: true, daemonize_for_me: true)
459
+ @controller.start
460
+ expect(ping_echo_server).to be true
461
+ end
462
+
463
+ it "passes environment variables" do
464
+ new_controller(env: {"ENV_FILE" => "spec/env_file.tmp"})
465
+ File.unlink("spec/env_file.tmp") if File.exist?("spec/env_file.tmp")
466
+ @controller.start
467
+ expect(File.exist?("spec/env_file.tmp")).to be true
468
+ end
245
469
  end
246
470
 
247
471
  describe DaemonController, "#stop" do
248
- include TestHelper
249
-
250
- before :each do
251
- new_controller
252
- end
253
-
254
- after :each do
255
- @controller.stop
256
- end
257
-
258
- it "raises no exception if the daemon is not running" do
259
- @controller.stop
260
- end
261
-
262
- it "waits until the daemon is no longer running" do
263
- new_controller(:stop_time => 0.3)
264
- @controller.start
265
- result = Benchmark.measure do
266
- @controller.stop
267
- end
268
- @controller.should_not be_running
269
- result.real.should be_between(0.3, 0.6)
270
- end
271
-
272
- it "raises StopTimeout if the daemon does not stop in time" do
273
- new_controller(:stop_time => 0.3, :stop_timeout => 0.1)
274
- @controller.start
275
- begin
276
- lambda { @controller.stop }.should raise_error(DaemonController::StopTimeout)
277
- ensure
278
- new_controller.stop
279
- end
280
- end
281
-
282
- describe "if stop command was given" do
283
- it "raises StopError if the stop command exits with an error" do
284
- new_controller(:stop_command => '(echo hello world; false)')
285
- begin
286
- begin
287
- @controller.stop
288
- violated
289
- rescue DaemonController::StopError => e
290
- e.message.should == 'hello world'
291
- end
292
- ensure
293
- new_controller.stop
294
- end
295
- end
296
-
297
- it "makes the stop command's error message available in the exception" do
298
- end
299
- end
472
+ include TestHelper
473
+
474
+ before :each do
475
+ new_controller
476
+ end
477
+
478
+ it "raises no exception if the daemon is not running" do
479
+ @controller.stop
480
+ end
481
+
482
+ it "waits until the daemon is no longer running" do
483
+ new_controller(stop_time: 0.3)
484
+ @controller.start
485
+ begin
486
+ result = Benchmark.measure do
487
+ @controller.stop
488
+ end
489
+ expect(@controller).not_to be_running
490
+ expect(result.real).to be_between(0.3, 3)
491
+ ensure
492
+ new_controller.stop
493
+ end
494
+ end
495
+
496
+ context "if the daemon does not stop in time" do
497
+ before :each do
498
+ new_controller(stop_time: 0.3, stop_timeout: 0.1)
499
+ @controller.start
500
+ end
501
+
502
+ after :each do
503
+ new_controller.stop
504
+ end
505
+
506
+ it "raises StopTimeout" do
507
+ expect { @controller.stop }.to raise_error(DaemonController::StopTimeout)
508
+ end
509
+
510
+ it "forcefully terminates the daemon and raises StopTimeout" do
511
+ pid = @controller.pid
512
+ allow(Process).to receive(:kill).and_call_original
513
+ expect { @controller.stop }.to raise_error(DaemonController::StopTimeout)
514
+ expect(Process).to have_received(:kill).with("SIGKILL", pid).once
515
+ expect(File.exist?("spec/echo_server.pid")).to be(false)
516
+ end
517
+
518
+ it "deletes the PID file" do
519
+ expect { @controller.stop }.to raise_error(DaemonController::StopTimeout)
520
+ expect(File.exist?("spec/echo_server.pid")).to be(false)
521
+ end
522
+ end
523
+
524
+ describe "if stop command was given" do
525
+ it "raises StopError if the stop command exits with an error" do
526
+ new_controller(stop_command: "(echo hello world; false)")
527
+ @controller.start
528
+ begin
529
+ expect { @controller.stop }.to raise_error(DaemonController::StopError)
530
+ ensure
531
+ new_controller.stop
532
+ end
533
+ end
534
+
535
+ it "makes the stop command's error message available in the exception" do
536
+ new_controller(stop_command: "(echo hello world; false)")
537
+ begin
538
+ @controller.start
539
+ @controller.stop
540
+ fail
541
+ rescue DaemonController::StopError => e
542
+ expect(e.message).to include("hello world")
543
+ expect(e.message).to include("(exited with status 1)")
544
+ ensure
545
+ new_controller.stop
546
+ end
547
+ end
548
+
549
+ it "calls the stop command if the PID file is invalid and :dont_stop_if_pid_file_invalid is not set" do
550
+ Dir.mktmpdir do |tmpdir|
551
+ new_controller(stop_command: "touch #{Shellwords.escape tmpdir}/stopped")
552
+ @controller.start
553
+ begin
554
+ File.open("spec/echo_server.pid", "w").close
555
+ @controller.stop
556
+ expect(File.exist?("#{tmpdir}/stopped")).to be_truthy
557
+ ensure
558
+ # Kill echo_server without PID file
559
+ kill_and_wait_echo_server
560
+ end
561
+ end
562
+ end
563
+
564
+ it "does not call the stop command if the PID file is invalid and :dont_stop_if_pid_file_invalid is set" do
565
+ Dir.mktmpdir do |tmpdir|
566
+ File.open("spec/echo_server.pid", "w").close
567
+ new_controller(stop_command: "touch #{Shellwords.escape tmpdir}/stopped",
568
+ dont_stop_if_pid_file_invalid: true)
569
+ @controller.stop
570
+ expect(File.exist?("#{tmpdir}/stopped")).to be_falsey
571
+ end
572
+ end
573
+ end
300
574
  end
301
575
 
302
576
  describe DaemonController, "#restart" do
303
- include TestHelper
304
-
305
- before :each do
306
- new_controller
307
- end
308
-
309
- it "raises no exception if the daemon is not running" do
310
- @controller.restart
311
- end
312
-
313
- describe 'with no restart command' do
314
- it "restart the daemon using stop and start" do
315
- @controller.should_receive(:stop)
316
- @controller.should_receive(:start)
317
- @controller.restart
318
- end
319
- end
320
-
321
- describe 'with a restart_command' do
322
- it 'restarts the daemon using the restart_command' do
323
- stop_cmd = "echo 'hello world'"
324
- new_controller :restart_command => stop_cmd
325
-
326
- @controller.should_receive(:run_command).with(stop_cmd)
327
- @controller.restart
328
- end
329
- end
577
+ include TestHelper
578
+
579
+ before :each do
580
+ new_controller
581
+ end
582
+
583
+ it "raises no exception if the daemon is not running" do
584
+ @controller.restart
585
+ end
586
+
587
+ describe "with no restart command" do
588
+ it "restart the daemon using stop and start" do
589
+ expect(@controller).to receive(:stop)
590
+ expect(@controller).to receive(:start)
591
+ @controller.restart
592
+ end
593
+ end
594
+
595
+ describe "with a restart_command" do
596
+ it "restarts the daemon using the restart_command" do
597
+ stop_cmd = "echo 'hello world'"
598
+ new_controller restart_command: stop_cmd
599
+
600
+ expect(@controller).to receive(:run_command).with(stop_cmd)
601
+ @controller.restart
602
+ end
603
+ end
330
604
  end
331
605
 
332
606
  describe DaemonController, "#connect" do
333
- include TestHelper
334
-
335
- before :each do
336
- new_controller
337
- end
338
-
339
- it "starts the daemon if it isn't already running" do
340
- socket = @controller.connect do
341
- TCPSocket.new('localhost', 3230)
342
- end
343
- socket.close
344
- @controller.stop
345
- end
346
-
347
- it "connects to the existing daemon if it's already running" do
348
- @controller.start
349
- begin
350
- socket = @controller.connect do
351
- TCPSocket.new('localhost', 3230)
352
- end
353
- socket.close
354
- ensure
355
- @controller.stop
356
- end
357
- end
607
+ include TestHelper
608
+
609
+ before :each do
610
+ new_controller
611
+ end
612
+
613
+ it "starts the daemon if it isn't already running" do
614
+ socket = @controller.connect do
615
+ TCPSocket.new("localhost", 3230)
616
+ end
617
+ socket.close
618
+ @controller.stop
619
+ end
620
+
621
+ it "connects to the existing daemon if it's already running" do
622
+ @controller.start
623
+ begin
624
+ socket = @controller.connect do
625
+ TCPSocket.new("localhost", 3230)
626
+ end
627
+ socket.close
628
+ ensure
629
+ @controller.stop
630
+ end
631
+ end
358
632
  end
359
633
 
360
634
  describe DaemonController do
361
- include TestHelper
362
-
363
- after :each do
364
- @server.close if @server && !@server.closed?
365
- File.unlink('spec/foo.sock') rescue nil
366
- end
367
-
368
- specify "if the ping command is a block that raises Errno::ECONNREFUSED, then that's " <<
369
- "an indication that the daemon cannot be connected to" do
370
- new_controller(:ping_command => lambda do
371
- raise Errno::ECONNREFUSED, "dummy"
372
- end)
373
- @controller.send(:run_ping_command).should be_false
374
- end
375
-
376
- specify "if the ping command is a block that returns an object that responds to #close, " <<
377
- "then the close method will be called on that object" do
378
- @server = TCPServer.new('localhost', 8278)
379
- socket = nil
380
- new_controller(:ping_command => lambda do
381
- socket = TCPSocket.new('localhost', 8278)
382
- end)
383
- @controller.send(:run_ping_command)
384
- socket.should be_closed
385
- end
386
-
387
- specify "if the ping command is a block that returns an object that responds to #close, " <<
388
- "and #close raises an exception, then that exception is ignored" do
389
- @server = TCPServer.new('localhost', 8278)
390
- o = Object.new
391
- o.should_receive(:close).and_return do
392
- raise StandardError, "foo"
393
- end
394
- new_controller(:ping_command => lambda do
395
- o
396
- end)
397
- lambda { @controller.send(:run_ping_command) }.should_not raise_error
398
- end
399
-
400
- specify "the ping command may be [:tcp, hostname, port]" do
401
- new_controller(:ping_command => [:tcp, "127.0.0.1", 8278])
402
- @controller.send(:run_ping_command).should be_false
403
-
404
- @server = TCPServer.new('127.0.0.1', 8278)
405
- @controller.send(:run_ping_command).should be_true
406
- end
407
-
408
- if DaemonController.can_ping_unix_sockets?
409
- specify "the ping command may be [:unix, filename]" do
410
- new_controller(:ping_command => [:unix, "spec/foo.sock"])
411
- @controller.send(:run_ping_command).should be_false
412
-
413
- @server = UNIXServer.new('spec/foo.sock')
414
- @controller.send(:run_ping_command).should be_true
415
- end
416
- else
417
- specify "a ping command of type [:unix, filename] is not supported on this Ruby implementation" do
418
- new_controller(:ping_command => [:unix, "spec/foo.sock"])
419
- @server = UNIXServer.new('spec/foo.sock')
420
- lambda { @controller.send(:run_ping_command) }.should raise_error(
421
- "Pinging Unix domain sockets is not supported on this Ruby implementation")
422
- end
423
- end
635
+ include TestHelper
636
+
637
+ after :each do
638
+ @server.close if @server && !@server.closed?
639
+ begin
640
+ File.unlink("spec/foo.sock")
641
+ rescue
642
+ nil
643
+ end
644
+ end
645
+
646
+ specify "if the ping command is a block that raises Errno::ECONNREFUSED, then that's " \
647
+ "an indication that the daemon cannot be connected to" do
648
+ new_controller(ping_command: lambda do
649
+ raise Errno::ECONNREFUSED, "dummy"
650
+ end)
651
+ expect(@controller.send(:run_ping_command)).to be false
652
+ end
653
+
654
+ specify "if the ping command is a block that returns an object that responds to #close, " \
655
+ "then the close method will be called on that object" do
656
+ @server = TCPServer.new("localhost", 8278)
657
+ socket = nil
658
+ new_controller(ping_command: lambda do
659
+ socket = TCPSocket.new("localhost", 8278)
660
+ end)
661
+ @controller.send(:run_ping_command)
662
+ expect(socket).to be_closed
663
+ end
664
+
665
+ specify "if the ping command is a block that returns an object that responds to #close, " \
666
+ "and #close raises an exception, then that exception is ignored" do
667
+ @server = TCPServer.new("localhost", 8278)
668
+ o = Object.new
669
+ expect(o).to receive(:close) { raise StandardError, "foo" }
670
+ new_controller(ping_command: lambda do
671
+ o
672
+ end)
673
+ expect { @controller.send(:run_ping_command) }.not_to raise_error
674
+ end
675
+
676
+ specify "the ping command may be [:tcp, hostname, port]" do
677
+ new_controller(ping_command: [:tcp, "127.0.0.1", 8278])
678
+ expect(@controller.send(:run_ping_command)).to be false
679
+
680
+ @server = TCPServer.new("127.0.0.1", 8278)
681
+ expect(@controller.send(:run_ping_command)).to be true
682
+ end
683
+
684
+ if DaemonController.can_ping_unix_sockets?
685
+ specify "the ping command may be [:unix, filename]" do
686
+ new_controller(ping_command: [:unix, "spec/foo.sock"])
687
+ expect(@controller.send(:run_ping_command)).to be false
688
+
689
+ @server = UNIXServer.new("spec/foo.sock")
690
+ expect(@controller.send(:run_ping_command)).to be true
691
+ end
692
+ else
693
+ specify "a ping command of type [:unix, filename] is not supported on this Ruby implementation" do
694
+ new_controller(ping_command: [:unix, "spec/foo.sock"])
695
+ @server = UNIXServer.new("spec/foo.sock")
696
+ expect { @controller.send(:run_ping_command) }.to raise_error(
697
+ "Pinging Unix domain sockets is not supported on this Ruby implementation"
698
+ )
699
+ end
700
+ end
424
701
  end