e1eecdace2d7c4e5a0a97cb3fea54a0b6ffa44f2
[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
38 namespace bp_sys {
39     // last signal sent:
40     extern int last_sig_sent;
41     extern pid_t last_forked_pid;
42 }
43
44 static void init_service_defaults(base_process_service &ps)
45 {
46     ps.set_restart_interval(time_val(10,0), 3);
47     ps.set_restart_delay(time_val(0, 200000000)); // 200 milliseconds
48     ps.set_stop_timeout(time_val(10,0));
49     ps.set_start_interruptible(false);
50 }
51
52 // Regular service start
53 void test_proc_service_start()
54 {
55     using namespace std;
56
57     service_set sset;
58
59     string command = "test-command";
60     list<pair<unsigned,unsigned>> command_offsets;
61     command_offsets.emplace_back(0, command.length());
62     std::list<prelim_dep> depends;
63
64     process_service p = process_service(&sset, "testproc", std::move(command), command_offsets, depends);
65     init_service_defaults(p);
66     sset.add_service(&p);
67
68     p.start(true);
69     sset.process_queues();
70
71     assert(p.get_state() == service_state_t::STARTING);
72
73     base_process_service_test::exec_succeeded(&p);
74     sset.process_queues();
75
76     assert(p.get_state() == service_state_t::STARTED);
77     assert(event_loop.active_timers.size() == 0);
78
79     sset.remove_service(&p);
80 }
81
82 // Unexpected termination
83 void test_proc_unexpected_term()
84 {
85     using namespace std;
86
87     service_set sset;
88
89     string command = "test-command";
90     list<pair<unsigned,unsigned>> command_offsets;
91     command_offsets.emplace_back(0, command.length());
92     std::list<prelim_dep> depends;
93
94     process_service p = process_service(&sset, "testproc", std::move(command), command_offsets, depends);
95     init_service_defaults(p);
96     sset.add_service(&p);
97
98     p.start(true);
99     sset.process_queues();
100
101     base_process_service_test::exec_succeeded(&p);
102     sset.process_queues();
103
104     assert(p.get_state() == service_state_t::STARTED);
105
106     base_process_service_test::handle_exit(&p, 0);
107     sset.process_queues();
108
109     assert(p.get_state() == service_state_t::STOPPED);
110     assert(event_loop.active_timers.size() == 0);
111
112     sset.remove_service(&p);
113 }
114
115 // Termination via stop request
116 void test_term_via_stop()
117 {
118     using namespace std;
119
120     service_set sset;
121
122     string command = "test-command";
123     list<pair<unsigned,unsigned>> command_offsets;
124     command_offsets.emplace_back(0, command.length());
125     std::list<prelim_dep> depends;
126
127     process_service p = process_service(&sset, "testproc", std::move(command), command_offsets, depends);
128     init_service_defaults(p);
129     sset.add_service(&p);
130
131     p.start(true);
132     sset.process_queues();
133
134     base_process_service_test::exec_succeeded(&p);
135     sset.process_queues();
136
137     assert(p.get_state() == service_state_t::STARTED);
138     assert(event_loop.active_timers.size() == 0);
139
140     p.stop(true);
141     sset.process_queues();
142
143     assert(p.get_state() == service_state_t::STOPPING);
144     assert(event_loop.active_timers.size() == 1);
145
146     base_process_service_test::handle_exit(&p, 0);
147     sset.process_queues();
148
149     assert(p.get_state() == service_state_t::STOPPED);
150     assert(event_loop.active_timers.size() == 0);
151
152     sset.remove_service(&p);
153 }
154
155 // Time-out during start
156 void test_proc_start_timeout()
157 {
158     using namespace std;
159
160     service_set sset;
161
162     string command = "test-command";
163     list<pair<unsigned,unsigned>> command_offsets;
164     command_offsets.emplace_back(0, command.length());
165     std::list<prelim_dep> depends;
166
167     process_service p {&sset, "testproc", std::move(command), command_offsets, depends};
168     init_service_defaults(p);
169     sset.add_service(&p);
170
171     p.start(true);
172     sset.process_queues();
173
174     assert(p.get_state() == service_state_t::STARTING);
175
176     p.timer_expired();
177     sset.process_queues();
178
179     assert(p.get_state() == service_state_t::STOPPING);
180
181     base_process_service_test::handle_exit(&p, 0);
182     sset.process_queues();
183
184     assert(p.get_state() == service_state_t::STOPPED);
185     assert(event_loop.active_timers.size() == 0);
186
187     sset.remove_service(&p);
188 }
189
190 // Test that a timeout doesn't stop a "waits for" dependent to fail to start
191 void test_proc_start_timeout2()
192 {
193     using namespace std;
194
195     service_set sset;
196
197     string command = "test-command";
198     list<pair<unsigned,unsigned>> command_offsets;
199     command_offsets.emplace_back(0, command.length());
200     std::list<prelim_dep> depends;
201
202     process_service p {&sset, "testproc", std::move(command), command_offsets, depends};
203     init_service_defaults(p);
204     sset.add_service(&p);
205
206     service_record ts {&sset, "test-service-1", service_type_t::INTERNAL, {{&p, dependency_type::WAITS_FOR}} };
207
208     ts.start(true);
209     sset.process_queues();
210
211     assert(p.get_state() == service_state_t::STARTING);
212     assert(ts.get_state() == service_state_t::STARTING);
213
214     p.timer_expired();
215     sset.process_queues();
216
217     assert(p.get_state() == service_state_t::STOPPING);
218
219     base_process_service_test::handle_exit(&p, 0);
220     sset.process_queues();
221
222     assert(p.get_state() == service_state_t::STOPPED);
223     assert(ts.get_state() == service_state_t::STARTED);
224     assert(event_loop.active_timers.size() == 0);
225
226     sset.remove_service(&p);
227 }
228
229 // Test stop timeout
230 void test_proc_stop_timeout()
231 {
232     using namespace std;
233
234     service_set sset;
235
236     string command = "test-command";
237     list<pair<unsigned,unsigned>> command_offsets;
238     command_offsets.emplace_back(0, command.length());
239     std::list<prelim_dep> depends;
240
241     process_service p = process_service(&sset, "testproc", std::move(command), command_offsets, depends);
242     init_service_defaults(p);
243     sset.add_service(&p);
244
245     p.start(true);
246     sset.process_queues();
247
248     assert(p.get_state() == service_state_t::STARTING);
249
250     base_process_service_test::exec_succeeded(&p);
251     sset.process_queues();
252
253     assert(p.get_state() == service_state_t::STARTED);
254
255     p.stop(true);
256     sset.process_queues();
257
258     assert(p.get_state() == service_state_t::STOPPING);
259     assert(bp_sys::last_sig_sent == SIGTERM);
260
261     p.timer_expired();
262     sset.process_queues();
263
264     // kill signal (SIGKILL) should have been sent; process not dead until it's dead, however
265     assert(p.get_state() == service_state_t::STOPPING);
266     assert(bp_sys::last_sig_sent == SIGKILL);
267
268     base_process_service_test::handle_exit(&p, 0);
269     sset.process_queues();
270
271     assert(p.get_state() == service_state_t::STOPPED);
272
273     // Note that timer is still active as we faked its expiry above
274     //assert(event_loop.active_timers.size() == 0);
275     event_loop.active_timers.clear();
276     sset.remove_service(&p);
277 }
278
279 // Smooth recovery
280 void test_proc_smooth_recovery1()
281 {
282     using namespace std;
283
284     service_set sset;
285
286     string command = "test-command";
287     list<pair<unsigned,unsigned>> command_offsets;
288     command_offsets.emplace_back(0, command.length());
289     std::list<prelim_dep> depends;
290
291     process_service p = process_service(&sset, "testproc", std::move(command), command_offsets, depends);
292     init_service_defaults(p);
293     p.set_smooth_recovery(true);
294     sset.add_service(&p);
295
296     p.start(true);
297     sset.process_queues();
298
299     base_process_service_test::exec_succeeded(&p);
300     sset.process_queues();
301
302     pid_t first_instance = bp_sys::last_forked_pid;
303
304     assert(p.get_state() == service_state_t::STARTED);
305
306     base_process_service_test::handle_exit(&p, 0);
307     sset.process_queues();
308
309     // since time hasn't been changed, we expect that the process has not yet been re-launched:
310     assert(first_instance == bp_sys::last_forked_pid);
311     assert(p.get_state() == service_state_t::STARTED);
312
313     p.timer_expired();
314     sset.process_queues();
315
316     // Now a new process should've been launched:
317     assert(first_instance + 1 == bp_sys::last_forked_pid);
318     assert(p.get_state() == service_state_t::STARTED);
319     event_loop.active_timers.clear();
320
321     sset.remove_service(&p);
322 }
323
324 // Smooth recovery without restart delay
325 void test_proc_smooth_recovery2()
326 {
327     using namespace std;
328
329     service_set sset;
330
331     string command = "test-command";
332     list<pair<unsigned,unsigned>> command_offsets;
333     command_offsets.emplace_back(0, command.length());
334     std::list<prelim_dep> depends;
335
336     process_service p = process_service(&sset, "testproc", std::move(command), command_offsets, depends);
337     init_service_defaults(p);
338     p.set_smooth_recovery(true);
339     p.set_restart_delay(time_val(0, 0));
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     assert(event_loop.active_timers.size() == 0);
352
353     base_process_service_test::handle_exit(&p, 0);
354     sset.process_queues();
355
356     // no restart delay, process should restart immediately:
357     assert(first_instance + 1 == bp_sys::last_forked_pid);
358     assert(p.get_state() == service_state_t::STARTED);
359     assert(event_loop.active_timers.size() == 0);
360
361     sset.remove_service(&p);
362 }
363
364 // Test stop timeout
365 void test_scripted_stop_timeout()
366 {
367     using namespace std;
368
369     service_set sset;
370
371     string command = "test-command";
372     string stopcommand = "stop-command";
373     list<pair<unsigned,unsigned>> command_offsets;
374     command_offsets.emplace_back(0, command.length());
375     std::list<prelim_dep> depends;
376
377     scripted_service p = scripted_service(&sset, "testscripted", std::move(command), command_offsets, depends);
378     init_service_defaults(p);
379     p.set_stop_command(stopcommand, command_offsets);
380     sset.add_service(&p);
381
382     p.start(true);
383     sset.process_queues();
384
385     assert(p.get_state() == service_state_t::STARTING);
386
387     base_process_service_test::exec_succeeded(&p);
388     sset.process_queues();
389     base_process_service_test::handle_exit(&p, 0);
390     sset.process_queues();
391
392     assert(p.get_state() == service_state_t::STARTED);
393
394     p.stop(true);
395     sset.process_queues();
396
397     assert(p.get_state() == service_state_t::STOPPING);
398
399     base_process_service_test::exec_succeeded(&p);
400     sset.process_queues();
401
402     // should still be stopping:
403     assert(p.get_state() == service_state_t::STOPPING);
404
405     p.timer_expired();
406     sset.process_queues();
407
408     // kill signal (SIGKILL) should have been sent; process not dead until it's dead, however
409     assert(p.get_state() == service_state_t::STOPPING);
410     assert(bp_sys::last_sig_sent == SIGKILL);
411
412     base_process_service_test::handle_exit(&p, SIGKILL);
413     sset.process_queues();
414
415     assert(p.get_state() == service_state_t::STOPPED);
416
417     event_loop.active_timers.clear();
418     sset.remove_service(&p);
419 }
420
421 void test_scripted_start_fail()
422 {
423     using namespace std;
424
425     service_set sset;
426
427     string command = "test-command";
428     string stopcommand = "stop-command";
429     list<pair<unsigned,unsigned>> command_offsets;
430     command_offsets.emplace_back(0, command.length());
431     std::list<prelim_dep> depends;
432
433     scripted_service p = scripted_service(&sset, "testscripted", std::move(command), command_offsets, depends);
434     init_service_defaults(p);
435     p.set_stop_command(stopcommand, command_offsets);
436     sset.add_service(&p);
437
438     service_record *s2 = new service_record(&sset, "test-service-2", service_type_t::INTERNAL, {{&p, REG}});
439     service_record *s3 = new service_record(&sset, "test-service-3", service_type_t::INTERNAL, {{&p, REG}, {s2, REG}});
440     sset.add_service(s2);
441     sset.add_service(s3);
442
443     s3->start(true);
444     sset.process_queues();
445
446     assert(p.get_state() == service_state_t::STARTING);
447
448     base_process_service_test::exec_succeeded(&p);
449     sset.process_queues();
450     base_process_service_test::handle_exit(&p, 0x1);  // exit fail
451     sset.process_queues();
452
453     // failed to start:
454     assert(p.get_state() == service_state_t::STOPPED);
455     assert(s2->get_state() == service_state_t::STOPPED);
456     assert(s3->get_state() == service_state_t::STOPPED);
457
458     event_loop.active_timers.clear();
459     sset.remove_service(&p);
460
461     assert(sset.count_active_services() == 0);
462 }
463
464 void test_scripted_stop_fail()
465 {
466     using namespace std;
467
468     service_set sset;
469
470     string command = "test-command";
471     string stopcommand = "stop-command";
472     list<pair<unsigned,unsigned>> command_offsets;
473     command_offsets.emplace_back(0, command.length());
474     std::list<prelim_dep> depends;
475
476     scripted_service p = scripted_service(&sset, "testscripted", std::move(command), command_offsets, depends);
477     init_service_defaults(p);
478     p.set_stop_command(stopcommand, command_offsets);
479     sset.add_service(&p);
480
481     service_record *s2 = new service_record(&sset, "test-service-2", service_type_t::INTERNAL, {});
482     service_record *s3 = new service_record(&sset, "test-service-3", service_type_t::INTERNAL, {{s2, REG}, {&p, REG}});
483     service_record *s4 = new service_record(&sset, "test-service-4", service_type_t::INTERNAL, {{&p, REG}, {s3, REG}});
484     sset.add_service(s2);
485     sset.add_service(s3);
486     sset.add_service(s4);
487
488     s4->start(true);
489     sset.process_queues();
490
491     base_process_service_test::exec_succeeded(&p);
492     sset.process_queues();
493     base_process_service_test::handle_exit(&p, 0x0);  // success
494     sset.process_queues();
495
496     assert(p.get_state() == service_state_t::STARTED);
497     assert(s2->get_state() == service_state_t::STARTED);
498     assert(s3->get_state() == service_state_t::STARTED);
499     assert(s4->get_state() == service_state_t::STARTED);
500
501     pid_t last_forked = bp_sys::last_forked_pid;
502
503     s4->stop(true);
504     sset.process_queues();
505
506     base_process_service_test::exec_succeeded(&p);
507     sset.process_queues();
508     base_process_service_test::handle_exit(&p, 0x1);  // failure
509     sset.process_queues();
510
511     // The stop command should be executed once:
512     assert((bp_sys::last_forked_pid - last_forked) == 1);
513
514     assert(p.get_state() == service_state_t::STOPPED);
515     assert(s2->get_state() == service_state_t::STOPPED);
516     assert(s3->get_state() == service_state_t::STOPPED);
517     assert(s4->get_state() == service_state_t::STOPPED);
518
519     event_loop.active_timers.clear();
520     sset.remove_service(&p);
521 }
522
523 #define RUN_TEST(name, spacing) \
524     std::cout << #name "..." spacing; \
525     name(); \
526     std::cout << "PASSED" << std::endl;
527
528 int main(int argc, char **argv)
529 {
530     RUN_TEST(test_proc_service_start, "   ");
531     RUN_TEST(test_proc_unexpected_term, " ");
532     RUN_TEST(test_term_via_stop, "        ");
533     RUN_TEST(test_proc_start_timeout, "   ");
534     RUN_TEST(test_proc_start_timeout2, "  ");
535     RUN_TEST(test_proc_stop_timeout, "    ");
536     RUN_TEST(test_proc_smooth_recovery1, "");
537     RUN_TEST(test_proc_smooth_recovery2, "");
538     RUN_TEST(test_scripted_stop_timeout, "");
539     RUN_TEST(test_scripted_start_fail, "  ");
540     RUN_TEST(test_scripted_stop_fail, "   ");
541 }