ifw-daq 3.1.0
IFW Data Acquisition modules
Loading...
Searching...
No Matches
testAsyncProcess.cpp
Go to the documentation of this file.
1/**
2 * @file
3 * @ingroup daq_test
4 * @copyright (c) Copyright ESO 2022
5 * All Rights Reserved
6 * ESO (eso.org) is an Intergovernmental Organisation, and therefore special legal conditions apply.
7 *
8 * @brief daq::AsyncProcess integration tests
9 *
10 * This starts subprocesses that depend on @c bash, sleep and @c dd so they are not part of unit
11 * tests.
12 */
13#include <thread>
14
15#include <gtest/gtest.h>
16
17#include <rad/ioExecutor.hpp>
18
20
21class AsyncProcessTest : public ::testing::Test {
22public:
24 }
25
26protected:
27 boost::asio::io_context m_ctx;
29};
30
32 std::vector<std::string> args = {
33 "bash",
34 "-c",
35 "for x in {1. .2}; do sleep 1 && dd if=/dev/random of=/dev/stdout count=2 bs=4096 "
36 "iflag=fullblock status=none; done"};
37
38 std::string stdout;
39 std::string stdout_at_term;
40 bool done = false;
41 auto proc = daq::AsyncProcess(m_ctx, args);
42 EXPECT_FALSE(proc.IsRunning());
43 EXPECT_FALSE(proc.GetPid().has_value());
44
45 // Simple slot that stores all output in a string.
46 proc.ConnectStdout([&](pid_t, std::string const& line) { stdout += line; });
47
48 boost::future<int> fut = proc.Initiate().then(m_exec, [&](boost::future<int> exit_code) -> int {
49 done = true;
50 stdout_at_term = stdout;
51 EXPECT_FALSE(proc.IsRunning());
52 auto code = exit_code.get();
53 EXPECT_EQ(code, 0) << "Unexpected exit code";
54 return code;
55 });
56
57 EXPECT_TRUE(proc.IsRunning());
58 EXPECT_TRUE(proc.GetPid().has_value());
59
60 m_ctx.run(); // runs until there's no more work
61
62 EXPECT_TRUE(done)
63 << "Expected continuation to have been executed as io_context::run has exited";
64 EXPECT_EQ(stdout, stdout_at_term)
65 << "Output changed after future continuation executed. "
66 "This indicates that the process termination synchronization in AsyncProcess "
67 "doesn't work as advertised!";
68 EXPECT_EQ(stdout.size(), 2 * 4096 * 2);
69 EXPECT_EQ(fut.get(), 0);
70}
71
72TEST_F(AsyncProcessTest, SignalWithSigterm) {
73 std::vector<std::string> args = {"sleep", "2"};
74
75 bool done = false;
76 auto proc = daq::AsyncProcess(m_ctx, args);
77 EXPECT_EQ(proc.Signal(SIGTERM), std::errc::no_such_process);
78 EXPECT_FALSE(proc.IsRunning());
79
80 // Note
81 boost::future<int> fut = proc.Initiate().then(m_exec, [&](boost::future<int> exit_code) -> int {
82 done = true;
83 EXPECT_FALSE(proc.IsRunning());
84 auto code = exit_code.get();
85 EXPECT_EQ(code, 15) << "Unexpected exit code for SIGTERM";
86 return code;
87 });
88
89 EXPECT_TRUE(proc.IsRunning());
90
91 proc.Signal(SIGTERM);
92
93 m_ctx.run(); // runs until there's no more work
94
95 EXPECT_TRUE(done)
96 << "Expected continuation to have been executed as io_context::run has exited";
97 EXPECT_EQ(fut.get(), 15);
98}
99
100// Disabled due to bug in boost (https://github.com/boostorg/process/issues/357)
101TEST_F(AsyncProcessTest, DISABLED_Abort) {
102 std::vector<std::string> args = {"sleep", "2"};
103
104 bool done = false;
105 auto proc = daq::AsyncProcess(m_ctx, args);
106 EXPECT_EQ(proc.Abort(), std::errc::no_such_process);
107 EXPECT_FALSE(proc.IsRunning());
108
109 // Note
110 boost::future<int> fut = proc.Initiate().then(m_exec, [&](boost::future<int> exit_code) -> int {
111 done = true;
112 EXPECT_FALSE(proc.IsRunning());
113 auto code = exit_code.get();
114 EXPECT_EQ(code, 9) << "Unexpected exit code for a SIGKILL";
115 return code;
116 });
117
118 EXPECT_TRUE(proc.IsRunning());
119
120 proc.Abort();
121
122 m_ctx.run(); // runs until there's no more work
123
124 EXPECT_TRUE(done)
125 << "Expected continuation to have been executed as io_context::run has exited";
126 EXPECT_EQ(fut.get(), 9);
127}
128
129// Test that allows error code or exception when aborting as workaround for
130// (https://github.com/boostorg/process/issues/357)
131TEST_F(AsyncProcessTest, AbortTolerant) {
132 std::vector<std::string> args = {"sleep", "2"};
133
134 bool done = false;
135 auto proc = daq::AsyncProcess(m_ctx, args);
136 EXPECT_EQ(proc.Abort(), std::errc::no_such_process);
137 EXPECT_FALSE(proc.IsRunning());
138
139 // Note
140 boost::future<int> fut = proc.Initiate().then(m_exec, [&](boost::future<int> exit_code) -> int {
141 done = true;
142 EXPECT_FALSE(proc.IsRunning());
143 auto code = exit_code.get();
144 EXPECT_EQ(code, 9) << "Unexpected exit code for a SIGKILL";
145 return code;
146 });
147
148 EXPECT_TRUE(proc.IsRunning());
149
150 proc.Abort();
151
152 m_ctx.run(); // runs until there's no more work
153
154 EXPECT_TRUE(done)
155 << "Expected continuation to have been executed as io_context::run has exited";
156 ASSERT_TRUE(fut.is_ready());
157 if (!fut.has_exception()) {
158 EXPECT_EQ(fut.get(), 9);
159 }
160}
161
162TEST_F(AsyncProcessTest, ReadAllMultiple) {
163 std::vector<std::string> args = {
164 "bash",
165 "-c",
166 "for x in {1. .2}; do sleep 1 && dd if=/dev/random of=/dev/stdout count=2 bs=4096 "
167 "iflag=fullblock status=none; done"};
168
169 for (int i = 0; i < 10; ++i) {
170 auto proc = std::make_shared<daq::AsyncProcess>(m_ctx, args);
171 auto stdout = std::make_shared<std::string>();
172 proc->ConnectStdout([&, stdout](pid_t, std::string const& line) { *stdout += line; });
173 // Note: Capture proc by value to keep AsyncProcess alive
174 proc->Initiate().then(m_exec, [&, proc, stdout](boost::future<int> exit_code) {
175 EXPECT_EQ(stdout->size(), 2 * 4096 * 2);
176 EXPECT_EQ(exit_code.get(), 0) << "Unexpected exit code";
177 });
178 }
179
180 m_ctx.run();
181}
daq::AsyncProcess class definition
rad::IoExecutor m_exec
boost::asio::io_context m_ctx
Represents a subprocess as an asynchronous operation.
Adapts boost::asio::io_context into a compatible boost::thread Executor type.
Definition: ioExecutor.hpp:12
TEST_F(AsyncProcessTest, ReadAll)
EXPECT_EQ(meta.rr_uri, "zpb.rr://meta")
ASSERT_TRUE(std::holds_alternative< OlasReceiver >(spec.receivers[0]))