Belle II Software  release-08-01-10
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 
14 using namespace Belle2;
15 
16 namespace {
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 & getStdErr() const
Return the captured stderr output if any.
Definition: IOIntercept.h:166
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
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
TEST_F(GlobalLabelTest, LargeNumberOfTimeDependentParameters)
Test large number of time-dep params for registration and retrieval.
Definition: globalLabel.cc:72
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.