4f04157060c20fe6adae87184a820bda5793853a
[oweals/dinit.git] / src / tests / tests.cc
1 #include <cassert>
2 #include <iostream>
3
4 #include "service.h"
5 #include "test_service.h"
6
7 constexpr static auto REG = dependency_type::REGULAR;
8 constexpr static auto WAITS = dependency_type::WAITS_FOR;
9 constexpr static auto MS = dependency_type::MILESTONE;
10
11 // Test 1: starting a service starts dependencies; stopping the service releases and
12 // stops dependencies.
13 void test1()
14 {
15     service_set sset;
16
17     service_record *s1 = new service_record(&sset, "test-service-1", service_type_t::INTERNAL, {});
18     service_record *s2 = new service_record(&sset, "test-service-2", service_type_t::INTERNAL, {{s1, REG}});
19     service_record *s3 = new service_record(&sset, "test-service-3", service_type_t::INTERNAL, {{s2, REG}});
20     sset.add_service(s1);
21     sset.add_service(s2);
22     sset.add_service(s3);
23
24     assert(sset.find_service("test-service-1") == s1);
25     assert(sset.find_service("test-service-2") == s2);
26     assert(sset.find_service("test-service-3") == s3);
27
28     // s3 depends on s2, which depends on s1. So starting s3 should start all three services:
29     sset.start_service(s3);
30
31     assert(s1->get_state() == service_state_t::STARTED);
32     assert(s2->get_state() == service_state_t::STARTED);
33     assert(s3->get_state() == service_state_t::STARTED);
34
35     // stopping s3 should release the other two services:
36     sset.stop_service(s3);
37
38     assert(s3->get_state() == service_state_t::STOPPED);
39     assert(s2->get_state() == service_state_t::STOPPED);
40     assert(s1->get_state() == service_state_t::STOPPED);
41 }
42
43 // Test 2: Multiple dependents will hold a dependency active if one of the dependents is
44 // stopped/released.
45 void test2()
46 {
47     service_set sset;
48
49     service_record *s1 = new service_record(&sset, "test-service-1", service_type_t::INTERNAL, {});
50     service_record *s2 = new service_record(&sset, "test-service-2", service_type_t::INTERNAL, {{s1, REG}});
51     service_record *s3 = new service_record(&sset, "test-service-3", service_type_t::INTERNAL, {{s2, REG}});
52     service_record *s4 = new service_record(&sset, "test-service-4", service_type_t::INTERNAL, {{s2, REG}});
53     sset.add_service(s1);
54     sset.add_service(s2);
55     sset.add_service(s3);
56     sset.add_service(s4);
57
58     // s3 depends on s2, which depends on s1. Similarly with s4. After starting both, all services
59     // should be started:
60     sset.start_service(s3);
61     sset.start_service(s4);
62
63     assert(s1->get_state() == service_state_t::STARTED);
64     assert(s2->get_state() == service_state_t::STARTED);
65     assert(s3->get_state() == service_state_t::STARTED);
66     assert(s4->get_state() == service_state_t::STARTED);
67
68     // after stopping s3, s4 should hold the other two services:
69     sset.stop_service(s3);
70
71     assert(s4->get_state() == service_state_t::STARTED);
72     assert(s3->get_state() == service_state_t::STOPPED);
73     assert(s2->get_state() == service_state_t::STARTED);
74     assert(s1->get_state() == service_state_t::STARTED);
75
76     // Now if we stop s4, s2 and s1 should also be released:
77     sset.stop_service(s4);
78
79     assert(s4->get_state() == service_state_t::STOPPED);
80     assert(s3->get_state() == service_state_t::STOPPED);
81     assert(s2->get_state() == service_state_t::STOPPED);
82     assert(s1->get_state() == service_state_t::STOPPED);
83 }
84
85 // Test 3: stopping a dependency causes its dependents to stop.
86 void test3()
87 {
88     service_set sset;
89
90     service_record *s1 = new service_record(&sset, "test-service-1", service_type_t::INTERNAL, {});
91     service_record *s2 = new service_record(&sset, "test-service-2", service_type_t::INTERNAL, {{s1, REG}});
92     service_record *s3 = new service_record(&sset, "test-service-3", service_type_t::INTERNAL, {{s2, REG}});
93     sset.add_service(s1);
94     sset.add_service(s2);
95     sset.add_service(s3);
96
97     assert(sset.find_service("test-service-1") == s1);
98     assert(sset.find_service("test-service-2") == s2);
99     assert(sset.find_service("test-service-3") == s3);
100
101     // Start all three services:
102     sset.start_service(s3);
103
104     // Now stop s1, which should also force s2 and s3 to stop:
105     sset.stop_service(s1);
106
107     assert(s3->get_state() == service_state_t::STOPPED);
108     assert(s2->get_state() == service_state_t::STOPPED);
109     assert(s1->get_state() == service_state_t::STOPPED);
110 }
111
112 // Test 4: an explicitly activated service with automatic restart will restart if it
113 // stops due to a dependency stopping, therefore also causing the dependency to restart.
114 void test4()
115 {
116     service_set sset;
117
118     service_record *s1 = new service_record(&sset, "test-service-1", service_type_t::INTERNAL, {});
119     service_record *s2 = new service_record(&sset, "test-service-2", service_type_t::INTERNAL, {{s1, REG}});
120     service_record *s3 = new service_record(&sset, "test-service-3", service_type_t::INTERNAL, {{s2, REG}});
121     s2->set_auto_restart(true);
122     sset.add_service(s1);
123     sset.add_service(s2);
124     sset.add_service(s3);
125
126     assert(sset.find_service("test-service-1") == s1);
127     assert(sset.find_service("test-service-2") == s2);
128     assert(sset.find_service("test-service-3") == s3);
129
130     // Start all three services:
131     sset.start_service(s3);
132
133     // Also explicitly activate s2:
134     sset.start_service(s2);
135
136     // Now stop s1, which should also force s2 and s3 to stop.
137     // s2 (and therefore s1) should restart:
138     sset.stop_service(s1);
139
140     assert(s3->get_state() == service_state_t::STOPPED);
141     assert(s2->get_state() == service_state_t::STARTED);
142     assert(s1->get_state() == service_state_t::STARTED);
143 }
144
145 // Test 5: test that services which do not start immediately correctly chain start of
146 // dependent services.
147 void test5()
148 {
149     service_set sset;
150
151     test_service *s1 = new test_service(&sset, "test-service-1", service_type_t::INTERNAL, {});
152     test_service *s2 = new test_service(&sset, "test-service-2", service_type_t::INTERNAL, {{s1, REG}});
153     test_service *s3 = new test_service(&sset, "test-service-3", service_type_t::INTERNAL, {{s2, REG}});
154
155     sset.add_service(s1);
156     sset.add_service(s2);
157     sset.add_service(s3);
158
159     sset.start_service(s3);
160
161     // All three should transition to STARTING state:
162     assert(s3->get_state() == service_state_t::STARTING);
163     assert(s2->get_state() == service_state_t::STARTING);
164     assert(s1->get_state() == service_state_t::STARTING);
165
166     s1->started();
167     sset.process_queues();
168     assert(s3->get_state() == service_state_t::STARTING);
169     assert(s2->get_state() == service_state_t::STARTING);
170     assert(s1->get_state() == service_state_t::STARTED);
171
172     s2->started();
173     sset.process_queues();
174     assert(s3->get_state() == service_state_t::STARTING);
175     assert(s2->get_state() == service_state_t::STARTED);
176     assert(s1->get_state() == service_state_t::STARTED);
177
178     s3->started();
179     sset.process_queues();
180     assert(s3->get_state() == service_state_t::STARTED);
181     assert(s2->get_state() == service_state_t::STARTED);
182     assert(s1->get_state() == service_state_t::STARTED);
183 }
184
185 // Test that service pinned in start state is not stopped when its dependency stops.
186 void test_pin1()
187 {
188     service_set sset;
189
190     service_record *s1 = new service_record(&sset, "test-service-1", service_type_t::INTERNAL, {});
191     service_record *s2 = new service_record(&sset, "test-service-2", service_type_t::INTERNAL, {{s1, REG}});
192     service_record *s3 = new service_record(&sset, "test-service-3", service_type_t::INTERNAL, {{s2, REG}});
193     s2->set_auto_restart(true);
194     sset.add_service(s1);
195     sset.add_service(s2);
196     sset.add_service(s3);
197
198     // Pin s3:
199     s3->pin_start();
200
201     // Start all three services:
202     sset.start_service(s3);
203
204     assert(s3->get_state() == service_state_t::STARTED);
205     assert(s2->get_state() == service_state_t::STARTED);
206     assert(s1->get_state() == service_state_t::STARTED);
207
208     // Stop s2:
209     s2->forced_stop();
210     s2->stop(true);
211     sset.process_queues();
212
213     // s3 should remain started due to pin:
214     assert(s3->get_state() == service_state_t::STARTED);
215     assert(s2->get_state() == service_state_t::STOPPING);
216     assert(s1->get_state() == service_state_t::STOPPING);
217
218     // If we now unpin, s3 should stop:
219     s3->unpin();
220     sset.process_queues();
221     assert(s3->get_state() == service_state_t::STOPPED);
222     assert(s2->get_state() == service_state_t::STOPPED);
223     assert(s1->get_state() == service_state_t::STOPPED);
224 }
225
226 // Test that STOPPING dependency of pinned service returns to STARTED when the pinned service has
227 // a start issued.
228 void test_pin2()
229 {
230     service_set sset;
231
232     service_record *s1 = new service_record(&sset, "test-service-1", service_type_t::INTERNAL, {});
233     service_record *s2 = new service_record(&sset, "test-service-2", service_type_t::INTERNAL, {{s1, REG}});
234     service_record *s3 = new service_record(&sset, "test-service-3", service_type_t::INTERNAL, {{s2, REG}});
235     s2->set_auto_restart(true);
236     sset.add_service(s1);
237     sset.add_service(s2);
238     sset.add_service(s3);
239
240     // Pin s3:
241     s3->pin_start();
242
243     // Start all three services:
244     sset.start_service(s3);
245
246     assert(s3->get_state() == service_state_t::STARTED);
247     assert(s2->get_state() == service_state_t::STARTED);
248     assert(s1->get_state() == service_state_t::STARTED);
249
250     // Issue stop to s3:
251     s3->stop(true);
252     sset.process_queues();
253
254     // s3 should remain started due to pin, but s1 and s2 are released and go STOPPING:
255     assert(s3->get_state() == service_state_t::STARTED);
256     assert(s2->get_state() == service_state_t::STOPPING);
257     assert(s1->get_state() == service_state_t::STOPPING);
258
259     // If we now issue start, STOPPING should revert to STARTED:
260     s3->start(true);
261     sset.process_queues();
262
263     assert(s3->get_state() == service_state_t::STARTED);
264     assert(s2->get_state() == service_state_t::STARTED);
265     assert(s1->get_state() == service_state_t::STARTED);
266 }
267
268 // Test that service pinned started is released when stop issued and stops when unpinned
269 void test_pin3()
270 {
271     service_set sset;
272
273     service_record *s1 = new service_record(&sset, "test-service-1", service_type_t::INTERNAL, {});
274     sset.add_service(s1);
275
276     // Pin s3:
277     s1->pin_start();
278
279     // Start the service:
280     sset.start_service(s1);
281
282     assert(s1->get_state() == service_state_t::STARTED);
283
284     // Issue stop:
285     s1->stop(true);
286     sset.process_queues();
287
288     // s3 should remain started:
289     assert(s1->get_state() == service_state_t::STARTED);
290
291     // If we now unpin, s1 should stop:
292     s1->unpin();
293     sset.process_queues();
294     assert(s1->get_state() == service_state_t::STOPPED);
295 }
296
297 // Test 7: stopping a soft dependency doesn't cause the dependent to stop.
298 void test7()
299 {
300     service_set sset;
301
302     service_record *s1 = new service_record(&sset, "test-service-1", service_type_t::INTERNAL, {});
303     service_record *s2 = new service_record(&sset, "test-service-2", service_type_t::INTERNAL, {{s1, REG}});
304     service_record *s3 = new service_record(&sset, "test-service-3", service_type_t::INTERNAL, {{s2, WAITS}});
305     sset.add_service(s1);
306     sset.add_service(s2);
307     sset.add_service(s3);
308
309     assert(sset.find_service("test-service-1") == s1);
310     assert(sset.find_service("test-service-2") == s2);
311     assert(sset.find_service("test-service-3") == s3);
312
313     // Start all three services:
314     sset.start_service(s3);
315
316     // Now stop s1, which should also force s2 but not s3 to stop:
317     sset.stop_service(s1);
318
319     assert(s3->get_state() == service_state_t::STARTED);
320     assert(s2->get_state() == service_state_t::STOPPED);
321     assert(s1->get_state() == service_state_t::STOPPED);
322 }
323
324 // Test 8: stopping a milestone dependency doesn't cause the dependent to stop
325 void test8()
326 {
327     service_set sset;
328
329     service_record *s1 = new service_record(&sset, "test-service-1", service_type_t::INTERNAL, {});
330     service_record *s2 = new service_record(&sset, "test-service-2", service_type_t::INTERNAL, {{s1, MS}});
331     sset.add_service(s1);
332     sset.add_service(s2);
333
334     assert(sset.find_service("test-service-1") == s1);
335     assert(sset.find_service("test-service-2") == s2);
336
337     // Start the services:
338     sset.start_service(s2);
339
340     assert(s2->get_state() == service_state_t::STARTED);
341     assert(s1->get_state() == service_state_t::STARTED);
342
343     // Now stop s1, which should not stop s2:
344     sset.stop_service(s1);
345
346     assert(s2->get_state() == service_state_t::STARTED);
347     assert(s1->get_state() == service_state_t::STOPPED);
348 }
349
350 // Test 9: a failing milestone dependency causes the dependent to fail
351 void test9()
352 {
353     service_set sset;
354
355     test_service *s1 = new test_service(&sset, "test-service-1", service_type_t::INTERNAL, {});
356     test_service *s2 = new test_service(&sset, "test-service-2", service_type_t::INTERNAL, {{s1, MS}});
357     sset.add_service(s1);
358     sset.add_service(s2);
359
360     assert(sset.find_service("test-service-1") == s1);
361     assert(sset.find_service("test-service-2") == s2);
362
363     // Start the services, but fail s1:
364     sset.start_service(s2);
365
366     assert(s1->get_state() == service_state_t::STARTING);
367     s1->failed_to_start();
368     sset.process_queues();
369
370     assert(s2->get_state() == service_state_t::STOPPED);
371 }
372
373 // Test 10: if start cancelled, remove from console queue
374 void test10()
375 {
376     service_set sset;
377
378     // Create s1 and s2. s2 depends on s1, and starts on the console.
379     test_service *s1 = new test_service(&sset, "test-service-1", service_type_t::INTERNAL, {});
380     test_service *s2 = new test_service(&sset, "test-service-2", service_type_t::INTERNAL, {{s1, REG}});
381     onstart_flags_t s2_flags;
382     s2_flags.starts_on_console = true;
383     s2->set_flags(s2_flags);
384     sset.add_service(s1);
385     sset.add_service(s2);
386
387     // Create s3, which starts and runs on console:
388     test_service *s3 = new test_service(&sset, "test-service-3", service_type_t::INTERNAL, {});
389     onstart_flags_t s3_flags;
390     s3_flags.starts_on_console = true;
391     s3_flags.runs_on_console = true;
392     sset.add_service(s3);
393
394     assert(sset.find_service("test-service-1") == s1);
395     assert(sset.find_service("test-service-2") == s2);
396     assert(sset.find_service("test-service-3") == s3);
397
398     // Start the s3 service, so it gets console:
399     sset.start_service(s3);
400     sset.process_queues();
401     s3->started();
402     sset.process_queues();
403
404     assert(! sset.is_queued_for_console(s3)); // should not be queued, because already has acquired
405     assert(sset.is_console_queue_empty());
406
407     // Start s2, which starts s1 as a dependency:
408
409     sset.start_service(s2);
410     sset.process_queues();
411
412     assert(s1->get_state() == service_state_t::STARTING);
413     assert(s2->get_state() == service_state_t::STARTING);
414
415     s1->started();
416     sset.process_queues();
417
418     // s2 should now be waiting for console:
419     assert(s1->get_state() == service_state_t::STARTED);
420     assert(s2->get_state() == service_state_t::STARTING);
421     assert(sset.is_queued_for_console(s2));
422
423     // stop s1, should stop s2, s2 should unqueue:
424     s1->stop();
425     sset.process_queues();
426
427     assert(s1->get_state() == service_state_t::STOPPED);
428     assert(s2->get_state() == service_state_t::STOPPED);
429     assert(! sset.is_queued_for_console(s2));
430 }
431
432 #define RUN_TEST(name) \
433     std::cout << #name "... "; \
434     name(); \
435     std::cout << "PASSED" << std::endl;
436
437 int main(int argc, char **argv)
438 {
439     RUN_TEST(test1);
440     RUN_TEST(test2);
441     RUN_TEST(test3);
442     RUN_TEST(test4);
443     RUN_TEST(test5);
444     RUN_TEST(test_pin1);
445     RUN_TEST(test_pin2);
446     RUN_TEST(test_pin3);
447     RUN_TEST(test7);
448     RUN_TEST(test8);
449     RUN_TEST(test9);
450     RUN_TEST(test10);
451 }