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         PION_LOG_CONFIG_BASIC;
00162         pion::PionLogger log_ptr = PION_GET_LOGGER("pion");
00163         if (verbose) {
00164             PION_LOG_SETLEVEL_WARN(log_ptr);
00165         } else {
00166             std::cout << "Use '-v' to enable logging of errors and warnings from pion.\n";
00167             PION_LOG_SETLEVEL_FATAL(log_ptr);
00168         }
00169     }
00170     ~PionUnitTestsConfig() {
00171         std::cout << "global teardown for all pion unit tests\n";
00172     }
00173 };
00174 
00175 
00176 /*
00177 Using BOOST_AUTO_TEST_SUITE_FIXTURE_TEMPLATE and
00178 BOOST_AUTO_TEST_CASE_FIXTURE_TEMPLATE has two additional benefits relative to 
00179 using BOOST_FIXTURE_TEST_SUITE and BOOST_AUTO_TEST_CASE:
00180 1) it allows a test to be run with more than one fixture, and
00181 2) it makes the current fixture part of the test name, e.g. 
00182    checkPropertyX<myFixture_F>
00183 
00184 For an example of 1), see HTTPMessageTests.cpp.
00185 
00186 There are probably simpler ways to achieve 2), but since it comes for free,
00187 it makes sense to use it.  The benefit of this is that the test names don't
00188 have to include redundant information about the fixture, e.g. 
00189 checkMyFixtureHasPropertyX.  (In this example, checkPropertyX<myFixture_F> is 
00190 not obviously better than checkMyFixtureHasPropertyX, but in many cases the 
00191 test names become too long and/or hard to parse, or the fixture just isn't
00192 part of the name, making some error reports ambiguous.)
00193 
00194 (BOOST_AUTO_TEST_CASE_FIXTURE_TEMPLATE is based on BOOST_AUTO_TEST_CASE_TEMPLATE,
00195 in unit_test_suite.hpp.)
00196 
00197 
00198 Minimal example demonstrating usage of BOOST_AUTO_TEST_CASE_FIXTURE_TEMPLATE:
00199 
00200 class ObjectToTest_F { // suffix _F is used for fixtures
00201 public:
00202     ObjectToTest_F() {
00203         m_value = 2;
00204     }
00205     int m_value;
00206     int getValue() { return m_value; }
00207 };
00208 
00209 // This illustrates the most common case, where just one fixture will be used,
00210 // so the list only has one fixture in it.
00211 // ObjectToTest_S is the name of the test suite.
00212 BOOST_AUTO_TEST_SUITE_FIXTURE_TEMPLATE(ObjectToTest_S,
00213                                        boost::mpl::list<ObjectToTest_F>)
00214 
00215 // One method for testing the fixture...
00216 BOOST_AUTO_TEST_CASE_FIXTURE_TEMPLATE(checkValueEqualsTwo) {
00217     BOOST_CHECK_EQUAL(F::m_value, 2);
00218     BOOST_CHECK_EQUAL(F::getValue(), 2);
00219 }
00220 
00221 // Another method for testing the fixture...
00222 BOOST_AUTO_TEST_CASE_FIXTURE_TEMPLATE(checkValueEqualsTwoAgain) {
00223     BOOST_CHECK_EQUAL(this->m_value, 2);
00224     BOOST_CHECK_EQUAL(this->getValue(), 2);
00225 }
00226 
00227 // The simplest, but, alas, non conformant to the C++ standard, method for testing the fixture.
00228 // This will compile with MSVC (unless language extensions are disabled (/Za)).
00229 // It won't compile with gcc unless -fpermissive is used.
00230 // See http://gcc.gnu.org/onlinedocs/gcc/Name-lookup.html.
00231 BOOST_AUTO_TEST_CASE_FIXTURE_TEMPLATE(checkValueEqualsTwoNonConformant) {
00232     BOOST_CHECK_EQUAL(m_value, 2);
00233     BOOST_CHECK_EQUAL(getValue(), 2);
00234 }
00235 
00236 BOOST_AUTO_TEST_SUITE_END()
00237 */
00238 
00239 #define BOOST_AUTO_TEST_SUITE_FIXTURE_TEMPLATE(suite_name, fixture_types) \
00240 BOOST_AUTO_TEST_SUITE(suite_name)                                         \
00241 typedef fixture_types BOOST_AUTO_TEST_CASE_FIXTURE_TYPES;                 \
00242 
00243 
00244 #define BOOST_AUTO_TEST_CASE_FIXTURE_TEMPLATE(test_name)        \
00245 template<typename F>                                            \
00246 struct test_name : public F                                     \
00247 { void test_method(); };                                        \
00248                                                                 \
00249 struct BOOST_AUTO_TC_INVOKER( test_name ) {                     \
00250     template<typename TestType>                                 \
00251     static void run( boost::type<TestType>* = 0 )               \
00252     {                                                           \
00253         test_name<TestType> t;                                  \
00254         t.test_method();                                        \
00255     }                                                           \
00256 };                                                              \
00257                                                                 \
00258 BOOST_AUTO_TU_REGISTRAR( test_name )(                           \
00259     boost::unit_test::ut_detail::template_test_case_gen<        \
00260         BOOST_AUTO_TC_INVOKER( test_name ),                     \
00261         BOOST_AUTO_TEST_CASE_FIXTURE_TYPES >(                   \
00262             BOOST_STRINGIZE( test_name ) ) );                   \
00263                                                                 \
00264 template<typename F>                                            \
00265 void test_name<F>::test_method()                                \
00266 
00267 
00268 // Macro for checking that a particular exception is thrown, for situations where the type of the exception is not in scope.  
00269 // For instance, in checkEmptyQueryMapException(), we'd really just like to say:
00270 //      BOOST_CHECK_THROW(p->setConfig(*m_vocab, config_ptr), pion::plugins::WebTrendsAnalyticsReactor::EmptyQueryMap);
00271 // but pion::plugins::WebTrendsAnalyticsReactor::EmptyQueryMap isn't defined, and the overhead to bring it into scope is prohibitive.
00272 #define CHECK_THROW_WITH_SPECIFIC_MESSAGE(S, M) \
00273     bool exception_caught = false;              \
00274     try {                                       \
00275         S;                                      \
00276     } catch (pion::PionException& e) {          \
00277         exception_caught = true;                \
00278         BOOST_CHECK_EQUAL(e.what(), M);         \
00279     }                                           \
00280     BOOST_CHECK(exception_caught);
00281 
00282 
00283 #endif

Generated on Fri Apr 30 14:48:53 2010 for pion-net by  doxygen 1.4.7