diff --git a/tests/cppunit.h b/tests/cppunit.h index a40aa29..8fe860f 100644 --- a/tests/cppunit.h +++ b/tests/cppunit.h @@ -3,6 +3,8 @@ #include "./testutils.h" +#include "../application/commandlineutils.h" + #include #include #include @@ -13,6 +15,18 @@ using namespace std; using namespace CppUtilities; using namespace CPPUNIT_NS; +/*! + * \brief Prints the names of all child tests of the specified \a test. + */ +void printTestNames(Test *test, Indentation indentation) +{ + for (int index = 0, count = test->getChildTestCount(); index != count; ++index) { + const auto childTest = test->getChildTestAt(index); + cerr << '\n' << indentation << " - " << childTest->getName(); + printTestNames(childTest, indentation + 4); + } +} + /*! * \brief Performs unit tests using cppunit. */ @@ -23,22 +37,41 @@ int main(int argc, char **argv) return -1; } + // list tests + TestFactoryRegistry ®istry = TestFactoryRegistry::getRegistry(); + if (testApp.onlyListUnits()) { + cerr << "Available tests:"; + printTestNames(registry.makeTest(), Indentation(0)); + cerr << '\n'; + return 0; + } + // run tests TextUi::TestRunner runner; - TestFactoryRegistry ®istry = TestFactoryRegistry::getRegistry(); if (!testApp.unitsSpecified() || testApp.units().empty()) { // no units specified -> test all runner.addTest(registry.makeTest()); } else { // pick specified units from overall test Test *overallTest = registry.makeTest(); + vector unavailableUnits; for (const char *unit : testApp.units()) { try { runner.addTest(overallTest->findTest(unit)); } catch (const invalid_argument &) { - cerr << "The specified test unit \"" << unit << "\" is not available and will be ignored.\n"; + unavailableUnits.emplace_back(unit); } } + if (!unavailableUnits.empty()) { + cerr << "The following tests specified via --unit are not available:"; + for (const char *unitName : unavailableUnits) { + cerr << "\n - " << unitName; + } + cerr << "\nAvailable tests:"; + printTestNames(overallTest, Indentation(0)); + cerr << '\n'; + return -1; + } } const auto ok = runner.run(string(), false); cerr << (ok ? "Tests successful\n" : "Tests failed\n"); diff --git a/tests/testutils.cpp b/tests/testutils.cpp index 60f5f74..dd62601 100644 --- a/tests/testutils.cpp +++ b/tests/testutils.cpp @@ -115,10 +115,12 @@ TestApplication::TestApplication() * \throws Throws std::runtime_error if an instance has already been created. */ TestApplication::TestApplication(int argc, const char *const *argv) - : m_testFilesPathArg("test-files-path", 'p', "specifies the path of the directory with test files") - , m_applicationPathArg("app-path", 'a', "specifies the path of the application to be tested") - , m_workingDirArg("working-dir", 'w', "specifies the directory to store working copies of test files") - , m_unitsArg("units", 'u', "specifies the units to test; omit to test all units") + : m_listArg("list", 'l', "lists available test units") + , m_runArg("run", 'r', "runs the tests") + , m_testFilesPathArg("test-files-path", 'p', "specifies the path of the directory with test files", { "path" }) + , m_applicationPathArg("app-path", 'a', "specifies the path of the application to be tested", { "path" }) + , m_workingDirArg("working-dir", 'w', "specifies the directory to store working copies of test files", { "path" }) + , m_unitsArg("units", 'u', "specifies the units to test; omit to test all units", { "unit1", "unit2", "unit3" }) { // check whether there is already an instance if (m_instance) { @@ -129,17 +131,11 @@ TestApplication::TestApplication(int argc, const char *const *argv) // handle specified arguments (if present) if (argc && argv) { // setup argument parser - for (Argument *arg : initializer_list{ &m_testFilesPathArg, &m_applicationPathArg, &m_workingDirArg }) { - arg->setRequiredValueCount(1); - arg->setValueNames({ "path" }); - arg->setCombinable(true); - } m_testFilesPathArg.setRequiredValueCount(Argument::varValueCount); m_unitsArg.setRequiredValueCount(Argument::varValueCount); - m_unitsArg.setValueNames({ "unit1", "unit2", "unit3" }); - m_unitsArg.setCombinable(true); - m_parser.setMainArguments( - { &m_testFilesPathArg, &m_applicationPathArg, &m_workingDirArg, &m_unitsArg, &m_parser.noColorArg(), &m_parser.helpArg() }); + m_runArg.setImplicit(true); + m_runArg.setSubArguments({ &m_testFilesPathArg, &m_applicationPathArg, &m_workingDirArg, &m_unitsArg }); + m_parser.setMainArguments({&m_listArg, &m_runArg, &m_parser.noColorArg(), &m_parser.helpArg()}); // parse arguments try { diff --git a/tests/testutils.h b/tests/testutils.h index 8285e13..570820d 100644 --- a/tests/testutils.h +++ b/tests/testutils.h @@ -41,6 +41,7 @@ public: const char *applicationPath(); bool unitsSpecified() const; const std::vector &units() const; + bool onlyListUnits() const; // static read-only accessors static const TestApplication *instance(); @@ -51,10 +52,12 @@ private: static std::string readTestfilePathFromSrcRef(); ArgumentParser m_parser; - Argument m_testFilesPathArg; - Argument m_applicationPathArg; - Argument m_workingDirArg; - Argument m_unitsArg; + OperationArgument m_listArg; + OperationArgument m_runArg; + ConfigValueArgument m_testFilesPathArg; + ConfigValueArgument m_applicationPathArg; + ConfigValueArgument m_workingDirArg; + ConfigValueArgument m_unitsArg; std::vector m_testFilesPaths; std::string m_workingDir; bool m_valid; @@ -129,6 +132,14 @@ inline const std::vector &TestApplication::units() const return m_unitsArg.values(); } +/*! + * \brief Returns whether the test application should only list available units and not actually run any tests. + */ +inline bool TestApplication::onlyListUnits() const +{ + return m_listArg.isPresent(); +} + /*! * \brief Convenience function to invoke TestApplication::testFilePath(). * \remarks A TestApplication must be present.