daemon_controller 2.0.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.
@@ -6,12 +6,16 @@ require "benchmark"
6
6
  require "socket"
7
7
  require "tmpdir"
8
8
  require "shellwords"
9
+ require "stringio"
10
+ require "logger"
9
11
 
10
12
  describe DaemonController, "#start" do
11
13
  include TestHelper
12
14
 
13
- after :each do
15
+ around :each do |example|
16
+ error = example.run
14
17
  @controller.stop if @controller
18
+ print_logs(example) if error
15
19
  end
16
20
 
17
21
  it "works" do
@@ -34,7 +38,7 @@ describe DaemonController, "#start" do
34
38
  write_file("spec/echo_server.pid", "1234")
35
39
  new_controller
36
40
  expect(@controller).to receive(:daemon_is_running?).and_return(false)
37
- expect(@controller).to receive(:spawn_daemon)
41
+ expect(@controller).to receive(:spawn_daemon).and_return(DaemonController::InternalCommandOkResult.new(1, ""))
38
42
  expect(@controller).to receive(:pid_file_available?).and_return(true)
39
43
  expect(@controller).to receive(:run_ping_command).at_least(:once).and_return(true)
40
44
  @controller.start
@@ -50,7 +54,10 @@ describe DaemonController, "#start" do
50
54
  end
51
55
  new_controller
52
56
  expect(@controller).to receive(:daemon_is_running?) { false }
53
- expect(@controller).to receive(:spawn_daemon) { thread.go! }
57
+ expect(@controller).to receive(:spawn_daemon) {
58
+ thread.go!
59
+ DaemonController::InternalCommandOkResult.new(1, "")
60
+ }
54
61
  expect(@controller).to receive(:run_ping_command).at_least(:once).and_return(true)
55
62
  begin
56
63
  result = Benchmark.measure do
@@ -76,6 +83,7 @@ describe DaemonController, "#start" do
76
83
  expect(@controller).to receive(:spawn_daemon) {
77
84
  thread.go!
78
85
  running = true
86
+ DaemonController::InternalCommandOkResult.new(1, "")
79
87
  }
80
88
  expect(@controller).to receive(:pid_file_available?).and_return(true)
81
89
  expect(@controller).to receive(:run_ping_command).at_least(:once) { ping_ok }
@@ -97,239 +105,316 @@ describe DaemonController, "#start" do
97
105
  expect(ping_echo_server).to be(true)
98
106
  end
99
107
 
100
- context "if the daemon doesn't start in time" do
101
- let(:start_timeout) { exec_is_slow? ? 4 : 0.5 }
102
- let(:min_start_timeout) { 0.5 }
103
- let(:max_start_timeout) { exec_is_slow? ? 6 : 1 }
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
104
114
 
105
- it "raises StartTimeout" do
106
- new_controller(start_command: "sleep 2", start_timeout: start_timeout)
107
- start_time = Time.now
108
- end_time = nil
109
- expect(@controller).to receive(:start_timed_out) { end_time = Time.now }
110
- expect { @controller.start }.to raise_error(DaemonController::StartTimeout)
111
- expect(end_time - start_time).to be_between(min_start_timeout, max_start_timeout)
112
- end
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
113
119
 
114
- it "reports logs written to stderr if the timeout happened before forking" do
115
- new_controller(start_command: "echo hello world; sleep 10", start_timeout: start_timeout)
116
- expect(@controller).to receive(:daemonization_timed_out)
117
- begin
118
- @controller.start
119
- fail
120
- rescue DaemonController::StartTimeout => e
121
- expect(e.message).to include("hello world")
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/)
122
123
  end
123
- end
124
124
 
125
- it "reports logs written to the log file if the timeout happened before forking" do
126
- new_controller(log_message2: "hello world",
127
- wait2: 10,
128
- start_timeout: start_timeout,
129
- no_daemonize: true)
130
- expect(@controller).to receive(:daemonization_timed_out)
131
- begin
132
- @controller.start
133
- fail
134
- rescue DaemonController::StartTimeout => e
135
- expect(e.message).to include("hello world")
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
136
132
  end
137
- end
138
133
 
139
- it "reports logs if the timeout happened after forking" do
140
- new_controller(log_message2: "hello world",
141
- wait2: 10,
142
- start_timeout: start_timeout)
143
- expect(@controller).not_to receive(:daemonization_timed_out)
144
- begin
145
- @controller.start
146
- fail
147
- rescue DaemonController::StartTimeout => e
148
- expect(e.message).to include("hello world")
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
149
142
  end
150
- end
151
143
 
152
- specify "if there are no logs, then the error says so" do
153
- new_controller(start_command: "sleep 10", start_timeout: start_timeout)
154
- expect(@controller).to receive(:daemonization_timed_out)
155
- begin
156
- @controller.start
157
- fail
158
- rescue DaemonController::StartTimeout => e
159
- expect(e.message).to eq("(logs empty; timed out)")
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
160
153
  end
161
- end
162
154
 
163
- specify "if logs cannot be captured, then the error says so" do
164
- new_controller(start_command: "sleep 10", start_timeout: start_timeout, log_file: "/dev/stderr")
165
- expect(@controller).to receive(:daemonization_timed_out)
166
- begin
167
- @controller.start
168
- fail
169
- rescue DaemonController::StartTimeout => e
170
- expect(e.message).to eq("(logs not available; timed out)")
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
171
167
  end
172
168
  end
173
- end
174
169
 
175
- it "kills the daemon forcefully if the daemon has forked but doesn't " \
176
- "become pingable in time, and there's a PID file" do
177
- new_controller(wait2: 3, start_timeout: 1)
178
- pid = nil
179
- expect(@controller).to receive(:start_timed_out) {
180
- @controller.send(:wait_until) do
181
- @controller.send(:pid_file_available?)
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)
182
174
  end
183
- pid = @controller.send(:read_pid_file)
184
- }
185
- begin
186
- block = lambda { @controller.start }
187
- expect(&block).to raise_error(DaemonController::StartTimeout)
188
- eventually(1) do
189
- !process_is_alive?(pid)
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/)
190
179
  end
191
180
 
192
- # The daemon should not be able to clean up its PID file since
193
- # it's killed with SIGKILL.
194
- expect(File.exist?("spec/echo_server.pid")).to be true
195
- ensure
196
- begin
197
- File.unlink("spec/echo_server.pid")
198
- rescue
199
- nil
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)")
200
184
  end
201
- end
202
- end
203
185
 
204
- it "kills the daemon if it doesn't start in time and hasn't forked yet" do
205
- new_controller(start_command: "./spec/unresponsive_daemon.rb",
206
- start_timeout: 0.2)
207
- pid = nil
208
- expect(@controller).to receive(:daemonization_timed_out) {
209
- @controller.send(:wait_until) do
210
- @controller.send(:pid_file_available?)
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
211
207
  end
212
- pid = @controller.send(:read_pid_file)
213
- }
214
- block = lambda { @controller.start }
215
- expect(&block).to raise_error(DaemonController::StartTimeout, /logs empty/)
216
- eventually(1) do
217
- !process_is_alive?(pid)
218
- end
219
- ensure
220
- begin
221
- File.unlink("spec/echo_server.pid")
222
- rescue
223
- nil
224
208
  end
225
209
  end
226
210
 
227
- it "raises an error if the daemon exits with an error before forking" do
228
- new_controller(start_command: "false")
229
- expect { @controller.start }.to raise_error(DaemonController::StartError)
230
- end
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
231
216
 
232
- it "raises an error if the daemon exits with an error after forking" do
233
- new_controller(crash_before_bind: true)
234
- expect { @controller.start }.to raise_error(DaemonController::StartError)
235
- end
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
236
224
 
237
- specify "if the daemon exits after forking without writing a PID file, then this is detected through log file inactivity" do
238
- new_controller(log_message1: "hello",
239
- log_message2: "world",
240
- no_write_pid_file: true,
241
- log_file_activity_timeout: 0.5)
242
- begin
243
- @controller.start
244
- fail
245
- rescue DaemonController::StartTimeout => e
246
- expect(e.message).to include("hello")
247
- expect(e.message).to include("world")
248
- expect(e.message).to include("(timed out)")
249
- ensure
250
- # Kill echo_server without PID file
251
- kill_and_wait_echo_server
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
252
234
  end
253
- end
254
235
 
255
- def find_echo_server_pid
256
- process_line = `ps aux`.lines.grep(/echo_server\.rb/).first
257
- process_line.split[1].to_i if process_line
258
- end
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)
259
240
 
260
- def kill_and_wait_echo_server
261
- pid = find_echo_server_pid
262
- if pid
263
- Process.kill("SIGTERM", pid)
264
- Timeout.timeout(5) do
265
- while find_echo_server_pid
266
- sleep(0.1)
267
- end
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
268
253
  end
269
- end
270
- end
271
254
 
272
- specify "the daemon's logs before forking is made available in the exception" do
273
- new_controller(start_command: "(echo hello world; false)")
274
- begin
275
- @controller.start
276
- rescue DaemonController::StartError => e
277
- expect(e.message).to eq("hello world\n(exited with status 1)")
278
- end
279
- end
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
280
275
 
281
- specify "the daemon's logs after forking is made available in the exception" do
282
- new_controller(crash_before_bind: true)
283
- begin
284
- @controller.start
285
- fail
286
- rescue DaemonController::StartError => e
287
- expect(e.message).to include("crashing, as instructed")
288
- end
289
- end
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
290
286
 
291
- specify "the daemon's exit signal is made available in the exception if the log file is a regular file" do
292
- new_controller(crash_before_bind: true, crash_signal: "SIGXCPU", no_daemonize: true)
293
- begin
294
- @controller.start
295
- fail
296
- rescue DaemonController::StartError => e
297
- expect(e.message).to include("crashing, as instructed")
298
- expect(e.message).to include("SIGXCPU")
299
- end
300
- end
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
301
297
 
302
- specify "the daemon's exit signal is made available in the exception if the log file is not a regular file" do
303
- new_controller(crash_before_bind: true,
304
- crash_signal: "SIGXCPU",
305
- no_daemonize: true,
306
- log_file: "/dev/stderr")
307
- begin
308
- @controller.start
309
- fail
310
- rescue DaemonController::StartError => e
311
- expect(e.message).to include("logs not available")
312
- expect(e.message).to include("SIGXCPU")
313
- end
314
- end
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
315
308
 
316
- specify "the daemon's logs are not available if the log file is not a regular file" do
317
- new_controller(crash_before_bind: true, log_file: "/dev/stderr")
318
- begin
319
- @controller.start
320
- fail
321
- rescue DaemonController::StartError => e
322
- expect(e.message).to eq("(logs not available)")
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
323
329
  end
324
- end
325
330
 
326
- specify "if the logs are available but empty, then the error exception says so" do
327
- new_controller(start_command: "exit 1")
328
- begin
329
- @controller.start
330
- fail
331
- rescue DaemonController::StartError => e
332
- expect(e.message).to eq("(logs empty; exited with status 1)")
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
333
418
  end
334
419
  end
335
420
 
@@ -375,7 +460,7 @@ describe DaemonController, "#start" do
375
460
  expect(ping_echo_server).to be true
376
461
  end
377
462
 
378
- it "receives environment variables" do
463
+ it "passes environment variables" do
379
464
  new_controller(env: {"ENV_FILE" => "spec/env_file.tmp"})
380
465
  File.unlink("spec/env_file.tmp") if File.exist?("spec/env_file.tmp")
381
466
  @controller.start
@@ -390,10 +475,6 @@ describe DaemonController, "#stop" do
390
475
  new_controller
391
476
  end
392
477
 
393
- after :each do
394
- @controller.stop if @controller
395
- end
396
-
397
478
  it "raises no exception if the daemon is not running" do
398
479
  @controller.stop
399
480
  end
@@ -401,50 +482,83 @@ describe DaemonController, "#stop" do
401
482
  it "waits until the daemon is no longer running" do
402
483
  new_controller(stop_time: 0.3)
403
484
  @controller.start
404
- result = Benchmark.measure do
405
- @controller.stop
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
406
493
  end
407
- expect(@controller).not_to be_running
408
- expect(result.real).to be_between(0.3, 0.6)
409
494
  end
410
495
 
411
- it "raises StopTimeout if the daemon does not stop in time" do
412
- new_controller(stop_time: 0.3, stop_timeout: 0.1)
413
- @controller.start
414
- begin
415
- expect { @controller.stop }.to raise_error(DaemonController::StopTimeout)
416
- ensure
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
417
503
  new_controller.stop
418
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
419
522
  end
420
523
 
421
524
  describe "if stop command was given" do
422
525
  it "raises StopError if the stop command exits with an error" do
423
526
  new_controller(stop_command: "(echo hello world; false)")
527
+ @controller.start
424
528
  begin
425
- begin
426
- @controller.stop
427
- fail
428
- rescue DaemonController::StopError => e
429
- expect(e.message).to eq("hello world\n(exited with status 1)")
430
- end
529
+ expect { @controller.stop }.to raise_error(DaemonController::StopError)
431
530
  ensure
432
531
  new_controller.stop
433
532
  end
434
533
  end
435
534
 
436
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
437
547
  end
438
548
 
439
549
  it "calls the stop command if the PID file is invalid and :dont_stop_if_pid_file_invalid is not set" do
440
550
  Dir.mktmpdir do |tmpdir|
441
- File.open("spec/echo_server.pid", "w").close
442
551
  new_controller(stop_command: "touch #{Shellwords.escape tmpdir}/stopped")
443
- @controller.stop
444
- expect(File.exist?("#{tmpdir}/stopped")).to be_truthy
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
445
561
  end
446
- ensure
447
- @controller = nil
448
562
  end
449
563
 
450
564
  it "does not call the stop command if the PID file is invalid and :dont_stop_if_pid_file_invalid is set" do