Belle II Software development
iointercept.cc
1/**************************************************************************
2 * basf2 (Belle II Analysis Software Framework) *
3 * Author: The Belle II Collaboration *
4 * *
5 * See git log for contributors and copyright holders. *
6 * This file is licensed under LGPL-3.0, see LICENSE.md. *
7 **************************************************************************/
8
9#include <iostream>
10#include <framework/utilities/IOIntercept.h>
11#include <framework/utilities/testhelpers/Fixtures.h>
12#include <gtest/gtest.h>
13
14using namespace Belle2;
15
16namespace {
18 using logconvert_params_t = std::tuple<LogConfig::ELogLevel, std::string, std::string, bool, bool>;
19
21 using IOInterceptTest = TestHelpers::LogMessageTest;
22
24 using IOInterceptDeathTest = IOInterceptTest;
25
27 class IOInterceptParamTest: public IOInterceptTest, public ::testing::WithParamInterface<logconvert_params_t> {};
28
30 TEST_P(IOInterceptParamTest, ConvertToLogMessage)
31 {
32 // first let's get the parameters
34 std::string raw_message, formatted_message;
35 bool useStdout, generateMessage;
36 std::tie(level, raw_message, formatted_message, useStdout, generateMessage) = GetParam();
37 // now create some output and convert it to log messages
38 IOIntercept::OutputToLogMessages capture("capture_name", level, level);
39 capture.start();
40 (useStdout ? std::cout : std::cerr) << raw_message;
41 capture.finish();
42 // check that the amount of log messages is correct
43 expectMessage(level, generateMessage ? 1 : 0, true);
44 if (!generateMessage) return;
45 // and also check the message level and content
46 expectMessageContent(level, "Output from capture_name:\ncapture_name: " + formatted_message);
47 }
48
52 logconvert_params_t logconvert_params[] = {
53 // test info message for stdout and stderr
54 {LogConfig::c_Info, "foo", "foo", false, true},
55 {LogConfig::c_Info, "foo", "foo", true, true},
56 // also for error
57 {LogConfig::c_Error, "foo", "foo", false, true},
58 {LogConfig::c_Error, "foo", "foo", true, true},
59 // ok, test the other levels only for stdout
60 {LogConfig::c_Warning, "foo", "foo", false, true},
61 {LogConfig::c_Result, "foo", "foo", false, true},
62 // test that empty message gets removed
63 {LogConfig::c_Info, "", "", false, false},
64 // also with whitespace
65 {LogConfig::c_Info, " ", "", false, false},
66 {LogConfig::c_Info, " \t ", "", false, false},
67 {LogConfig::c_Info, " \n ", "", false, false},
68 // trim space at end
69 {LogConfig::c_Info, "message\n\n \t\r\n", "message", false, true},
70 // trim space at beginning
71 {LogConfig::c_Info, " \n\t\nmessage", "message", false, true},
72 // but not the spaces in the first line which also contains text to not break alignment of formatted output
73 {LogConfig::c_Info, " \n\tmessage", "\tmessage", false, true},
74 {LogConfig::c_Info, " \n message", " message", false, true},
75
76 };
77
79 INSTANTIATE_TEST_SUITE_P(Params, IOInterceptParamTest, ::testing::ValuesIn(logconvert_params));
80
82 TEST_F(IOInterceptTest, LogMessagesNoFinish)
83 {
84 {
85 IOIntercept::OutputToLogMessages logmessages("capture_name");
86 logmessages.setIndent("-");
87 logmessages.start();
88 std::cout << "this is my message";
89 }
90 // check that the amount of log messages is correct
91 expectMessage(LogConfig::c_Info, 1, true);
92 // and also check the message level and content
93 expectMessageContent(LogConfig::c_Info, "Output from capture_name:\n-this is my message");
94 }
95
97 TEST_F(IOInterceptTest, InterceptGuard)
98 {
99 IOIntercept::OutputToLogMessages logmessages("capture_name");
100 logmessages.setIndent("-");
101 {
103 std::cout << "this is my message";
104 }
105 std::cout << "this will not be captured" << std::endl;
106 {
107 // cppcheck-suppress unreadVariable ; cppcheck doesn't realize this has side effects.
108 auto guard = IOIntercept::start_intercept(logmessages);
109 std::cerr << "this is my error";
110 }
111 std::cerr << "this will not be captured" << std::endl;
112 ASSERT_EQ(m_messages.size(), 2);
113 // and also check the message level and content
114 expectMessageContent(LogConfig::c_Error, "Output from capture_name:\n-this is my error");
115 m_messages.pop_back();
116 expectMessageContent(LogConfig::c_Info, "Output from capture_name:\n-this is my message");
117 }
118
120 TEST_F(IOInterceptTest, ConvertCheckIndent)
121 {
122 IOIntercept::OutputToLogMessages capture("indent");
123 capture.start();
124 std::cout << "this is\na multi line message";
125 capture.finish();
126 expectMessage(LogConfig::c_Info, 1, true);
127 expectMessageContent(LogConfig::c_Info, "Output from indent:\nindent: this is\nindent: a multi line message");
128 }
129
131 TEST_F(IOInterceptTest, ConvertSetIndent)
132 {
133 IOIntercept::OutputToLogMessages capture("indent");
134 capture.start();
135 std::cout << "this is\na multi line message";
136 capture.setIndent("--->");
137 capture.finish();
138 expectMessage(LogConfig::c_Info, 1, true);
139 expectMessageContent(LogConfig::c_Info, "Output from indent:\n--->this is\n--->a multi line message");
140 }
141
143 TEST_F(IOInterceptTest, ConvertEmptyIndent)
144 {
145 IOIntercept::OutputToLogMessages capture("indent");
146 capture.start();
147 capture.setIndent("");
148 std::cout << "this is\na multi line message";
149 capture.finish();
150 expectMessage(LogConfig::c_Info, 1, true);
151 expectMessageContent(LogConfig::c_Info, "Output from indent:\nthis is\na multi line message");
152 }
153
155 TEST_F(IOInterceptTest, CaptureStdOut)
156 {
158 ASSERT_FALSE(capture.finish());
159 ASSERT_TRUE(capture.start());
160 ASSERT_TRUE(capture.start());
161 std::cout << "this is a test";
162 ASSERT_TRUE(capture.finish());
163 ASSERT_EQ(capture.getStdOut(), "this is a test");
164 ASSERT_FALSE(capture.finish());
165 ASSERT_EQ(capture.getStdOut(), "this is a test");
166
167 // Ok, again with raw write to fd
168 ASSERT_TRUE(capture.start());
169 write(fileno(stdout), "this is a test", 14);
170 ASSERT_TRUE(capture.finish());
171 ASSERT_EQ(capture.getStdOut(), "this is a test");
172 }
173
175 TEST_F(IOInterceptTest, CaptureStdErr)
176 {
178 ASSERT_FALSE(capture.finish());
179 ASSERT_TRUE(capture.start());
180 ASSERT_TRUE(capture.start());
181 std::cerr << "this is a test";
182 ASSERT_TRUE(capture.finish());
183 ASSERT_EQ(capture.getStdErr(), "this is a test");
184 ASSERT_FALSE(capture.finish());
185 ASSERT_EQ(capture.getStdErr(), "this is a test");
186
187 // Ok, again with raw write to fd
188 ASSERT_TRUE(capture.start());
189 write(fileno(stderr), "this is a test", 14);
190 ASSERT_TRUE(capture.finish());
191 ASSERT_EQ(capture.getStdErr(), "this is a test");
192 }
193
195 TEST_F(IOInterceptTest, CaptureLargeOutput)
196 {
198 ASSERT_TRUE(capture.start());
199 std::string out;
200 int written{0};
201 for (int i = 0; i < 100000; ++i) {
202 std::cout << (char)(i % 255) << std::flush;
203 if (std::cout.good()) written = i + 1;
204 out.push_back(i % 255);
205 }
206 ASSERT_TRUE(capture.finish());
207 const std::string& captured = capture.getStdOut();
208 // pipes have a limited capacity. We create them non-blocking which
209 // means that once they're full we will just not get any output back.
210 // So check that the begin of the capture works
211 ASSERT_TRUE(captured.size() <= out.size());
212 ASSERT_EQ(captured.size(), written);
213 if (captured.size() < out.size()) {
214 std::cout << "Output truncated after " << captured.size() << " bytes" << std::endl;
215 }
216 ASSERT_EQ(captured, out.substr(0, captured.size()));
217 // and that capturing still works after overflow
218 ASSERT_TRUE(capture.start());
219 std::cout << "once more";
220 ASSERT_TRUE(capture.finish());
221 ASSERT_EQ(capture.getStdOut(), "once more");
222
223 // and once more for C
224 ASSERT_TRUE(capture.start());
225 printf("once more");
226 ASSERT_TRUE(capture.finish());
227 ASSERT_EQ(capture.getStdOut(), "once more");
228 }
229
233 void generateStdErr()
234 {
236 std::cerr << "start->";
237 discard.start();
238 std::cerr << "this should not show up" << std::endl << std::flush;
239 write(fileno(stderr), "nor this\n", 9);
240 discard.finish();
241 std::cerr << "<-end";
242 std::exit(0);
243 }
244
246 TEST_F(IOInterceptDeathTest, DiscardStdOut)
247 {
249 ASSERT_FALSE(discard.finish());
250 ASSERT_TRUE(discard.start());
251 ASSERT_TRUE(discard.start());
252 ASSERT_TRUE(discard.finish());
253 ASSERT_FALSE(discard.finish());
254 // hard to test if there's no output ... let's use a death test and
255 // verify that stderr of child process matches what we expect
256 EXPECT_EXIT(generateStdErr(), ::testing::ExitedWithCode(0), "^start-><-end$");
257 }
258
261 void generateAbort()
262 {
264 std::cerr << "start->";
265 output.start();
266 std::cerr << "now we abort" << std::endl;
267 std::abort();
268 }
269
271 TEST_F(IOInterceptDeathTest, HandleAbort)
272 {
273 EXPECT_EXIT(generateAbort(), ::testing::ExitedWithCode(1), "^start->now we abort\nabort\\(\\) called, exiting\n$");
274 }
275}
Class to intercept stdout and stderr and either capture, discard or keep them unmodified depending on...
Definition: IOIntercept.h:151
const std::string & getStdOut() const
Return the captured stdout output if any.
Definition: IOIntercept.h:160
bool start()
Start intercepting the output.
Definition: IOIntercept.h:155
bool finish()
Finish intercepting the output.
Definition: IOIntercept.h:158
const std::string & getStdErr() const
Return the captured stderr output if any.
Definition: IOIntercept.h:166
Simple RAII guard for output interceptor.
Definition: IOIntercept.h:301
Capture stdout and stderr and convert into log messages.
Definition: IOIntercept.h:226
ELogLevel
Definition of the supported log levels.
Definition: LogConfig.h:26
@ c_Error
Error: for things that went wrong and have to be fixed.
Definition: LogConfig.h:30
@ c_Info
Info: for informational messages, e.g.
Definition: LogConfig.h:27
@ c_Warning
Warning: for potential problems that the user should pay attention to.
Definition: LogConfig.h:29
@ c_Result
Result: for informational summary messages, e.g.
Definition: LogConfig.h:28
Test fixture to be able to check the contents and types of emitted log messages in detail.
Definition: Fixtures.h:23
InterceptorScopeGuard< T > start_intercept(T &interceptor)
Convenience wrapper to simplify use of InterceptorScopeGuard<T>.
Definition: IOIntercept.h:345
Abstract base class for different kinds of events.