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