pion
5.0.6
|
00001 // --------------------------------------------------------------------- 00002 // pion: a Boost C++ framework for building lightweight HTTP interfaces 00003 // --------------------------------------------------------------------- 00004 // Copyright (C) 2007-2014 Splunk Inc. (https://github.com/splunk/pion) 00005 // 00006 // Distributed under the Boost Software License, Version 1.0. 00007 // See http://www.boost.org/LICENSE_1_0.txt 00008 // 00009 00010 #ifndef __PION_TEST_UNIT_TEST_HEADER__ 00011 #define __PION_TEST_UNIT_TEST_HEADER__ 00012 00013 #include <iostream> 00014 #include <fstream> 00015 #include <boost/version.hpp> 00016 #include <boost/algorithm/string.hpp> 00017 #include <boost/thread/mutex.hpp> 00018 #include <boost/thread/condition.hpp> 00019 #include <boost/test/unit_test.hpp> 00020 #include <boost/test/unit_test_log.hpp> 00021 #include <boost/test/unit_test_log_formatter.hpp> 00022 #include <boost/test/test_case_template.hpp> 00023 #include <boost/test/utils/xml_printer.hpp> 00024 #include <pion/logger.hpp> 00025 00026 #ifdef _MSC_VER 00027 #include <direct.h> 00028 #define CHANGE_DIRECTORY _chdir 00029 #define GET_DIRECTORY(a,b) _getcwd(a,b) 00030 #else 00031 #include <unistd.h> 00032 #define CHANGE_DIRECTORY chdir 00033 #define GET_DIRECTORY(a,b) getcwd(a,b) 00034 #endif 00035 00036 #define DIRECTORY_MAX_SIZE 1000 00037 00038 00039 namespace pion { // begin namespace pion 00040 namespace test { // begin namespace test 00041 00043 class safe_xml_log_formatter 00044 : public boost::unit_test::unit_test_log_formatter 00045 { 00046 public: 00047 00049 safe_xml_log_formatter() 00050 : m_entry_in_progress(false) 00051 {} 00052 00054 virtual ~safe_xml_log_formatter() {} 00055 00057 virtual void log_start(std::ostream& ostr, 00058 boost::unit_test::counter_t test_cases_amount ) 00059 { 00060 ostr << "<TestLog>" << std::endl; 00061 } 00062 00064 virtual void log_finish(std::ostream& ostr) 00065 { 00066 ostr << "</TestLog>" << std::endl; 00067 } 00068 00070 virtual void log_build_info(std::ostream& ostr) 00071 { 00072 ostr << "<BuildInfo" 00073 << " platform" << attr_value() << BOOST_PLATFORM 00074 << " compiler" << attr_value() << BOOST_COMPILER 00075 << " stl" << attr_value() << BOOST_STDLIB 00076 << " boost=\"" << BOOST_VERSION/100000 << "." 00077 << BOOST_VERSION/100 % 1000 << "." 00078 << BOOST_VERSION % 100 << '\"' 00079 << "/>" << std::endl; 00080 } 00081 00083 virtual void test_unit_start(std::ostream& ostr, 00084 boost::unit_test::test_unit const& tu ) 00085 { 00086 ostr << "<" << tu_type_name( tu ) << " name" << attr_value() << tu.p_name.get() << ">" << std::endl; 00087 } 00088 00090 virtual void test_unit_finish(std::ostream& ostr, 00091 boost::unit_test::test_unit const& tu, 00092 unsigned long elapsed ) 00093 { 00094 if ( tu.p_type == boost::unit_test::tut_case ) 00095 ostr << "<TestingTime>" << elapsed << "</TestingTime>"; 00096 ostr << "</" << tu_type_name( tu ) << ">" << std::endl; 00097 } 00098 00100 virtual void test_unit_skipped(std::ostream& ostr, 00101 boost::unit_test::test_unit const& tu ) 00102 { 00103 ostr << "<" << tu_type_name( tu ) 00104 << " name" << attr_value() << tu.p_name.get() 00105 << " skipped" << attr_value() << "yes" 00106 << "/>" << std::endl; 00107 } 00108 00110 virtual void log_exception(std::ostream& ostr, 00111 boost::unit_test::log_checkpoint_data const& checkpoint_data, 00112 boost::execution_exception const& ex ) 00113 { 00114 boost::execution_exception::location const& loc = ex.where(); 00115 00116 ostr << "<Exception file" << attr_value() << loc.m_file_name 00117 << " line" << attr_value() << loc.m_line_num; 00118 00119 if( !loc.m_function.is_empty() ) 00120 ostr << " function" << attr_value() << loc.m_function; 00121 00122 ostr << ">" << boost::unit_test::cdata() << ex.what(); 00123 00124 if( !checkpoint_data.m_file_name.is_empty() ) { 00125 ostr << "<LastCheckpoint file" << attr_value() << checkpoint_data.m_file_name 00126 << " line" << attr_value() << checkpoint_data.m_line_num 00127 << ">" 00128 << boost::unit_test::cdata() << checkpoint_data.m_message 00129 << "</LastCheckpoint>"; 00130 } 00131 00132 ostr << "</Exception>" << std::endl; 00133 } 00134 00136 virtual void log_entry_start( std::ostream& ostr, 00137 boost::unit_test::log_entry_data const& entry_data, 00138 log_entry_types let ) 00139 { 00140 boost::mutex::scoped_lock entry_lock(m_mutex); 00141 while (m_entry_in_progress) { 00142 m_entry_complete.wait(entry_lock); 00143 } 00144 m_entry_in_progress = true; 00145 00146 static boost::unit_test::literal_string xml_tags[] = { "Info", "Message", "Warning", "Error", "FatalError" }; 00147 m_curr_tag = xml_tags[let]; 00148 ostr << '<' << m_curr_tag 00149 << BOOST_TEST_L( " file" ) << attr_value() << entry_data.m_file_name 00150 << BOOST_TEST_L( " line" ) << attr_value() << entry_data.m_line_num 00151 << BOOST_TEST_L( "><![CDATA[" ); 00152 00153 ostr.flush(); 00154 } 00155 00158 virtual void log_entry_value( std::ostream& ostr, boost::unit_test::const_string value ) 00159 { 00160 boost::mutex::scoped_lock entry_lock(m_mutex); 00161 if (m_entry_in_progress) { 00162 ostr << value; 00163 ostr.flush(); 00164 } 00165 } 00166 00169 virtual void log_entry_finish( std::ostream& ostr ) 00170 { 00171 boost::mutex::scoped_lock entry_lock(m_mutex); 00172 if (m_entry_in_progress) { 00173 ostr << BOOST_TEST_L( "]]></" ) << m_curr_tag << BOOST_TEST_L( ">" ) << std::endl; 00174 m_curr_tag.clear(); 00175 m_entry_in_progress = false; 00176 m_entry_complete.notify_all(); 00177 } 00178 } 00179 00180 private: 00181 00183 static boost::unit_test::const_string tu_type_name( boost::unit_test::test_unit const& tu ) 00184 { 00185 return tu.p_type == boost::unit_test::tut_case ? "TestCase" : "TestSuite"; 00186 } 00187 00189 typedef boost::unit_test::attr_value attr_value; 00190 00192 volatile bool m_entry_in_progress; 00193 00195 boost::condition m_entry_complete; 00196 00198 boost::mutex m_mutex; 00199 00201 boost::unit_test::const_string m_curr_tag; 00202 }; 00203 00204 00210 struct config { 00211 config() { 00212 std::cout << "global setup for all pion unit tests\n"; 00213 00214 // argc and argv do not include parameters handled by the boost unit test framework, such as --log_level. 00215 int argc = boost::unit_test::framework::master_test_suite().argc; 00216 char** argv = boost::unit_test::framework::master_test_suite().argv; 00217 bool verbose = false; 00218 00219 if (argc > 1) { 00220 if (argv[1][0] == '-' && argv[1][1] == 'v') { 00221 verbose = true; 00222 } else if (strlen(argv[1]) > 13 && strncmp(argv[1], "--log_output=", 13) == 0) { 00223 const char * const test_log_filename = argv[1] + 13; 00224 m_test_log_file.open(test_log_filename); 00225 if (m_test_log_file.is_open()) { 00226 boost::unit_test::unit_test_log.set_stream(m_test_log_file); 00227 boost::unit_test::unit_test_log.set_formatter(new safe_xml_log_formatter); 00228 } else { 00229 std::cerr << "unable to open " << test_log_filename << std::endl; 00230 } 00231 } 00232 } 00233 00234 if (verbose) { 00235 PION_LOG_CONFIG_BASIC; 00236 } else { 00237 std::cout << "Use '-v' to enable logging of errors and warnings from pion.\n"; 00238 } 00239 00240 pion::logger log_ptr = PION_GET_LOGGER("pion"); 00241 PION_LOG_SETLEVEL_WARN(log_ptr); 00242 } 00243 virtual ~config() { 00244 std::cout << "global teardown for all pion unit tests\n"; 00245 } 00246 00248 static std::ofstream m_test_log_file; 00249 }; 00250 00251 00252 // removes line endings from a c-style string 00253 static inline char* trim(char* str) { 00254 for (long len = strlen(str) - 1; len >= 0; len--) { 00255 if (str[len] == '\n' || str[len] == '\r') 00256 str[len] = '\0'; 00257 else 00258 break; 00259 } 00260 return str; 00261 } 00262 00263 // reads lines from a file, stripping line endings and ignoring blank lines 00264 // and comment lines (starting with a '#') 00265 static inline bool read_lines_from_file(const std::string& filename, std::list<std::string>& lines) { 00266 // open file 00267 std::ifstream a_file(filename.c_str(), std::ios::in | std::ios::binary); 00268 if (! a_file.is_open()) 00269 return false; 00270 00271 // read data from file 00272 lines.clear(); 00273 std::string one_line; 00274 while (std::getline(a_file, one_line)) { 00275 boost::trim(one_line); 00276 if (!one_line.empty() && one_line[0] != '#') 00277 lines.push_back(one_line); 00278 } 00279 00280 // close file 00281 a_file.close(); 00282 00283 return true; 00284 } 00285 00286 // Check for file match, use std::list for sorting the files, which will allow 00287 // random order matching... 00288 static inline bool check_files_match(const std::string& fileA, const std::string& fileB) { 00289 // open and read data from files 00290 std::list<std::string> a_lines, b_lines; 00291 BOOST_REQUIRE(read_lines_from_file(fileA, a_lines)); 00292 BOOST_REQUIRE(read_lines_from_file(fileB, b_lines)); 00293 00294 // sort lines read 00295 a_lines.sort(); 00296 b_lines.sort(); 00297 00298 // files match if lines match 00299 return (a_lines == b_lines); 00300 } 00301 00302 static inline bool check_files_exact_match(const std::string& fileA, const std::string& fileB, bool ignore_line_endings = false) { 00303 // open files 00304 std::ifstream a_file(fileA.c_str(), std::ios::in | std::ios::binary); 00305 BOOST_REQUIRE(a_file.is_open()); 00306 00307 std::ifstream b_file(fileB.c_str(), std::ios::in | std::ios::binary); 00308 BOOST_REQUIRE(b_file.is_open()); 00309 00310 // read and compare data in files 00311 static const unsigned int BUF_SIZE = 4096; 00312 char a_buf[BUF_SIZE]; 00313 char b_buf[BUF_SIZE]; 00314 00315 if (ignore_line_endings) { 00316 while (a_file.getline(a_buf, BUF_SIZE)) { 00317 if (! b_file.getline(b_buf, BUF_SIZE)) 00318 return false; 00319 trim(a_buf); 00320 trim(b_buf); 00321 if (strlen(a_buf) != strlen(b_buf)) 00322 return false; 00323 if (memcmp(a_buf, b_buf, strlen(a_buf)) != 0) 00324 return false; 00325 } 00326 if (b_file.getline(b_buf, BUF_SIZE)) 00327 return false; 00328 } else { 00329 while (a_file.read(a_buf, BUF_SIZE)) { 00330 if (! b_file.read(b_buf, BUF_SIZE)) 00331 return false; 00332 if (memcmp(a_buf, b_buf, BUF_SIZE) != 0) 00333 return false; 00334 } 00335 if (b_file.read(b_buf, BUF_SIZE)) 00336 return false; 00337 } 00338 if (a_file.gcount() != b_file.gcount()) 00339 return false; 00340 if (memcmp(a_buf, b_buf, a_file.gcount()) != 0) 00341 return false; 00342 00343 a_file.close(); 00344 b_file.close(); 00345 00346 // files match 00347 return true; 00348 } 00349 00350 00351 } // end namespace test 00352 } // end namespace pion 00353 00354 00355 /* 00356 Using BOOST_AUTO_TEST_SUITE_FIXTURE_TEMPLATE and 00357 BOOST_AUTO_TEST_CASE_FIXTURE_TEMPLATE has two additional benefits relative to 00358 using BOOST_FIXTURE_TEST_SUITE and BOOST_AUTO_TEST_CASE: 00359 1) it allows a test to be run with more than one fixture, and 00360 2) it makes the current fixture part of the test name, e.g. 00361 checkPropertyX<myFixture_F> 00362 00363 For an example of 1), see http_message_tests.cpp. 00364 00365 There are probably simpler ways to achieve 2), but since it comes for free, 00366 it makes sense to use it. The benefit of this is that the test names don't 00367 have to include redundant information about the fixture, e.g. 00368 checkMyFixtureHasPropertyX. (In this example, checkPropertyX<myFixture_F> is 00369 not obviously better than checkMyFixtureHasPropertyX, but in many cases the 00370 test names become too long and/or hard to parse, or the fixture just isn't 00371 part of the name, making some error reports ambiguous.) 00372 00373 (BOOST_AUTO_TEST_CASE_FIXTURE_TEMPLATE is based on BOOST_AUTO_TEST_CASE_TEMPLATE, 00374 in unit_test_suite.hpp.) 00375 00376 00377 Minimal example demonstrating usage of BOOST_AUTO_TEST_CASE_FIXTURE_TEMPLATE: 00378 00379 class ObjectToTest_F { // suffix _F is used for fixtures 00380 public: 00381 ObjectToTest_F() { 00382 m_value = 2; 00383 } 00384 int m_value; 00385 int get_value() { return m_value; } 00386 }; 00387 00388 // This illustrates the most common case, where just one fixture will be used, 00389 // so the list only has one fixture in it. 00390 // ObjectToTest_S is the name of the test suite. 00391 BOOST_AUTO_TEST_SUITE_FIXTURE_TEMPLATE(ObjectToTest_S, 00392 boost::mpl::list<ObjectToTest_F>) 00393 00394 // One method for testing the fixture... 00395 BOOST_AUTO_TEST_CASE_FIXTURE_TEMPLATE(checkValueEqualsTwo) { 00396 BOOST_CHECK_EQUAL(F::m_value, 2); 00397 BOOST_CHECK_EQUAL(F::get_value(), 2); 00398 } 00399 00400 // Another method for testing the fixture... 00401 BOOST_AUTO_TEST_CASE_FIXTURE_TEMPLATE(checkValueEqualsTwoAgain) { 00402 BOOST_CHECK_EQUAL(this->m_value, 2); 00403 BOOST_CHECK_EQUAL(this->get_value(), 2); 00404 } 00405 00406 // The simplest, but, alas, non conformant to the C++ standard, method for testing the fixture. 00407 // This will compile with MSVC (unless language extensions are disabled (/Za)). 00408 // It won't compile with gcc unless -fpermissive is used. 00409 // See http://gcc.gnu.org/onlinedocs/gcc/Name-lookup.html. 00410 BOOST_AUTO_TEST_CASE_FIXTURE_TEMPLATE(checkValueEqualsTwoNonConformant) { 00411 BOOST_CHECK_EQUAL(m_value, 2); 00412 BOOST_CHECK_EQUAL(get_value(), 2); 00413 } 00414 00415 BOOST_AUTO_TEST_SUITE_END() 00416 */ 00417 00418 #define BOOST_AUTO_TEST_SUITE_FIXTURE_TEMPLATE(suite_name, fixture_types) \ 00419 BOOST_AUTO_TEST_SUITE(suite_name) \ 00420 typedef fixture_types BOOST_AUTO_TEST_CASE_FIXTURE_TYPES; \ 00421 00422 00423 #define BOOST_AUTO_TEST_CASE_FIXTURE_TEMPLATE(test_name) \ 00424 template<typename F> \ 00425 struct test_name : public F \ 00426 { void test_method(); }; \ 00427 \ 00428 struct BOOST_AUTO_TC_INVOKER( test_name ) { \ 00429 template<typename TestType> \ 00430 static void run( boost::type<TestType>* = 0 ) \ 00431 { \ 00432 test_name<TestType> t; \ 00433 t.test_method(); \ 00434 } \ 00435 }; \ 00436 \ 00437 BOOST_AUTO_TU_REGISTRAR( test_name )( \ 00438 boost::unit_test::ut_detail::template_test_case_gen< \ 00439 BOOST_AUTO_TC_INVOKER( test_name ), \ 00440 BOOST_AUTO_TEST_CASE_FIXTURE_TYPES >( \ 00441 BOOST_STRINGIZE( test_name ) ) ); \ 00442 \ 00443 template<typename F> \ 00444 void test_name<F>::test_method() \ 00445 00446 00447 00448 #endif