diff --git a/UnitTest++/ArgumentsReader.cpp b/UnitTest++/ArgumentsReader.cpp new file mode 100644 index 0000000..2470dda --- /dev/null +++ b/UnitTest++/ArgumentsReader.cpp @@ -0,0 +1,81 @@ +#include "ArgumentsReader.h" + +using namespace UnitTest; + +ArgumentsReader::ArgumentsReader(int argc, char**argv) +{ + _arguments.reserve(argc); + for (int i = 0; i < argc; i++) + { + _arguments.push_back(argv[i]); + } +} + + +ArgumentsReader::~ArgumentsReader() +{ +} + + +bool ArgumentsReader::findArgumentListIndex(const string & argumentName, size_t & outFrom, size_t & outCount) const +{ + if (_arguments.empty()) + { + return false; + } + outCount = 0; + if (argumentName.empty()) + { + outFrom = 1; + } + else + { + outFrom = 0; + for (size_t i = 1; i < _arguments.size(); i++) + { + if (argumentName == _arguments[i]) + { + outFrom = i + 1; + break; + } + } + if (outFrom == 0) + { + return false; + } + } + + for (size_t i = outFrom; i < _arguments.size(); i++) + { + const string & value = _arguments[i]; + if (value.size() >= 2 && value.substr(0, 2) == "--") + { + break; + } + outCount++; + } + + return true; +} + + +vector ArgumentsReader::extractValues(const string & argumentName) const +{ + size_t from, count; + if (!findArgumentListIndex(argumentName, from, count)) + { + return vector(); + } + vector values; + for (size_t i = from; i < from + count; i++) + { + values.push_back(getArgument(i)); + } + return values; +} + + +string ArgumentsReader::getArgument(size_t index) const +{ + return _arguments[index]; +} diff --git a/UnitTest++/ArgumentsReader.h b/UnitTest++/ArgumentsReader.h new file mode 100644 index 0000000..52fb02b --- /dev/null +++ b/UnitTest++/ArgumentsReader.h @@ -0,0 +1,21 @@ +#include +#include + +namespace UnitTest { + + using namespace std; + + class ArgumentsReader + { + public: + ArgumentsReader(int argc, char**argv); + virtual ~ArgumentsReader(); + + bool findArgumentListIndex(const string & argumentName, size_t & outFrom, size_t & outCount) const; + vector extractValues(const string & argumentName) const; + string getArgument(size_t index) const; + + private: + vector _arguments; + }; +} \ No newline at end of file diff --git a/UnitTest++/PredicateCmdBuilder.cpp b/UnitTest++/PredicateCmdBuilder.cpp new file mode 100644 index 0000000..7896fb9 --- /dev/null +++ b/UnitTest++/PredicateCmdBuilder.cpp @@ -0,0 +1,23 @@ +#include "PredicateCmdBuilder.h" + +using namespace UnitTest; + + +void PredicateCmdBuilder::fillSuitePredicate(const ArgumentsReader & arguments, SuitePredicate & outPredicate, bool allowImplicitArgs) +{ + outPredicate.addSuites(arguments.extractValues("--suite")); + outPredicate.addTests(arguments.extractValues("--test")); + + if (allowImplicitArgs) + { + vector implicitArguments = arguments.extractValues(""); + outPredicate.addSuites(implicitArguments); + outPredicate.addTests(implicitArguments); + } + + if (outPredicate.empty()) + { + outPredicate.addAll(); + } +} + diff --git a/UnitTest++/PredicateCmdBuilder.h b/UnitTest++/PredicateCmdBuilder.h new file mode 100644 index 0000000..cfafe08 --- /dev/null +++ b/UnitTest++/PredicateCmdBuilder.h @@ -0,0 +1,16 @@ +#ifndef UNITTEST_PredicateCmdBuilder_H +#define UNITTEST_TESTRUNNERCMD_H + +#include "ArgumentsReader.h" +#include "SuitePredicate.h" + +namespace UnitTest { + + class PredicateCmdBuilder + { + public: + static void fillSuitePredicate(const ArgumentsReader & arguments, SuitePredicate & outPredicate, bool allowImplicitArgs = true); + }; +} + +#endif \ No newline at end of file diff --git a/UnitTest++/SuitePredicate.cpp b/UnitTest++/SuitePredicate.cpp new file mode 100644 index 0000000..9157059 --- /dev/null +++ b/UnitTest++/SuitePredicate.cpp @@ -0,0 +1,105 @@ +#include "SuitePredicate.h" + +using namespace std; +using namespace UnitTest; + + +SuitePredicate::SuitePredicate() + : _all(false) +{ +} + + +SuitePredicate::~SuitePredicate() +{ +} + + +void SuitePredicate::addSuite(const string & suiteName) +{ + _suiteNames.push_back(suiteName); +} + + +void SuitePredicate::addTest(const string & testName) +{ + _testNames.push_back(testName); +} + + +void SuitePredicate::addSuites(const vector & suiteNames) +{ + for (size_t i = 0; i < suiteNames.size(); i++) + { + addSuite(suiteNames[i]); + } +} + + +void SuitePredicate::addTests(const vector & testNames) +{ + for (size_t i = 0; i < testNames.size(); i++) + { + addTest(testNames[i]); + } +} + + +void SuitePredicate::addAll() +{ + _all = true; +} + + +bool SuitePredicate::empty() +{ + if (!_suiteNames.empty()) + { + return false; + } + if (!_testNames.empty()) + { + return false; + } + if (_all) + { + return false; + } + return true; +} + + +bool SuitePredicate::operator()(Test const * const test) const +{ + if (_all) + { + return true; + } + return (mustExecuteSuite(test) || mustExecuteTest(test)); +} + + +bool SuitePredicate::mustExecuteSuite(Test const * const test) const +{ + for (size_t i = 0; i < _suiteNames.size(); i++) + { + if (_suiteNames[i] == test->m_details.suiteName) + { + return true; + } + } + return false; +} + + +bool SuitePredicate::mustExecuteTest(Test const * const test) const +{ + for (size_t i = 0; i < _testNames.size(); i++) + { + if (_testNames[i] == test->m_details.testName) + { + return true; + } + } + return false; +} diff --git a/UnitTest++/SuitePredicate.h b/UnitTest++/SuitePredicate.h new file mode 100644 index 0000000..b7589f2 --- /dev/null +++ b/UnitTest++/SuitePredicate.h @@ -0,0 +1,40 @@ +#ifndef UNITTEST_SUITEPREDICATE_H +#define UNITTEST_SUITEPREDICATE_H + +#include +#include +#include "Test.h" + +namespace UnitTest +{ + using namespace std; + + class UNITTEST_LINKAGE SuitePredicate + { + public: + SuitePredicate(); + virtual ~SuitePredicate(); + + void addSuite(const string & suiteName); + void addTest(const string & testName); + void addSuites(const vector & suiteNames); + void addTests(const vector & testNames); + void addAll(); + bool empty(); + + bool operator()(Test const * const test) const; + + const vector & getSuites() const { return _suiteNames; } + const vector & getTests() const { return _testNames; } + + private: + bool mustExecuteSuite(Test const * const test) const; + bool mustExecuteTest(Test const * const test) const; + + vector _suiteNames; + vector _testNames; + bool _all; + }; +} + +#endif \ No newline at end of file diff --git a/UnitTest++/TestDetails.cpp b/UnitTest++/TestDetails.cpp index deb6639..3c76dbb 100644 --- a/UnitTest++/TestDetails.cpp +++ b/UnitTest++/TestDetails.cpp @@ -1,4 +1,5 @@ #include "TestDetails.h" +#include // "c" version required for gcc namespace UnitTest { @@ -18,5 +19,29 @@ namespace UnitTest { , timeConstraintExempt(details.timeConstraintExempt) {} + bool TestDetails::sameTest(const TestDetails & details) const + { + if (&details == this) + { + return true; + } + // Fast pointer comparison + if (details.suiteName == suiteName && + details.testName == testName && + details.filename == filename) + { + return true; + } + + // Long string comparison + if (!strcmp(details.suiteName, suiteName) && + !strcmp(details.testName, testName) && + !strcmp(details.filename, filename)) + { + return true; + } + + return false; + } } diff --git a/UnitTest++/TestDetails.h b/UnitTest++/TestDetails.h index cf6f1d3..1dac653 100644 --- a/UnitTest++/TestDetails.h +++ b/UnitTest++/TestDetails.h @@ -17,6 +17,8 @@ namespace UnitTest { int const lineNumber; mutable bool timeConstraintExempt; + bool sameTest(const TestDetails & details) const; + TestDetails(TestDetails const&); // Why is it public? --> http://gcc.gnu.org/bugs.html#cxx_rvalbind private: TestDetails& operator=(TestDetails const&); diff --git a/UnitTest++/TestRunner.cpp b/UnitTest++/TestRunner.cpp index 8e0581d..d79ddea 100644 --- a/UnitTest++/TestRunner.cpp +++ b/UnitTest++/TestRunner.cpp @@ -4,6 +4,7 @@ #include "TestReporterStdout.h" #include "TimeHelpers.h" #include "MemoryOutStream.h" +#include "PredicateCmdBuilder.h" #include @@ -17,6 +18,21 @@ namespace UnitTest { return runner.RunTestsIf(Test::GetTestList(), NULL, True(), 0); } + int RunTestsCmd(int argc, char**argv, bool allowImplicitArgs) + { + SuitePredicate predicate; + ArgumentsReader arguments(argc, argv); + PredicateCmdBuilder::fillSuitePredicate(arguments, predicate, allowImplicitArgs); + + TestReporterStdout reporter; + TestRunner runner(reporter); + return runner.RunTestsIf(Test::GetTestList(), 0, predicate, 0); + } + + int RunTestsCmd(int argc, char**argv) + { + return RunTestsCmd(argc, argv, true); + } TestRunner::TestRunner(TestReporter& reporter) : m_reporter(&reporter) diff --git a/UnitTest++/TestRunner.h b/UnitTest++/TestRunner.h index 914249a..d0605f8 100644 --- a/UnitTest++/TestRunner.h +++ b/UnitTest++/TestRunner.h @@ -11,6 +11,25 @@ namespace UnitTest { class TestResults; class Timer; + /** + * Run tests/suites using custom selection from command line. + * + * Commands: + * --test One or multiple test names to execute, can be combined with --suite + * --suite One or multiple suite names to execute, can be combined with --test + * + * Special feature: You do not have to specify explicitely --test and --suite, you + * can mix suite names and test names and the cmd will find the way. The + * constraint is that it must have no arguments beginning with --. + * For disabling this feature, set allowImplicitArgs=false + * + * Usage examples: + * Explicit: myTests.exe --suite MySuite1 MyOtherSuite --test MySpecialTest MyOtherTest + * Implicit: myTests.exe MySpecialTest MyOtherTest MySuite1 + */ + UNITTEST_LINKAGE int RunTestsCmd(int argc, char**argv, bool allowImplicitArgs); + UNITTEST_LINKAGE int RunTestsCmd(int argc, char**argv); // Important: keep version with same signature as int main() + UNITTEST_LINKAGE int RunAllTests(); struct True diff --git a/tests/Main.cpp b/tests/Main.cpp index 4a0f402..3a629a4 100644 --- a/tests/Main.cpp +++ b/tests/Main.cpp @@ -1,6 +1,6 @@ #include "UnitTest++/UnitTestPP.h" -int main(int, char const *[]) +int main(int argc, char**argv) { - return UnitTest::RunAllTests(); + return UnitTest::RunTestsCmd(argc, argv); } diff --git a/tests/TestArgumentsReader.cpp b/tests/TestArgumentsReader.cpp new file mode 100644 index 0000000..b6c808e --- /dev/null +++ b/tests/TestArgumentsReader.cpp @@ -0,0 +1,88 @@ +#include "UnitTest++/UnitTestPP.h" +#include "UnitTest++/ArgumentsReader.h" + +using namespace UnitTest; + +SUITE(ArgumentsReader) +{ + TEST(Extract_IgnoreUndesired) + { + char full[] = "C:\\myProgram.exe"; + char valFake1[] = "fake"; + char valFake2[] = "fakeBis"; + char* argv[] = { full, valFake1, valFake2 }; + ArgumentsReader args(3, argv); + + CHECK(args.extractValues("--unexisting").empty()); + } + + TEST(Extract_CanExtractImplicitly) + { + char full[] = "C:\\myProgram.exe"; + char val1[] = "val1"; + char val2[] = "val2"; + char* argv[] = { full, val1, val2 }; + ArgumentsReader args(3, argv); + + vector values = args.extractValues(""); + REQUIRE CHECK_EQUAL(2u, values.size()); + CHECK_EQUAL(val1, values[0]); + CHECK_EQUAL(val2, values[1]); + } + + + TEST(Extract_ReadSimple) + { + char full[] = "C:\\myProgram.exe"; + char paramToto[] = "--toto"; + char val1[] = "myTest"; + char* argv[] = { full, paramToto, val1 }; + ArgumentsReader args(3, argv); + + vector values = args.extractValues(paramToto); + REQUIRE CHECK_EQUAL(1u, values.size()); + CHECK_EQUAL(val1, values[0]); + } + + + TEST(Extract_CanReadMultiple) + { + char full[] = "C:\\myProgram.exe"; + char fake[] = "fake"; + char paramToto[] = "--toto"; + char val1[] = "myTest"; + char val2[] = "myOtherTest"; + char* argv[] = { full, fake, paramToto, val1, val2 }; + ArgumentsReader args(5, argv); + + CHECK(args.extractValues("--unexisting").empty()); + + vector values = args.extractValues(paramToto); + REQUIRE CHECK_EQUAL(2u, values.size()); + CHECK_EQUAL(val1, values[0]); + CHECK_EQUAL(val2, values[1]); + } + + + TEST(Extract_BlindTest) + { + char full[] = "C:\\myProgram.exe"; + char fake[] = "fake"; + char paramToto[] = "--toto"; + char totoVal1[] = "myTest"; + char totoVal2[] = "myOtherTest"; + char paramTiti[] = "--titi"; + char titiVal[] = "yourTest"; + char* argv[] = { full, fake, paramToto, totoVal1, totoVal2, paramTiti, titiVal }; + ArgumentsReader args(7, argv); + + vector valuesToto = args.extractValues(paramToto); + REQUIRE CHECK_EQUAL(2u, valuesToto.size()); + CHECK_EQUAL(totoVal1, valuesToto[0]); + CHECK_EQUAL(totoVal2, valuesToto[1]); + + vector valuesTiti = args.extractValues(paramTiti); + REQUIRE CHECK_EQUAL(1u, valuesTiti.size()); + CHECK_EQUAL(titiVal, valuesTiti[0]); + } +} diff --git a/tests/TestSuitePredicate.cpp b/tests/TestSuitePredicate.cpp new file mode 100644 index 0000000..2f9046c --- /dev/null +++ b/tests/TestSuitePredicate.cpp @@ -0,0 +1,100 @@ +#include "UnitTest++/UnitTestPP.h" +#include "UnitTest++/SuitePredicate.h" + +SUITE(SuitePredicate) +{ + using namespace UnitTest; + + + TEST(Operator_DetectsTests) + { + SuitePredicate predicate; + predicate.addTest("test1"); + predicate.addTest("testFake"); + + Test test1("test1", "suite1"); + Test test2("test2"); + + CHECK(predicate.operator()(&test1)); + CHECK(!predicate.operator()(&test2)); + } + + + TEST(Operator_DetectsSuites) + { + SuitePredicate predicate; + predicate.addSuite("suite1"); + predicate.addSuite("suiteFake"); + + Test test1("test1", "suite1"); + Test test2("test2"); + + CHECK(predicate.operator()(&test1)); + CHECK(!predicate.operator()(&test2)); + } + + + TEST(Operator_WhenAddAll_AllAreAdded) + { + SuitePredicate predicate; + predicate.addAll(); + + Test test1("test1", "suite1"); + Test test2("test2"); + + CHECK(predicate.operator()(&test1)); + CHECK(predicate.operator()(&test2)); + } + + TEST(AddMultiple_AndCheckWithGetters) + { + vector tests; + tests.push_back("test1"); + tests.push_back("test2"); + + vector suites; + suites.push_back("suite1"); + suites.push_back("suite2"); + + SuitePredicate predicate; + predicate.addTests(tests); + predicate.addSuites(suites); + + REQUIRE CHECK_EQUAL(tests.size(), predicate.getTests().size()); + CHECK_ARRAY_EQUAL(tests, predicate.getTests(), tests.size()); + + REQUIRE CHECK_EQUAL(suites.size(), predicate.getSuites().size()); + CHECK_ARRAY_EQUAL(suites, predicate.getSuites(), suites.size()); + } + + + TEST(EmptyPredicate_IsFlaggedAsEmpty) + { + SuitePredicate predicate; + CHECK(predicate.empty()); + } + + + TEST(EmptyPredicate_WhenTestAdded_IsFlaggedAsNonEmpty) + { + SuitePredicate predicate; + predicate.addTest("toto"); + CHECK(!predicate.empty()); + } + + + TEST(EmptyPredicate_WhenSuiteAdded_IsFlaggedAsNonEmpty) + { + SuitePredicate predicate; + predicate.addSuite("toto"); + CHECK(!predicate.empty()); + } + + + TEST(EmptyPredicate_WhenAllAdded_IsFlaggedAsNonEmpty) + { + SuitePredicate predicate; + predicate.addAll(); + CHECK(!predicate.empty()); + } +} diff --git a/tests/TestTestDetails.cpp b/tests/TestTestDetails.cpp new file mode 100644 index 0000000..b5c6a28 --- /dev/null +++ b/tests/TestTestDetails.cpp @@ -0,0 +1,36 @@ +#include "UnitTest++/UnitTestPP.h" +#include "UnitTest++/TestDetails.h" + + +SUITE(TestDetails) +{ + using namespace UnitTest; + + TEST(SameTest_SameValues_AreEqual) + { + TestDetails details1("test1", "suite1", "file1", 1234); + TestDetails details2("test1", "suite1", "file1", 1234); + CHECK(details1.sameTest(details2)); + } + + TEST(SameTest_DifferentValues_AreNotEqual) + { + TestDetails details1("test1", "suite1", "file1", 1234); + TestDetails details2("test2", "suite2", "file2", 4321); + CHECK(!details1.sameTest(details2)); + } + + TEST(SameTest_LineNumber_AreIgnored) + { + TestDetails details1("test1", "suite1", "file1", 1234); + TestDetails details2("test1", "suite1", "file1", 0); + CHECK(details1.sameTest(details2)); + } + + TEST(SameTest_CopyConstructor_AreEqual) + { + TestDetails details1("test1", "suite1", "file1", 1234); + TestDetails details2(details1, 4321); + CHECK(details1.sameTest(details2)); + } +}