Tests: add a test for interrupted start of skippable scripted service.
[oweals/dinit.git] / src / tests / proctests.cc
1 #include <cassert>
2 #include <iostream>
3 #include <list>
4 #include <utility>
5 #include <string>
6
7 #include "service.h"
8 #include "proc-service.h"
9
10 // Tests of process-service related functionality.
11 //
12 // These tests work mostly by completely mocking out the base_process_service class. The mock
13 // implementations can be found in test-baseproc.cc.
14
15 extern eventloop_t event_loop;
16
17 constexpr static auto REG = dependency_type::REGULAR;
18 constexpr static auto WAITS = dependency_type::WAITS_FOR;
19 constexpr static auto MS = dependency_type::MILESTONE;
20
21 // Friend interface to access base_process_service private/protected members.
22 class base_process_service_test
23 {
24     public:
25     static void exec_succeeded(base_process_service *bsp)
26     {
27         bsp->waiting_for_execstat = false;
28         bsp->exec_succeeded();
29     }
30
31     static void handle_exit(base_process_service *bsp, int exit_status)
32     {
33         bsp->pid = -1;
34         bsp->handle_exit_status(bp_sys::exit_status(true, false, exit_status));
35     }
36
37     static void handle_signal_exit(base_process_service *bsp, int signo)
38     {
39         bsp->pid = -1;
40         bsp->handle_exit_status(bp_sys::exit_status(false, true, signo));
41     }
42 };
43
44 namespace bp_sys {
45     // last signal sent:
46     extern int last_sig_sent;
47     extern pid_t last_forked_pid;
48 }
49
50 static void init_service_defaults(base_process_service &ps)
51 {
52     ps.set_restart_interval(time_val(10,0), 3);
53     ps.set_restart_delay(time_val(0, 200000000)); // 200 milliseconds
54     ps.set_stop_timeout(time_val(10,0));
55     ps.set_start_interruptible(false);
56 }
57
58 // Regular service start
59 void test_proc_service_start()
60 {
61     using namespace std;
62
63     service_set sset;
64
65     string command = "test-command";
66     list<pair<unsigned,unsigned>> command_offsets;
67     command_offsets.emplace_back(0, command.length());
68     std::list<prelim_dep> depends;
69
70     process_service p = process_service(&sset, "testproc", std::move(command), command_offsets, depends);
71     init_service_defaults(p);
72     sset.add_service(&p);
73
74     p.start(true);
75     sset.process_queues();
76
77     assert(p.get_state() == service_state_t::STARTING);
78
79     base_process_service_test::exec_succeeded(&p);
80     sset.process_queues();
81
82     assert(p.get_state() == service_state_t::STARTED);
83     assert(event_loop.active_timers.size() == 0);
84
85     sset.remove_service(&p);
86 }
87
88 // Unexpected termination
89 void test_proc_unexpected_term()
90 {
91     using namespace std;
92
93     service_set sset;
94
95     string command = "test-command";
96     list<pair<unsigned,unsigned>> command_offsets;
97     command_offsets.emplace_back(0, command.length());
98     std::list<prelim_dep> depends;
99
100     process_service p = process_service(&sset, "testproc", std::move(command), command_offsets, depends);
101     init_service_defaults(p);
102     sset.add_service(&p);
103
104     p.start(true);
105     sset.process_queues();
106
107     base_process_service_test::exec_succeeded(&p);
108     sset.process_queues();
109
110     assert(p.get_state() == service_state_t::STARTED);
111
112     base_process_service_test::handle_exit(&p, 0);
113     sset.process_queues();
114
115     assert(p.get_state() == service_state_t::STOPPED);
116     assert(event_loop.active_timers.size() == 0);
117
118     sset.remove_service(&p);
119 }
120
121 // Termination via stop request
122 void test_term_via_stop()
123 {
124     using namespace std;
125
126     service_set sset;
127
128     string command = "test-command";
129     list<pair<unsigned,unsigned>> command_offsets;
130     command_offsets.emplace_back(0, command.length());
131     std::list<prelim_dep> depends;
132
133     process_service p = process_service(&sset, "testproc", std::move(command), command_offsets, depends);
134     init_service_defaults(p);
135     sset.add_service(&p);
136
137     p.start(true);
138     sset.process_queues();
139
140     base_process_service_test::exec_succeeded(&p);
141     sset.process_queues();
142
143     assert(p.get_state() == service_state_t::STARTED);
144     assert(event_loop.active_timers.size() == 0);
145
146     p.stop(true);
147     sset.process_queues();
148
149     assert(p.get_state() == service_state_t::STOPPING);
150     assert(event_loop.active_timers.size() == 1);
151
152     base_process_service_test::handle_exit(&p, 0);
153     sset.process_queues();
154
155     assert(p.get_state() == service_state_t::STOPPED);
156     assert(event_loop.active_timers.size() == 0);
157
158     sset.remove_service(&p);
159 }
160
161 // Time-out during start
162 void test_proc_start_timeout()
163 {
164     using namespace std;
165
166     service_set sset;
167
168     string command = "test-command";
169     list<pair<unsigned,unsigned>> command_offsets;
170     command_offsets.emplace_back(0, command.length());
171     std::list<prelim_dep> depends;
172
173     process_service p {&sset, "testproc", std::move(command), command_offsets, depends};
174     init_service_defaults(p);
175     sset.add_service(&p);
176
177     p.start(true);
178     sset.process_queues();
179
180     assert(p.get_state() == service_state_t::STARTING);
181
182     p.timer_expired();
183     sset.process_queues();
184
185     assert(p.get_state() == service_state_t::STOPPING);
186
187     base_process_service_test::handle_exit(&p, 0);
188     sset.process_queues();
189
190     assert(p.get_state() == service_state_t::STOPPED);
191     assert(event_loop.active_timers.size() == 0);
192
193     sset.remove_service(&p);
194 }
195
196 // Test that a timeout doesn't stop a "waits for" dependent to fail to start
197 void test_proc_start_timeout2()
198 {
199     using namespace std;
200
201     service_set sset;
202
203     string command = "test-command";
204     list<pair<unsigned,unsigned>> command_offsets;
205     command_offsets.emplace_back(0, command.length());
206     std::list<prelim_dep> depends;
207
208     process_service p {&sset, "testproc", std::move(command), command_offsets, depends};
209     init_service_defaults(p);
210     sset.add_service(&p);
211
212     service_record ts {&sset, "test-service-1", service_type_t::INTERNAL, {{&p, dependency_type::WAITS_FOR}} };
213
214     ts.start(true);
215     sset.process_queues();
216
217     assert(p.get_state() == service_state_t::STARTING);
218     assert(ts.get_state() == service_state_t::STARTING);
219
220     p.timer_expired();
221     sset.process_queues();
222
223     assert(p.get_state() == service_state_t::STOPPING);
224
225     base_process_service_test::handle_exit(&p, 0);
226     sset.process_queues();
227
228     assert(p.get_state() == service_state_t::STOPPED);
229     assert(ts.get_state() == service_state_t::STARTED);
230     assert(event_loop.active_timers.size() == 0);
231
232     sset.remove_service(&p);
233 }
234
235 // Test stop timeout
236 void test_proc_stop_timeout()
237 {
238     using namespace std;
239
240     service_set sset;
241
242     string command = "test-command";
243     list<pair<unsigned,unsigned>> command_offsets;
244     command_offsets.emplace_back(0, command.length());
245     std::list<prelim_dep> depends;
246
247     process_service p = process_service(&sset, "testproc", std::move(command), command_offsets, depends);
248     init_service_defaults(p);
249     sset.add_service(&p);
250
251     p.start(true);
252     sset.process_queues();
253
254     assert(p.get_state() == service_state_t::STARTING);
255
256     base_process_service_test::exec_succeeded(&p);
257     sset.process_queues();
258
259     assert(p.get_state() == service_state_t::STARTED);
260
261     p.stop(true);
262     sset.process_queues();
263
264     assert(p.get_state() == service_state_t::STOPPING);
265     assert(bp_sys::last_sig_sent == SIGTERM);
266
267     p.timer_expired();
268     sset.process_queues();
269
270     // kill signal (SIGKILL) should have been sent; process not dead until it's dead, however
271     assert(p.get_state() == service_state_t::STOPPING);
272     assert(bp_sys::last_sig_sent == SIGKILL);
273
274     base_process_service_test::handle_exit(&p, 0);
275     sset.process_queues();
276
277     assert(p.get_state() == service_state_t::STOPPED);
278
279     // Note that timer is still active as we faked its expiry above
280     //assert(event_loop.active_timers.size() == 0);
281     event_loop.active_timers.clear();
282     sset.remove_service(&p);
283 }
284
285 // Smooth recovery
286 void test_proc_smooth_recovery1()
287 {
288     using namespace std;
289
290     service_set sset;
291
292     string command = "test-command";
293     list<pair<unsigned,unsigned>> command_offsets;
294     command_offsets.emplace_back(0, command.length());
295     std::list<prelim_dep> depends;
296
297     process_service p = process_service(&sset, "testproc", std::move(command), command_offsets, depends);
298     init_service_defaults(p);
299     p.set_smooth_recovery(true);
300     sset.add_service(&p);
301
302     p.start(true);
303     sset.process_queues();
304
305     base_process_service_test::exec_succeeded(&p);
306     sset.process_queues();
307
308     pid_t first_instance = bp_sys::last_forked_pid;
309
310     assert(p.get_state() == service_state_t::STARTED);
311
312     base_process_service_test::handle_exit(&p, 0);
313     sset.process_queues();
314
315     // since time hasn't been changed, we expect that the process has not yet been re-launched:
316     assert(first_instance == bp_sys::last_forked_pid);
317     assert(p.get_state() == service_state_t::STARTED);
318
319     p.timer_expired();
320     sset.process_queues();
321
322     // Now a new process should've been launched:
323     assert(first_instance + 1 == bp_sys::last_forked_pid);
324     assert(p.get_state() == service_state_t::STARTED);
325     event_loop.active_timers.clear();
326
327     sset.remove_service(&p);
328 }
329
330 // Smooth recovery without restart delay
331 void test_proc_smooth_recovery2()
332 {
333     using namespace std;
334
335     service_set sset;
336
337     string command = "test-command";
338     list<pair<unsigned,unsigned>> command_offsets;
339     command_offsets.emplace_back(0, command.length());
340     std::list<prelim_dep> depends;
341
342     process_service p = process_service(&sset, "testproc", std::move(command), command_offsets, depends);
343     init_service_defaults(p);
344     p.set_smooth_recovery(true);
345     p.set_restart_delay(time_val(0, 0));
346     sset.add_service(&p);
347
348     p.start(true);
349     sset.process_queues();
350
351     base_process_service_test::exec_succeeded(&p);
352     sset.process_queues();
353
354     pid_t first_instance = bp_sys::last_forked_pid;
355
356     assert(p.get_state() == service_state_t::STARTED);
357     assert(event_loop.active_timers.size() == 0);
358
359     base_process_service_test::handle_exit(&p, 0);
360     sset.process_queues();
361
362     // no restart delay, process should restart immediately:
363     assert(first_instance + 1 == bp_sys::last_forked_pid);
364     assert(p.get_state() == service_state_t::STARTED);
365     assert(event_loop.active_timers.size() == 0);
366
367     sset.remove_service(&p);
368 }
369
370 // Test stop timeout
371 void test_scripted_stop_timeout()
372 {
373     using namespace std;
374
375     service_set sset;
376
377     string command = "test-command";
378     string stopcommand = "stop-command";
379     list<pair<unsigned,unsigned>> command_offsets;
380     command_offsets.emplace_back(0, command.length());
381     std::list<prelim_dep> depends;
382
383     scripted_service p = scripted_service(&sset, "testscripted", std::move(command), command_offsets, depends);
384     init_service_defaults(p);
385     p.set_stop_command(stopcommand, command_offsets);
386     sset.add_service(&p);
387
388     p.start(true);
389     sset.process_queues();
390
391     assert(p.get_state() == service_state_t::STARTING);
392
393     base_process_service_test::exec_succeeded(&p);
394     sset.process_queues();
395     base_process_service_test::handle_exit(&p, 0);
396     sset.process_queues();
397
398     assert(p.get_state() == service_state_t::STARTED);
399
400     p.stop(true);
401     sset.process_queues();
402
403     assert(p.get_state() == service_state_t::STOPPING);
404
405     base_process_service_test::exec_succeeded(&p);
406     sset.process_queues();
407
408     // should still be stopping:
409     assert(p.get_state() == service_state_t::STOPPING);
410
411     p.timer_expired();
412     sset.process_queues();
413
414     // kill signal (SIGKILL) should have been sent; process not dead until it's dead, however
415     assert(p.get_state() == service_state_t::STOPPING);
416     assert(bp_sys::last_sig_sent == SIGKILL);
417
418     base_process_service_test::handle_exit(&p, SIGKILL);
419     sset.process_queues();
420
421     assert(p.get_state() == service_state_t::STOPPED);
422
423     event_loop.active_timers.clear();
424     sset.remove_service(&p);
425 }
426
427 void test_scripted_start_fail()
428 {
429     using namespace std;
430
431     service_set sset;
432
433     string command = "test-command";
434     string stopcommand = "stop-command";
435     list<pair<unsigned,unsigned>> command_offsets;
436     command_offsets.emplace_back(0, command.length());
437     std::list<prelim_dep> depends;
438
439     scripted_service p = scripted_service(&sset, "testscripted", std::move(command), command_offsets, depends);
440     init_service_defaults(p);
441     p.set_stop_command(stopcommand, command_offsets);
442     sset.add_service(&p);
443
444     service_record *s2 = new service_record(&sset, "test-service-2", service_type_t::INTERNAL, {{&p, REG}});
445     service_record *s3 = new service_record(&sset, "test-service-3", service_type_t::INTERNAL, {{&p, REG}, {s2, REG}});
446     sset.add_service(s2);
447     sset.add_service(s3);
448
449     s3->start(true);
450     sset.process_queues();
451
452     assert(p.get_state() == service_state_t::STARTING);
453
454     base_process_service_test::exec_succeeded(&p);
455     sset.process_queues();
456     base_process_service_test::handle_exit(&p, 0x1);  // exit fail
457     sset.process_queues();
458
459     // failed to start:
460     assert(p.get_state() == service_state_t::STOPPED);
461     assert(s2->get_state() == service_state_t::STOPPED);
462     assert(s3->get_state() == service_state_t::STOPPED);
463
464     event_loop.active_timers.clear();
465     sset.remove_service(&p);
466
467     assert(sset.count_active_services() == 0);
468 }
469
470 void test_scripted_stop_fail()
471 {
472     using namespace std;
473
474     service_set sset;
475
476     string command = "test-command";
477     string stopcommand = "stop-command";
478     list<pair<unsigned,unsigned>> command_offsets;
479     command_offsets.emplace_back(0, command.length());
480     std::list<prelim_dep> depends;
481
482     scripted_service p = scripted_service(&sset, "testscripted", std::move(command), command_offsets, depends);
483     init_service_defaults(p);
484     p.set_stop_command(stopcommand, command_offsets);
485     sset.add_service(&p);
486
487     service_record *s2 = new service_record(&sset, "test-service-2", service_type_t::INTERNAL, {});
488     service_record *s3 = new service_record(&sset, "test-service-3", service_type_t::INTERNAL, {{s2, REG}, {&p, REG}});
489     service_record *s4 = new service_record(&sset, "test-service-4", service_type_t::INTERNAL, {{&p, REG}, {s3, REG}});
490     sset.add_service(s2);
491     sset.add_service(s3);
492     sset.add_service(s4);
493
494     s4->start(true);
495     sset.process_queues();
496
497     base_process_service_test::exec_succeeded(&p);
498     sset.process_queues();
499     base_process_service_test::handle_exit(&p, 0x0);  // success
500     sset.process_queues();
501
502     assert(p.get_state() == service_state_t::STARTED);
503     assert(s2->get_state() == service_state_t::STARTED);
504     assert(s3->get_state() == service_state_t::STARTED);
505     assert(s4->get_state() == service_state_t::STARTED);
506
507     pid_t last_forked = bp_sys::last_forked_pid;
508
509     s4->stop(true);
510     sset.process_queues();
511
512     base_process_service_test::exec_succeeded(&p);
513     sset.process_queues();
514     base_process_service_test::handle_exit(&p, 0x1);  // failure
515     sset.process_queues();
516
517     // The stop command should be executed once:
518     assert((bp_sys::last_forked_pid - last_forked) == 1);
519
520     assert(p.get_state() == service_state_t::STOPPED);
521     assert(s2->get_state() == service_state_t::STOPPED);
522     assert(s3->get_state() == service_state_t::STOPPED);
523     assert(s4->get_state() == service_state_t::STOPPED);
524
525     event_loop.active_timers.clear();
526     sset.remove_service(&p);
527 }
528
529 void test_scripted_start_skip()
530 {
531     using namespace std;
532
533     service_set sset;
534
535     string command = "test-command";
536     list<pair<unsigned,unsigned>> command_offsets;
537     command_offsets.emplace_back(0, command.length());
538     std::list<prelim_dep> depends;
539
540     scripted_service p = scripted_service(&sset, "testscripted", std::move(command), command_offsets, depends);
541     init_service_defaults(p);
542     onstart_flags_t sflags;
543     sflags.skippable = true;
544     p.set_flags(sflags);
545     sset.add_service(&p);
546
547     service_record *s2 = new service_record(&sset, "test-service-2", service_type_t::INTERNAL, {{&p, REG}});
548     sset.add_service(s2);
549
550     s2->start(true);
551     sset.process_queues();
552     assert(p.get_state() == service_state_t::STARTING);
553
554     base_process_service_test::exec_succeeded(&p);
555     sset.process_queues();
556     assert(p.get_state() == service_state_t::STARTING);
557
558     base_process_service_test::handle_signal_exit(&p, SIGINT); // interrupted
559     sset.process_queues();
560
561     assert(p.get_state() == service_state_t::STARTED);
562     assert(s2->get_state() == service_state_t::STARTED);
563     assert(p.was_start_skipped());
564     assert(! s2->was_start_skipped());
565     assert(sset.count_active_services() == 2);
566
567     s2->stop(true);
568     sset.process_queues();
569
570     assert(p.get_state() == service_state_t::STOPPED);
571     assert(s2->get_state() == service_state_t::STOPPED);
572     assert(sset.count_active_services() == 0);
573
574     event_loop.active_timers.clear();
575     sset.remove_service(&p);
576 }
577
578 // Test interrupting start of a service marked skippable
579 void test_scripted_start_skip2()
580 {
581     using namespace std;
582
583     service_set sset;
584
585     string command = "test-command";
586     list<pair<unsigned,unsigned>> command_offsets;
587     command_offsets.emplace_back(0, command.length());
588     std::list<prelim_dep> depends;
589
590     scripted_service p = scripted_service(&sset, "testscripted", std::move(command), command_offsets, depends);
591     init_service_defaults(p);
592     onstart_flags_t sflags;
593     sflags.skippable = true;
594     p.set_flags(sflags);
595     p.set_start_interruptible(true);
596     sset.add_service(&p);
597
598     service_record *s2 = new service_record(&sset, "test-service-2", service_type_t::INTERNAL, {{&p, REG}});
599     sset.add_service(s2);
600
601     s2->start(true);
602     sset.process_queues();
603     assert(p.get_state() == service_state_t::STARTING);
604
605     base_process_service_test::exec_succeeded(&p);
606     sset.process_queues();
607     assert(p.get_state() == service_state_t::STARTING);
608
609     s2->stop(true);  // abort startup; p should be cancelled
610     sset.process_queues();
611
612     assert(p.get_state() == service_state_t::STOPPING);
613
614     base_process_service_test::handle_signal_exit(&p, SIGINT); // interrupted
615     sset.process_queues();
616
617     assert(p.get_state() == service_state_t::STOPPED);
618     assert(s2->get_state() == service_state_t::STOPPED);
619     assert(sset.count_active_services() == 0);
620
621     event_loop.active_timers.clear();
622     sset.remove_service(&p);
623 }
624
625 #define RUN_TEST(name, spacing) \
626     std::cout << #name "..." spacing; \
627     name(); \
628     std::cout << "PASSED" << std::endl;
629
630 int main(int argc, char **argv)
631 {
632     RUN_TEST(test_proc_service_start, "   ");
633     RUN_TEST(test_proc_unexpected_term, " ");
634     RUN_TEST(test_term_via_stop, "        ");
635     RUN_TEST(test_proc_start_timeout, "   ");
636     RUN_TEST(test_proc_start_timeout2, "  ");
637     RUN_TEST(test_proc_stop_timeout, "    ");
638     RUN_TEST(test_proc_smooth_recovery1, "");
639     RUN_TEST(test_proc_smooth_recovery2, "");
640     RUN_TEST(test_scripted_stop_timeout, "");
641     RUN_TEST(test_scripted_start_fail, "  ");
642     RUN_TEST(test_scripted_stop_fail, "   ");
643     RUN_TEST(test_scripted_start_skip, "  ");
644     RUN_TEST(test_scripted_start_skip2, " ");
645 }