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