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