pion-net
4.0.9
|
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