ifw-daq  2.1.0-pre1
IFW Data Acquisition modules
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 
21 class AsyncProcessTest : public ::testing::Test {
22 public:
24  }
25 protected:
26  boost::asio::io_context m_ctx;
28 };
29 
31  std::vector<std::string> args = {
32  "bash",
33  "-c",
34  "for x in {1. .2}; do sleep 1 && dd if=/dev/random of=/dev/stdout count=2 bs=4096 "
35  "iflag=fullblock status=none; done"};
36 
37  std::string stdout;
38  std::string stdout_at_term;
39  bool done = false;
40  auto proc = daq::AsyncProcess(m_ctx, args);
41  EXPECT_FALSE(proc.IsRunning());
42  EXPECT_FALSE(proc.GetPid().has_value());
43 
44  // Simple slot that stores all output in a string.
45  proc.ConnectStdout([&](pid_t, std::string const& line) { stdout += line; });
46 
47  boost::future<int> fut = proc.Initiate().then(m_exec, [&](boost::future<int> exit_code) -> int {
48  done = true;
49  stdout_at_term = stdout;
50  EXPECT_FALSE(proc.IsRunning());
51  auto code = exit_code.get();
52  EXPECT_EQ(code, 0) << "Unexpected exit code";
53  return code;
54  });
55 
56  EXPECT_TRUE(proc.IsRunning());
57  EXPECT_TRUE(proc.GetPid().has_value());
58 
59  m_ctx.run(); // runs until there's no more work
60 
61  EXPECT_TRUE(done)
62  << "Expected continuation to have been executed as io_context::run has exited";
63  EXPECT_EQ(stdout, stdout_at_term)
64  << "Output changed after future continuation executed. "
65  "This indicates that the process termination synchronization in AsyncProcess "
66  "doesn't work as advertised!";
67  EXPECT_EQ(stdout.size(), 2 * 4096 * 2);
68  EXPECT_EQ(fut.get(), 0);
69 }
70 
71 TEST_F(AsyncProcessTest, SignalWithSigterm) {
72  std::vector<std::string> args = {"sleep", "2"};
73 
74  bool done = false;
75  auto proc = daq::AsyncProcess(m_ctx, args);
76  EXPECT_EQ(proc.Signal(SIGTERM), std::errc::no_such_process);
77  EXPECT_FALSE(proc.IsRunning());
78 
79  // Note
80  boost::future<int> fut =
81  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 
101  std::vector<std::string> args = {"sleep", "2"};
102 
103  bool done = false;
104  auto proc = daq::AsyncProcess(m_ctx, args);
105  EXPECT_EQ(proc.Abort(), std::errc::no_such_process);
106  EXPECT_FALSE(proc.IsRunning());
107 
108  // Note
109  boost::future<int> fut =
110  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_F(AsyncProcessTest, ReadAllMultiple) {
130  std::vector<std::string> args = {
131  "bash",
132  "-c",
133  "for x in {1. .2}; do sleep 1 && dd if=/dev/random of=/dev/stdout count=2 bs=4096 "
134  "iflag=fullblock status=none; done"};
135 
136  for (int i = 0; i < 10; ++i) {
137  auto proc = std::make_shared<daq::AsyncProcess>(m_ctx, args);
138  auto stdout = std::make_shared<std::string>();
139  proc->ConnectStdout([&, stdout](pid_t, std::string const& line) { *stdout += line; });
140  // Note: Capture proc by value to keep AsyncProcess alive
141  proc->Initiate().then(m_exec, [&, proc, stdout](boost::future<int> exit_code) {
142  EXPECT_EQ(stdout->size(), 2 * 4096 * 2);
143  EXPECT_EQ(exit_code.get(), 0) << "Unexpected exit code";
144  });
145  }
146 
147  m_ctx.run();
148 }
ioExecutor.hpp
rad::IoExecutor
Adapts boost::asio::io_context into a compatible boost::thread Executor type.
Definition: ioExecutor.hpp:12
AsyncProcessTest::m_ctx
boost::asio::io_context m_ctx
Definition: testAsyncProcess.cpp:26
AsyncProcessTest::m_exec
rad::IoExecutor m_exec
Definition: testAsyncProcess.cpp:27
AsyncProcessTest::AsyncProcessTest
AsyncProcessTest()
Definition: testAsyncProcess.cpp:23
asyncProcess.hpp
daq::AsyncProcess class definition
AsyncProcessTest
Definition: testAsyncProcess.cpp:21
TEST_F
TEST_F(AsyncProcessTest, ReadAll)
Definition: testAsyncProcess.cpp:30
segfault.i
i
Definition: segfault.py:30
daq::AsyncProcess
Represents a subprocess as an asynchronous operation.
Definition: asyncProcess.hpp:124