pion-net  4.0.9
common/include/pion/PionUnitTestDefs.hpp
00001 // -----------------------------------------------------------------------
00002 // pion-common: a collection of common libraries used by the Pion Platform
00003 // -----------------------------------------------------------------------
00004 // Copyright (C) 2007-2008 Atomic Labs, Inc.  (http://www.atomiclabs.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_PIONUNITTESTDEFS_HEADER__
00011 #define __PION_PIONUNITTESTDEFS_HEADER__
00012 
00013 #include <iostream>
00014 #include <fstream>
00015 #include <boost/test/unit_test.hpp>
00016 #include <boost/test/test_case_template.hpp>
00017 #include <pion/PionLogger.hpp>
00018 
00019 #ifdef _MSC_VER
00020     #include <direct.h>
00021     #define CHANGE_DIRECTORY _chdir
00022     #define GET_DIRECTORY(a,b) _getcwd(a,b)
00023 #else
00024     #include <unistd.h>
00025     #define CHANGE_DIRECTORY chdir
00026     #define GET_DIRECTORY(a,b) getcwd(a,b)
00027 #endif
00028 
00029 #define DIRECTORY_MAX_SIZE 1000
00030 
00031 
00032 struct PionUnitTest {
00033     // This is passed to xmlSetGenericErrorFunc() to make libxml do nothing when an error
00034     // occurs, rather than its default behavior of writing a message to stderr.
00035     static void doNothing(void* ctx, const char* msg, ...) {
00036     }
00037 
00038     // removes line endings from a c-style string
00039     static char* trim(char* str) {
00040         for (long len = strlen(str) - 1; len >= 0; len--) {
00041             if (str[len] == '\n' || str[len] == '\r')
00042                 str[len] = '\0';
00043             else
00044                 break;
00045         }
00046         return str;
00047     }
00048 
00049     // reads lines from a file, stripping line endings and ignoring blank lines
00050     // and comment lines (starting with a '#')
00051     static bool read_lines_from_file(const std::string& filename, std::list<std::string>& lines) {
00052         // open file
00053         std::ifstream a_file(filename.c_str(), std::ios::in | std::ios::binary);
00054         if (! a_file.is_open())
00055             return false;
00056 
00057         // read data from file
00058         static const unsigned int BUF_SIZE = 4096;
00059         char *ptr, buf[BUF_SIZE+1];
00060         buf[BUF_SIZE] = '\0';
00061         lines.clear();
00062 
00063         while (a_file.getline(buf, BUF_SIZE)) {
00064             ptr = trim(buf);
00065             if (*ptr != '\0' && *ptr != '#')
00066                 lines.push_back(ptr);
00067         }
00068 
00069         // close file
00070         a_file.close();
00071 
00072         return true;
00073     }
00074 
00075     // Check for file match, use std::list for sorting the files, which will allow
00076     // random order matching...
00077     static bool check_files_match(const std::string& fileA, const std::string& fileB) {
00078         // open and read data from files
00079         std::list<std::string> a_lines, b_lines;
00080         BOOST_REQUIRE(read_lines_from_file(fileA, a_lines));
00081         BOOST_REQUIRE(read_lines_from_file(fileB, b_lines));
00082 
00083         // sort lines read
00084         a_lines.sort();
00085         b_lines.sort();
00086 
00087         // files match if lines match
00088         return (a_lines == b_lines);
00089     }
00090 
00091     static bool check_files_exact_match(const std::string& fileA, const std::string& fileB, bool ignore_line_endings = false) {
00092         // open files
00093         std::ifstream a_file(fileA.c_str(), std::ios::in | std::ios::binary);
00094         BOOST_REQUIRE(a_file.is_open());
00095 
00096         std::ifstream b_file(fileB.c_str(), std::ios::in | std::ios::binary);
00097         BOOST_REQUIRE(b_file.is_open());
00098 
00099         // read and compare data in files
00100         static const unsigned int BUF_SIZE = 4096;
00101         char a_buf[BUF_SIZE];
00102         char b_buf[BUF_SIZE];
00103 
00104         if (ignore_line_endings) {
00105             while (a_file.getline(a_buf, BUF_SIZE)) {
00106                 if (! b_file.getline(b_buf, BUF_SIZE))
00107                     return false;
00108                 PionUnitTest::trim(a_buf);
00109                 PionUnitTest::trim(b_buf);
00110                 if (strlen(a_buf) != strlen(b_buf))
00111                     return false;
00112                 if (memcmp(a_buf, b_buf, strlen(a_buf)) != 0)
00113                     return false;
00114             }
00115             if (b_file.getline(b_buf, BUF_SIZE))
00116                 return false;
00117         } else {
00118             while (a_file.read(a_buf, BUF_SIZE)) {
00119                 if (! b_file.read(b_buf, BUF_SIZE))
00120                     return false;
00121                 if (memcmp(a_buf, b_buf, BUF_SIZE) != 0)
00122                     return false;
00123             }
00124             if (b_file.read(b_buf, BUF_SIZE))
00125                 return false;
00126         }
00127         if (a_file.gcount() != b_file.gcount())
00128             return false;
00129         if (memcmp(a_buf, b_buf, a_file.gcount()) != 0)
00130             return false;
00131 
00132         a_file.close();
00133         b_file.close();
00134 
00135         // files match
00136         return true;
00137     }
00138 };
00139 
00140 
00141 // PionUnitTestsConfig is intended for use as a global fixture.  By including the 
00142 // following line in one source code file of a unit test project, the constructor will
00143 // run once before the first test and the destructor will run once after the last test:
00144 
00145 // BOOST_GLOBAL_FIXTURE(PionUnitTestsConfig);
00146 
00147 struct PionUnitTestsConfig {
00148     PionUnitTestsConfig() {
00149         std::cout << "global setup for all pion unit tests\n";
00150 
00151         // argc and argv do not include parameters handled by the boost unit test framework, such as --log_level.
00152         int argc = boost::unit_test::framework::master_test_suite().argc;
00153         char** argv = boost::unit_test::framework::master_test_suite().argv;
00154 
00155         bool verbose = false;
00156         if (argc > 1) {
00157             if (argv[1][0] == '-' && argv[1][1] == 'v') {
00158                 verbose = true;
00159             }
00160         }
00161         if (verbose) {
00162             PION_LOG_CONFIG_BASIC;
00163         } else {
00164             std::cout << "Use '-v' to enable logging of errors and warnings from pion.\n";
00165         }
00166         pion::PionLogger log_ptr = PION_GET_LOGGER("pion");
00167         PION_LOG_SETLEVEL_WARN(log_ptr);
00168     }
00169     ~PionUnitTestsConfig() {
00170         std::cout << "global teardown for all pion unit tests\n";
00171     }
00172 };
00173 
00174 
00175 /*
00176 Using BOOST_AUTO_TEST_SUITE_FIXTURE_TEMPLATE and
00177 BOOST_AUTO_TEST_CASE_FIXTURE_TEMPLATE has two additional benefits relative to 
00178 using BOOST_FIXTURE_TEST_SUITE and BOOST_AUTO_TEST_CASE:
00179 1) it allows a test to be run with more than one fixture, and
00180 2) it makes the current fixture part of the test name, e.g. 
00181    checkPropertyX<myFixture_F>
00182 
00183 For an example of 1), see HTTPMessageTests.cpp.
00184 
00185 There are probably simpler ways to achieve 2), but since it comes for free,
00186 it makes sense to use it.  The benefit of this is that the test names don't
00187 have to include redundant information about the fixture, e.g. 
00188 checkMyFixtureHasPropertyX.  (In this example, checkPropertyX<myFixture_F> is 
00189 not obviously better than checkMyFixtureHasPropertyX, but in many cases the 
00190 test names become too long and/or hard to parse, or the fixture just isn't
00191 part of the name, making some error reports ambiguous.)
00192 
00193 (BOOST_AUTO_TEST_CASE_FIXTURE_TEMPLATE is based on BOOST_AUTO_TEST_CASE_TEMPLATE,
00194 in unit_test_suite.hpp.)
00195 
00196 
00197 Minimal example demonstrating usage of BOOST_AUTO_TEST_CASE_FIXTURE_TEMPLATE:
00198 
00199 class ObjectToTest_F { // suffix _F is used for fixtures
00200 public:
00201     ObjectToTest_F() {
00202         m_value = 2;
00203     }
00204     int m_value;
00205     int getValue() { return m_value; }
00206 };
00207 
00208 // This illustrates the most common case, where just one fixture will be used,
00209 // so the list only has one fixture in it.
00210 // ObjectToTest_S is the name of the test suite.
00211 BOOST_AUTO_TEST_SUITE_FIXTURE_TEMPLATE(ObjectToTest_S,
00212                                        boost::mpl::list<ObjectToTest_F>)
00213 
00214 // One method for testing the fixture...
00215 BOOST_AUTO_TEST_CASE_FIXTURE_TEMPLATE(checkValueEqualsTwo) {
00216     BOOST_CHECK_EQUAL(F::m_value, 2);
00217     BOOST_CHECK_EQUAL(F::getValue(), 2);
00218 }
00219 
00220 // Another method for testing the fixture...
00221 BOOST_AUTO_TEST_CASE_FIXTURE_TEMPLATE(checkValueEqualsTwoAgain) {
00222     BOOST_CHECK_EQUAL(this->m_value, 2);
00223     BOOST_CHECK_EQUAL(this->getValue(), 2);
00224 }
00225 
00226 // The simplest, but, alas, non conformant to the C++ standard, method for testing the fixture.
00227 // This will compile with MSVC (unless language extensions are disabled (/Za)).
00228 // It won't compile with gcc unless -fpermissive is used.
00229 // See http://gcc.gnu.org/onlinedocs/gcc/Name-lookup.html.
00230 BOOST_AUTO_TEST_CASE_FIXTURE_TEMPLATE(checkValueEqualsTwoNonConformant) {
00231     BOOST_CHECK_EQUAL(m_value, 2);
00232     BOOST_CHECK_EQUAL(getValue(), 2);
00233 }
00234 
00235 BOOST_AUTO_TEST_SUITE_END()
00236 */
00237 
00238 #define BOOST_AUTO_TEST_SUITE_FIXTURE_TEMPLATE(suite_name, fixture_types) \
00239 BOOST_AUTO_TEST_SUITE(suite_name)                                         \
00240 typedef fixture_types BOOST_AUTO_TEST_CASE_FIXTURE_TYPES;                 \
00241 
00242 
00243 #define BOOST_AUTO_TEST_CASE_FIXTURE_TEMPLATE(test_name)        \
00244 template<typename F>                                            \
00245 struct test_name : public F                                     \
00246 { void test_method(); };                                        \
00247                                                                 \
00248 struct BOOST_AUTO_TC_INVOKER( test_name ) {                     \
00249     template<typename TestType>                                 \
00250     static void run( boost::type<TestType>* = 0 )               \
00251     {                                                           \
00252         test_name<TestType> t;                                  \
00253         t.test_method();                                        \
00254     }                                                           \
00255 };                                                              \
00256                                                                 \
00257 BOOST_AUTO_TU_REGISTRAR( test_name )(                           \
00258     boost::unit_test::ut_detail::template_test_case_gen<        \
00259         BOOST_AUTO_TC_INVOKER( test_name ),                     \
00260         BOOST_AUTO_TEST_CASE_FIXTURE_TYPES >(                   \
00261             BOOST_STRINGIZE( test_name ) ) );                   \
00262                                                                 \
00263 template<typename F>                                            \
00264 void test_name<F>::test_method()                                \
00265 
00266 
00267 // Macro for checking that a particular exception is thrown, for situations where the type of the exception is not in scope.  
00268 // For instance, in checkEmptyQueryMapException(), we'd really just like to say:
00269 //      BOOST_CHECK_THROW(p->setConfig(*m_vocab, config_ptr), pion::plugins::WebTrendsAnalyticsReactor::EmptyQueryMap);
00270 // but pion::plugins::WebTrendsAnalyticsReactor::EmptyQueryMap isn't defined, and the overhead to bring it into scope is prohibitive.
00271 #define CHECK_THROW_WITH_SPECIFIC_MESSAGE(S, M) \
00272     bool exception_caught = false;              \
00273     try {                                       \
00274         S;                                      \
00275     } catch (pion::PionException& e) {          \
00276         exception_caught = true;                \
00277         BOOST_CHECK_EQUAL(e.what(), M);         \
00278     }                                           \
00279     BOOST_CHECK(exception_caught);
00280 
00281 
00282 #endif