- Main Page
- Classes
- Files

- File List

# include/pion/test/unit_test.hpp

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