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}
const std::string & getStdOut() const
Return the captured stdout output if any.
bool start()
Start intercepting the output.
bool finish()
Finish intercepting the output.
const std::string & getStdErr() const
Return the captured stderr output if any.
Simple RAII guard for output interceptor.
Capture stdout and stderr and convert into log messages.
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
InterceptOutput< DiscardStream, DiscardStream > DiscardStdOutStdErr
Discard both stdout and stderr.
InterceptOutput< CaptureStream, KeepStream > CaptureStdOut
Capture only stdout and don't modify stderr.
InterceptorScopeGuard< T > start_intercept(T &interceptor)
Convenience wrapper to simplify use of InterceptorScopeGuard<T>.
InterceptOutput< KeepStream, DiscardStream > DiscardStdErr
Discard only stderr and don't modify stdout.
InterceptOutput< KeepStream, CaptureStream > CaptureStdErr
Capture only stderr and don't modify stdout.
Abstract base class for different kinds of events.