pion-net  4.0.9
PionUnitTestDefs.hpp
1 // -----------------------------------------------------------------------
2 // pion-common: a collection of common libraries used by the Pion Platform
3 // -----------------------------------------------------------------------
4 // Copyright (C) 2007-2008 Atomic Labs, Inc. (http://www.atomiclabs.com)
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_PIONUNITTESTDEFS_HEADER__
11 #define __PION_PIONUNITTESTDEFS_HEADER__
12 
13 #include <iostream>
14 #include <fstream>
15 #include <boost/test/unit_test.hpp>
16 #include <boost/test/test_case_template.hpp>
17 #include <pion/PionLogger.hpp>
18 
19 #ifdef _MSC_VER
20  #include <direct.h>
21  #define CHANGE_DIRECTORY _chdir
22  #define GET_DIRECTORY(a,b) _getcwd(a,b)
23 #else
24  #include <unistd.h>
25  #define CHANGE_DIRECTORY chdir
26  #define GET_DIRECTORY(a,b) getcwd(a,b)
27 #endif
28 
29 #define DIRECTORY_MAX_SIZE 1000
30 
31 
32 struct PionUnitTest {
33  // This is passed to xmlSetGenericErrorFunc() to make libxml do nothing when an error
34  // occurs, rather than its default behavior of writing a message to stderr.
35  static void doNothing(void* ctx, const char* msg, ...) {
36  }
37 
38  // removes line endings from a c-style string
39  static char* trim(char* str) {
40  for (long len = strlen(str) - 1; len >= 0; len--) {
41  if (str[len] == '\n' || str[len] == '\r')
42  str[len] = '\0';
43  else
44  break;
45  }
46  return str;
47  }
48 
49  // reads lines from a file, stripping line endings and ignoring blank lines
50  // and comment lines (starting with a '#')
51  static bool read_lines_from_file(const std::string& filename, std::list<std::string>& lines) {
52  // open file
53  std::ifstream a_file(filename.c_str(), std::ios::in | std::ios::binary);
54  if (! a_file.is_open())
55  return false;
56 
57  // read data from file
58  static const unsigned int BUF_SIZE = 4096;
59  char *ptr, buf[BUF_SIZE+1];
60  buf[BUF_SIZE] = '\0';
61  lines.clear();
62 
63  while (a_file.getline(buf, BUF_SIZE)) {
64  ptr = trim(buf);
65  if (*ptr != '\0' && *ptr != '#')
66  lines.push_back(ptr);
67  }
68 
69  // close file
70  a_file.close();
71 
72  return true;
73  }
74 
75  // Check for file match, use std::list for sorting the files, which will allow
76  // random order matching...
77  static bool check_files_match(const std::string& fileA, const std::string& fileB) {
78  // open and read data from files
79  std::list<std::string> a_lines, b_lines;
80  BOOST_REQUIRE(read_lines_from_file(fileA, a_lines));
81  BOOST_REQUIRE(read_lines_from_file(fileB, b_lines));
82 
83  // sort lines read
84  a_lines.sort();
85  b_lines.sort();
86 
87  // files match if lines match
88  return (a_lines == b_lines);
89  }
90 
91  static bool check_files_exact_match(const std::string& fileA, const std::string& fileB, bool ignore_line_endings = false) {
92  // open files
93  std::ifstream a_file(fileA.c_str(), std::ios::in | std::ios::binary);
94  BOOST_REQUIRE(a_file.is_open());
95 
96  std::ifstream b_file(fileB.c_str(), std::ios::in | std::ios::binary);
97  BOOST_REQUIRE(b_file.is_open());
98 
99  // read and compare data in files
100  static const unsigned int BUF_SIZE = 4096;
101  char a_buf[BUF_SIZE];
102  char b_buf[BUF_SIZE];
103 
104  if (ignore_line_endings) {
105  while (a_file.getline(a_buf, BUF_SIZE)) {
106  if (! b_file.getline(b_buf, BUF_SIZE))
107  return false;
108  PionUnitTest::trim(a_buf);
109  PionUnitTest::trim(b_buf);
110  if (strlen(a_buf) != strlen(b_buf))
111  return false;
112  if (memcmp(a_buf, b_buf, strlen(a_buf)) != 0)
113  return false;
114  }
115  if (b_file.getline(b_buf, BUF_SIZE))
116  return false;
117  } else {
118  while (a_file.read(a_buf, BUF_SIZE)) {
119  if (! b_file.read(b_buf, BUF_SIZE))
120  return false;
121  if (memcmp(a_buf, b_buf, BUF_SIZE) != 0)
122  return false;
123  }
124  if (b_file.read(b_buf, BUF_SIZE))
125  return false;
126  }
127  if (a_file.gcount() != b_file.gcount())
128  return false;
129  if (memcmp(a_buf, b_buf, a_file.gcount()) != 0)
130  return false;
131 
132  a_file.close();
133  b_file.close();
134 
135  // files match
136  return true;
137  }
138 };
139 
140 
141 // PionUnitTestsConfig is intended for use as a global fixture. By including the
142 // following line in one source code file of a unit test project, the constructor will
143 // run once before the first test and the destructor will run once after the last test:
144 
145 // BOOST_GLOBAL_FIXTURE(PionUnitTestsConfig);
146 
149  std::cout << "global setup for all pion unit tests\n";
150 
151  // argc and argv do not include parameters handled by the boost unit test framework, such as --log_level.
152  int argc = boost::unit_test::framework::master_test_suite().argc;
153  char** argv = boost::unit_test::framework::master_test_suite().argv;
154 
155  bool verbose = false;
156  if (argc > 1) {
157  if (argv[1][0] == '-' && argv[1][1] == 'v') {
158  verbose = true;
159  }
160  }
161  if (verbose) {
162  PION_LOG_CONFIG_BASIC;
163  } else {
164  std::cout << "Use '-v' to enable logging of errors and warnings from pion.\n";
165  }
166  pion::PionLogger log_ptr = PION_GET_LOGGER("pion");
167  PION_LOG_SETLEVEL_WARN(log_ptr);
168  }
170  std::cout << "global teardown for all pion unit tests\n";
171  }
172 };
173 
174 
175 /*
176 Using BOOST_AUTO_TEST_SUITE_FIXTURE_TEMPLATE and
177 BOOST_AUTO_TEST_CASE_FIXTURE_TEMPLATE has two additional benefits relative to
178 using BOOST_FIXTURE_TEST_SUITE and BOOST_AUTO_TEST_CASE:
179 1) it allows a test to be run with more than one fixture, and
180 2) it makes the current fixture part of the test name, e.g.
181  checkPropertyX<myFixture_F>
182 
183 For an example of 1), see HTTPMessageTests.cpp.
184 
185 There are probably simpler ways to achieve 2), but since it comes for free,
186 it makes sense to use it. The benefit of this is that the test names don't
187 have to include redundant information about the fixture, e.g.
188 checkMyFixtureHasPropertyX. (In this example, checkPropertyX<myFixture_F> is
189 not obviously better than checkMyFixtureHasPropertyX, but in many cases the
190 test names become too long and/or hard to parse, or the fixture just isn't
191 part of the name, making some error reports ambiguous.)
192 
193 (BOOST_AUTO_TEST_CASE_FIXTURE_TEMPLATE is based on BOOST_AUTO_TEST_CASE_TEMPLATE,
194 in unit_test_suite.hpp.)
195 
196 
197 Minimal example demonstrating usage of BOOST_AUTO_TEST_CASE_FIXTURE_TEMPLATE:
198 
199 class ObjectToTest_F { // suffix _F is used for fixtures
200 public:
201  ObjectToTest_F() {
202  m_value = 2;
203  }
204  int m_value;
205  int getValue() { return m_value; }
206 };
207 
208 // This illustrates the most common case, where just one fixture will be used,
209 // so the list only has one fixture in it.
210 // ObjectToTest_S is the name of the test suite.
211 BOOST_AUTO_TEST_SUITE_FIXTURE_TEMPLATE(ObjectToTest_S,
212  boost::mpl::list<ObjectToTest_F>)
213 
214 // One method for testing the fixture...
215 BOOST_AUTO_TEST_CASE_FIXTURE_TEMPLATE(checkValueEqualsTwo) {
216  BOOST_CHECK_EQUAL(F::m_value, 2);
217  BOOST_CHECK_EQUAL(F::getValue(), 2);
218 }
219 
220 // Another method for testing the fixture...
221 BOOST_AUTO_TEST_CASE_FIXTURE_TEMPLATE(checkValueEqualsTwoAgain) {
222  BOOST_CHECK_EQUAL(this->m_value, 2);
223  BOOST_CHECK_EQUAL(this->getValue(), 2);
224 }
225 
226 // The simplest, but, alas, non conformant to the C++ standard, method for testing the fixture.
227 // This will compile with MSVC (unless language extensions are disabled (/Za)).
228 // It won't compile with gcc unless -fpermissive is used.
229 // See http://gcc.gnu.org/onlinedocs/gcc/Name-lookup.html.
230 BOOST_AUTO_TEST_CASE_FIXTURE_TEMPLATE(checkValueEqualsTwoNonConformant) {
231  BOOST_CHECK_EQUAL(m_value, 2);
232  BOOST_CHECK_EQUAL(getValue(), 2);
233 }
234 
235 BOOST_AUTO_TEST_SUITE_END()
236 */
237 
238 #define BOOST_AUTO_TEST_SUITE_FIXTURE_TEMPLATE(suite_name, fixture_types) \
239 BOOST_AUTO_TEST_SUITE(suite_name) \
240 typedef fixture_types BOOST_AUTO_TEST_CASE_FIXTURE_TYPES; \
241 
242 
243 #define BOOST_AUTO_TEST_CASE_FIXTURE_TEMPLATE(test_name) \
244 template<typename F> \
245 struct test_name : public F \
246 { void test_method(); }; \
247  \
248 struct BOOST_AUTO_TC_INVOKER( test_name ) { \
249  template<typename TestType> \
250  static void run( boost::type<TestType>* = 0 ) \
251  { \
252  test_name<TestType> t; \
253  t.test_method(); \
254  } \
255 }; \
256  \
257 BOOST_AUTO_TU_REGISTRAR( test_name )( \
258  boost::unit_test::ut_detail::template_test_case_gen< \
259  BOOST_AUTO_TC_INVOKER( test_name ), \
260  BOOST_AUTO_TEST_CASE_FIXTURE_TYPES >( \
261  BOOST_STRINGIZE( test_name ) ) ); \
262  \
263 template<typename F> \
264 void test_name<F>::test_method() \
265 
266 
267 // Macro for checking that a particular exception is thrown, for situations where the type of the exception is not in scope.
268 // For instance, in checkEmptyQueryMapException(), we'd really just like to say:
269 // BOOST_CHECK_THROW(p->setConfig(*m_vocab, config_ptr), pion::plugins::WebTrendsAnalyticsReactor::EmptyQueryMap);
270 // but pion::plugins::WebTrendsAnalyticsReactor::EmptyQueryMap isn't defined, and the overhead to bring it into scope is prohibitive.
271 #define CHECK_THROW_WITH_SPECIFIC_MESSAGE(S, M) \
272  bool exception_caught = false; \
273  try { \
274  S; \
275  } catch (pion::PionException& e) { \
276  exception_caught = true; \
277  BOOST_CHECK_EQUAL(e.what(), M); \
278  } \
279  BOOST_CHECK(exception_caught);
280 
281 
282 #endif