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.
- checksums.yaml +4 -4
- data/README.md +77 -75
- data/lib/daemon_controller/version.rb +1 -1
- data/lib/daemon_controller.rb +270 -374
- data/spec/daemon_controller_spec.rb +341 -227
- data/spec/echo_server.rb +9 -1
- data/spec/test_helper.rb +66 -20
- data/spec/unresponsive_daemon.rb +6 -12
- metadata +2 -3
- data/spec/run_echo_server +0 -9
@@ -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
|
-
|
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) {
|
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
|
101
|
-
|
102
|
-
|
103
|
-
|
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
|
-
|
106
|
-
|
107
|
-
|
108
|
-
|
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
|
-
|
115
|
-
|
116
|
-
|
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
|
-
|
126
|
-
|
127
|
-
|
128
|
-
|
129
|
-
|
130
|
-
|
131
|
-
|
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
|
-
|
140
|
-
|
141
|
-
|
142
|
-
|
143
|
-
|
144
|
-
|
145
|
-
|
146
|
-
|
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
|
-
|
153
|
-
|
154
|
-
|
155
|
-
|
156
|
-
|
157
|
-
|
158
|
-
|
159
|
-
|
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
|
-
|
164
|
-
|
165
|
-
|
166
|
-
|
167
|
-
|
168
|
-
|
169
|
-
|
170
|
-
|
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
|
-
|
176
|
-
|
177
|
-
|
178
|
-
|
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
|
-
|
184
|
-
|
185
|
-
|
186
|
-
|
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
|
-
|
193
|
-
|
194
|
-
|
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
|
-
|
205
|
-
|
206
|
-
|
207
|
-
|
208
|
-
|
209
|
-
|
210
|
-
|
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
|
-
|
228
|
-
|
229
|
-
|
230
|
-
|
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
|
-
|
233
|
-
|
234
|
-
|
235
|
-
|
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
|
-
|
238
|
-
|
239
|
-
|
240
|
-
|
241
|
-
|
242
|
-
|
243
|
-
|
244
|
-
|
245
|
-
|
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
|
-
|
256
|
-
|
257
|
-
|
258
|
-
|
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
|
-
|
261
|
-
|
262
|
-
|
263
|
-
|
264
|
-
|
265
|
-
|
266
|
-
|
267
|
-
|
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
|
-
|
273
|
-
|
274
|
-
|
275
|
-
|
276
|
-
|
277
|
-
|
278
|
-
|
279
|
-
|
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
|
-
|
282
|
-
|
283
|
-
|
284
|
-
|
285
|
-
|
286
|
-
|
287
|
-
|
288
|
-
|
289
|
-
|
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
|
-
|
292
|
-
|
293
|
-
|
294
|
-
|
295
|
-
|
296
|
-
|
297
|
-
|
298
|
-
|
299
|
-
|
300
|
-
|
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
|
-
|
303
|
-
|
304
|
-
|
305
|
-
|
306
|
-
|
307
|
-
|
308
|
-
|
309
|
-
|
310
|
-
|
311
|
-
|
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
|
-
|
317
|
-
|
318
|
-
|
319
|
-
|
320
|
-
|
321
|
-
|
322
|
-
|
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
|
-
|
327
|
-
|
328
|
-
|
329
|
-
|
330
|
-
|
331
|
-
|
332
|
-
|
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 "
|
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
|
-
|
405
|
-
|
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
|
-
|
412
|
-
|
413
|
-
|
414
|
-
|
415
|
-
|
416
|
-
|
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
|
-
|
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.
|
444
|
-
|
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
|