diff --git a/tests/iotests.cpp b/tests/iotests.cpp index 1869478..061faca 100644 --- a/tests/iotests.cpp +++ b/tests/iotests.cpp @@ -18,10 +18,6 @@ using namespace IoUtilities; using namespace CPPUNIT_NS; -namespace UnitTests { -extern string testFilesPath; -} - class IoTests : public TestFixture { CPPUNIT_TEST_SUITE(IoTests); diff --git a/tests/testutils.cpp b/tests/testutils.cpp index c55db1e..e187495 100644 --- a/tests/testutils.cpp +++ b/tests/testutils.cpp @@ -7,6 +7,8 @@ #include #include +#include + using namespace std; using namespace ApplicationUtilities; @@ -16,7 +18,7 @@ TestApplication *TestApplication::m_instance = nullptr; /*! * \class TestApplication - * \brief The TestApplication class simplifies writing test applications. + * \brief The TestApplication class simplifies writing test applications that require opening test files. * \remarks Only one instance is allowed at a time (singletone class). */ @@ -26,12 +28,16 @@ TestApplication *TestApplication::m_instance = nullptr; */ 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") + m_testFilesPathArg("test-files-path", "p", "specifies the path of the directory with test files"), + m_workingDirArg("working-dir", "w", "specifies the directory to store working copies of test files") { + // check whether there is already an instance if(m_instance) { throw runtime_error("only one TestApplication instance allowed at a time"); } m_instance = this; + + // read TEST_FILE_PATH environment variable if(const char *testFilesPathEnv = getenv("TEST_FILE_PATH")) { if(const auto len = strlen(testFilesPathEnv)) { m_testFilesPathEnvValue.reserve(len + 1); @@ -39,13 +45,20 @@ TestApplication::TestApplication(int argc, char **argv) : m_testFilesPathEnvValue += '/'; } } + + // setup argument parser m_testFilesPathArg.setRequiredValueCount(1); m_testFilesPathArg.setValueNames({"path"}); m_testFilesPathArg.setCombinable(true); - m_parser.setMainArguments({&m_testFilesPathArg, &m_helpArg}); + m_workingDirArg.setRequiredValueCount(1); + m_workingDirArg.setValueNames({"path"}); + m_workingDirArg.setCombinable(true); + m_parser.setMainArguments({&m_testFilesPathArg, &m_workingDirArg, &m_helpArg}); + + // parse arguments try { m_parser.parseArgs(argc, argv); - cerr << "Directories used to search for testfiles: " << endl; + 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; @@ -57,6 +70,30 @@ TestApplication::TestApplication(int argc, char **argv) : cerr << m_testFilesPathEnvValue << endl; } cerr << "./testfiles/" << endl << endl; + cerr << "Directory used to store working copies:" << endl; + if(m_workingDirArg.isPresent()) { + if(!m_workingDirArg.values().front().empty()) { + m_workingDir = m_workingDirArg.values().front() + '/'; + } else { + m_workingDir = "./"; + } + } else if(const char *workingDirEnv = getenv("WORKING_DIR")) { + if(const auto len = strlen(workingDirEnv)) { + m_workingDir.reserve(len + 1); + m_workingDir += workingDirEnv; + m_workingDir += '/'; + } + } else { + if(m_testFilesPathArg.isPresent()) { + m_workingDir = m_testFilesPathArgValue + "workingdir/"; + } else if(!m_testFilesPathEnvValue.empty()) { + m_workingDir = m_testFilesPathEnvValue + "workingdir/"; + } else { + m_workingDir = "./testfiles/workingdir/"; + } + } + cerr << m_workingDir << endl << endl; + m_valid = true; cerr << "Executing test cases ..." << endl; } catch(const Failure &failure) { @@ -74,18 +111,22 @@ TestApplication::~TestApplication() } /*! - * \brief Returns the full path of tbe test file with the specified \a name. + * \brief Returns the full path of the test file with the specified \a name. */ string TestApplication::testFilePath(const string &name) const { string path; - fstream file; + fstream file; // used to check whether the file is present + + // check the path specified by command line argument if(m_testFilesPathArg.isPresent()) { file.open(path = m_testFilesPathArgValue + name, ios_base::in); if(file.good()) { return path; } } + + // check the path specified by environment variable if(!m_testFilesPathEnvValue.empty()) { file.clear(); file.open(path = m_testFilesPathEnvValue + name, ios_base::in); @@ -93,7 +134,41 @@ string TestApplication::testFilePath(const string &name) const return path; } } + + // file still not found -> return default path return "./testfiles/" + name; } +/*! + * \brief Returns the full path to a working copy of the test file with the specified \a name. + */ +string TestApplication::workingCopyPath(const string &name) const +{ + // create file streams + fstream origFile, workingCopy; + origFile.exceptions(ios_base::badbit | ios_base::failbit); + workingCopy.exceptions(ios_base::badbit | ios_base::failbit); + + // ensure working directory is present + struct stat currentStat; + if(stat(m_workingDir.c_str(), ¤tStat) || !S_ISDIR(currentStat.st_mode)) { + if(mkdir(m_workingDir.c_str(), S_IRWXU | S_IRWXG | S_IROTH | S_IXOTH)) { + cerr << "Unable to create working copy for \"" << name << "\": can't create working directory." << endl; + return string(); + } + } + + // copy file + try { + origFile.open(testFilePath(name), ios_base::in | ios_base::binary); + string path = m_workingDir + name; + workingCopy.open(path, ios_base::out | ios_base::binary | ios_base::trunc); + workingCopy << origFile.rdbuf(); + return path; + } catch(const ios_base::failure &) { + cerr << "Unable to create working copy for \"" << name << "\": an IO error occured." << endl; + } + return string(); +} + } diff --git a/tests/testutils.h b/tests/testutils.h index 6c7eace..cc3da3e 100644 --- a/tests/testutils.h +++ b/tests/testutils.h @@ -15,20 +15,26 @@ public: operator bool() const; std::string testFilePath(const std::string &name) const; + std::string workingCopyPath(const std::string &name) const; static const TestApplication *instance(); private: ApplicationUtilities::ArgumentParser m_parser; ApplicationUtilities::HelpArgument m_helpArg; ApplicationUtilities::Argument m_testFilesPathArg; + ApplicationUtilities::Argument m_workingDirArg; std::string m_testFilesPathArgValue; std::string m_testFilesPathEnvValue; + std::string m_workingDir; bool m_valid; static TestApplication *m_instance; }; /*! * \brief Returns whether the TestApplication instance is valid. + * + * An instance is considered invalid if an error occured when + * parsing the command line arguments. */ inline TestApplication::operator bool() const { @@ -52,6 +58,15 @@ inline LIB_EXPORT std::string testFilePath(const std::string &name) return TestApplication::instance()->testFilePath(name); } +/*! + * \brief Convenience function which returns the full path to a working copy of the test file with the specified \a name. + * \remarks A TestApplication must be present. + */ +inline LIB_EXPORT std::string workingCopyPath(const std::string &name) +{ + return TestApplication::instance()->workingCopyPath(name); +} + } #endif // TESTUTILS_H