blob: 0460bc37f00c790e0f6ff4f0873afce492754f4b [file] [log] [blame]
Philipp Schraderfa8fc492023-09-26 14:52:02 -07001#include <signal.h>
2#include <sys/types.h>
3
4#include <filesystem>
5
6#include "gtest/gtest.h"
7
8#include "aos/events/shm_event_loop.h"
9#include "aos/starter/subprocess.h"
10#include "aos/testing/path.h"
11#include "aos/testing/tmpdir.h"
12#include "aos/util/file.h"
13
14namespace aos::starter::testing {
15
16namespace {
17void Wait(pid_t pid) {
18 int status;
19 if (waitpid(pid, &status, 0) != pid) {
20 if (errno != ECHILD) {
21 PLOG(ERROR) << "Failed to wait for PID " << pid << ": " << status;
22 FAIL();
23 }
24 }
25 LOG(INFO) << "Succesfully waited for PID " << pid;
26}
27
28} // namespace
29
30// Validates that killing a child process right after startup doesn't have any
31// unexpected consequences. The child process should exit even if it hasn't
32// `exec()`d yet.
33TEST(SubprocessTest, KillDuringStartup) {
34 const std::string config_file =
35 ::aos::testing::ArtifactPath("aos/events/pingpong_config.json");
36 aos::FlatbufferDetachedBuffer<aos::Configuration> config =
37 aos::configuration::ReadConfig(config_file);
38 aos::ShmEventLoop event_loop(&config.message());
39
40 // Run an application that takes a really long time to exit. The exact details
41 // here don't matter. We just need to to survive long enough until we can call
42 // Terminate() below.
43 auto application =
44 std::make_unique<Application>("sleep", "sleep", &event_loop, []() {});
45 application->set_args({"100"});
46
47 // Track whether we exit via our own timer callback. We don't want to exit
48 // because of any strange interactions with the child process.
49 bool exited_as_expected = false;
50
51 // Here's the sequence of events that we expect to see:
52 // 1. Start child process.
53 // 2. Stop child process (via `Terminate()`).
54 // 3. Wait 1 second.
55 // 4. Set `exited_as_expected` to `true`.
56 // 5. Exit the event loop.
57 //
58 // At the end, if `exited_as_expected` is `false`, then something unexpected
59 // happened and we failed the test here.
60 aos::TimerHandler *shutdown_timer = event_loop.AddTimer([&]() {
61 exited_as_expected = true;
62 event_loop.Exit();
63 });
64 aos::TimerHandler *trigger_timer = event_loop.AddTimer([&]() {
65 application->Start();
66 application->Terminate();
67 shutdown_timer->Schedule(event_loop.monotonic_now() +
68 std::chrono::seconds(1));
69 });
70 trigger_timer->Schedule(event_loop.monotonic_now());
71 event_loop.Run();
72 application->Stop();
73 Wait(application->get_pid());
74
75 EXPECT_TRUE(exited_as_expected) << "It looks like we killed ourselves.";
76}
77
78// Validates that the code in subprocess.cc doesn't accidentally block signals
79// in the child process.
80TEST(SubprocessTest, CanKillAfterStartup) {
81 const std::string config_file =
82 ::aos::testing::ArtifactPath("aos/events/pingpong_config.json");
83 aos::FlatbufferDetachedBuffer<aos::Configuration> config =
84 aos::configuration::ReadConfig(config_file);
85 aos::ShmEventLoop event_loop(&config.message());
86
87 // We create a directory in which we create some files so this test here and
88 // the subsequently created child process can "signal" one another. We roughly
89 // expect the following sequence of events:
90 // 1. Start the child process.
91 // 2. Test waits for "startup" file to be created by child.
92 // 3. Child process creates the "startup" file.
93 // 4. Test sees "startup" file being created, sends SIGTERM to child.
94 // 5. Child sees SIGTERM, creates "shutdown" file, and exits.
95 // 6. Test waits for child to exit.
96 // 7. Test validates that the "shutdown" file was created by the child.
97 auto signal_dir = std::filesystem::path(aos::testing::TestTmpDir()) /
98 "startup_file_signals";
99 ASSERT_TRUE(std::filesystem::create_directory(signal_dir));
100 auto startup_signal_file = signal_dir / "startup";
101 auto shutdown_signal_file = signal_dir / "shutdown";
102
103 auto application = std::make_unique<Application>("/bin/bash", "/bin/bash",
104 &event_loop, []() {});
105 application->set_args(
106 {"-c", absl::StrCat("cleanup() { touch ", shutdown_signal_file.string(),
107 "; exit 0; }; trap cleanup SIGTERM; touch ",
108 startup_signal_file.string(),
109 "; while true; do sleep 0.1; done;")});
110
111 // Wait for the child process to create the "startup" file.
112 ASSERT_FALSE(std::filesystem::exists(startup_signal_file));
113 application->Start();
114 while (!std::filesystem::exists(startup_signal_file)) {
115 std::this_thread::sleep_for(std::chrono::milliseconds(50));
116 }
117 ASSERT_FALSE(std::filesystem::exists(shutdown_signal_file));
118
119 // Manually kill the application here. The Stop() and Terminate() helpers
120 // trigger some timeout behaviour that interferes with the test here. This
121 // should cause the child to exit and create the "shutdown" file.
122 PCHECK(kill(application->get_pid(), SIGTERM) == 0);
123 Wait(application->get_pid());
124 ASSERT_TRUE(std::filesystem::exists(shutdown_signal_file));
125}
126
127} // namespace aos::starter::testing