# 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