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