diff --git a/CMakeLists.txt b/CMakeLists.txt index 8a72f66..05eefec 100644 --- a/CMakeLists.txt +++ b/CMakeLists.txt @@ -26,6 +26,8 @@ set(HEADER_FILES math/math.h misc/memory.h misc/random.h + tests/testutils.h + tests/cppunit.h ) set(SRC_FILES application/argumentparser.cpp @@ -45,6 +47,7 @@ set(SRC_FILES io/path.cpp math/math.cpp misc/random.cpp + tests/testutils.cpp ) set(TEST_HEADER_FILES @@ -126,7 +129,7 @@ if(NOT TARGET check) enable_testing() endif() add_executable(${META_PROJECT_NAME}_tests EXCLUDE_FROM_ALL ${TEST_HEADER_FILES} ${TEST_SRC_FILES}) -target_link_libraries(${META_PROJECT_NAME}_tests c++utilities cppunit) +target_link_libraries(${META_PROJECT_NAME}_tests ${META_PROJECT_NAME} cppunit) set_target_properties(${META_PROJECT_NAME}_tests PROPERTIES CXX_STANDARD 11) add_test(NAME ${META_PROJECT_NAME}_cppunit COMMAND ${META_PROJECT_NAME}_tests -p "${CMAKE_CURRENT_SOURCE_DIR}/testfiles") add_dependencies(check ${META_PROJECT_NAME}_tests) diff --git a/c++utilities.pro b/c++utilities.pro index 502cc77..b5390d1 100644 --- a/c++utilities.pro +++ b/c++utilities.pro @@ -42,7 +42,9 @@ HEADERS += \ io/path.h \ math/math.h \ misc/memory.h \ - misc/random.h + misc/random.h \ + tests/testutils.h \ + tests/cppunit.h \ SOURCES += \ application/argumentparser.cpp \ @@ -61,7 +63,8 @@ SOURCES += \ io/inifile.cpp \ io/path.cpp \ math/math.cpp \ - misc/random.cpp + misc/random.cpp \ + tests/testutils.cpp OTHER_FILES += \ README.md \ @@ -82,7 +85,7 @@ mingw-w64-install { target.path = $$(INSTALL_ROOT)/lib INSTALLS += target } -for(dir, $$list(application io conversion chrono math misc)) { +for(dir, $$list(application io conversion chrono math misc tests)) { eval(inc_$${dir} = $${dir}) inc_$${dir}.path = $$(INSTALL_ROOT)/include/$$projectname/$${dir} inc_$${dir}.files = $${dir}/*.h diff --git a/tests/cppunit.cpp b/tests/cppunit.cpp index beeae4c..afa419b 100644 --- a/tests/cppunit.cpp +++ b/tests/cppunit.cpp @@ -1,49 +1 @@ -#include "../application/argumentparser.h" -#include "../application/failure.h" - -#include -#include - -#include - -using namespace std; -using namespace ApplicationUtilities; -using namespace CPPUNIT_NS; - -namespace UnitTests { - -string testFilesPath("tests"); - -} - -int main(int argc, char **argv) -{ - // setup argument parser - ArgumentParser parser; - HelpArgument helpArg(parser); - Argument testFilesPathArg("test-files-path", "p", "specifies the path to the directory with test files"); - testFilesPathArg.setRequiredValueCount(1); - testFilesPathArg.setValueNames({"path"}); - testFilesPathArg.setCombinable(true); - parser.setMainArguments({&testFilesPathArg, &helpArg}); - - try { - // parse arguments - parser.parseArgs(argc, argv); - if(testFilesPathArg.isPresent()) { - UnitTests::testFilesPath = testFilesPathArg.values().front(); - } - cerr << "Direcoty for test files: " << UnitTests::testFilesPath << endl; - - // run tests - TextUi::TestRunner runner; - TestFactoryRegistry ®istry = TestFactoryRegistry::getRegistry(); - runner.addTest(registry.makeTest()); - return !runner.run(string(), false); - } catch(const Failure &failure) { - cerr << "Invalid arguments specified: " << failure.what() << endl; - return -1; - } - - -} +#include "cppunit.h" diff --git a/tests/cppunit.h b/tests/cppunit.h new file mode 100644 index 0000000..363a1c7 --- /dev/null +++ b/tests/cppunit.h @@ -0,0 +1,32 @@ +#ifndef CPPUNIT_H +#define CPPUNIT_H + +#include "./testutils.h" + +#include +#include + +#include + +using namespace std; +using namespace TestUtilities; +using namespace CPPUNIT_NS; + +/*! + * \brief Performs unit tests using cppunit. + */ +int main(int argc, char **argv) +{ + TestApplication testApp(argc, argv); + if(testApp) { + // run tests + TextUi::TestRunner runner; + TestFactoryRegistry ®istry = TestFactoryRegistry::getRegistry(); + runner.addTest(registry.makeTest()); + return !runner.run(string(), false); + } else { + return -1; + } +} + +#endif // CPPUNIT_H diff --git a/tests/iotests.cpp b/tests/iotests.cpp index 3fe8943..1869478 100644 --- a/tests/iotests.cpp +++ b/tests/iotests.cpp @@ -1,3 +1,5 @@ +#include "./testutils.h" + #include "../io/binaryreader.h" #include "../io/binarywriter.h" #include "../io/bitreader.h" @@ -71,7 +73,7 @@ void IoTests::testBinaryReader() { // read test file fstream testFile; - testFile.open(UnitTests::testFilesPath + "/some_data", ios_base::in | ios_base::binary); + testFile.open(TestUtilities::testFilePath("some_data"), ios_base::in | ios_base::binary); BinaryReader reader(&testFile); CPPUNIT_ASSERT(reader.readUInt16LE() == 0x0102u); CPPUNIT_ASSERT(reader.readUInt16BE() == 0x0102u); @@ -116,7 +118,7 @@ void IoTests::testBinaryWriter() { // prepare reading expected data fstream testFile; - testFile.open(UnitTests::testFilesPath + "/some_data", ios_base::in | ios_base::binary); + testFile.open(TestUtilities::testFilePath("some_data"), ios_base::in | ios_base::binary); // prepare output stream stringstream outputStream(ios_base::in | ios_base::out | ios_base::binary); @@ -214,7 +216,7 @@ void IoTests::testIniFile() // prepare reading test file fstream inputFile; inputFile.exceptions(ios_base::failbit | ios_base::badbit); - inputFile.open(UnitTests::testFilesPath + "/test.ini", ios_base::in); + inputFile.open(TestUtilities::testFilePath("test.ini"), ios_base::in); IniFile ini; ini.parse(inputFile); @@ -236,12 +238,12 @@ void IoTests::testIniFile() // write values to another file fstream outputFile; outputFile.exceptions(ios_base::failbit | ios_base::badbit); - outputFile.open(UnitTests::testFilesPath + "/output.ini", ios_base::out | ios_base::trunc); + outputFile.open(TestUtilities::testFilePath("output.ini"), ios_base::out | ios_base::trunc); ini.make(outputFile); // parse written values (again) outputFile.close(); - outputFile.open(UnitTests::testFilesPath + "/output.ini", ios_base::in); + outputFile.open(TestUtilities::testFilePath("output.ini"), ios_base::in); IniFile ini2; ini2.parse(outputFile); CPPUNIT_ASSERT(ini.data() == ini2.data()); @@ -254,7 +256,7 @@ void IoTests::testCopy() { // prepare streams fstream testFile; - testFile.open(UnitTests::testFilesPath + "/some_data", ios_base::in | ios_base::binary); + testFile.open(TestUtilities::testFilePath("some_data"), ios_base::in | ios_base::binary); testFile.exceptions(ios_base::failbit | ios_base::badbit); stringstream outputStream(ios_base::in | ios_base::out | ios_base::binary); outputStream.exceptions(ios_base::failbit | ios_base::badbit); diff --git a/tests/testutils.cpp b/tests/testutils.cpp new file mode 100644 index 0000000..c55db1e --- /dev/null +++ b/tests/testutils.cpp @@ -0,0 +1,99 @@ +#include "./testutils.h" + +#include "../application/failure.h" + +#include +#include +#include +#include + +using namespace std; +using namespace ApplicationUtilities; + +namespace TestUtilities { + +TestApplication *TestApplication::m_instance = nullptr; + +/*! + * \class TestApplication + * \brief The TestApplication class simplifies writing test applications. + * \remarks Only one instance is allowed at a time (singletone class). + */ + +/*! + * \brief Constructs a TestApplication instance. + * \throws Throws std::runtime_error if an instance has already been created. + */ +TestApplication::TestApplication(int argc, char **argv) : + m_helpArg(m_parser), + m_testFilesPathArg("test-files-path", "p", "specifies the path of the directory with test files") +{ + if(m_instance) { + throw runtime_error("only one TestApplication instance allowed at a time"); + } + m_instance = this; + if(const char *testFilesPathEnv = getenv("TEST_FILE_PATH")) { + if(const auto len = strlen(testFilesPathEnv)) { + m_testFilesPathEnvValue.reserve(len + 1); + m_testFilesPathEnvValue += testFilesPathEnv; + m_testFilesPathEnvValue += '/'; + } + } + m_testFilesPathArg.setRequiredValueCount(1); + m_testFilesPathArg.setValueNames({"path"}); + m_testFilesPathArg.setCombinable(true); + m_parser.setMainArguments({&m_testFilesPathArg, &m_helpArg}); + try { + m_parser.parseArgs(argc, argv); + cerr << "Directories used to search for testfiles: " << endl; + if(m_testFilesPathArg.isPresent()) { + if(!m_testFilesPathArg.values().front().empty()) { + cerr << (m_testFilesPathArgValue = m_testFilesPathArg.values().front() + '/') << endl; + } else { + cerr << (m_testFilesPathArgValue = "./") << endl; + } + } + if(!m_testFilesPathEnvValue.empty()) { + cerr << m_testFilesPathEnvValue << endl; + } + cerr << "./testfiles/" << endl << endl; + m_valid = true; + cerr << "Executing test cases ..." << endl; + } catch(const Failure &failure) { + cerr << "Invalid arguments specified: " << failure.what() << endl; + m_valid = false; + } +} + +/*! + * \brief Destroys the TestApplication. + */ +TestApplication::~TestApplication() +{ + m_instance = nullptr; +} + +/*! + * \brief Returns the full path of tbe test file with the specified \a name. + */ +string TestApplication::testFilePath(const string &name) const +{ + string path; + fstream file; + if(m_testFilesPathArg.isPresent()) { + file.open(path = m_testFilesPathArgValue + name, ios_base::in); + if(file.good()) { + return path; + } + } + if(!m_testFilesPathEnvValue.empty()) { + file.clear(); + file.open(path = m_testFilesPathEnvValue + name, ios_base::in); + if(file.good()) { + return path; + } + } + return "./testfiles/" + name; +} + +} diff --git a/tests/testutils.h b/tests/testutils.h new file mode 100644 index 0000000..6c7eace --- /dev/null +++ b/tests/testutils.h @@ -0,0 +1,57 @@ +#ifndef TESTUTILS_H +#define TESTUTILS_H + +#include "../application/argumentparser.h" + +#include + +namespace TestUtilities { + +class LIB_EXPORT TestApplication +{ +public: + TestApplication(int argc, char **argv); + ~TestApplication(); + + operator bool() const; + std::string testFilePath(const std::string &name) const; + static const TestApplication *instance(); + +private: + ApplicationUtilities::ArgumentParser m_parser; + ApplicationUtilities::HelpArgument m_helpArg; + ApplicationUtilities::Argument m_testFilesPathArg; + std::string m_testFilesPathArgValue; + std::string m_testFilesPathEnvValue; + bool m_valid; + static TestApplication *m_instance; +}; + +/*! + * \brief Returns whether the TestApplication instance is valid. + */ +inline TestApplication::operator bool() const +{ + return m_valid; +} + +/*! + * \brief Returns the current TestApplication instance. + */ +inline const TestApplication *TestApplication::instance() +{ + return TestApplication::m_instance; +} + +/*! + * \brief Convenience function which returns the full path of the test file with the specified \a name. + * \remarks A TestApplication must be present. + */ +inline LIB_EXPORT std::string testFilePath(const std::string &name) +{ + return TestApplication::instance()->testFilePath(name); +} + +} + +#endif // TESTUTILS_H