pion  5.0.6
include/pion/test/unit_test.hpp
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