pion  5.0.6
unit_test.hpp
1 // ---------------------------------------------------------------------
2 // pion: a Boost C++ framework for building lightweight HTTP interfaces
3 // ---------------------------------------------------------------------
4 // Copyright (C) 2007-2014 Splunk Inc. (https://github.com/splunk/pion)
5 //
6 // Distributed under the Boost Software License, Version 1.0.
7 // See http://www.boost.org/LICENSE_1_0.txt
8 //
9 
10 #ifndef __PION_TEST_UNIT_TEST_HEADER__
11 #define __PION_TEST_UNIT_TEST_HEADER__
12 
13 #include <iostream>
14 #include <fstream>
15 #include <boost/version.hpp>
16 #include <boost/algorithm/string.hpp>
17 #include <boost/thread/mutex.hpp>
18 #include <boost/thread/condition.hpp>
19 #include <boost/test/execution_monitor.hpp>
20 #include <boost/test/unit_test.hpp>
21 #include <boost/test/unit_test_log.hpp>
22 #include <boost/test/unit_test_log_formatter.hpp>
23 #include <boost/test/test_case_template.hpp>
24 #include <boost/test/utils/xml_printer.hpp>
25 #include <pion/logger.hpp>
26 
27 #ifdef _MSC_VER
28  #include <direct.h>
29  #define CHANGE_DIRECTORY _chdir
30  #define GET_DIRECTORY(a,b) _getcwd(a,b)
31 #else
32  #include <unistd.h>
33  #define CHANGE_DIRECTORY chdir
34  #define GET_DIRECTORY(a,b) getcwd(a,b)
35 #endif
36 
37 #define DIRECTORY_MAX_SIZE 1000
38 
39 
40 namespace pion { // begin namespace pion
41 namespace test { // begin namespace test
42 
45  : public boost::unit_test::unit_test_log_formatter
46  {
47  public:
48 
51  : m_entry_in_progress(false)
52  {}
53 
56 
58  virtual void log_start(std::ostream& ostr,
59  boost::unit_test::counter_t test_cases_amount )
60  {
61  ostr << "<TestLog>" << std::endl;
62  }
63 
65  virtual void log_finish(std::ostream& ostr)
66  {
67  ostr << "</TestLog>" << std::endl;
68  }
69 
71  virtual void log_build_info(std::ostream& ostr)
72  {
73  ostr << "<BuildInfo"
74  << " platform" << attr_value() << BOOST_PLATFORM
75  << " compiler" << attr_value() << BOOST_COMPILER
76  << " stl" << attr_value() << BOOST_STDLIB
77  << " boost=\"" << BOOST_VERSION/100000 << "."
78  << BOOST_VERSION/100 % 1000 << "."
79  << BOOST_VERSION % 100 << '\"'
80  << "/>" << std::endl;
81  }
82 
84  virtual void test_unit_start(std::ostream& ostr,
85  boost::unit_test::test_unit const& tu )
86  {
87  ostr << "<" << tu_type_name( tu ) << " name" << attr_value() << tu.p_name.get() << ">" << std::endl;
88  }
89 
91  virtual void test_unit_finish(std::ostream& ostr,
92  boost::unit_test::test_unit const& tu,
93  unsigned long elapsed )
94  {
95  if ( tu.p_type == boost::unit_test::TUT_CASE )
96  ostr << "<TestingTime>" << elapsed << "</TestingTime>";
97  ostr << "</" << tu_type_name( tu ) << ">" << std::endl;
98  }
99 
101  virtual void test_unit_skipped(std::ostream& ostr,
102  boost::unit_test::test_unit const& tu )
103  {
104  ostr << "<" << tu_type_name( tu )
105  << " name" << attr_value() << tu.p_name.get()
106  << " skipped" << attr_value() << "yes"
107  << "/>" << std::endl;
108  }
109 
111  virtual void log_exception(std::ostream& ostr,
112  boost::unit_test::log_checkpoint_data const& checkpoint_data,
113  boost::execution_exception const& ex )
114  {
115  boost::execution_exception::location const& loc = ex.where();
116 
117  ostr << "<Exception file" << attr_value() << loc.m_file_name
118  << " line" << attr_value() << loc.m_line_num;
119 
120  if( !loc.m_function.is_empty() )
121  ostr << " function" << attr_value() << loc.m_function;
122 
123  ostr << ">" << boost::unit_test::utils::cdata() << ex.what();
124 
125  if( !checkpoint_data.m_file_name.is_empty() ) {
126  ostr << "<LastCheckpoint file" << attr_value() << checkpoint_data.m_file_name
127  << " line" << attr_value() << checkpoint_data.m_line_num
128  << ">"
129  << boost::unit_test::utils::cdata() << checkpoint_data.m_message
130  << "</LastCheckpoint>";
131  }
132 
133  ostr << "</Exception>" << std::endl;
134  }
135 
137  virtual void log_entry_start( std::ostream& ostr,
138  boost::unit_test::log_entry_data const& entry_data,
139  log_entry_types let )
140  {
141  boost::mutex::scoped_lock entry_lock(m_mutex);
142  while (m_entry_in_progress) {
143  m_entry_complete.wait(entry_lock);
144  }
145  m_entry_in_progress = true;
146 
147  static boost::unit_test::literal_string xml_tags[] = { "Info", "Message", "Warning", "Error", "FatalError" };
148  m_curr_tag = xml_tags[let];
149  ostr << '<' << m_curr_tag
150  << BOOST_TEST_L( " file" ) << attr_value() << entry_data.m_file_name
151  << BOOST_TEST_L( " line" ) << attr_value() << entry_data.m_line_num
152  << BOOST_TEST_L( "><![CDATA[" );
153 
154  ostr.flush();
155  }
156 
159  virtual void log_entry_value( std::ostream& ostr, boost::unit_test::const_string value )
160  {
161  boost::mutex::scoped_lock entry_lock(m_mutex);
162  if (m_entry_in_progress) {
163  ostr << value;
164  ostr.flush();
165  }
166  }
167 
170  virtual void log_entry_finish( std::ostream& ostr )
171  {
172  boost::mutex::scoped_lock entry_lock(m_mutex);
173  if (m_entry_in_progress) {
174  ostr << BOOST_TEST_L( "]]></" ) << m_curr_tag << BOOST_TEST_L( ">" ) << std::endl;
175  m_curr_tag.clear();
176  m_entry_in_progress = false;
177  m_entry_complete.notify_all();
178  }
179  }
180 
181  virtual void log_exception_start( std::ostream& os, boost::unit_test::log_checkpoint_data const& lcd, boost::execution_exception const& ex )
182  {
183  os << "<TestExceptionLog>" << std::endl;
184  }
185 
186  virtual void log_exception_finish( std::ostream& os )
187  {
188  os << "</TestExceptionLog>" << std::endl;
189  }
190 
191  virtual void entry_context_start( std::ostream& os, boost::unit_test::log_level l )
192  {
193  os << "<EntryContext>" << std::endl;
194  }
195 
196  virtual void entry_context_finish( std::ostream& os )
197  {
198  os << "</EntryContext>" << std::endl;
199  }
200 
201  virtual void log_entry_context( std::ostream& os, boost::unit_test::const_string value )
202  {
203  log_entry_value(os, value);
204  }
205 
206  private:
207 
209  static boost::unit_test::const_string tu_type_name( boost::unit_test::test_unit const& tu )
210  {
211  return tu.p_type == boost::unit_test::TUT_CASE ? "TestCase" : "TestSuite";
212  }
213 
215  typedef boost::unit_test::utils::attr_value attr_value;
216 
218  volatile bool m_entry_in_progress;
219 
221  boost::condition m_entry_complete;
222 
224  boost::mutex m_mutex;
225 
227  boost::unit_test::const_string m_curr_tag;
228  };
229 
230 
236  struct config {
237  config() {
238  std::cout << "global setup for all pion unit tests\n";
239 
240  // argc and argv do not include parameters handled by the boost unit test framework, such as --log_level.
241  int argc = boost::unit_test::framework::master_test_suite().argc;
242  char** argv = boost::unit_test::framework::master_test_suite().argv;
243  bool verbose = false;
244 
245  if (argc > 1) {
246  if (argv[1][0] == '-' && argv[1][1] == 'v') {
247  verbose = true;
248  } else if (strlen(argv[1]) > 13 && strncmp(argv[1], "--log_output=", 13) == 0) {
249  const char * const test_log_filename = argv[1] + 13;
250  m_test_log_file.open(test_log_filename);
251  if (m_test_log_file.is_open()) {
252  boost::unit_test::unit_test_log.set_stream(m_test_log_file);
253  boost::unit_test::unit_test_log.set_formatter(new safe_xml_log_formatter);
254  } else {
255  std::cerr << "unable to open " << test_log_filename << std::endl;
256  }
257  }
258  }
259 
260  if (verbose) {
261  PION_LOG_CONFIG_BASIC;
262  } else {
263  std::cout << "Use '-v' to enable logging of errors and warnings from pion.\n";
264  }
265 
266  pion::logger log_ptr = PION_GET_LOGGER("pion");
267  PION_LOG_SETLEVEL_WARN(log_ptr);
268  }
269  virtual ~config() {
270  std::cout << "global teardown for all pion unit tests\n";
271  }
272 
274  static std::ofstream m_test_log_file;
275  };
276 
277 
278  // removes line endings from a c-style string
279  static inline char* trim(char* str) {
280  for (long len = strlen(str) - 1; len >= 0; len--) {
281  if (str[len] == '\n' || str[len] == '\r')
282  str[len] = '\0';
283  else
284  break;
285  }
286  return str;
287  }
288 
289  // reads lines from a file, stripping line endings and ignoring blank lines
290  // and comment lines (starting with a '#')
291  static inline bool read_lines_from_file(const std::string& filename, std::list<std::string>& lines) {
292  // open file
293  std::ifstream a_file(filename.c_str(), std::ios::in | std::ios::binary);
294  if (! a_file.is_open())
295  return false;
296 
297  // read data from file
298  lines.clear();
299  std::string one_line;
300  while (std::getline(a_file, one_line)) {
301  boost::trim(one_line);
302  if (!one_line.empty() && one_line[0] != '#')
303  lines.push_back(one_line);
304  }
305 
306  // close file
307  a_file.close();
308 
309  return true;
310  }
311 
312  // Check for file match, use std::list for sorting the files, which will allow
313  // random order matching...
314  static inline bool check_files_match(const std::string& fileA, const std::string& fileB) {
315  // open and read data from files
316  std::list<std::string> a_lines, b_lines;
317  BOOST_REQUIRE(read_lines_from_file(fileA, a_lines));
318  BOOST_REQUIRE(read_lines_from_file(fileB, b_lines));
319 
320  // sort lines read
321  a_lines.sort();
322  b_lines.sort();
323 
324  // files match if lines match
325  return (a_lines == b_lines);
326  }
327 
328  static inline bool check_files_exact_match(const std::string& fileA, const std::string& fileB, bool ignore_line_endings = false) {
329  // open files
330  std::ifstream a_file(fileA.c_str(), std::ios::in | std::ios::binary);
331  BOOST_REQUIRE(a_file.is_open());
332 
333  std::ifstream b_file(fileB.c_str(), std::ios::in | std::ios::binary);
334  BOOST_REQUIRE(b_file.is_open());
335 
336  // read and compare data in files
337  static const unsigned int BUF_SIZE = 4096;
338  char a_buf[BUF_SIZE];
339  char b_buf[BUF_SIZE];
340 
341  if (ignore_line_endings) {
342  while (a_file.getline(a_buf, BUF_SIZE)) {
343  if (! b_file.getline(b_buf, BUF_SIZE))
344  return false;
345  trim(a_buf);
346  trim(b_buf);
347  if (strlen(a_buf) != strlen(b_buf))
348  return false;
349  if (memcmp(a_buf, b_buf, strlen(a_buf)) != 0)
350  return false;
351  }
352  if (b_file.getline(b_buf, BUF_SIZE))
353  return false;
354  } else {
355  while (a_file.read(a_buf, BUF_SIZE)) {
356  if (! b_file.read(b_buf, BUF_SIZE))
357  return false;
358  if (memcmp(a_buf, b_buf, BUF_SIZE) != 0)
359  return false;
360  }
361  if (b_file.read(b_buf, BUF_SIZE))
362  return false;
363  }
364  if (a_file.gcount() != b_file.gcount())
365  return false;
366  if (memcmp(a_buf, b_buf, a_file.gcount()) != 0)
367  return false;
368 
369  a_file.close();
370  b_file.close();
371 
372  // files match
373  return true;
374  }
375 
376 
377 } // end namespace test
378 } // end namespace pion
379 
380 
381 /*
382 Using BOOST_AUTO_TEST_SUITE_FIXTURE_TEMPLATE and
383 BOOST_AUTO_TEST_CASE_FIXTURE_TEMPLATE has two additional benefits relative to
384 using BOOST_FIXTURE_TEST_SUITE and BOOST_AUTO_TEST_CASE:
385 1) it allows a test to be run with more than one fixture, and
386 2) it makes the current fixture part of the test name, e.g.
387  checkPropertyX<myFixture_F>
388 
389 For an example of 1), see http_message_tests.cpp.
390 
391 There are probably simpler ways to achieve 2), but since it comes for free,
392 it makes sense to use it. The benefit of this is that the test names don't
393 have to include redundant information about the fixture, e.g.
394 checkMyFixtureHasPropertyX. (In this example, checkPropertyX<myFixture_F> is
395 not obviously better than checkMyFixtureHasPropertyX, but in many cases the
396 test names become too long and/or hard to parse, or the fixture just isn't
397 part of the name, making some error reports ambiguous.)
398 
399 (BOOST_AUTO_TEST_CASE_FIXTURE_TEMPLATE is based on BOOST_AUTO_TEST_CASE_TEMPLATE,
400 in unit_test_suite.hpp.)
401 
402 
403 Minimal example demonstrating usage of BOOST_AUTO_TEST_CASE_FIXTURE_TEMPLATE:
404 
405 class ObjectToTest_F { // suffix _F is used for fixtures
406 public:
407  ObjectToTest_F() {
408  m_value = 2;
409  }
410  int m_value;
411  int get_value() { return m_value; }
412 };
413 
414 // This illustrates the most common case, where just one fixture will be used,
415 // so the list only has one fixture in it.
416 // ObjectToTest_S is the name of the test suite.
417 BOOST_AUTO_TEST_SUITE_FIXTURE_TEMPLATE(ObjectToTest_S,
418  boost::mpl::list<ObjectToTest_F>)
419 
420 // One method for testing the fixture...
421 BOOST_AUTO_TEST_CASE_FIXTURE_TEMPLATE(checkValueEqualsTwo) {
422  BOOST_CHECK_EQUAL(F::m_value, 2);
423  BOOST_CHECK_EQUAL(F::get_value(), 2);
424 }
425 
426 // Another method for testing the fixture...
427 BOOST_AUTO_TEST_CASE_FIXTURE_TEMPLATE(checkValueEqualsTwoAgain) {
428  BOOST_CHECK_EQUAL(this->m_value, 2);
429  BOOST_CHECK_EQUAL(this->get_value(), 2);
430 }
431 
432 // The simplest, but, alas, non conformant to the C++ standard, method for testing the fixture.
433 // This will compile with MSVC (unless language extensions are disabled (/Za)).
434 // It won't compile with gcc unless -fpermissive is used.
435 // See http://gcc.gnu.org/onlinedocs/gcc/Name-lookup.html.
436 BOOST_AUTO_TEST_CASE_FIXTURE_TEMPLATE(checkValueEqualsTwoNonConformant) {
437  BOOST_CHECK_EQUAL(m_value, 2);
438  BOOST_CHECK_EQUAL(get_value(), 2);
439 }
440 
441 BOOST_AUTO_TEST_SUITE_END()
442 */
443 
444 #define BOOST_AUTO_TEST_SUITE_FIXTURE_TEMPLATE(suite_name, fixture_types) \
445 BOOST_AUTO_TEST_SUITE(suite_name) \
446 typedef fixture_types BOOST_AUTO_TEST_CASE_FIXTURE_TYPES; \
447 
448 
449 #define BOOST_AUTO_TEST_CASE_FIXTURE_TEMPLATE(test_name) \
450 template<typename F> \
451 struct test_name : public F \
452 { void test_method(); }; \
453  \
454 struct BOOST_AUTO_TC_INVOKER( test_name ) { \
455  template<typename TestType> \
456  static void run( boost::type<TestType>* = 0 ) \
457  { \
458  test_name<TestType> t; \
459  t.test_method(); \
460  } \
461 }; \
462  \
463 BOOST_AUTO_TU_REGISTRAR( test_name )( \
464  boost::unit_test::ut_detail::template_test_case_gen< \
465  BOOST_AUTO_TC_INVOKER( test_name ), \
466  BOOST_AUTO_TEST_CASE_FIXTURE_TYPES >( \
467  BOOST_STRINGIZE( test_name ), __FILE__, __LINE__ ), \
468  boost::unit_test::decorator::collector::instance() ); \
469  \
470 template<typename F> \
471 void test_name<F>::test_method() \
472 
473 
474 
475 #endif
virtual void test_unit_finish(std::ostream &ostr, boost::unit_test::test_unit const &tu, unsigned long elapsed)
wrapper to flush output for xml_log_formatter::test_unit_finish
Definition: unit_test.hpp:91
virtual ~safe_xml_log_formatter()
virtual destructor
Definition: unit_test.hpp:55
thread-safe version of Boost.Test&#39;s xml_log_formatter class
Definition: unit_test.hpp:44
virtual void log_build_info(std::ostream &ostr)
wrapper to flush output for xml_log_formatter::log_build_info
Definition: unit_test.hpp:71
virtual void log_entry_start(std::ostream &ostr, boost::unit_test::log_entry_data const &entry_data, log_entry_types let)
thread-safe wrapper for xml_log_formatter::log_entry_start
Definition: unit_test.hpp:137
virtual void log_finish(std::ostream &ostr)
wrapper to flush output for xml_log_formatter::log_finish
Definition: unit_test.hpp:65
virtual void log_exception(std::ostream &ostr, boost::unit_test::log_checkpoint_data const &checkpoint_data, boost::execution_exception const &ex)
wrapper to flush output for xml_log_formatter::log_exception
Definition: unit_test.hpp:111
static std::ofstream m_test_log_file
xml log results output stream (needs to be global)
Definition: unit_test.hpp:274
virtual void log_start(std::ostream &ostr, boost::unit_test::counter_t test_cases_amount)
wrapper to flush output for xml_log_formatter::log_start
Definition: unit_test.hpp:58
virtual void test_unit_start(std::ostream &ostr, boost::unit_test::test_unit const &tu)
wrapper to flush output for xml_log_formatter::test_unit_start
Definition: unit_test.hpp:84
virtual void log_entry_finish(std::ostream &ostr)
Definition: unit_test.hpp:170
safe_xml_log_formatter()
default constructor
Definition: unit_test.hpp:50
virtual void log_entry_value(std::ostream &ostr, boost::unit_test::const_string value)
Definition: unit_test.hpp:159
virtual void test_unit_skipped(std::ostream &ostr, boost::unit_test::test_unit const &tu)
wrapper to flush output for xml_log_formatter::test_unit_skipped
Definition: unit_test.hpp:101