1 /*
2  *     Copyright 2021-Present Couchbase, Inc.
3  *
4  *   Use of this software is governed by the Business Source License included
5  *   in the file licenses/BSL-Couchbase.txt.  As of the Change Date specified
6  *   in that file, in accordance with the Business Source License, use of this
7  *   software will be governed by the Apache License, Version 2.0, included in
8  *   the file licenses/APL2.txt.
9  */
10 
11 #include <folly/Synchronized.h>
12 #include <folly/portability/GTest.h>
13 #include <nlohmann/json.hpp>
14 #include <platform/dirutils.h>
15 #include <platform/process_monitor.h>
16 #include <filesystem>
17 #include <thread>
18 #include <vector>
19 
20 #ifndef WIN32
21 #include <csignal>
22 #endif
23 
testExitCode(int exitcode)24 static void testExitCode(int exitcode) {
25     std::filesystem::path exe{std::filesystem::current_path() /
26                               "process_monitor_child"};
27     if (!std::filesystem::exists(exe)) {
28         exe = exe.generic_string() + ".exe";
29         if (!std::filesystem::exists(exe)) {
30             FAIL() << "Failed to locate " << exe.generic_string();
31         }
32     }
33 
34     const auto lockfile =
35             std::filesystem::path(cb::io::mktemp("./process_monitor."));
36     const auto exitcodestring = std::to_string(exitcode);
37     std::vector<std::string> argv = {{exe.generic_string(),
38                                       "--lockfile",
39                                       lockfile.generic_string(),
40                                       "--exitcode",
41                                       exitcodestring}};
42 
43     std::atomic_bool notified{false};
44     auto child = ProcessMonitor::create(
45             argv, [&notified](const auto&) { notified.store(true); });
46     EXPECT_TRUE(child);
47     EXPECT_TRUE(child->isRunning());
48     EXPECT_FALSE(notified.load());
49     remove(lockfile);
50     const auto timeout =
51             std::chrono::steady_clock::now() + std::chrono::seconds{10};
52     while (child->isRunning() && std::chrono::steady_clock::now() < timeout) {
53         std::this_thread::sleep_for(std::chrono::microseconds{10});
54     }
55     EXPECT_LT(std::chrono::steady_clock::now(), timeout)
56             << "Timeout waiting for the child to terminate";
57     EXPECT_TRUE(notified.load());
58     auto& ec = child->getExitCode();
59 
60     if (exitcode == EXIT_SUCCESS) {
61         EXPECT_TRUE(ec.isSuccess()) << ec.to_string() << " " << ec.to_json();
62         EXPECT_EQ("Success", ec.to_string());
63     } else {
64         EXPECT_FALSE(ec.isSuccess()) << ec.to_string() << " " << ec.to_json();
65         EXPECT_EQ("Failure", ec.to_string());
66     }
67 }
68 
TEST(ProcessMonitorChild,Success)69 TEST(ProcessMonitorChild, Success) {
70     testExitCode(EXIT_SUCCESS);
71 }
72 
TEST(ProcessMonitorChild,Failure)73 TEST(ProcessMonitorChild, Failure) {
74     testExitCode(EXIT_FAILURE);
75 }
76 
77 #ifndef WIN32
TEST(ProcessMonitorChild,Abort)78 TEST(ProcessMonitorChild, Abort) {
79     std::filesystem::path exe{std::filesystem::current_path() /
80                               "process_monitor_child"};
81     if (!std::filesystem::exists(exe)) {
82         FAIL() << "Failed to locate " << exe.generic_string();
83     }
84 
85     const auto lockfile =
86             std::filesystem::path(cb::io::mktemp("./process_monitor."));
87     std::vector<std::string> argv = {
88             {exe.generic_string(), "--lockfile", lockfile.generic_string()}};
89 
90     auto child = ProcessMonitor::create(argv, [](const auto&) {});
91     EXPECT_TRUE(child);
92     EXPECT_TRUE(child->isRunning());
93 
94     auto descr = child->describe();
95     ASSERT_EQ(0, kill(descr["pid"].get<int>(), SIGALRM));
96 
97     const auto timeout =
98             std::chrono::steady_clock::now() + std::chrono::seconds{10};
99     while (child->isRunning() && std::chrono::steady_clock::now() < timeout) {
100         std::this_thread::sleep_for(std::chrono::microseconds{10});
101     }
102     EXPECT_LT(std::chrono::steady_clock::now(), timeout)
103             << "Timeout waiting for the child to terminate";
104     // Clean up the lockfile
105     remove(lockfile);
106 
107     auto& ec = child->getExitCode();
108     EXPECT_FALSE(ec.isSuccess()) << ec.to_string() << " " << ec.to_json();
109     auto json = ec.to_json();
110     EXPECT_EQ(0, json["WCOREDUMP"]);
111     EXPECT_EQ(0, json["WEXITSTATUS"]);
112     EXPECT_EQ(false, json["WIFEXITED"]);
113     EXPECT_EQ(true, json["WIFSIGNALED"]);
114     EXPECT_EQ(SIGALRM, json["WTERMSIG"]);
115 }
116 #endif
117 
118 /**
119  * Test the monitor functionality for "parent" processes (it works
120  * for all processes we have search rights for and not just parent
121  * processes, so we may test it by creating a child and then use a
122  * second monitor to monitor the child.
123  */
TEST(ProcessMonitor,OtherProcess)124 TEST(ProcessMonitor, OtherProcess) {
125     std::filesystem::path exe{std::filesystem::current_path() /
126                               "process_monitor_child"};
127     if (!std::filesystem::exists(exe)) {
128         exe = exe.generic_string() + ".exe";
129         if (!std::filesystem::exists(exe)) {
130             FAIL() << "Failed to locate " << exe.generic_string();
131         }
132     }
133 
134     const auto lockfile =
135             std::filesystem::path(cb::io::mktemp("./process_monitor."));
136     std::vector<std::string> argv = {
137             {exe.generic_string(), "--lockfile", lockfile.generic_string()}};
138 
139     std::atomic_bool child_notified{false};
140     auto child = ProcessMonitor::create(argv, [&child_notified](const auto&) {
141         child_notified.store(true);
142     });
143 
144     // Create the monitor to watch the other process
145     std::atomic_bool other_notified{false};
146     auto other = ProcessMonitor::create(
147             child->describe()["pid"].get<int>(),
148             [&other_notified](const auto&) { other_notified.store(true); });
149 
150     EXPECT_TRUE(child->isRunning());
151     EXPECT_TRUE(other->isRunning());
152 
153     // Tell the child to exit and verify that its cone
154     remove(lockfile);
155 
156     const auto timeout =
157             std::chrono::steady_clock::now() + std::chrono::seconds{10};
158     while ((child->isRunning() || other->isRunning()) &&
159            std::chrono::steady_clock::now() < timeout) {
160         std::this_thread::sleep_for(std::chrono::microseconds{10});
161     }
162 
163     EXPECT_FALSE(child->isRunning());
164     EXPECT_FALSE(other->isRunning());
165 
166     // And both should have been notified
167     EXPECT_TRUE(child_notified);
168     EXPECT_TRUE(other_notified);
169 
170     // The child monitor should return EXIT_SUCCESS
171     EXPECT_TRUE(child->getExitCode().isSuccess());
172     // The other monitor can't get the exit code and should return failure
173     EXPECT_FALSE(other->getExitCode().isSuccess());
174 }
175