From b0984d66584f580d9a7d054c4f20a81df539d3ba Mon Sep 17 00:00:00 2001 From: Jonathan Hyry Date: Thu, 2 May 2024 13:56:53 -0700 Subject: [PATCH 01/32] Fix catching the object by value. --- src/TestCPP.cpp | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/TestCPP.cpp b/src/TestCPP.cpp index aa287e5..8be0714 100644 --- a/src/TestCPP.cpp +++ b/src/TestCPP.cpp @@ -259,7 +259,7 @@ namespace TestCPP { this->pass = false; logTestFailure(errorMessage); } - catch(string errorMessage) { + catch(string& errorMessage) { this->pass = false; logTestFailure(errorMessage); } From 1525053f4e2947c15cd85b5f01ea7321253678bd Mon Sep 17 00:00:00 2001 From: Jonathan Hyry Date: Thu, 2 May 2024 13:56:53 -0700 Subject: [PATCH 02/32] Fix catching the object by value. --- src/TestCPP.cpp | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/TestCPP.cpp b/src/TestCPP.cpp index aa287e5..8be0714 100644 --- a/src/TestCPP.cpp +++ b/src/TestCPP.cpp @@ -259,7 +259,7 @@ namespace TestCPP { this->pass = false; logTestFailure(errorMessage); } - catch(string errorMessage) { + catch(string& errorMessage) { this->pass = false; logTestFailure(errorMessage); } From 4e6cf83890bade3eaa39865c38339d5c4dacf2db Mon Sep 17 00:00:00 2001 From: Jonathan Hyry Date: Thu, 2 May 2024 14:22:42 -0700 Subject: [PATCH 03/32] Hopefully this will fix the permissions issue. --- .github/workflows/cmake-static-analysis.yml | 2 ++ 1 file changed, 2 insertions(+) diff --git a/.github/workflows/cmake-static-analysis.yml b/.github/workflows/cmake-static-analysis.yml index 8f6768b..b6b3da0 100644 --- a/.github/workflows/cmake-static-analysis.yml +++ b/.github/workflows/cmake-static-analysis.yml @@ -22,6 +22,8 @@ jobs: - name: Run static analysis uses: JacobDomagala/StaticAnalysis@master with: + github_token: ${{ secrets.GITHUB_TOKEN }} + language: c++ # Exclude any issues found in ${Project_root_dir}/lib From 5d3b582b65f3638e86074a10327bfec48d44ce22 Mon Sep 17 00:00:00 2001 From: Jonathan Hyry Date: Thu, 2 May 2024 14:45:50 -0700 Subject: [PATCH 04/32] Reverting the previous change, there was just a permission issue in the repo settings in GH. --- .github/workflows/cmake-static-analysis.yml | 2 -- 1 file changed, 2 deletions(-) diff --git a/.github/workflows/cmake-static-analysis.yml b/.github/workflows/cmake-static-analysis.yml index b6b3da0..8f6768b 100644 --- a/.github/workflows/cmake-static-analysis.yml +++ b/.github/workflows/cmake-static-analysis.yml @@ -22,8 +22,6 @@ jobs: - name: Run static analysis uses: JacobDomagala/StaticAnalysis@master with: - github_token: ${{ secrets.GITHUB_TOKEN }} - language: c++ # Exclude any issues found in ${Project_root_dir}/lib From e1972ea667a7820122f9d4aca172d74208bb7c82 Mon Sep 17 00:00:00 2001 From: Jonathan Hyry Date: Fri, 3 May 2024 12:35:50 -0700 Subject: [PATCH 05/32] Change TestCaseName to TestObjName, extract strings Use the TestCaseName class (now named TestObjName) also for TestSuite to protect against null char * in string construction, which also simplifies the setSuiteName logic. Change the setSuiteName signature appropriately for the new type. Change private suiteName member type from string to TestObjName. Change constructor parameter for suite name from string to TestObjName, and ensure it's moved properly when storing it. Coincidentally, fixes GH issue #7. Extract strings into structs per-class so strings that are used more than once only need to be changed once, and can be referenced by tests for log verification when appropriate. Also if something will then be used again, just reference it. --- include/TestCPP.h | 48 +++++++++++++++++++++++++++++++++++------------ src/TestCPP.cpp | 47 +++++++++++++++++++++------------------------- 2 files changed, 57 insertions(+), 38 deletions(-) diff --git a/include/TestCPP.h b/include/TestCPP.h index 0567063..1e17348 100644 --- a/include/TestCPP.h +++ b/include/TestCPP.h @@ -69,16 +69,16 @@ namespace TestCPP { TestFailedException (string&& msg); }; - class TestCaseName { + class TestObjName { public: - TestCaseName () = default; - TestCaseName (const char* name); + TestObjName () = default; + TestObjName (const char* name); const string& getTestName (); friend std::ostream& operator<< ( std::ostream& s, - TestCaseName& tcName + TestObjName& tcName ) { s << tcName.getTestName(); @@ -88,6 +88,10 @@ namespace TestCPP { private: string testCaseName; + struct TCNStr { + static constexpr const char * NVTN = + "Not a valid test name!"; + }; }; class TestCase { @@ -99,7 +103,7 @@ namespace TestCPP { }; TestCase ( - TestCaseName&& testName, + TestObjName&& testName, function test, bool testPassedMessage = true, bool captureOut = false, @@ -132,7 +136,7 @@ namespace TestCPP { bool pass; long long lastRunTime; - TestCaseName testName; + TestObjName testName; function test; TestCaseOutCompareOptions option; @@ -168,24 +172,44 @@ namespace TestCPP { system_clock::now() - start ); } + + struct TCStr { + static constexpr const char * APOS = "'"; + static constexpr const char * FAIL = " failed! ("; + static constexpr const char * NCONTAIN = + " does not contain "; + static constexpr const char * NEQUIV = + " is not equivalent to "; + static constexpr const char * PASS = " passed! ("; + static constexpr const char * REASON = "Reason: "; + static constexpr const char * SEC = "s)"; + static constexpr const char * START_RUN = + "Starting run of test "; + static constexpr const char * TEST_ = "Test "; + static constexpr const char * UNK_CMP_OPT = + "Unknown comparison option! "; + static constexpr const char * UNK_EXC = + "Unknown error occurred in test!"; + static constexpr const char * UNK_OPT = "Unknown option "; + }; }; class TestSuite { public: template - TestSuite (string suiteName, + TestSuite (TestObjName&& suiteName, typename enable_if::type) { this->testPassedMessage = true; - this->setSuiteName(suiteName); + this->setSuiteName(move(suiteName)); this->tests = vector(); } template - TestSuite (string suiteName, TestType ...tests) { + TestSuite (TestObjName&& suiteName, TestType ...tests) { this->testPassedMessage = true; - this->setSuiteName(suiteName); + this->setSuiteName(move(suiteName)); this->tests = vector(); this->addTests(tests...); @@ -215,7 +239,7 @@ namespace TestCPP { return T(args...); } - void setSuiteName (string testSuiteName); + void setSuiteName (TestObjName&& testSuiteName); template static void assertEquals ( @@ -329,7 +353,7 @@ namespace TestCPP { unsigned lastRunFailCount; unsigned long long totalRuntime; - string suiteName; + TestObjName suiteName; vector tests; }; } diff --git a/src/TestCPP.cpp b/src/TestCPP.cpp index 8be0714..6e9d9bf 100644 --- a/src/TestCPP.cpp +++ b/src/TestCPP.cpp @@ -88,16 +88,16 @@ namespace TestCPP { #endif } - TestCaseName::TestCaseName (const char* name) { + TestObjName::TestObjName (const char* name) { if (name) { this->testCaseName = name; } else { - throw TestCPPException("Not a valid test name!"); + throw TestCPPException(TCNStr::NVTN); } } - const string& TestCaseName::getTestName () { + const string& TestObjName::getTestName () { return this->testCaseName; } @@ -115,7 +115,7 @@ namespace TestCPP { unique_ptr TestCase::clogOriginal = nullptr; unique_ptr TestCase::stderrOriginal = nullptr; - TestCase::TestCase (TestCaseName&& name, + TestCase::TestCase (TestObjName&& name, function test, bool msg, bool captureOut, bool captureLog, @@ -231,21 +231,21 @@ namespace TestCPP { void TestCase::logTestFailure (string reason) { clog << fixed; clog << setprecision(4); - clog << "Test " << this->testName << " failed! (" + clog << TCStr::TEST_ << this->testName << TCStr::FAIL << static_cast(this->lastRunTime) - /1000000000.0 << "s)" << endl; - clog << "Reason: " << reason << endl; + /1000000000.0 << TCStr::SEC << endl; + clog << TCStr::REASON << reason << endl; } void TestCase::runTest () { - clog << "Starting run of test " << this->testName << endl; + clog << TCStr::START_RUN << this->testName << endl; this->lastRunTime = duration(this->test).count(); if (this->notifyTestPassed) { clog << fixed; clog << setprecision(4); - clog << "Test " << this->testName << " passed! (" + clog << TCStr::TEST_ << this->testName << TCStr::PASS << static_cast(this->lastRunTime) - /1000000000.0 << "s)" << endl; + /1000000000.0 << TCStr::SEC << endl; } this->pass = true; } @@ -269,7 +269,7 @@ namespace TestCPP { } catch (...) { this->pass = false; - logTestFailure("Unknown error occurred in test!"); + logTestFailure(TCStr::UNK_EXC); } return false; @@ -342,7 +342,7 @@ namespace TestCPP { default: stringstream error; - error << "Unknown option " << opt; + error << TCStr::UNK_OPT << opt; throw TestCPPException(error.str()); } } @@ -390,8 +390,9 @@ namespace TestCPP { } else { stringstream nomatch; - nomatch << "'" << source << "' is not equivalent to '"; - nomatch << against << "'"; + nomatch << TCStr::APOS << source << TCStr::APOS; + nomatch << TCStr::NEQUIV << TCStr::APOS; + nomatch << against << TCStr::APOS; if (this->clogOriginal != nullptr) { ostream tmp(this->clogOriginal.get()); @@ -411,8 +412,9 @@ namespace TestCPP { } else { stringstream nomatch; - nomatch << "'" << source << "' does not contain '"; - nomatch << against << "'"; + nomatch << TCStr::APOS << source << TCStr::APOS; + nomatch << TCStr::NCONTAIN << TCStr::APOS; + nomatch << against << TCStr::APOS; if (this->clogOriginal != nullptr) { ostream tmp(this->clogOriginal.get()); @@ -428,7 +430,7 @@ namespace TestCPP { default: stringstream re; - re << "Unknown comparison option! " << opt; + re << TCStr::UNK_CMP_OPT << opt; throw TestCPPException(re.str()); } } @@ -446,15 +448,8 @@ namespace TestCPP { } } - void TestSuite::setSuiteName (string testSuiteName) { - if (testSuiteName.data()) { - this->suiteName = testSuiteName; - } - else { - stringstream e; - e << "An invalid string was passed as the Test Suite Name!"; - throw TestCPPException(e.str()); - } + void TestSuite::setSuiteName (TestObjName&& testSuiteName) { + this->suiteName = move(testSuiteName); } unsigned TestSuite::getLastRunFailCount () { From 432af2c0c20cdbeffe84be06cd27e5ae39d52215 Mon Sep 17 00:00:00 2001 From: Jonathan Hyry Date: Fri, 3 May 2024 12:37:32 -0700 Subject: [PATCH 06/32] TestObjName does not allow construction with std::string In order to protect against null char *, we don't know how a string is constructed before it's passed to TestObjName. --- demo/src/main.cpp | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/demo/src/main.cpp b/demo/src/main.cpp index eb65b50..a06e0d5 100644 --- a/demo/src/main.cpp +++ b/demo/src/main.cpp @@ -12,7 +12,7 @@ int main(void) { try { TestSuite suite( - string("Demo Test Suite"), + "Demo Test Suite", make_tuple( "simpleTest", From b7ed12a3069bdd79a0912568de77b96609f836ab Mon Sep 17 00:00:00 2001 From: Jonathan Hyry Date: Mon, 13 May 2024 08:06:03 -0700 Subject: [PATCH 07/32] Major refactor and API change I broke up TestCPP.h and the corresponding .cpp file into many pieces. It just started to get too big, and I wanted more focused TUs. So this is the first commit for that, where I split them out into different header files, put them in an 'internal' subdirectory, and turned TestCPP.h into an aggregate include for including the types and definitions needed by the library. So the common definitions are now split out into their own header-only definition in internal/TestCPPCommon.h. The exception types are now in their own internal/TestCPPExceptions.h header with corresponding .cpp. TestCase and TestSuite now have their own headers, internal/TestCPPTestCase.h and internal/TestCPPTestSuite.h, with corresponding .cpps. And finally TestCPPUtil.h is moved into internal and the TestObjName type has been moved into the Util header and definition .cpp, but is still in the base namespace TestCPP, not in TestCPP::Util. The biggest functional change is splitting the assertions out of TestSuite and into their own Assertions class, in their own TU. So from now on, the TestCPP assertions should be referenced through the Assertions class, not the TestSuite class, which makes a lot more sense. I added a couple new assertions a few weeks back, and I can see adding more, so they really need their own type to encapsulate that functionality. There's some significant cleanup to do, but that will probably be in a separate PR. --- include/TestCPP.h | 341 +----------------- include/internal/TestCPPAssertions.h | 251 +++++++++++++ include/internal/TestCPPCommon.h | 106 ++++++ include/internal/TestCPPExceptions.h | 102 ++++++ include/internal/TestCPPTestCase.h | 276 ++++++++++++++ include/internal/TestCPPTestSuite.h | 190 ++++++++++ include/internal/TestCPPUtil.h | 137 +++++++ src/TestCPPAssertions.cpp | 125 +++++++ .../TestCPPExceptions.cpp | 47 ++- src/{TestCPP.cpp => TestCPPTestCase.cpp} | 268 ++------------ src/TestCPPTestSuite.cpp | 150 ++++++++ src/TestCPPUtil.cpp | 21 +- 12 files changed, 1436 insertions(+), 578 deletions(-) create mode 100644 include/internal/TestCPPAssertions.h create mode 100644 include/internal/TestCPPCommon.h create mode 100644 include/internal/TestCPPExceptions.h create mode 100644 include/internal/TestCPPTestCase.h create mode 100644 include/internal/TestCPPTestSuite.h create mode 100644 include/internal/TestCPPUtil.h create mode 100644 src/TestCPPAssertions.cpp rename include/TestCPPUtil.h => src/TestCPPExceptions.cpp (56%) rename src/{TestCPP.cpp => TestCPPTestCase.cpp} (61%) create mode 100644 src/TestCPPTestSuite.cpp diff --git a/include/TestCPP.h b/include/TestCPP.h index 1e17348..74f6ad2 100644 --- a/include/TestCPP.h +++ b/include/TestCPP.h @@ -25,337 +25,14 @@ OTHER DEALINGS IN THE SOFTWARE. For more information, please refer to */ -//Original author: Jonathan Hyry CSU-Fullerton SECS 6896-02 Fall 2014/15 - -#ifndef TESTCPP_CLASSES_ -#define TESTCPP_CLASSES_ - -#include -#include -#include -#include -#include -#include -#include -#include -#include - -using std::atomic_int; -using std::chrono::nanoseconds; -using std::chrono::system_clock; -using std::chrono::duration_cast; -using std::enable_if; -using std::endl; -using std::forward; -using std::function; -using std::move; -using std::runtime_error; -using std::string; -using std::streambuf; -using std::stringstream; -using std::unique_ptr; -using std::vector; - -namespace TestCPP { - class TestCPPException : public runtime_error { - public: - TestCPPException (const char * msg); - TestCPPException (string&& msg); - }; - - class TestFailedException : public TestCPPException { - public: - TestFailedException (const char * msg); - TestFailedException (string&& msg); - }; - - class TestObjName { - public: - TestObjName () = default; - TestObjName (const char* name); - - const string& getTestName (); - - friend std::ostream& operator<< ( - std::ostream& s, - TestObjName& tcName - ) - { - s << tcName.getTestName(); - return s; - } - - private: - string testCaseName; - - struct TCNStr { - static constexpr const char * NVTN = - "Not a valid test name!"; - }; - }; - - class TestCase { - - public: - enum TestCaseOutCompareOptions { - CONTAINS, - EXACT - }; - - TestCase ( - TestObjName&& testName, - function test, - bool testPassedMessage = true, - bool captureOut = false, - bool captureLog = false, - bool captureErr = false, - TestCaseOutCompareOptions opt = CONTAINS - ); - - TestCase (TestCase& o); - TestCase (TestCase&& o); - - TestCase& operator= (TestCase& rhs); - TestCase& operator= (TestCase&& rhs); - - ~TestCase (); - - void setNotifyPassed (bool); - void outCompareOption (TestCaseOutCompareOptions opt); - void clearStdoutCapture (); - void clearLogCapture (); - void clearStderrCapture (); - bool checkStdout (string against); - bool checkLog (string against); - bool checkStderr (string against); - bool go (); - long long getLastRuntime (); - - private: - bool notifyTestPassed; - bool pass; - long long lastRunTime; - - TestObjName testName; - function test; - - TestCaseOutCompareOptions option; - - void captureStdout (); - void captureClog (); - void captureStdErr (); - void logTestFailure (string); - void runTest (); - bool checkOutput (TestCaseOutCompareOptions opt, string source, - string against); - - static atomic_int stdoutCaptureCasesConstructed; - static atomic_int logCaptureCasesConstructed; - static atomic_int stderrCaptureCasesConstructed; - static atomic_int stdoutCaptureCasesDestroyed; - static atomic_int logCaptureCasesDestroyed; - static atomic_int stderrCaptureCasesDestroyed; - - static unique_ptr stdoutBuffer; - static unique_ptr clogBuffer; - static unique_ptr stderrBuffer; - static unique_ptr stdoutOriginal; - static unique_ptr clogOriginal; - static unique_ptr stderrOriginal; - - template - static nanoseconds duration (F func, Args&&... args) - { - auto start = system_clock::now(); - func(forward(args)...); - return duration_cast( - system_clock::now() - start - ); - } - - struct TCStr { - static constexpr const char * APOS = "'"; - static constexpr const char * FAIL = " failed! ("; - static constexpr const char * NCONTAIN = - " does not contain "; - static constexpr const char * NEQUIV = - " is not equivalent to "; - static constexpr const char * PASS = " passed! ("; - static constexpr const char * REASON = "Reason: "; - static constexpr const char * SEC = "s)"; - static constexpr const char * START_RUN = - "Starting run of test "; - static constexpr const char * TEST_ = "Test "; - static constexpr const char * UNK_CMP_OPT = - "Unknown comparison option! "; - static constexpr const char * UNK_EXC = - "Unknown error occurred in test!"; - static constexpr const char * UNK_OPT = "Unknown option "; - }; - }; - - class TestSuite { - - public: - template - TestSuite (TestObjName&& suiteName, - typename enable_if::type) - { - this->testPassedMessage = true; - this->setSuiteName(move(suiteName)); - this->tests = vector(); - } - - template - TestSuite (TestObjName&& suiteName, TestType ...tests) { - this->testPassedMessage = true; - this->setSuiteName(move(suiteName)); - this->tests = vector(); - - this->addTests(tests...); - } - - template - void addTest (T&& test) { - this->tests.emplace_back( - std::get<0>(test), - std::get<1>(test), - this->testPassedMessage - ); - } - - template - typename enable_if::type - inline addTests () { } - - template - void addTests (Test test, OtherTests ...tests) { - addTest(move(test)); - addTests(tests...); - } - - template - static T getTestObject (ConstructionArgs ...args) { - return T(args...); - } - - void setSuiteName (TestObjName&& testSuiteName); - - template - static void assertEquals ( - T1 expected, T2 actual, - string failureMessage = "Arguments are not equivalent!" - ) - { - if (expected != actual) { - stringstream err; - - err << "Equivalence assertion failed!" << endl; - err << failureMessage << endl; - err << "Expected: <" << expected << ">" << endl; - err << "Actual: <" << actual << ">" << endl; - - throw TestFailedException(err.str()); - } - } - - template - static void assertNotEquals ( - T1 expected, T2 actual, - string failureMessage = "Arguments are equivalent!" - ) - { - if (expected == actual) { - stringstream err; - - err << "Non-Equivalence assertion failed!" << endl; - err << failureMessage << endl; - err << "Expected: <" << expected << ">" << endl; - err << "Actual: <" << actual << ">" << endl; - - throw TestFailedException(err.str()); - } - } - - template - static void assertNull ( - T ptr, - string failureMessage = "Object is not null!" - ) - { - bool null = ptr == nullptr; - - if (!null) { - stringstream err; - - err << "Null assertion failed!" << endl; - err << failureMessage << endl; - - throw TestFailedException(err.str()); - } - } - - template - static void assertNotNull ( - T ptr, - string failureMessage = "Object is null!" - ) - { - bool notNull = ptr != nullptr; - - if (!notNull) { - stringstream err; - - err << "Not Null assertion failed!" << endl; - err << failureMessage << endl; - - throw TestFailedException(err.str()); - } - } - - static void assertThrows ( - function shouldThrow, - string failureMessage = - "Should have thrown something!" - ); - - static void assertNoThrows ( - function shouldNotThrow, - string failureMessage = - "Should not have thrown anything!" - ); - - static void assertTrue ( - bool condition, - string failureMessage = "Condition is false!" - ); - - static void assertFalse ( - bool condition, - string failureMessage = "Condition is true!" - ); - - static void fail ( - string failureMessage = "Forced test failure!" - ); - - void enableTestPassedMessage (); - void disableTestPassedMessage (); - - unsigned getLastRunFailCount (); - - void run (); - - private: - bool testPassedMessage; - bool lastRunSucceeded; - unsigned lastRunSuccessCount; - unsigned lastRunFailCount; - unsigned long long totalRuntime; - - TestObjName suiteName; - vector tests; - }; -} +#ifndef TESTCPP_AGGREGATE_ +#define TESTCPP_AGGREGATE_ + +#include "internal/TestCPPAssertions.h" +#include "internal/TestCPPCommon.h" +#include "internal/TestCPPExceptions.h" +#include "internal/TestCPPTestCase.h" +#include "internal/TestCPPTestSuite.h" +#include "internal/TestCPPUtil.h" #endif diff --git a/include/internal/TestCPPAssertions.h b/include/internal/TestCPPAssertions.h new file mode 100644 index 0000000..517f013 --- /dev/null +++ b/include/internal/TestCPPAssertions.h @@ -0,0 +1,251 @@ +/* +This is free and unencumbered software released into the public domain. + +Anyone is free to copy, modify, publish, use, compile, sell, or +distribute this software, either in source code form or as a compiled +binary, for any purpose, commercial or non-commercial, and by any +means. + +In jurisdictions that recognize copyright laws, the author or authors +of this software dedicate any and all copyright interest in the +software to the public domain. We make this dedication for the benefit +of the public at large and to the detriment of our heirs and +successors. We intend this dedication to be an overt act of +relinquishment in perpetuity of all present and future rights to this +software under copyright law. + +THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, +EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF +MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. +IN NO EVENT SHALL THE AUTHORS BE LIABLE FOR ANY CLAIM, DAMAGES OR +OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, +ARISING FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR +OTHER DEALINGS IN THE SOFTWARE. + +For more information, please refer to + */ + +#ifndef TESTCPP_ASSERTIONS_ +#define TESTCPP_ASSERTIONS_ + +#include "TestCPPExceptions.h" +#include "TestCPPTestCase.h" + +/** + * The base namespace for all TestCPP library code. + */ +namespace TestCPP { + + /** + * @class Assertions + * @author Jonathan Hyry + * @date 11/05/24 + * @file TestCPPAssertions.h + * @brief Contains the TestCPP library's assertions. + * + * Defines some assertions here, where they are templated. + * Declares the rest. + * + * This is the place where the TestCPP Assertions API is defined. + */ + class Assertions { + public: + /** + * @brief Check that something equals something else using the + * built-in operator== for each type. + * @param expected The value that the actual value should be + * equivalent to. + * @param actual The actual value that will be checked against + * the expected value. + * @param failureMessage Failure message that should be logged + * if the assertion fails. This + * defaults to a generic failure + * message related to the assertion + * type. + */ + template + static void assertEquals ( + T1 expected, T2 actual, + string failureMessage = "Arguments are not equivalent!" + ) + { + if (expected != actual) { + stringstream err; + + err << "Equivalence assertion failed!" << endl; + err << failureMessage << endl; + err << "Expected: <" << expected << ">" << endl; + err << "Actual: <" << actual << ">" << endl; + + throw TestFailedException(err.str()); + } + } + + /** + * @brief Check that something is not equivalent to something + * else using the built-in operator== for each type. + * @param expected The value that the actual value should not be + * equivalent to. + * @param actual The actual value that will be checked against + * the expected value. + * @param failureMessage Failure message that should be logged + * if the assertion fails. This + * defaults to a generic failure + * message related to the assertion + * type. + */ + template + static void assertNotEquals ( + T1 expected, T2 actual, + string failureMessage = "Arguments are equivalent!" + ) + { + if (expected == actual) { + stringstream err; + + err << "Non-Equivalence assertion failed!" << endl; + err << failureMessage << endl; + err << "Expected: <" << expected << ">" << endl; + err << "Actual: <" << actual << ">" << endl; + + throw TestFailedException(err.str()); + } + } + + /** + * @brief Check that a pointer is null. + * @param ptr The pointer to check. + * @param failureMessage Failure message that should be logged + * if the assertion fails. This + * defaults to a generic failure + * message related to the assertion + * type. + */ + template + static void assertNull ( + T ptr, + string failureMessage = "Object is not null!" + ) + { + bool null = ptr == nullptr; + + if (!null) { + stringstream err; + + err << "Null assertion failed!" << endl; + err << failureMessage << endl; + + throw TestFailedException(err.str()); + } + } + + /** + * @brief Check that a pointer is non-null. + * @param ptr The pointer to check. + * @param failureMessage Failure message that should be logged + * if the assertion fails. This + * defaults to a generic failure + * message related to the assertion + * type. + */ + template + static void assertNotNull ( + T ptr, + string failureMessage = "Object is null!" + ) + { + bool notNull = ptr != nullptr; + + if (!notNull) { + stringstream err; + + err << "Not Null assertion failed!" << endl; + err << failureMessage << endl; + + throw TestFailedException(err.str()); + } + } + + /** + * @brief Verify that a function throws something. + * @param shouldThrow The function to check, to ensure that it + * throws something. + * @param failureMessage Failure message that should be logged + * if the assertion fails. This + * defaults to a generic failure + * message related to the assertion + * type. + */ + static void assertThrows ( + function shouldThrow, + string failureMessage = + "Should have thrown something!" + ); + + /** + * @brief Verify that a function does not throw something. + * @param shouldThrow The function to check, to ensure that it + * does not throw something. + * @param failureMessage Failure message that should be logged + * if the assertion fails. This + * defaults to a generic failure + * message related to the assertion + * type. + */ + static void assertNoThrows ( + function shouldNotThrow, + string failureMessage = + "Should not have thrown anything!" + ); + + /** + * @brief Verify that a logical condition is true. + * @param condition The result of the logical condition to + * check. + * @param failureMessage Failure message that should be logged + * if the assertion fails. This + * defaults to a generic failure + * message related to the assertion + * type. + */ + static void assertTrue ( + bool condition, + string failureMessage = "Condition is false!" + ); + + /** + * @brief Verify that a logical condition is not true. + * @param condition The result of the logical condition to + * check. + * @param failureMessage Failure message that should be logged + * if the assertion fails. This + * defaults to a generic failure + * message related to the assertion + * type. + */ + static void assertFalse ( + bool condition, + string failureMessage = "Condition is true!" + ); + + /** + * @brief Force a test to fail. + * @param failureMessage Failure message that should be logged + * if the assertion fails. This + * defaults to a generic failure + * message related to the assertion + * type. + * + * Useful in certain situations when: + * - Generating code for test cases, to ensure that skeletons + * fail by default. + * - Forcing test failure in certain circumstances where there + * is nothing to assert but a certain code path is taken. + */ + static void fail ( + string failureMessage = "Forced test failure!" + ); + }; +} + +#endif diff --git a/include/internal/TestCPPCommon.h b/include/internal/TestCPPCommon.h new file mode 100644 index 0000000..5747719 --- /dev/null +++ b/include/internal/TestCPPCommon.h @@ -0,0 +1,106 @@ +/* +This is free and unencumbered software released into the public domain. + +Anyone is free to copy, modify, publish, use, compile, sell, or +distribute this software, either in source code form or as a compiled +binary, for any purpose, commercial or non-commercial, and by any +means. + +In jurisdictions that recognize copyright laws, the author or authors +of this software dedicate any and all copyright interest in the +software to the public domain. We make this dedication for the benefit +of the public at large and to the detriment of our heirs and +successors. We intend this dedication to be an overt act of +relinquishment in perpetuity of all present and future rights to this +software under copyright law. + +THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, +EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF +MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. +IN NO EVENT SHALL THE AUTHORS BE LIABLE FOR ANY CLAIM, DAMAGES OR +OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, +ARISING FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR +OTHER DEALINGS IN THE SOFTWARE. + +For more information, please refer to + */ + +#ifndef TESTCPP_COMMON_ +#define TESTCPP_COMMON_ + +/** + * The base namespace for all TestCPP library code. + */ +namespace TestCPP { + + /** + * @class TestCPPCommon + * @author Jonathan Hyry + * @date 03/05/24 + * @file TestCPP.h + * @brief Categories of common objects that are used. + */ + class TestCPPCommon { + public: + /** + * @class Nums + * @author Jonathan Hyry + * @date 03/05/24 + * @file TestCPP.h + * @brief Common magic numbers used by the library. + */ + struct Nums { + static constexpr const int TIME_PRECISION = 4; + static constexpr const double NANOS_IN_SEC = 1000000000.0; + }; + + /** + * @class Strings + * @author Jonathan Hyry + * @date 03/05/24 + * @file TestCPP.h + * @brief Common literal strings used by the library. + * + * As struct member name prefixes and postfixes, underscores + * denote leading and trailing spaces, respectively. + */ + struct Strings { + static constexpr const char * ALL_ = "All "; + static constexpr const char * APOS = "'"; + static constexpr const char * _FAIL_ = " failed! "; + static constexpr const char * FINISHED_SUITE_ = + "Finished running test suite "; + static constexpr const char * FWSL = "/"; + static constexpr const char * _IN_ABOUT_ = " in about "; + static constexpr const char * _NCONTAIN_ = + " does not contain "; + static constexpr const char * _NEQUIV_ = + " is not equivalent to "; + static constexpr const char * NTR = "No tests to run!"; + static constexpr const char * NVTN = + "Not a valid test name!"; + static constexpr const char * PARENL = "("; + static constexpr const char * PARENR = ")"; + static constexpr const char * _PASS_ = " passed! "; + static constexpr const char * _PASSED = " passed"; + static constexpr const char * REASON_ = "Reason: "; + static constexpr const char * SEC = "s"; + static constexpr const char * SP = " "; + static constexpr const char * START_RUN = + "Starting run of test "; + static constexpr const char * SUITE_ = "Suite "; + static constexpr const char * _SUITE_TESTS_PASSED = + " suite tests passed!"; + static constexpr const char * TEST_ = "Test "; + static constexpr const char * TEST_EXC_ = + "Exception occurred during test run: "; + static constexpr const char * UNK_CMP_OPT_ = + "Unknown comparison option! "; + static constexpr const char * UNK_EXC = + "Unknown error occurred in test!"; + static constexpr const char * UNK_OPT_ = "Unknown option "; + }; + }; +} + +#endif \ No newline at end of file diff --git a/include/internal/TestCPPExceptions.h b/include/internal/TestCPPExceptions.h new file mode 100644 index 0000000..ec4bb64 --- /dev/null +++ b/include/internal/TestCPPExceptions.h @@ -0,0 +1,102 @@ +/* +This is free and unencumbered software released into the public domain. + +Anyone is free to copy, modify, publish, use, compile, sell, or +distribute this software, either in source code form or as a compiled +binary, for any purpose, commercial or non-commercial, and by any +means. + +In jurisdictions that recognize copyright laws, the author or authors +of this software dedicate any and all copyright interest in the +software to the public domain. We make this dedication for the benefit +of the public at large and to the detriment of our heirs and +successors. We intend this dedication to be an overt act of +relinquishment in perpetuity of all present and future rights to this +software under copyright law. + +THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, +EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF +MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. +IN NO EVENT SHALL THE AUTHORS BE LIABLE FOR ANY CLAIM, DAMAGES OR +OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, +ARISING FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR +OTHER DEALINGS IN THE SOFTWARE. + +For more information, please refer to + */ + +#ifndef TESTCPP_EXCEPTIONS_ +#define TESTCPP_EXCEPTIONS_ + +#include +#include + +using std::runtime_error; +using std::string; + +/** + * The base namespace for all TestCPP library code. + */ +namespace TestCPP { + + /** + * @class TestCPPException + * @author Jonathan Hyry + * @date 03/05/24 + * @file TestCPP.h + * @brief Provides a custom base exception for failure conditions. + * + * There are two types of failures in the library: + * - Errors caused by bugs, system problems, etc. + * - Test failures + * + * This type, when used directly, is for representing the first type + * of failure: errors in the library caused by bugs or other + * problems. + */ + class TestCPPException : public runtime_error { + public: + /** + * Construct an exception of this type with a string literal for + * its failure message. + */ + TestCPPException (const char * msg); + + /** + * Construct an exception of this type with a string object for + * its failure message. + */ + TestCPPException (string&& msg); + }; + + /** + * @class TestFailedException + * @author Jonathan Hyry + * @date 03/05/24 + * @file TestCPP.h + * @brief Provides an exception type specifically for test failures. + * + * There are two types of failures in the library: + * - Errors caused by bugs, system problems, etc. + * - Test failures + * + * This type is for representing the second type of failure: test + * failures. + */ + class TestFailedException : public TestCPPException { + public: + /** + * Construct an exception of this type with a string literal for + * its failure message. + */ + TestFailedException (const char * msg); + + /** + * Construct an exception of this type with a string literal for + * its failure message. + */ + TestFailedException (string&& msg); + }; +} + +#endif diff --git a/include/internal/TestCPPTestCase.h b/include/internal/TestCPPTestCase.h new file mode 100644 index 0000000..cc2c3f5 --- /dev/null +++ b/include/internal/TestCPPTestCase.h @@ -0,0 +1,276 @@ +/* +This is free and unencumbered software released into the public domain. + +Anyone is free to copy, modify, publish, use, compile, sell, or +distribute this software, either in source code form or as a compiled +binary, for any purpose, commercial or non-commercial, and by any +means. + +In jurisdictions that recognize copyright laws, the author or authors +of this software dedicate any and all copyright interest in the +software to the public domain. We make this dedication for the benefit +of the public at large and to the detriment of our heirs and +successors. We intend this dedication to be an overt act of +relinquishment in perpetuity of all present and future rights to this +software under copyright law. + +THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, +EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF +MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. +IN NO EVENT SHALL THE AUTHORS BE LIABLE FOR ANY CLAIM, DAMAGES OR +OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, +ARISING FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR +OTHER DEALINGS IN THE SOFTWARE. + +For more information, please refer to + */ + +//Original author: Jonathan Hyry CSU-Fullerton SECS 6896-02 Fall 2014/15 + +#ifndef TESTCPP_TESTCASE_TYPE_ +#define TESTCPP_TESTCASE_TYPE_ + +#include +#include +#include +#include +#include +#include +#include +#include +#include + +#include "TestCPPUtil.h" + +using std::atomic_int; +using std::chrono::nanoseconds; +using std::chrono::system_clock; +using std::chrono::duration_cast; +using std::enable_if; +using std::endl; +using std::forward; +using std::function; +using std::move; +using std::runtime_error; +using std::string; +using std::streambuf; +using std::stringstream; +using std::unique_ptr; +using std::vector; + +/** + * The base namespace for all TestCPP library code. + */ +namespace TestCPP { + + /** + * @class TestCase + * @author Jonathan Hyry + * @date 03/05/24 + * @file TestCPP.h + * @brief Provides an interface for implementing tests. + * + * TestCase is one of three core types that define the TestCPP API. + * TestCase provides faculties for defining and controlling tests + * and the stdout/stderr/clog environment. + * TestCase objects are defined by the following elements: + * - their name + * - the code that will be run that defines what the test does + * and how it does it + * - whether to log test passage + * - output capturing flags + * - how captured output is analyzed (exact or contains) + */ + class TestCase { + + public: + /** + * Provides a specification for how to analyze captured stdout, + * stderr, and clog data. + */ + enum TestCaseOutCompareOptions { + CONTAINS, + EXACT + }; + + /** + * Instantiate and define a test case. + * All parameters are optional other than the test name and the + * test function. + * Optional parameters have the following default values: + * - testPassedMessage defaults to true, so by default test + * cases will emit a message when the pass. + * - captureOut defaults to false, the test case by default + * does not capture stdout data. + * - captureLog defaults to false, the test case by default + * does not capture data streamed to clog. + * - captureErr defaults to false, the test case by default + * does not capture stderr data. + * - opt defaults to CONTAINS, so when output is captured and + * checked for something, the default technique is to check + * that the output of a given stream contains a given + * string. + */ + TestCase ( + TestObjName&& testName, + function test, + bool testPassedMessage = true, + bool captureOut = false, + bool captureLog = false, + bool captureErr = false, + TestCaseOutCompareOptions opt = CONTAINS + ); + + /** + * Construct a TestCase by copying it from another TestCase. + */ + TestCase (TestCase& o); + + /** + * Construct a TestCase by moving all data from another + * TestCase. + */ + TestCase (TestCase&& o); + + /** + * @brief Copy a TestCase into another TestCase. + * @param rhs The test case to copy from. + */ + TestCase& operator= (TestCase& rhs); + + /** + * @brief Move a TestCase into another TestCase. + */ + TestCase& operator= (TestCase&& rhs); + + /** + * @brief Destroy a TestCase object. + */ + ~TestCase (); + + /** + * @brief Set whether to notify in std::clog when a test passes. + * @param shouldNotify Will notify on test passed if true. + */ + void setNotifyPassed (bool shouldNotify); + + /** + * @brief Set the output comparison mode. + * @param opt Accepts specified mode from the referenced enum. + * + * If this is called with an option specified that is different + * from what is set for this test case already, subsequent + * calls to any of the output check functions will use the + * updated mode that is specified in the call to this function. + */ + void outCompareOption (TestCaseOutCompareOptions opt); + + /** + * @brief Clears the captured output from stdout. + * + * This can be used for checking sections of output based on + * test configuration. + */ + void clearStdoutCapture (); + + /** + * @brief Clears the captured output from std::clog. + * + * This can be used for checking sections of output based on + * test configuration. + */ + void clearLogCapture (); + + /** + * @brief Clears the captured output from stderr. + * + * This can be used for checking sections of output based on + * test configuration. + */ + void clearStderrCapture (); + + /** + * @brief Check the argument against what is captured from + * stdout using the configured comparison mode. + * @param against The value to check the captured output against + * @return True if the argument results in a successful check + * using the configured comparison mode. + */ + bool checkStdout (string against); + + /** + * @brief Check the argument against what is captured from + * std::clog using the configured comparison mode. + * @param against The value to check the captured output against + * @return True if the argument results in a successful check + * using the configured comparison mode. + */ + bool checkLog (string against); + + /** + * @brief Check the argument against what is captured from + * stderr using the configured comparison mode. + * @param against The value to check the captured output against + * @return True if the argument results in a successful check + * using the configured comparison mode, false + * otherwise. + */ + bool checkStderr (string against); + + /** + * @brief Run the test case. + * @return True if the test ran successfully, false otherwise. + */ + bool go (); + + /** + * @brief Returns the duration of the last run in nanoseconds. + * @return The duration of the last run in nanoseconds. + */ + long long getLastRuntime (); + + private: + bool notifyTestPassed; + bool pass; + long long lastRunTime; + + TestObjName testName; + function test; + + TestCaseOutCompareOptions option; + + void captureStdout (); + void captureClog (); + void captureStdErr (); + void logTestFailure (string); + void runTest (); + bool checkOutput (TestCaseOutCompareOptions opt, string source, + string against); + + static atomic_int stdoutCaptureCasesConstructed; + static atomic_int logCaptureCasesConstructed; + static atomic_int stderrCaptureCasesConstructed; + static atomic_int stdoutCaptureCasesDestroyed; + static atomic_int logCaptureCasesDestroyed; + static atomic_int stderrCaptureCasesDestroyed; + + static unique_ptr stdoutBuffer; + static unique_ptr clogBuffer; + static unique_ptr stderrBuffer; + static unique_ptr stdoutOriginal; + static unique_ptr clogOriginal; + static unique_ptr stderrOriginal; + + template + static nanoseconds duration (F func, Args&&... args) + { + auto start = system_clock::now(); + func(forward(args)...); + return duration_cast( + system_clock::now() - start + ); + } + }; +} + +#endif diff --git a/include/internal/TestCPPTestSuite.h b/include/internal/TestCPPTestSuite.h new file mode 100644 index 0000000..9945227 --- /dev/null +++ b/include/internal/TestCPPTestSuite.h @@ -0,0 +1,190 @@ +/* +This is free and unencumbered software released into the public domain. + +Anyone is free to copy, modify, publish, use, compile, sell, or +distribute this software, either in source code form or as a compiled +binary, for any purpose, commercial or non-commercial, and by any +means. + +In jurisdictions that recognize copyright laws, the author or authors +of this software dedicate any and all copyright interest in the +software to the public domain. We make this dedication for the benefit +of the public at large and to the detriment of our heirs and +successors. We intend this dedication to be an overt act of +relinquishment in perpetuity of all present and future rights to this +software under copyright law. + +THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, +EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF +MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. +IN NO EVENT SHALL THE AUTHORS BE LIABLE FOR ANY CLAIM, DAMAGES OR +OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, +ARISING FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR +OTHER DEALINGS IN THE SOFTWARE. + +For more information, please refer to + */ + +//Original author: Jonathan Hyry CSU-Fullerton SECS 6896-02 Fall 2014/15 + +#ifndef TESTCPP_TESTSUITE_TYPE_ +#define TESTCPP_TESTSUITE_TYPE_ + +#include +#include +#include +#include +#include +#include +#include +#include +#include + +using std::atomic_int; +using std::chrono::nanoseconds; +using std::chrono::system_clock; +using std::chrono::duration_cast; +using std::enable_if; +using std::endl; +using std::forward; +using std::function; +using std::move; +using std::runtime_error; +using std::string; +using std::streambuf; +using std::stringstream; +using std::unique_ptr; +using std::vector; + +/** + * The base namespace for all TestCPP library code. + */ +namespace TestCPP { + + /** + * @class TestSuite + * @author Jonathan Hyry + * @date 05/05/24 + * @file TestCPP.h + * @brief Defines a container for a collection of TestCase objects. + * + * + */ + class TestSuite { + + public: + /** + * @brief Specialization for constructing a test suite with no + * tests. + */ + template + TestSuite (TestObjName&& suiteName, + typename enable_if::type) + { + this->testPassedMessage = true; + this->setSuiteName(move(suiteName)); + this->tests = vector(); + } + + /** + * @brief The general case for constructing a test suite with + * tests. + */ + template + TestSuite (TestObjName&& suiteName, TestType ...tests) { + this->testPassedMessage = true; + this->setSuiteName(move(suiteName)); + this->tests = vector(); + + this->addTests(tests...); + } + + /** + * @brief Add a test to this test suite. + * + * The test should be defined as a tuple with 2 elements to use + * this. The first element is the test name, and the second + * element is the test function that defines the test. + */ + template + void addTest (T&& test) { + this->tests.emplace_back( + std::get<0>(test), + std::get<1>(test), + this->testPassedMessage + ); + } + + /** + * @brief Specialization to handle when someone tries to call + * the template function with no tests. + * + * This is a noop. + */ + template + typename enable_if::type + inline addTests () { } + + /** + * @brief Add one or more tests at once to the test suite. + * @param test The first test to add. + */ + template + void addTests (Test test, OtherTests ...tests) { + addTest(move(test)); + addTests(tests...); + } + + /** + * @brief Get the TestCase object that is associated with the + * given test case construction arguments. + * @return The TestCase with the given construction arguments. + */ + template + static T getTestObject (ConstructionArgs ...args) { + return T(args...); + } + + /** + * @brief Sets the name of this test suite. + */ + void setSuiteName (TestObjName&& testSuiteName); + + /** + * @brief After called, all tests in the suite will emit a + * message if they pass. + */ + void enableTestPassedMessage (); + + /** + * @brief After called, all tests in the suite will not emit a + * message if they pass. + */ + void disableTestPassedMessage (); + + /** + * @brief Calculate the total number of tests in the suite that + * failed after the last suite run. + * @return The total number of tests that failed in the last + * suite run. + */ + unsigned getLastRunFailCount (); + + /** + * @brief Run all tests in the test suite. + */ + void run (); + + private: + bool testPassedMessage; + bool lastRunSucceeded; + unsigned lastRunSuccessCount; + unsigned lastRunFailCount; + unsigned long long totalRuntime; + + TestObjName suiteName; + vector tests; + }; +} + +#endif diff --git a/include/internal/TestCPPUtil.h b/include/internal/TestCPPUtil.h new file mode 100644 index 0000000..839625a --- /dev/null +++ b/include/internal/TestCPPUtil.h @@ -0,0 +1,137 @@ +/* +This is free and unencumbered software released into the public domain. + +Anyone is free to copy, modify, publish, use, compile, sell, or +distribute this software, either in source code form or as a compiled +binary, for any purpose, commercial or non-commercial, and by any +means. + +In jurisdictions that recognize copyright laws, the author or authors +of this software dedicate any and all copyright interest in the +software to the public domain. We make this dedication for the benefit +of the public at large and to the detriment of our heirs and +successors. We intend this dedication to be an overt act of +relinquishment in perpetuity of all present and future rights to this +software under copyright law. + +THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, +EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF +MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. +IN NO EVENT SHALL THE AUTHORS BE LIABLE FOR ANY CLAIM, DAMAGES OR +OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, +ARISING FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR +OTHER DEALINGS IN THE SOFTWARE. + +For more information, please refer to + */ + +#ifndef TESTCPP_UTIL_ +#define TESTCPP_UTIL_ + +#include + +using std::string; + +/** + * The base namespace for all TestCPP library code. + */ +namespace TestCPP { + + /** + * @class TestObjName + * @author Jonathan Hyry + * @date 03/05/24 + * @file TestCPP.h + * @brief Provides a null-safe name for test objects. + * + * Both TestCase and TestSuite objects use this for their name to + * protect against nullptr/NULL being used to create a std::string + * and name the test with that non-null-char-*-based std::string. + */ + class TestObjName { + public: + /** + * @brief Construct an empty Test Object Name object. + * @return The empty TestObjName. + * + * This should never be called by user code but is required for + * the code to compile. + */ + TestObjName () = default; + + /** + * @brief Construct a Test Object Name object with a string + * literal or existing const char *. + * @return The TestObjName, where it is verified that the name + * used to construct it was not null. + */ + TestObjName (const char* name); + + /** + * @brief Get the encapsulated name for the TestCPP object that + * holds this object. + * @return The name of the TestCPP object that this object + * names. + */ + const string& getName (); + + /** + * @brief Output the test object name to the specified stream. + * @param s The stream to output to. + * @param tcName The test object name object. + * @return The stream for chaining. + */ + friend std::ostream& operator<< ( + std::ostream& s, + TestObjName& tcName + ) + { + s << tcName.getName(); + return s; + } + + private: + string testCaseName; + }; + + /** + * The namespace for general TestCPP utility code. + */ + namespace Util { + + /** + * @brief Log a message that will only be output when debug + * logging is enabled. + * + * @param message The debug message to log. + * @param omitNewline Whether or not to end the log with a + * newline character. + * Defaults to false. + */ + void debugLog (const string& message, bool omitNewline = false); + + /** + * @brief Check if a std::string is contained within another + * std::string; this exists since we're not using C++26 + * here. + * + * @param source The base std::string to check within. + * @param contains Checks the base std::string to see if it + * contains this std::string. + * + * @return True if source includes contains in whole, false + * otherwise. + */ + bool stringContains (const string& source, + const string& contains); + + /** + * @brief Safely converts unsigned integer values to signed. + * @param toCast The unsigned value to convert. + * @return The signed value equivalent to the unsigned value. + */ + int unsignedToSigned(unsigned toCast); + } +} + +#endif diff --git a/src/TestCPPAssertions.cpp b/src/TestCPPAssertions.cpp new file mode 100644 index 0000000..57344f9 --- /dev/null +++ b/src/TestCPPAssertions.cpp @@ -0,0 +1,125 @@ +/* +This is free and unencumbered software released into the public domain. + +Anyone is free to copy, modify, publish, use, compile, sell, or +distribute this software, either in source code form or as a compiled +binary, for any purpose, commercial or non-commercial, and by any +means. + +In jurisdictions that recognize copyright laws, the author or authors +of this software dedicate any and all copyright interest in the +software to the public domain. We make this dedication for the benefit +of the public at large and to the detriment of our heirs and +successors. We intend this dedication to be an overt act of +relinquishment in perpetuity of all present and future rights to this +software under copyright law. + +THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, +EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF +MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. +IN NO EVENT SHALL THE AUTHORS BE LIABLE FOR ANY CLAIM, DAMAGES OR +OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, +ARISING FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR +OTHER DEALINGS IN THE SOFTWARE. + +For more information, please refer to + */ + +#include "internal/TestCPPAssertions.h" +#include "internal/TestCPPTestCase.h" + +using std::clog; +using std::current_exception; +using std::endl; +using std::exception; +using std::exception_ptr; +using std::function; +using std::move; +using std::rethrow_exception; +using std::string; +using std::stringstream; + +namespace TestCPP { + + void Assertions::assertThrows ( + function shouldThrow, + string failureMessage + ) + { + try { + shouldThrow(); + } + catch (...) { + exception_ptr eptr = current_exception(); + + if (eptr) { + try { + rethrow_exception(eptr); + } + catch (const exception& e) { + clog << "assertThrows caught exception: " + << TestFailedException(e.what()).what() + << endl; + } + } + else { + clog << "Something was thrown, not sure what." << endl + << "This satisfies the assertion, so no failure is" + << " present. " + << TestFailedException("Unknown thrown object"). + what(); + } + + return; + } + + throw TestFailedException(move(failureMessage)); + } + + void Assertions::assertNoThrows ( + function shouldNotThrow, + string failureMessage + ) + { + try { + shouldNotThrow(); + } + catch (...) { + throw TestFailedException(move(failureMessage)); + } + } + + void Assertions::assertTrue ( + bool condition, + string failureMessage + ) + { + if (!condition) { + stringstream err; + + err << "Boolean Truth assertion failed!" << endl; + err << failureMessage << endl; + + throw TestFailedException(err.str()); + } + } + + void Assertions::assertFalse ( + bool condition, + string failureMessage + ) + { + if (condition) { + stringstream err; + + err << "Boolean False assertion failed!" << endl; + err << failureMessage << endl; + + throw TestFailedException(err.str()); + } + } + + void Assertions::fail(string failureMessage) { + throw TestFailedException(move(failureMessage)); + } +} \ No newline at end of file diff --git a/include/TestCPPUtil.h b/src/TestCPPExceptions.cpp similarity index 56% rename from include/TestCPPUtil.h rename to src/TestCPPExceptions.cpp index cae25cf..78d6ae0 100644 --- a/include/TestCPPUtil.h +++ b/src/TestCPPExceptions.cpp @@ -25,20 +25,49 @@ OTHER DEALINGS IN THE SOFTWARE. For more information, please refer to */ -#ifndef TESTCPP_UTIL_ -#define TESTCPP_UTIL_ +#include -#include +#include "internal/TestCPPExceptions.h" +#ifdef TESTCPP_STACKTRACE_ENABLED +#include +#endif + +using std::clog; +using std::move; using std::string; +using std::runtime_error; namespace TestCPP { - namespace Util { - void debugLog (const string& message, bool omitNewline = false); - bool stringContains (const string& source, - const string& contains); - int unsignedToSigned(unsigned toCast); + + TestCPPException::TestCPPException (const char * msg) : + runtime_error(msg) + { +#ifdef TESTCPP_STACKTRACE_ENABLED + clog << boost::stacktrace::stacktrace(); +#endif + } + TestCPPException::TestCPPException (string&& msg) : + runtime_error(move(msg)) + { +#ifdef TESTCPP_STACKTRACE_ENABLED + clog << boost::stacktrace::stacktrace(); +#endif } -} + TestFailedException::TestFailedException (const char * msg) : + TestCPPException(msg) + { +#ifdef TESTCPP_STACKTRACE_ENABLED + clog << boost::stacktrace::stacktrace(); #endif + } + + TestFailedException::TestFailedException (string&& msg) : + TestCPPException(move(msg)) + { +#ifdef TESTCPP_STACKTRACE_ENABLED + clog << boost::stacktrace::stacktrace(); +#endif + } +} diff --git a/src/TestCPP.cpp b/src/TestCPPTestCase.cpp similarity index 61% rename from src/TestCPP.cpp rename to src/TestCPPTestCase.cpp index 6e9d9bf..95ca948 100644 --- a/src/TestCPP.cpp +++ b/src/TestCPPTestCase.cpp @@ -27,7 +27,9 @@ For more information, please refer to //Original author: Jonathan Hyry CSU-Fullerton SECS 6896-02 Fall 2014/15 -#include "TestCPP.h" +#include "internal/TestCPPCommon.h" +#include "internal/TestCPPExceptions.h" +#include "internal/TestCPPTestCase.h" #ifdef TESTCPP_STACKTRACE_ENABLED #include @@ -35,16 +37,14 @@ For more information, please refer to #include -#include "TestCPPUtil.h" +#include "internal/TestCPPUtil.h" using TestCPP::Util::debugLog; using std::cerr; using std::clog; using std::cout; -using std::current_exception; using std::endl; using std::exception; -using std::exception_ptr; using std::fixed; using std::function; using std::invalid_argument; @@ -56,50 +56,10 @@ using std::setprecision; using std::string; using std::tuple; -namespace TestCPP { - TestCPPException::TestCPPException (const char * msg) : - runtime_error(msg) - { -#ifdef TESTCPP_STACKTRACE_ENABLED - clog << boost::stacktrace::stacktrace(); -#endif - } - TestCPPException::TestCPPException (string&& msg) : - runtime_error(move(msg)) - { -#ifdef TESTCPP_STACKTRACE_ENABLED - clog << boost::stacktrace::stacktrace(); -#endif - } - - TestFailedException::TestFailedException (const char * msg) : - TestCPPException(msg) - { -#ifdef TESTCPP_STACKTRACE_ENABLED - clog << boost::stacktrace::stacktrace(); -#endif - } - - TestFailedException::TestFailedException (string&& msg) : - TestCPPException(move(msg)) - { -#ifdef TESTCPP_STACKTRACE_ENABLED - clog << boost::stacktrace::stacktrace(); -#endif - } - - TestObjName::TestObjName (const char* name) { - if (name) { - this->testCaseName = name; - } - else { - throw TestCPPException(TCNStr::NVTN); - } - } +using TCPPNum = TestCPP::TestCPPCommon::Nums; +using TCPPStr = TestCPP::TestCPPCommon::Strings; - const string& TestObjName::getTestName () { - return this->testCaseName; - } +namespace TestCPP { atomic_int TestCase::stdoutCaptureCasesConstructed; atomic_int TestCase::logCaptureCasesConstructed; @@ -230,22 +190,28 @@ namespace TestCPP { void TestCase::logTestFailure (string reason) { clog << fixed; - clog << setprecision(4); - clog << TCStr::TEST_ << this->testName << TCStr::FAIL - << static_cast(this->lastRunTime) - /1000000000.0 << TCStr::SEC << endl; - clog << TCStr::REASON << reason << endl; + clog << setprecision(TCPPNum::TIME_PRECISION); + clog << TCPPStr::TEST_ << this->testName << TCPPStr::_FAIL_ + << TCPPStr::PARENL + << static_cast(this->lastRunTime)/ + TCPPNum::NANOS_IN_SEC + << TCPPStr::SEC << TCPPStr::PARENR + << endl; + clog << TCPPStr::REASON_ << reason << endl; } void TestCase::runTest () { - clog << TCStr::START_RUN << this->testName << endl; + clog << TCPPStr::START_RUN << this->testName << endl; this->lastRunTime = duration(this->test).count(); if (this->notifyTestPassed) { clog << fixed; - clog << setprecision(4); - clog << TCStr::TEST_ << this->testName << TCStr::PASS - << static_cast(this->lastRunTime) - /1000000000.0 << TCStr::SEC << endl; + clog << setprecision(TCPPNum::TIME_PRECISION); + clog << TCPPStr::TEST_ << this->testName << TCPPStr::_PASS_ + << TCPPStr::PARENL + << static_cast(this->lastRunTime)/ + TCPPNum::NANOS_IN_SEC + << TCPPStr::SEC << TCPPStr::PARENR + << endl; } this->pass = true; } @@ -269,7 +235,7 @@ namespace TestCPP { } catch (...) { this->pass = false; - logTestFailure(TCStr::UNK_EXC); + logTestFailure(TCPPStr::UNK_EXC); } return false; @@ -342,7 +308,7 @@ namespace TestCPP { default: stringstream error; - error << TCStr::UNK_OPT << opt; + error << TCPPStr::UNK_OPT_ << opt; throw TestCPPException(error.str()); } } @@ -390,9 +356,9 @@ namespace TestCPP { } else { stringstream nomatch; - nomatch << TCStr::APOS << source << TCStr::APOS; - nomatch << TCStr::NEQUIV << TCStr::APOS; - nomatch << against << TCStr::APOS; + nomatch << TCPPStr::APOS << source << TCPPStr::APOS; + nomatch << TCPPStr::_NEQUIV_ << TCPPStr::APOS; + nomatch << against << TCPPStr::APOS; if (this->clogOriginal != nullptr) { ostream tmp(this->clogOriginal.get()); @@ -412,9 +378,9 @@ namespace TestCPP { } else { stringstream nomatch; - nomatch << TCStr::APOS << source << TCStr::APOS; - nomatch << TCStr::NCONTAIN << TCStr::APOS; - nomatch << against << TCStr::APOS; + nomatch << TCPPStr::APOS << source << TCPPStr::APOS; + nomatch << TCPPStr::_NCONTAIN_ << TCPPStr::APOS; + nomatch << against << TCPPStr::APOS; if (this->clogOriginal != nullptr) { ostream tmp(this->clogOriginal.get()); @@ -430,178 +396,8 @@ namespace TestCPP { default: stringstream re; - re << TCStr::UNK_CMP_OPT << opt; + re << TCPPStr::UNK_CMP_OPT_ << opt; throw TestCPPException(re.str()); } } - - void TestSuite::enableTestPassedMessage () { - this->testPassedMessage = true; - for (TestCase test : this->tests) { - test.setNotifyPassed(true); - } - } - void TestSuite::disableTestPassedMessage () { - this->testPassedMessage = false; - for (TestCase test : this->tests) { - test.setNotifyPassed(false); - } - } - - void TestSuite::setSuiteName (TestObjName&& testSuiteName) { - this->suiteName = move(testSuiteName); - } - - unsigned TestSuite::getLastRunFailCount () { - return this->lastRunFailCount; - } - - void TestSuite::run () { - if (this->tests.size() == 0) { - clog << "No tests to run!" << endl; - return; - } - - this->lastRunSucceeded = true; - this->lastRunFailCount = 0; - this->lastRunSuccessCount = 0; - this->totalRuntime = 0; - - clog << endl - << "Starting to run test suite '" << this->suiteName << "'" - << endl << endl; - - for (TestCase test : this->tests) { - bool testPassed = false; - try { - testPassed = test.go(); - } - catch (exception& e) { - clog << "Exception occurred during test run: " - << e.what() << endl; - } - catch (...) { - cerr << "An unknown error occurred during test run." - << endl; - } - - if (!testPassed && this->lastRunSucceeded) { - this->lastRunFailCount++; - this->lastRunSucceeded = false; - } - else if (!testPassed) { - this->lastRunFailCount++; - } - else { - this->lastRunSuccessCount++; - } - - this->totalRuntime += test.getLastRuntime(); - } - - clog << endl; - - if (this->testPassedMessage && - this->lastRunFailCount == 0) { - clog << "All '" << this->suiteName - << "' suite tests passed!" << endl; - } - - double suiteRuntimeElapsed = static_cast( - this->totalRuntime)/1000000000.0; - - clog << fixed; - clog << setprecision(0); - clog << "Finished running suite '" << this->suiteName << "' in " - << suiteRuntimeElapsed << "s ("<< this->lastRunSuccessCount - << "/" << this->tests.size() << " passed)" << endl; - clog << endl; - } - - template<> - void TestSuite::addTest (TestCase&& test) { - this->tests.emplace_back(test); - } - - void TestSuite::assertThrows ( - function shouldThrow, - string failureMessage - ) - { - try { - shouldThrow(); - } - catch (...) { - exception_ptr eptr = current_exception(); - - if (eptr) { - try { - rethrow_exception(eptr); - } - catch (const exception& e) { - clog << "assertThrows caught exception: " - << TestFailedException(e.what()).what() - << endl; - } - } - else { - clog << "Something was thrown, not sure what." << endl - << "This satisfies the assertion, so no failure is" - << " present. " - << TestFailedException("Unknown thrown object"). - what(); - } - - return; - } - - throw TestFailedException(move(failureMessage)); - } - - void TestSuite::assertNoThrows ( - function shouldNotThrow, - string failureMessage - ) - { - try { - shouldNotThrow(); - } - catch (...) { - throw TestFailedException(move(failureMessage)); - } - } - - void TestSuite::assertTrue ( - bool condition, - string failureMessage - ) - { - if (!condition) { - stringstream err; - - err << "Boolean Truth assertion failed!" << std::endl; - err << failureMessage << std::endl; - - throw TestFailedException(err.str()); - } - } - - void TestSuite::assertFalse ( - bool condition, - string failureMessage - ) - { - if (condition) { - stringstream err; - - err << "Boolean False assertion failed!" << std::endl; - err << failureMessage << std::endl; - - throw TestFailedException(err.str()); - } - } - - void TestSuite::fail(string failureMessage) { - throw TestFailedException(move(failureMessage)); - } } diff --git a/src/TestCPPTestSuite.cpp b/src/TestCPPTestSuite.cpp new file mode 100644 index 0000000..ca68aa2 --- /dev/null +++ b/src/TestCPPTestSuite.cpp @@ -0,0 +1,150 @@ +/* +This is free and unencumbered software released into the public domain. + +Anyone is free to copy, modify, publish, use, compile, sell, or +distribute this software, either in source code form or as a compiled +binary, for any purpose, commercial or non-commercial, and by any +means. + +In jurisdictions that recognize copyright laws, the author or authors +of this software dedicate any and all copyright interest in the +software to the public domain. We make this dedication for the benefit +of the public at large and to the detriment of our heirs and +successors. We intend this dedication to be an overt act of +relinquishment in perpetuity of all present and future rights to this +software under copyright law. + +THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, +EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF +MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. +IN NO EVENT SHALL THE AUTHORS BE LIABLE FOR ANY CLAIM, DAMAGES OR +OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, +ARISING FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR +OTHER DEALINGS IN THE SOFTWARE. + +For more information, please refer to + */ + +//Original author: Jonathan Hyry CSU-Fullerton SECS 6896-02 Fall 2014/15 + +#include + +#include "internal/TestCPPCommon.h" +#include "internal/TestCPPTestCase.h" +#include "internal/TestCPPTestSuite.h" + +using std::cerr; +using std::clog; +using std::cout; +using std::endl; +using std::exception; +using std::fixed; +using std::function; +using std::invalid_argument; +using std::move; +using std::ostream; +using std::rethrow_exception; +using std::runtime_error; +using std::setprecision; +using std::string; +using std::tuple; + +using TCPPNum = TestCPP::TestCPPCommon::Nums; +using TCPPStr = TestCPP::TestCPPCommon::Strings; + +namespace TestCPP { + + void TestSuite::enableTestPassedMessage () { + this->testPassedMessage = true; + for (TestCase test : this->tests) { + test.setNotifyPassed(true); + } + } + void TestSuite::disableTestPassedMessage () { + this->testPassedMessage = false; + for (TestCase test : this->tests) { + test.setNotifyPassed(false); + } + } + + void TestSuite::setSuiteName (TestObjName&& testSuiteName) { + this->suiteName = move(testSuiteName); + } + + unsigned TestSuite::getLastRunFailCount () { + return this->lastRunFailCount; + } + + void TestSuite::run () { + if (this->tests.size() == 0) { + clog << TCPPStr::NTR << endl; + return; + } + + this->lastRunSucceeded = true; + this->lastRunFailCount = 0; + this->lastRunSuccessCount = 0; + this->totalRuntime = 0; + + clog << endl + << TCPPStr::START_RUN << TCPPStr::SUITE_ + << TCPPStr::APOS << this->suiteName << TCPPStr::APOS + << endl + << endl; + + for (TestCase test : this->tests) { + bool testPassed = false; + try { + testPassed = test.go(); + } + catch (exception& e) { + clog << TCPPStr::TEST_EXC_ << e.what() + << endl; + } + catch (...) { + cerr << TCPPStr::UNK_EXC + << endl; + } + + if (!testPassed && this->lastRunSucceeded) { + this->lastRunFailCount++; + this->lastRunSucceeded = false; + } + else if (!testPassed) { + this->lastRunFailCount++; + } + else { + this->lastRunSuccessCount++; + } + + this->totalRuntime += test.getLastRuntime(); + } + + clog << endl; + + if (this->testPassedMessage && + this->lastRunFailCount == 0) { + clog << TCPPStr::ALL_ << TCPPStr::APOS << this->suiteName + << TCPPStr::APOS << TCPPStr::_SUITE_TESTS_PASSED + << endl; + } + + double suiteRuntimeElapsed = static_cast( + this->totalRuntime)/TCPPNum::NANOS_IN_SEC; + + clog << fixed; + clog << setprecision(0); + clog << TCPPStr::FINISHED_SUITE_ << TCPPStr::APOS + << this->suiteName << TCPPStr::APOS << TCPPStr::_IN_ABOUT_ + << suiteRuntimeElapsed << TCPPStr::SEC << TCPPStr::SP + << TCPPStr::PARENL << this->lastRunSuccessCount + << TCPPStr::FWSL << this->tests.size() << TCPPStr::_PASSED + << TCPPStr::PARENR + << endl; + } + + template<> + void TestSuite::addTest (TestCase&& test) { + this->tests.emplace_back(test); + } +} diff --git a/src/TestCPPUtil.cpp b/src/TestCPPUtil.cpp index b48693e..6215740 100644 --- a/src/TestCPPUtil.cpp +++ b/src/TestCPPUtil.cpp @@ -28,7 +28,9 @@ For more information, please refer to #include #include -#include "TestCPPUtil.h" +#include "internal/TestCPPCommon.h" +#include "internal/TestCPPExceptions.h" +#include "internal/TestCPPUtil.h" #ifdef DEBUG_LOG #include @@ -39,8 +41,25 @@ using std::clog; using std::endl; #endif +using TCPPStr = TestCPP::TestCPPCommon::Strings; + namespace TestCPP { + + TestObjName::TestObjName (const char* name) { + if (name) { + this->testCaseName = name; + } + else { + throw TestCPPException(TCPPStr::NVTN); + } + } + + const string& TestObjName::getName () { + return this->testCaseName; + } + namespace Util { + void debugLog(const string& message, bool omitNewline) { #ifdef DEBUG_LOG clog << message; From 59ea694fdb004d7b0a6a74c9a030bd3f4c9e9662 Mon Sep 17 00:00:00 2001 From: Jonathan Hyry Date: Mon, 13 May 2024 08:12:38 -0700 Subject: [PATCH 08/32] Refactoring, new Assertions-specific test suite Since Assertions are now in their own TU, they now have their own test module. Assertion tests are removed from TestSuite tests. Removed now-unnecessary TestCPPUtil.h #include. Refactored assertion calls. Formatting. --- demo/src/main.cpp | 1 - demo/src/tests.cpp | 7 +++- test/include/Assertions/AssertionsSuite.h | 33 ++++++++++++++++ test/include/Assertions/AssertionsTests.h | 15 +++++++ test/include/TestSuite/TestSuiteSuite.h | 16 -------- test/include/TestSuite/TestSuiteTests.h | 4 -- test/src/Assertions/AssertionsTests.cpp | 48 +++++++++++++++++++++++ test/src/TestCPPAssertionsMain.cpp | 25 ++++++++++++ test/src/TestCPPTestCaseMain.cpp | 1 - test/src/TestCPPTestSuiteMain.cpp | 1 - test/src/TestCase/TestCaseTestChunks.cpp | 1 - test/src/TestSuite/TestSuiteTests.cpp | 40 ------------------- 12 files changed, 126 insertions(+), 66 deletions(-) create mode 100644 test/include/Assertions/AssertionsSuite.h create mode 100644 test/include/Assertions/AssertionsTests.h create mode 100644 test/src/Assertions/AssertionsTests.cpp create mode 100644 test/src/TestCPPAssertionsMain.cpp diff --git a/demo/src/main.cpp b/demo/src/main.cpp index a06e0d5..dd0cd66 100644 --- a/demo/src/main.cpp +++ b/demo/src/main.cpp @@ -1,5 +1,4 @@ #include "TestCPP.h" -#include "TestCPPUtil.h" using TestCPP::TestSuite; using std::string; diff --git a/demo/src/tests.cpp b/demo/src/tests.cpp index 7660d8a..77f6042 100644 --- a/demo/src/tests.cpp +++ b/demo/src/tests.cpp @@ -6,13 +6,16 @@ namespace TestCPP { void simpleTest () { int lower = 5; int higher = 9; - TestSuite::assertTrue(higher > lower, "Something is seriously wrong."); + Assertions::assertTrue( + higher > lower, + "Something is seriously wrong." + ); } void otherSimpleTest () { string s1 = string("A string"); string s2 = string("another string"); - TestSuite::assertNotEquals(s1, s2); + Assertions::assertNotEquals(s1, s2); } } } diff --git a/test/include/Assertions/AssertionsSuite.h b/test/include/Assertions/AssertionsSuite.h new file mode 100644 index 0000000..68596b1 --- /dev/null +++ b/test/include/Assertions/AssertionsSuite.h @@ -0,0 +1,33 @@ +#ifndef TESTCPP_ASSERTIONS_SUITE_ +#define TESTCPP_ASSERTIONS_SUITE_ + +#include "AssertionsTests.h" + +namespace TestCPP { + namespace Testing { + namespace AssertionsSuite { + TestSuite suite( + "TestCPP Assertions Tests", + + make_tuple( + "assertNull Test", + function(AssertionsTests::TestAssertNull) + ), + make_tuple( + "assertNotNull Test", + function(AssertionsTests::TestAssertNotNull) + ), + make_tuple( + "assertTrue Test", + function(AssertionsTests::TestAssertTrue) + ), + make_tuple( + "assertFalse Test", + function(AssertionsTests::TestAssertFalse) + ) + ); + } + } +} + +#endif diff --git a/test/include/Assertions/AssertionsTests.h b/test/include/Assertions/AssertionsTests.h new file mode 100644 index 0000000..9a69fb7 --- /dev/null +++ b/test/include/Assertions/AssertionsTests.h @@ -0,0 +1,15 @@ +#ifndef TESTCPP_ASSERTIONS_TESTS_ +#define TESTCPP_ASSERTIONS_TESTS_ + +namespace TestCPP { + namespace Testing { + namespace AssertionsTests { + void TestAssertTrue (); + void TestAssertFalse (); + void TestAssertNull (); + void TestAssertNotNull (); + } + } +} + +#endif diff --git a/test/include/TestSuite/TestSuiteSuite.h b/test/include/TestSuite/TestSuiteSuite.h index ca195ec..4aa4194 100644 --- a/test/include/TestSuite/TestSuiteSuite.h +++ b/test/include/TestSuite/TestSuiteSuite.h @@ -12,22 +12,6 @@ namespace TestCPP { make_tuple( "Suite construction Test", function(TestSuiteTests::TestConstructSuite) - ), - make_tuple( - "assertNull Test", - function(TestSuiteTests::TestAssertNull) - ), - make_tuple( - "assertNotNull Test", - function(TestSuiteTests::TestAssertNotNull) - ), - make_tuple( - "assertTrue Test", - function(TestSuiteTests::TestAssertTrue) - ), - make_tuple( - "assertFalse Test", - function(TestSuiteTests::TestAssertFalse) ) ); } diff --git a/test/include/TestSuite/TestSuiteTests.h b/test/include/TestSuite/TestSuiteTests.h index 22aeb86..6fa855b 100644 --- a/test/include/TestSuite/TestSuiteTests.h +++ b/test/include/TestSuite/TestSuiteTests.h @@ -5,10 +5,6 @@ namespace TestCPP { namespace Testing { namespace TestSuiteTests { void TestConstructSuite (); - void TestAssertTrue (); - void TestAssertFalse (); - void TestAssertNull (); - void TestAssertNotNull (); } } } diff --git a/test/src/Assertions/AssertionsTests.cpp b/test/src/Assertions/AssertionsTests.cpp new file mode 100644 index 0000000..10f4634 --- /dev/null +++ b/test/src/Assertions/AssertionsTests.cpp @@ -0,0 +1,48 @@ +#include "TestCPP.h" +#include "Assertions/AssertionsTests.h" + +namespace TestCPP { + namespace Testing { + namespace AssertionsTests { + void TestAssertTrue () { + int lower = 5; + int higher = 9; + + Assertions::assertTrue(higher > lower, + "Negated condtion!"); + } + + void TestAssertFalse () { + int lower = 5; + int higher = 9; + + Assertions::assertFalse(higher < lower, + "Negated condtion!"); + } + + void TestAssertNull () { + string * nullString = nullptr; + + Assertions::assertNull(nullString, "nullptr is null!"); + } + + void TestAssertNotNull () { + string notNull("non-null"); + + Assertions::assertNotNull( + ¬Null, + "A constructed std::string is not null!" + ); + Assertions::assertNotNull( + "non-null", + "A const char * is not null!" + ); + + int testInt = 5; + + Assertions::assertNotNull(&testInt, + "An int pointer is not null!"); + } + } + } +} \ No newline at end of file diff --git a/test/src/TestCPPAssertionsMain.cpp b/test/src/TestCPPAssertionsMain.cpp new file mode 100644 index 0000000..b0e5374 --- /dev/null +++ b/test/src/TestCPPAssertionsMain.cpp @@ -0,0 +1,25 @@ +#include "TestCPP.h" + +using TestCPP::TestCase; +using TestCPP::TestSuite; +using std::string; +using std::make_tuple; +using std::function; + +#include "Assertions/AssertionsSuite.h" + +int main(void) +{ + try { + TestCPP::Testing::AssertionsSuite::suite.run(); + return TestCPP::Util::unsignedToSigned( + TestCPP::Testing::AssertionsSuite::suite. + getLastRunFailCount() + ); + } + catch (std::exception& e) { + std::cerr << "Test suite run failed with an exception: " + << e.what() << std::endl; + return -1; + } +} diff --git a/test/src/TestCPPTestCaseMain.cpp b/test/src/TestCPPTestCaseMain.cpp index 89287f2..3db3ee6 100644 --- a/test/src/TestCPPTestCaseMain.cpp +++ b/test/src/TestCPPTestCaseMain.cpp @@ -1,5 +1,4 @@ #include "TestCPP.h" -#include "TestCPPUtil.h" using TestCPP::TestCase; using TestCPP::TestSuite; diff --git a/test/src/TestCPPTestSuiteMain.cpp b/test/src/TestCPPTestSuiteMain.cpp index 61f8968..8cb0f8a 100644 --- a/test/src/TestCPPTestSuiteMain.cpp +++ b/test/src/TestCPPTestSuiteMain.cpp @@ -1,5 +1,4 @@ #include "TestCPP.h" -#include "TestCPPUtil.h" using TestCPP::TestCase; using TestCPP::TestSuite; diff --git a/test/src/TestCase/TestCaseTestChunks.cpp b/test/src/TestCase/TestCaseTestChunks.cpp index 7d6d4fe..661cd48 100644 --- a/test/src/TestCase/TestCaseTestChunks.cpp +++ b/test/src/TestCase/TestCaseTestChunks.cpp @@ -1,7 +1,6 @@ #include #include "TestCPP.h" -#include "TestCPPUtil.h" #include "TestCase/TestCaseTestChunks.h" using TestCPP::TestCase; diff --git a/test/src/TestSuite/TestSuiteTests.cpp b/test/src/TestSuite/TestSuiteTests.cpp index 6ee58d7..e4f070f 100644 --- a/test/src/TestSuite/TestSuiteTests.cpp +++ b/test/src/TestSuite/TestSuiteTests.cpp @@ -9,46 +9,6 @@ namespace TestCPP { "Suite construction" )); } - - void TestAssertTrue () { - int lower = 5; - int higher = 9; - - TestSuite::assertTrue(higher > lower, - "Negated condtion!"); - } - - void TestAssertFalse () { - int lower = 5; - int higher = 9; - - TestSuite::assertFalse(higher < lower, - "Negated condtion!"); - } - - void TestAssertNull () { - string * nullString = nullptr; - - TestSuite::assertNull(nullString, "nullptr is null!"); - } - - void TestAssertNotNull () { - string notNull("non-null"); - - TestSuite::assertNotNull( - ¬Null, - "A constructed std::string is not null!" - ); - TestSuite::assertNotNull( - "non-null", - "A const char * is not null!" - ); - - int testInt = 5; - - TestSuite::assertNotNull(&testInt, - "An int pointer is not null!"); - } } } } From caa1d057d2e10d7059ad5356756740c79558f180 Mon Sep 17 00:00:00 2001 From: Jonathan Hyry Date: Mon, 13 May 2024 08:18:58 -0700 Subject: [PATCH 09/32] Test coverage to get patch coverage up for the PR. Refactoring. Cover the patched line to get the CodeCov patch coverage to an acceptable level, plus a little. Added 3 new tests for this. Refactoring for API changes. Use TestCPPCommon to check output is as expected. Remove now-unnecessary TestCPPUtil.h #include. --- test/include/TestCase/TestCaseSuite.h | 12 ++++ test/include/TestCase/TestCaseTests.h | 3 + test/src/TestCase/TestCaseTests.cpp | 95 +++++++++++++++++++++++---- 3 files changed, 98 insertions(+), 12 deletions(-) diff --git a/test/include/TestCase/TestCaseSuite.h b/test/include/TestCase/TestCaseSuite.h index ba3115b..68b7bc2 100644 --- a/test/include/TestCase/TestCaseSuite.h +++ b/test/include/TestCase/TestCaseSuite.h @@ -17,6 +17,18 @@ namespace TestCPP { "Case runner Test", function(TestCaseTests::TestTestCaseGo) ), + make_tuple( + "Case runner Test - string thrown", + function(TestCaseTests::TestTestCaseGoThrowStr) + ), + make_tuple( + "Case runner Test - char thrown", + function(TestCaseTests::TestTestCaseGoThrowChr) + ), + make_tuple( + "Case runner Test - test catchall", + function(TestCaseTests::TestTestCaseGoThrowInt) + ), make_tuple( "Case setNotifyPassed Test", function( diff --git a/test/include/TestCase/TestCaseTests.h b/test/include/TestCase/TestCaseTests.h index d1569f8..90b526c 100644 --- a/test/include/TestCase/TestCaseTests.h +++ b/test/include/TestCase/TestCaseTests.h @@ -6,6 +6,9 @@ namespace TestCPP { namespace TestCaseTests { void TestConstructCase (); void TestTestCaseGo (); + void TestTestCaseGoThrowStr (); + void TestTestCaseGoThrowChr (); + void TestTestCaseGoThrowInt (); void TestTestCaseSetNotifyPassed (); } } diff --git a/test/src/TestCase/TestCaseTests.cpp b/test/src/TestCase/TestCaseTests.cpp index 530a68d..8fef9ec 100644 --- a/test/src/TestCase/TestCaseTests.cpp +++ b/test/src/TestCase/TestCaseTests.cpp @@ -1,9 +1,10 @@ #include "TestCPP.h" -#include "TestCPPUtil.h" #include "TestCase/TestCaseTestChunks.h" using TestCPP::Util::debugLog; +using TCPPStr = TestCPP::TestCPPCommon::Strings; + namespace TestCPP { namespace Testing { namespace TestCaseTests { @@ -15,7 +16,7 @@ namespace TestCPP { )); debugLog("Construct with nullptr string"); - TestSuite::assertThrows( + Assertions::assertThrows( []() { debugLog("Construct with nullptr string", true); debugLog(" - assertThrows lambda"); @@ -41,12 +42,82 @@ namespace TestCPP { true )); - TestSuite::assertTrue( + Assertions::assertTrue( test->go(), "Should have succeeded basic no-op test!" ); } + void TestTestCaseGoThrowStr () { + const string throwStr = "Test throw string!"; + + auto test = unique_ptr(new TestCase( + "SUB-TEST TestCaseGo case Test - throws str", + function([&throwStr](){ + throw throwStr; + }), + true, false, true, false, + TestCase::TestCaseOutCompareOptions::CONTAINS + )); + + Assertions::assertFalse( + test->go(), + "Should have succeeded str throws test!" + ); + + Assertions::assertTrue( + test->checkLog(throwStr), + "Something is off, expected output does not exist!" + ); + } + + void TestTestCaseGoThrowChr () { + constexpr const char * throwChr = + "Test throw const char *!"; + + auto test = unique_ptr(new TestCase( + "SUB-TEST TestCaseGo case Test - throws chr", + function([&throwChr](){ + throw throwChr; + }), + true, false, true, false, + TestCase::TestCaseOutCompareOptions::CONTAINS + )); + + Assertions::assertFalse( + test->go(), + "Should have succeeded chr throws test!" + ); + + string tcLog(throwChr); + + Assertions::assertTrue( + test->checkLog(tcLog), + "Something is off, expected output does not exist!" + ); + } + + void TestTestCaseGoThrowInt () { + auto test = unique_ptr(new TestCase( + "SUB-TEST TestCaseGo case Test - throws int", + function([](){ + throw -1; + }), + true, false, true, false, + TestCase::TestCaseOutCompareOptions::CONTAINS + )); + + Assertions::assertFalse( + test->go(), + "Should have succeeded catchall throws test!" + ); + + Assertions::assertTrue( + test->checkLog(TCPPStr::UNK_EXC), + "Something is off, expected output does not exist!" + ); + } + void TestTestCaseSetNotifyPassed () { auto test = unique_ptr(new TestCase( "TestCaseSetNotifyPassed case Test", @@ -55,7 +126,7 @@ namespace TestCPP { TestCase::TestCaseOutCompareOptions::CONTAINS )); - TestSuite::assertTrue( + Assertions::assertTrue( test->go(), "TestSetNotifyPassed go() 1" ); @@ -64,26 +135,26 @@ namespace TestCPP { tcLog << "Test "; tcLog << "TestCaseSetNotifyPassed case Test passed! ("; - TestSuite::assertTrue( + Assertions::assertTrue( !test->checkLog(tcLog.str()), "TestSetNotifyPassed checkLog() 1" ); - TestSuite::assertTrue( + Assertions::assertTrue( !test->checkLog(string("s)")), "TestSetNotifyPassed checkLog() 2" ); test->setNotifyPassed(true); - TestSuite::assertTrue( + Assertions::assertTrue( test->go(), "TestSetNotifyPassed go() 2" ); - TestSuite::assertTrue( + Assertions::assertTrue( test->checkLog(tcLog.str()), "TestSetNotifyPassed checkLog() 3" ); - TestSuite::assertTrue( + Assertions::assertTrue( test->checkLog(string("s)")), "TestSetNotifyPassed checkLog() 4" ); @@ -91,16 +162,16 @@ namespace TestCPP { test->clearLogCapture(); test->setNotifyPassed(false); - TestSuite::assertTrue( + Assertions::assertTrue( test->go(), "TestSetNotifyPassed go() 3" ); - TestSuite::assertTrue( + Assertions::assertTrue( !test->checkLog(tcLog.str()), "TestSetNotifyPassed checkLog() 5" ); - TestSuite::assertTrue( + Assertions::assertTrue( !test->checkLog(string("s)")), "TestSetNotifyPassed checkLog() 6" ); From 039a7e38fc56bcf27565d2f1ceb5e26d6f88a442 Mon Sep 17 00:00:00 2001 From: Jonathan Hyry Date: Mon, 13 May 2024 08:28:30 -0700 Subject: [PATCH 10/32] Version bump to 1.0.0. New test target. Install new internal headers. Bump the major version because of the API change. This is still considered a beta-project, regardless. Work on the build takes the refactorings into account. This includes ensuring the new header structure is properly accounted for in packages and installation. --- CMakeLists.txt | 2 +- cmake/BuildTypeHandling.cmake | 29 +++++++++++++++++++++++++++++ cmake/Includes.cmake | 6 ++++++ cmake/Installing.cmake | 13 +++++++++++++ cmake/Linking.cmake | 33 +++++++++++++++++++++++++++++++++ cmake/Targets.cmake | 11 ++++++++++- cmake/Testing.cmake | 5 +++++ 7 files changed, 97 insertions(+), 2 deletions(-) diff --git a/CMakeLists.txt b/CMakeLists.txt index 04f62a9..ce2e566 100644 --- a/CMakeLists.txt +++ b/CMakeLists.txt @@ -3,7 +3,7 @@ cmake_minimum_required (VERSION 3.16) set (PROJECT_NAME "TestCPP") set (PROJECT_GROUP_NAME "cpptesting") -project (${PROJECT_NAME} VERSION 0.2.1 LANGUAGES CXX) +project (${PROJECT_NAME} VERSION 1.0.0 LANGUAGES CXX) set (CMAKE_CXX_STANDARD 11) set (CMAKE_CXX_STANDARD_REQUIRED ON) diff --git a/cmake/BuildTypeHandling.cmake b/cmake/BuildTypeHandling.cmake index eba34cd..37d1387 100644 --- a/cmake/BuildTypeHandling.cmake +++ b/cmake/BuildTypeHandling.cmake @@ -135,6 +135,12 @@ if (${CMAKE_BUILD_TYPE} STREQUAL "Release") PUBLIC ${MSVC_RELEASE_BUILD_OPTS} ) + + target_compile_options ( + ${PROJECT_NAME}_Assertions_test + PUBLIC + ${MSVC_RELEASE_BUILD_OPTS} + ) endif () else () @@ -164,6 +170,12 @@ if (${CMAKE_BUILD_TYPE} STREQUAL "Release") PUBLIC ${GCC_CLANG_RELEASE_BUILD_OPTS} ) + + target_compile_options ( + ${PROJECT_NAME}_Assertions_test + PUBLIC + ${GCC_CLANG_RELEASE_BUILD_OPTS} + ) endif () endif () @@ -195,6 +207,11 @@ else () PUBLIC DEBUG_LOG ) + target_compile_definitions ( + ${PROJECT_NAME}_Assertions_test + PUBLIC + DEBUG_LOG + ) endif () if (MSVC) @@ -224,6 +241,12 @@ else () PUBLIC ${MSVC_DEBUG_BUILD_OPTS} ) + + target_compile_options ( + ${PROJECT_NAME}_Assertions_test + PUBLIC + ${MSVC_DEBUG_BUILD_OPTS} + ) endif () else () @@ -256,6 +279,12 @@ else () ${COVERAGE_BUILD_OPTS} ) + target_compile_options ( + ${PROJECT_NAME}_Assertions_test + PUBLIC + ${COVERAGE_BUILD_OPTS} + ) + else () target_compile_options ( ${PROJECT_NAME} diff --git a/cmake/Includes.cmake b/cmake/Includes.cmake index 200a7ae..a34ba82 100644 --- a/cmake/Includes.cmake +++ b/cmake/Includes.cmake @@ -32,4 +32,10 @@ if (BUILD_TESTING) test/include include ) + + target_include_directories ( + ${PROJECT_NAME}_Assertions_test PRIVATE + test/include + include + ) endif () diff --git a/cmake/Installing.cmake b/cmake/Installing.cmake index 39ac2b2..f8ac878 100644 --- a/cmake/Installing.cmake +++ b/cmake/Installing.cmake @@ -4,12 +4,25 @@ set_target_properties ( PUBLIC_HEADER include/TestCPP.h ) +set_target_properties ( + ${PROJECT_NAME} + PROPERTIES + PRIVATE_HEADER + include/internal/TestCPPAssertions.h + include/internal/TestCPPCommon.h + include/internal/TestCPPExceptions.h + include/internal/TestCPPTestCase.h + include/internal/TestCPPTestSuite.h + include/internal/TestCPPUtil.h +) include (GNUInstallDirs) install ( TARGETS ${PROJECT_NAME} EXPORT ${PROJECT_NAME}Targets PUBLIC_HEADER DESTINATION ${CMAKE_INSTALL_INCLUDEDIR}/${PROJECT_NAME} + PRIVATE_HEADER + DESTINATION ${CMAKE_INSTALL_INCLUDEDIR}/${PROJECT_NAME}/internal ) export ( EXPORT ${PROJECT_NAME}Targets diff --git a/cmake/Linking.cmake b/cmake/Linking.cmake index f6c6cb0..f585edf 100644 --- a/cmake/Linking.cmake +++ b/cmake/Linking.cmake @@ -46,6 +46,13 @@ if (BUILD_TESTING) ole32 dbgeng ) + target_link_libraries ( + ${PROJECT_NAME}_Assertions_test + ${PROJECT_NAME} + gcov + ole32 + dbgeng + ) elseif (${TESTCPP_STACKTRACE_ENABLED}) target_link_libraries ( @@ -60,6 +67,12 @@ if (BUILD_TESTING) gcov dl ) + target_link_libraries ( + ${PROJECT_NAME}_Assertions_test + ${PROJECT_NAME} + gcov + dl + ) else () target_link_libraries ( @@ -72,6 +85,11 @@ if (BUILD_TESTING) ${PROJECT_NAME} gcov ) + target_link_libraries ( + ${PROJECT_NAME}_Assertions_test + ${PROJECT_NAME} + gcov + ) endif () else () @@ -88,6 +106,12 @@ if (BUILD_TESTING) ole32 dbgeng ) + target_link_libraries ( + ${PROJECT_NAME}_Assertions_test + ${PROJECT_NAME} + ole32 + dbgeng + ) elseif (${TESTCPP_STACKTRACE_ENABLED}) target_link_libraries ( @@ -100,6 +124,11 @@ if (BUILD_TESTING) ${PROJECT_NAME} dl ) + target_link_libraries ( + ${PROJECT_NAME}_Assertions_test + ${PROJECT_NAME} + dl + ) else () target_link_libraries ( @@ -110,6 +139,10 @@ if (BUILD_TESTING) ${PROJECT_NAME}_TestSuite_test ${PROJECT_NAME} ) + target_link_libraries ( + ${PROJECT_NAME}_Assertions_test + ${PROJECT_NAME} + ) endif () endif () endif () diff --git a/cmake/Targets.cmake b/cmake/Targets.cmake index 49fa350..66b198e 100644 --- a/cmake/Targets.cmake +++ b/cmake/Targets.cmake @@ -1,7 +1,10 @@ add_library ( ${PROJECT_NAME} + src/TestCPPAssertions.cpp + src/TestCPPExceptions.cpp + src/TestCPPTestCase.cpp + src/TestCPPTestSuite.cpp src/TestCPPUtil.cpp - src/TestCPP.cpp ) add_library ( ${PROJECT_GROUP_NAME}::${PROJECT_NAME} @@ -30,4 +33,10 @@ if (BUILD_TESTING) test/src/TestSuite/TestSuiteTests.cpp test/src/TestCPPTestSuiteMain.cpp ) + + add_executable ( + ${PROJECT_NAME}_Assertions_test + test/src/Assertions/AssertionsTests.cpp + test/src/TestCPPAssertionsMain.cpp + ) endif () diff --git a/cmake/Testing.cmake b/cmake/Testing.cmake index e454ef8..b7c4c1b 100644 --- a/cmake/Testing.cmake +++ b/cmake/Testing.cmake @@ -8,4 +8,9 @@ if (BUILD_TESTING) NAME ${PROJECT_NAME}TestSuiteTests COMMAND ${PROJECT_NAME}_TestSuite_test ) + + add_test ( + NAME ${PROJECT_NAME}AssertionsTests + COMMAND ${PROJECT_NAME}_Assertions_test + ) endif () From 1232cd4eae054cf6a4a21abced28bbcc7a8d3c82 Mon Sep 17 00:00:00 2001 From: Jonathan Hyry Date: Mon, 13 May 2024 08:29:10 -0700 Subject: [PATCH 11/32] Restructure and add to the CodeLite project. --- TestFramework.project | 24 +++++++++++++++++++++--- 1 file changed, 21 insertions(+), 3 deletions(-) diff --git a/TestFramework.project b/TestFramework.project index d6badc7..31c2d53 100644 --- a/TestFramework.project +++ b/TestFramework.project @@ -3,12 +3,23 @@ - + + + + - + + + + + + + + + @@ -43,11 +54,14 @@ - + + + + @@ -59,6 +73,10 @@ + + + + From 0020270d674a794ef4106a9893f58899ccbd5e09 Mon Sep 17 00:00:00 2001 From: Jonathan Hyry Date: Mon, 13 May 2024 08:59:32 -0700 Subject: [PATCH 12/32] Fix PRIVATE_HEADER property There are now multiple private headers, needed to encapsulate them into a list in order to have them properly applied to packing/installing build steps. --- cmake/Installing.cmake | 26 +++++++++++++++++++------- 1 file changed, 19 insertions(+), 7 deletions(-) diff --git a/cmake/Installing.cmake b/cmake/Installing.cmake index f8ac878..7606865 100644 --- a/cmake/Installing.cmake +++ b/cmake/Installing.cmake @@ -1,19 +1,31 @@ +list ( + APPEND + TESTCPP_PUBLIC_HEADERS + include/TestCPP.h +) + +list ( + APPEND + TESTCPP_PRIVATE_HEADERS + include/internal/TestCPPAssertions.h + include/internal/TestCPPCommon.h + include/internal/TestCPPExceptions.h + include/internal/TestCPPTestCase.h + include/internal/TestCPPTestSuite.h + include/internal/TestCPPUtil.h +) + set_target_properties ( ${PROJECT_NAME} PROPERTIES PUBLIC_HEADER - include/TestCPP.h + "${TESTCPP_PUBLIC_HEADERS}" ) set_target_properties ( ${PROJECT_NAME} PROPERTIES PRIVATE_HEADER - include/internal/TestCPPAssertions.h - include/internal/TestCPPCommon.h - include/internal/TestCPPExceptions.h - include/internal/TestCPPTestCase.h - include/internal/TestCPPTestSuite.h - include/internal/TestCPPUtil.h + "${TESTCPP_PRIVATE_HEADERS}" ) include (GNUInstallDirs) install ( From 651c2e456fb7b74b2413be1130cc77d8dc08f094 Mon Sep 17 00:00:00 2001 From: Jonathan Hyry Date: Mon, 13 May 2024 09:17:32 -0700 Subject: [PATCH 13/32] Remove unnecessary include. Add necessary include. --- src/TestCPPAssertions.cpp | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/TestCPPAssertions.cpp b/src/TestCPPAssertions.cpp index 57344f9..25dc566 100644 --- a/src/TestCPPAssertions.cpp +++ b/src/TestCPPAssertions.cpp @@ -26,7 +26,7 @@ For more information, please refer to */ #include "internal/TestCPPAssertions.h" -#include "internal/TestCPPTestCase.h" +#include "internal/TestCPPExceptions.h" using std::clog; using std::current_exception; From c736989850b9080763e310ebc2311b118407ff3c Mon Sep 17 00:00:00 2001 From: Jonathan Hyry Date: Mon, 13 May 2024 09:23:18 -0700 Subject: [PATCH 14/32] Accidentally moved TestCaseTests.cpp to the wrong virtual directory This fixes that. --- TestFramework.project | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/TestFramework.project b/TestFramework.project index 31c2d53..bab697a 100644 --- a/TestFramework.project +++ b/TestFramework.project @@ -18,7 +18,6 @@ - @@ -54,6 +53,7 @@ + From e0c3e6eebcae26d87d643e05d5a727f5410779dd Mon Sep 17 00:00:00 2001 From: Jonathan Hyry Date: Mon, 13 May 2024 09:31:11 -0700 Subject: [PATCH 15/32] Let's try this again. Fix Assertions includes and add std usings. --- include/internal/TestCPPAssertions.h | 11 ++++++++++- src/TestCPPAssertions.cpp | 1 - 2 files changed, 10 insertions(+), 2 deletions(-) diff --git a/include/internal/TestCPPAssertions.h b/include/internal/TestCPPAssertions.h index 517f013..8032643 100644 --- a/include/internal/TestCPPAssertions.h +++ b/include/internal/TestCPPAssertions.h @@ -28,8 +28,17 @@ For more information, please refer to #ifndef TESTCPP_ASSERTIONS_ #define TESTCPP_ASSERTIONS_ +#include +#include +#include +#include + #include "TestCPPExceptions.h" -#include "TestCPPTestCase.h" + +using std::endl; +using std::function; +using std::string; +using std::stringstream; /** * The base namespace for all TestCPP library code. diff --git a/src/TestCPPAssertions.cpp b/src/TestCPPAssertions.cpp index 25dc566..f689234 100644 --- a/src/TestCPPAssertions.cpp +++ b/src/TestCPPAssertions.cpp @@ -26,7 +26,6 @@ For more information, please refer to */ #include "internal/TestCPPAssertions.h" -#include "internal/TestCPPExceptions.h" using std::clog; using std::current_exception; From b3e147d63cff5938072897595974ab547b25da55 Mon Sep 17 00:00:00 2001 From: Jonathan Hyry Date: Mon, 13 May 2024 09:44:56 -0700 Subject: [PATCH 16/32] Move the operator<< definition to the source file. --- include/internal/TestCPPUtil.h | 6 +----- src/TestCPPUtil.cpp | 9 +++++++++ 2 files changed, 10 insertions(+), 5 deletions(-) diff --git a/include/internal/TestCPPUtil.h b/include/internal/TestCPPUtil.h index 839625a..045def4 100644 --- a/include/internal/TestCPPUtil.h +++ b/include/internal/TestCPPUtil.h @@ -84,11 +84,7 @@ namespace TestCPP { friend std::ostream& operator<< ( std::ostream& s, TestObjName& tcName - ) - { - s << tcName.getName(); - return s; - } + ); private: string testCaseName; diff --git a/src/TestCPPUtil.cpp b/src/TestCPPUtil.cpp index 6215740..424cf85 100644 --- a/src/TestCPPUtil.cpp +++ b/src/TestCPPUtil.cpp @@ -58,6 +58,15 @@ namespace TestCPP { return this->testCaseName; } + std::ostream& operator<< ( + std::ostream& s, + TestObjName& tcName + ) + { + s << tcName.getName(); + return s; + } + namespace Util { void debugLog(const string& message, bool omitNewline) { From e871d72f98e824da3d10bf0612918bfe20eb6603 Mon Sep 17 00:00:00 2001 From: Jonathan Hyry Date: Mon, 13 May 2024 09:59:52 -0700 Subject: [PATCH 17/32] Forgot to include ostream. --- src/TestCPPUtil.cpp | 1 + 1 file changed, 1 insertion(+) diff --git a/src/TestCPPUtil.cpp b/src/TestCPPUtil.cpp index 424cf85..37a8662 100644 --- a/src/TestCPPUtil.cpp +++ b/src/TestCPPUtil.cpp @@ -26,6 +26,7 @@ For more information, please refer to */ #include +#include #include #include "internal/TestCPPCommon.h" From 4535c338610ab6247326ef72a7091bef1a5703b2 Mon Sep 17 00:00:00 2001 From: Jonathan Hyry Date: Mon, 13 May 2024 10:05:01 -0700 Subject: [PATCH 18/32] This include should have been conditional. Now it is, if it doesn't need iostream for debug logging then just include ostream for the friend operator<< overload. --- src/TestCPPUtil.cpp | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/src/TestCPPUtil.cpp b/src/TestCPPUtil.cpp index 37a8662..671d8ea 100644 --- a/src/TestCPPUtil.cpp +++ b/src/TestCPPUtil.cpp @@ -26,7 +26,6 @@ For more information, please refer to */ #include -#include #include #include "internal/TestCPPCommon.h" @@ -35,6 +34,8 @@ For more information, please refer to #ifdef DEBUG_LOG #include +#else +#include #endif #ifdef DEBUG_LOG From 268dc815d01b3389c1d8642de3ba91264e5bd93e Mon Sep 17 00:00:00 2001 From: Jonathan Hyry Date: Mon, 13 May 2024 10:10:39 -0700 Subject: [PATCH 19/32] Fix clang error -wunused-lambda-capture I think this is not required for capture because it's constexpr. --- test/src/TestCase/TestCaseTests.cpp | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/test/src/TestCase/TestCaseTests.cpp b/test/src/TestCase/TestCaseTests.cpp index 8fef9ec..0db1108 100644 --- a/test/src/TestCase/TestCaseTests.cpp +++ b/test/src/TestCase/TestCaseTests.cpp @@ -77,7 +77,7 @@ namespace TestCPP { auto test = unique_ptr(new TestCase( "SUB-TEST TestCaseGo case Test - throws chr", - function([&throwChr](){ + function([](){ throw throwChr; }), true, false, true, false, From a8b5ef08e9e02dc13f031ae57b28cd326d25b321 Mon Sep 17 00:00:00 2001 From: Jonathan Hyry Date: Mon, 13 May 2024 10:59:00 -0700 Subject: [PATCH 20/32] Workaround for MSVC error C3493 I have to ignore the clang warning to work around the MSVC issue since the library is for C++11 not >=C++14, where there is an available workaround that doesn't involve ignoring a warning. --- cmake/BuildTypeHandling.cmake | 26 ++++++++++++++++++-------- test/src/TestCase/TestCaseTests.cpp | 2 +- 2 files changed, 19 insertions(+), 9 deletions(-) diff --git a/cmake/BuildTypeHandling.cmake b/cmake/BuildTypeHandling.cmake index 37d1387..67543b7 100644 --- a/cmake/BuildTypeHandling.cmake +++ b/cmake/BuildTypeHandling.cmake @@ -80,13 +80,23 @@ endif () list ( APPEND GCC_CLANG_RELEASE_BUILD_OPTS - -O3 # Optimize the Release build - -Wall # Enable most warnings - -Wextra # Enable even more warnings - -Wpedantic # Enable most of the rest of the warnings - -Werror # Treat all warnings as errors - -Wno-unused-parameter # Unused parameters occur in the Release - # build in debugLog + -O3 # Optimize the Release build + -Wall # Enable most warnings + -Wextra # Enable even more warnings + -Wpedantic # Enable most of the rest of the warnings + -Werror # Treat all warnings as errors + -Wno-unused-parameter # Unused parameters occur in the Release + # build in debugLog + -Wno-unused-lambda-capture # Avoid MSVC error C3493 - There is + # implementation divergence here and + # since we're not using >=C++14 there + # is no workaround other than to ignore + # this warning (the MSVC issue is an + # error). A workaround for >=C++14 is to + # use an explicit capture - if ever I + # change the library to use >=C++14 I + # can remove this and use an explicit + # capture. ) list ( APPEND @@ -94,7 +104,7 @@ list ( -g # Enable all debugging information -Og # Ensure the compiler doesn't use optimizations that would harm # debuggability of the resulting code - -Wall -Wextra -Wpedantic -Werror + -Wall -Wextra -Wpedantic -Werror -Wno-unused-lambda-capture ) list ( APPEND diff --git a/test/src/TestCase/TestCaseTests.cpp b/test/src/TestCase/TestCaseTests.cpp index 0db1108..8fef9ec 100644 --- a/test/src/TestCase/TestCaseTests.cpp +++ b/test/src/TestCase/TestCaseTests.cpp @@ -77,7 +77,7 @@ namespace TestCPP { auto test = unique_ptr(new TestCase( "SUB-TEST TestCaseGo case Test - throws chr", - function([](){ + function([&throwChr](){ throw throwChr; }), true, false, true, false, From 426adcd26c8c45ab5f1e1df67c17984cdca57b2f Mon Sep 17 00:00:00 2001 From: Jonathan Hyry Date: Sat, 22 Jun 2024 09:14:17 -0700 Subject: [PATCH 21/32] Cleanup, TC member initialization, TC capture bools, fix message bug Initialize everything in TestCase with sane defaults. Name the parameter in the logTestFailure function prototype. Add boolean members that indicate whether for the given TestCase the output should be captured. Add implementation details for capture bools. This ensures all are counted properly. Rename the testPassedMessage TestSuite member to testSuitePassedMessage. This properly differentiates it from the similar member of TestCase. No addTest specializations in header file, only the template. Moved the specialization from the header file that was there to the source file. This fixed an issue when testing where the compiler could not find the proper specialization. Flesh out/add/fix docs. Fixed a huge bug in how the test passage message is enabled/disabled en- mass through a TestSuite. Previously the vector was iterated over with the newer style, but this causes a copy to be made on every iteration of every TestCase, which is completely not what we want to do here. Fall back to index-based vector iteration so we're properly modifying each original TestCase and not making copies thinking we're modifying the actual TestCases. --- include/internal/TestCPPTestCase.h | 14 ++++--- include/internal/TestCPPTestSuite.h | 19 +++------ src/TestCPPTestCase.cpp | 60 +++++++++++++++++++++++++++++ src/TestCPPTestSuite.cpp | 36 +++++++++++++---- 4 files changed, 104 insertions(+), 25 deletions(-) diff --git a/include/internal/TestCPPTestCase.h b/include/internal/TestCPPTestCase.h index cc2c3f5..721062a 100644 --- a/include/internal/TestCPPTestCase.h +++ b/include/internal/TestCPPTestCase.h @@ -230,19 +230,23 @@ namespace TestCPP { long long getLastRuntime (); private: - bool notifyTestPassed; - bool pass; - long long lastRunTime; + bool notifyTestPassed = false; + bool pass = false; + bool stdoutCaptured = false; + bool clogCaptured = false; + bool stderrCaptured = false; + long long lastRunTime = -1; TestObjName testName; function test; - TestCaseOutCompareOptions option; + TestCaseOutCompareOptions option = + TestCaseOutCompareOptions::CONTAINS; void captureStdout (); void captureClog (); void captureStdErr (); - void logTestFailure (string); + void logTestFailure (string failureMessage); void runTest (); bool checkOutput (TestCaseOutCompareOptions opt, string source, string against); diff --git a/include/internal/TestCPPTestSuite.h b/include/internal/TestCPPTestSuite.h index 9945227..4277174 100644 --- a/include/internal/TestCPPTestSuite.h +++ b/include/internal/TestCPPTestSuite.h @@ -81,7 +81,7 @@ namespace TestCPP { TestSuite (TestObjName&& suiteName, typename enable_if::type) { - this->testPassedMessage = true; + this->testSuitePassedMessage = true; this->setSuiteName(move(suiteName)); this->tests = vector(); } @@ -92,7 +92,7 @@ namespace TestCPP { */ template TestSuite (TestObjName&& suiteName, TestType ...tests) { - this->testPassedMessage = true; + this->testSuitePassedMessage = true; this->setSuiteName(move(suiteName)); this->tests = vector(); @@ -102,18 +102,10 @@ namespace TestCPP { /** * @brief Add a test to this test suite. * - * The test should be defined as a tuple with 2 elements to use - * this. The first element is the test name, and the second - * element is the test function that defines the test. + * Appropriate specializations defined in the source file. */ template - void addTest (T&& test) { - this->tests.emplace_back( - std::get<0>(test), - std::get<1>(test), - this->testPassedMessage - ); - } + void addTest (T&& test); /** * @brief Specialization to handle when someone tries to call @@ -128,6 +120,7 @@ namespace TestCPP { /** * @brief Add one or more tests at once to the test suite. * @param test The first test to add. + * @param tests The rest of the tests to add. */ template void addTests (Test test, OtherTests ...tests) { @@ -176,7 +169,7 @@ namespace TestCPP { void run (); private: - bool testPassedMessage; + bool testSuitePassedMessage; bool lastRunSucceeded; unsigned lastRunSuccessCount; unsigned lastRunFailCount; diff --git a/src/TestCPPTestCase.cpp b/src/TestCPPTestCase.cpp index 95ca948..a76f091 100644 --- a/src/TestCPPTestCase.cpp +++ b/src/TestCPPTestCase.cpp @@ -97,6 +97,10 @@ namespace TestCPP { captureStdErr(); } + this->stdoutCaptured = captureOut; + this->clogCaptured = captureLog; + this->stderrCaptured = captureErr; + this->option = opt; } @@ -107,6 +111,20 @@ namespace TestCPP { this->pass = o.pass; this->lastRunTime = o.lastRunTime; + this->stdoutCaptured = o.stdoutCaptured; + this->clogCaptured = o.clogCaptured; + this->stderrCaptured = o.stderrCaptured; + + if (this->stdoutCaptured) { + captureStdout(); + } + if (this->clogCaptured) { + captureClog(); + } + if (this->stderrCaptured) { + captureStdErr(); + } + this->testName = o.testName; this->test = o.test; } @@ -118,6 +136,20 @@ namespace TestCPP { this->pass = move(o.pass); this->lastRunTime = move(o.lastRunTime); + this->stdoutCaptured = move(o.stdoutCaptured); + this->clogCaptured = move(o.clogCaptured); + this->stderrCaptured = move(o.stderrCaptured); + + if (this->stdoutCaptured) { + captureStdout(); + } + if (this->clogCaptured) { + captureClog(); + } + if (this->stderrCaptured) { + captureStdErr(); + } + this->testName = move(o.testName); this->test = move(o.test); } @@ -165,6 +197,20 @@ namespace TestCPP { this->pass = rhs.pass; this->lastRunTime = rhs.lastRunTime; + this->stdoutCaptured = rhs.stdoutCaptured; + this->clogCaptured = rhs.clogCaptured; + this->stderrCaptured = rhs.stderrCaptured; + + if (this->stdoutCaptured) { + captureStdout(); + } + if (this->clogCaptured) { + captureClog(); + } + if (this->stderrCaptured) { + captureStdErr(); + } + this->testName = rhs.testName; this->test = rhs.test; @@ -178,6 +224,20 @@ namespace TestCPP { this->pass = move(rhs.pass); this->lastRunTime = move(rhs.lastRunTime); + this->stdoutCaptured = move(rhs.stdoutCaptured); + this->clogCaptured = move(rhs.clogCaptured); + this->stderrCaptured = move(rhs.stderrCaptured); + + if (this->stdoutCaptured) { + captureStdout(); + } + if (this->clogCaptured) { + captureClog(); + } + if (this->stderrCaptured) { + captureStdErr(); + } + this->testName = move(rhs.testName); this->test = move(rhs.test); diff --git a/src/TestCPPTestSuite.cpp b/src/TestCPPTestSuite.cpp index ca68aa2..89736a7 100644 --- a/src/TestCPPTestSuite.cpp +++ b/src/TestCPPTestSuite.cpp @@ -55,15 +55,15 @@ using TCPPStr = TestCPP::TestCPPCommon::Strings; namespace TestCPP { void TestSuite::enableTestPassedMessage () { - this->testPassedMessage = true; - for (TestCase test : this->tests) { - test.setNotifyPassed(true); + this->testSuitePassedMessage = true; + for (unsigned i = 0; i < this->tests.size(); i += 1) { + this->tests[i].setNotifyPassed(true); } } void TestSuite::disableTestPassedMessage () { - this->testPassedMessage = false; - for (TestCase test : this->tests) { - test.setNotifyPassed(false); + this->testSuitePassedMessage = false; + for (unsigned i = 0; i < this->tests.size(); i += 1) { + this->tests[i].setNotifyPassed(false); } } @@ -122,7 +122,7 @@ namespace TestCPP { clog << endl; - if (this->testPassedMessage && + if (this->testSuitePassedMessage && this->lastRunFailCount == 0) { clog << TCPPStr::ALL_ << TCPPStr::APOS << this->suiteName << TCPPStr::APOS << TCPPStr::_SUITE_TESTS_PASSED @@ -143,8 +143,30 @@ namespace TestCPP { << endl; } + /** + * @brief Add a test to this test suite. + * + * The test should be a TestCase object. + */ template<> void TestSuite::addTest (TestCase&& test) { this->tests.emplace_back(test); } + + /** + * @brief Add a test to this test suite. + * + * The test should be defined as a tuple with 2 elements to use + * this. The first element is the test name, and the second + * element is the test function that defines the test. + */ + template<> + void TestSuite::addTest (tuple>&& test) { + this->tests.emplace_back( + std::get<0>(test), + std::get<1>(test), + this->testSuitePassedMessage + ); + } } From 700e235eb455fba00a6b30665c84346259e919f4 Mon Sep 17 00:00:00 2001 From: Jonathan Hyry Date: Sat, 22 Jun 2024 09:19:55 -0700 Subject: [PATCH 22/32] Expand TestSuite test suite The following is what was previously tested in TestSuite: - Bare construction, no tests, only suite name The following is now tested, in addition to what was previously tested: - All possible construction parameters > Suite construction with TestCase objects > Suite construction with tuples > Suite construction with various mixes of TestCases and tuples - TestSuite::enableTestPassedMessage with various mixes of numbers of tests. - TestSuite::disableTestPassedMessage with various mixes of numbers of tests. --- test/include/TestSuite/TestSuiteSuite.h | 60 ++- test/include/TestSuite/TestSuiteTests.h | 11 +- test/src/TestSuite/TestSuiteTests.cpp | 525 +++++++++++++++++++++++- 3 files changed, 590 insertions(+), 6 deletions(-) diff --git a/test/include/TestSuite/TestSuiteSuite.h b/test/include/TestSuite/TestSuiteSuite.h index 4aa4194..68cef2c 100644 --- a/test/include/TestSuite/TestSuiteSuite.h +++ b/test/include/TestSuite/TestSuiteSuite.h @@ -10,8 +10,64 @@ namespace TestCPP { "TestCPP TestSuite Tests", make_tuple( - "Suite construction Test", - function(TestSuiteTests::TestConstructSuite) + "Suite construction Test - no tests", + function( + TestSuiteTests::TestConstructSuiteBare + ) + ), + make_tuple( + "Suite construction Test - TestCases", + function( + TestSuiteTests::TestConstructSuiteTestCases + ) + ), + make_tuple( + "Suite construction Test - tuples", + function( + TestSuiteTests::TestConstructSuiteTuples + ) + ), + make_tuple( + "Suite construction Test - mixed", + function( + TestSuiteTests::TestConstructSuiteMixed + ) + ), + make_tuple( + "Suite enable test passed message - no tests", + function( + TestSuiteTests::TestEnableTestPassedMessageNoTests + ) + ), + make_tuple( + "Suite enable test passed message - one test", + function( + TestSuiteTests::TestEnableTestPassedMessageOneTest + ) + ), + make_tuple( + "Suite enable test passed message - many tests", + function( + TestSuiteTests::TestEnableTestPassedMessageManyTests + ) + ), + make_tuple( + "Suite disable test passed message - no tests", + function( + TestSuiteTests::TestDisableTestPassedMessageNoTests + ) + ), + make_tuple( + "Suite disable test passed message - one test", + function( + TestSuiteTests::TestDisableTestPassedMessageOneTest + ) + ), + make_tuple( + "Suite disable test passed message - many tests", + function( + TestSuiteTests::TestDisableTestPassedMessageManyTests + ) ) ); } diff --git a/test/include/TestSuite/TestSuiteTests.h b/test/include/TestSuite/TestSuiteTests.h index 6fa855b..878c6a6 100644 --- a/test/include/TestSuite/TestSuiteTests.h +++ b/test/include/TestSuite/TestSuiteTests.h @@ -4,7 +4,16 @@ namespace TestCPP { namespace Testing { namespace TestSuiteTests { - void TestConstructSuite (); + void TestConstructSuiteBare (); + void TestConstructSuiteTestCases (); + void TestConstructSuiteTuples (); + void TestConstructSuiteMixed (); + void TestEnableTestPassedMessageNoTests (); + void TestEnableTestPassedMessageOneTest (); + void TestEnableTestPassedMessageManyTests (); + void TestDisableTestPassedMessageNoTests (); + void TestDisableTestPassedMessageOneTest (); + void TestDisableTestPassedMessageManyTests (); } } } diff --git a/test/src/TestSuite/TestSuiteTests.cpp b/test/src/TestSuite/TestSuiteTests.cpp index e4f070f..19dc164 100644 --- a/test/src/TestSuite/TestSuiteTests.cpp +++ b/test/src/TestSuite/TestSuiteTests.cpp @@ -4,11 +4,530 @@ namespace TestCPP { namespace Testing { namespace TestSuiteTests { - void TestConstructSuite () { - auto test = unique_ptr(new TestSuite( - "Suite construction" + void TestConstructSuiteBare () { + auto testSuite = unique_ptr(new TestSuite( + "Suite construction - bare" )); } + void TestConstructSuiteTestCases () { + TestCase test1("dummy 1", [](){}), + test2("dummy 2", [](){}); + + auto testSuite = unique_ptr(new TestSuite( + "Suite construction - TestCases", + test1 + )); + testSuite = unique_ptr(new TestSuite( + "Suite construction - TestCases", + test1, test2 + )); + } + void TestConstructSuiteTuples () { + auto testSuite = unique_ptr(new TestSuite( + "Suite construction - tuples", + make_tuple("dummy 1", function([](){})) + )); + testSuite = unique_ptr(new TestSuite( + "Suite construction - tuples", + make_tuple("dummy 1", function([](){})), + make_tuple("dummy 2", function([](){})) + )); + } + void TestConstructSuiteMixed () { + TestCase test1("dummy 1", [](){}), + test2("dummy 2", [](){}); + auto testSuite = unique_ptr(new TestSuite( + "Suite construction - mixed", + test1, + make_tuple("dummy 1", function([](){})) + )); + testSuite = unique_ptr(new TestSuite( + "Suite construction - mixed", + test1, test2, + make_tuple("dummy 1", function([](){})) + )); + testSuite = unique_ptr(new TestSuite( + "Suite construction - mixed", + test1, + make_tuple("dummy 1", function([](){})), + make_tuple("dummy 2", function([](){})) + )); + testSuite = unique_ptr(new TestSuite( + "Suite construction - mixed", + test1, test2, + make_tuple("dummy 1", function([](){})), + make_tuple("dummy 2", function([](){})) + )); + testSuite = unique_ptr(new TestSuite( + "Suite construction - mixed", + test1, + make_tuple("dummy 1", function([](){})), + make_tuple("dummy 2", function([](){})), + test2 + )); + testSuite = unique_ptr(new TestSuite( + "Suite construction - mixed", + make_tuple("dummy 1", function([](){})), + test1, test2, + make_tuple("dummy 2", function([](){})) + )); + testSuite = unique_ptr(new TestSuite( + "Suite construction - mixed", + test1, + make_tuple("dummy 1", function([](){})), + test2, + make_tuple("dummy 2", function([](){})) + )); + testSuite = unique_ptr(new TestSuite( + "Suite construction - mixed", + make_tuple("dummy 1", function([](){})), + test2, + make_tuple("dummy 2", function([](){})), + test1 + )); + } + + void TestEnableTestPassedMessageNoTests() { + auto testSuite = unique_ptr(new TestSuite( + "SUBSUITE - Test Passed Message Enabled - no tests" + )); + testSuite->enableTestPassedMessage(); + testSuite->run(); + } + void TestEnableTestPassedMessageOneTest() { + const char * suiteName = + "SUBSUITE - Test Passed Message Enabled - one test"; + const char * testName = + "SUBTEST - enable test passed message dummy 1"; + + TestCase test( + testName, + [](){}, + false, + false, + true, + false + ); + + auto testSuite = unique_ptr(new TestSuite( + suiteName, test + )); + + testSuite->enableTestPassedMessage(); + testSuite->run(); + + stringstream tsLog; + tsLog << "All '"; + tsLog << suiteName; + tsLog << "' suite tests passed!"; + + stringstream tcLog; + tcLog << "Test "; + tcLog << testName; + tcLog << " passed! ("; + + string secondsParen = "s)"; + + Assertions::assertTrue( + test.checkLog(tcLog.str()), + "Should have notified on test passage! 1" + ); + Assertions::assertTrue( + test.checkLog(secondsParen), + "Should have notified on test passage! 2" + ); + Assertions::assertTrue( + test.checkLog(tsLog.str()), + "Should have notified on suite passage! 1" + ); + + test.clearLogCapture(); + + test = TestCase( + testName, + [](){}, + true, + false, + true, + false + ); + + testSuite = unique_ptr(new TestSuite( + suiteName, test + )); + + testSuite->enableTestPassedMessage(); + testSuite->run(); + + Assertions::assertTrue( + test.checkLog(tcLog.str()), + "Should have notified on test passage! 3" + ); + Assertions::assertTrue( + test.checkLog(secondsParen), + "Should have notified on test passage! 4" + ); + Assertions::assertTrue( + test.checkLog(tsLog.str()), + "Should have notified on suite passage! 2" + ); + } + void TestEnableTestPassedMessageManyTests() { + const char * suiteName = + "SUBSUITE - Test Passed Message Enabled - many tests"; + const char * test1Name = + "SUBTEST - enable test passed message dummy 1"; + const char * test2Name = + "SUBTEST - enable test passed message dummy 2"; + const char * test3Name = + "SUBTEST - enable test passed message dummy 3"; + + TestCase test1( + test1Name, + [](){}, + false, + false, + true, + false + ); + TestCase test2( + test2Name, + [](){}, + false, + false, + true, + false + ); + TestCase test3( + test3Name, + [](){}, + false, + false, + true, + false + ); + + auto testSuite = unique_ptr(new TestSuite( + suiteName, test1, test2, test3 + )); + testSuite->enableTestPassedMessage(); + testSuite->run(); + + stringstream tsLog; + tsLog << "All '"; + tsLog << suiteName; + tsLog << "' suite tests passed!"; + + stringstream tc1Log; + tc1Log << "Test "; + tc1Log << test1Name; + tc1Log << " passed! ("; + stringstream tc2Log; + tc2Log << "Test "; + tc2Log << test2Name; + tc2Log << " passed! ("; + stringstream tc3Log; + tc3Log << "Test "; + tc3Log << test3Name; + tc3Log << " passed! ("; + + string secondsParen = "s)"; + + Assertions::assertTrue( + test1.checkLog(tc1Log.str()), + "Should have notified on test passage! 1" + ); + Assertions::assertTrue( + test1.checkLog(tc2Log.str()), + "Should have notified on test passage! 2" + ); + Assertions::assertTrue( + test1.checkLog(tc3Log.str()), + "Should have notified on test passage! 3" + ); + Assertions::assertTrue( + test1.checkLog(secondsParen), + "Should have notified on test passage! 4" + ); + Assertions::assertTrue( + test1.checkLog(tsLog.str()), + "Should have notified on suite passage! 1" + ); + + test1.clearLogCapture(); + + test1 = TestCase( + test1Name, + [](){}, + true, + false, + true, + false + ); + test2 = TestCase( + test2Name, + [](){}, + true, + false, + true, + false + ); + test3 = TestCase( + test3Name, + [](){}, + true, + false, + true, + false + ); + + testSuite = unique_ptr(new TestSuite( + suiteName, test1, test2, test3 + )); + + testSuite->enableTestPassedMessage(); + testSuite->run(); + + Assertions::assertTrue( + test1.checkLog(tc1Log.str()), + "Should have notified on test passage! 5" + ); + Assertions::assertTrue( + test1.checkLog(tc2Log.str()), + "Should have notified on test passage! 6" + ); + Assertions::assertTrue( + test1.checkLog(tc3Log.str()), + "Should have notified on test passage! 7" + ); + Assertions::assertTrue( + test1.checkLog(secondsParen), + "Should have notified on test passage! 8" + ); + Assertions::assertTrue( + test1.checkLog(tsLog.str()), + "Should have notified on suite passage! 2" + ); + } + + void TestDisableTestPassedMessageNoTests() { + auto testSuite = unique_ptr(new TestSuite( + "Test Passed Message Disabled - no tests" + )); + testSuite->disableTestPassedMessage(); + testSuite->run(); + } + void TestDisableTestPassedMessageOneTest() { + const char * suiteName = + "SUBSUITE - Test Passed Message Disabled - one test"; + const char * testName = + "SUBTEST - disable test passed message dummy 1"; + + TestCase test( + testName, + [](){}, + false, + false, + true, + false + ); + + auto testSuite = unique_ptr(new TestSuite( + suiteName, test + )); + + testSuite->disableTestPassedMessage(); + testSuite->run(); + + stringstream tsLog; + tsLog << "All '"; + tsLog << suiteName; + tsLog << "' suite tests passed!"; + + stringstream tcLog; + tcLog << "Test "; + tcLog << testName; + tcLog << " passed! ("; + + string secondsParen = "s)"; + + Assertions::assertFalse( + test.checkLog(tcLog.str()), + "Should not have notified on test passage! 1" + ); + Assertions::assertFalse( + test.checkLog(secondsParen), + "Should not have notified on test passage! 2" + ); + Assertions::assertFalse( + test.checkLog(tsLog.str()), + "Should not have notified on suite passage! 1" + ); + + test = TestCase( + testName, + [](){}, + true, + false, + true, + false + ); + + testSuite = unique_ptr(new TestSuite( + suiteName, test + )); + + testSuite->disableTestPassedMessage(); + testSuite->run(); + + Assertions::assertFalse( + test.checkLog(tcLog.str()), + "Should not have notified on test passage! 3" + ); + Assertions::assertFalse( + test.checkLog(secondsParen), + "Should not have notified on test passage! 4" + ); + Assertions::assertFalse( + test.checkLog(tsLog.str()), + "Should not have notified on suite passage! 2" + ); + } + void TestDisableTestPassedMessageManyTests() { + const char * suiteName = + "SUBSUITE - Test Passed Message Disabled - many tests"; + const char * test1Name = + "SUBTEST - disable test passed message dummy 1"; + const char * test2Name = + "SUBTEST - disable test passed message dummy 2"; + const char * test3Name = + "SUBTEST - disable test passed message dummy 3"; + + TestCase test1( + test1Name, + [](){}, + false, + false, + true, + false + ); + TestCase test2( + test2Name, + [](){}, + false, + false, + true, + false + ); + TestCase test3( + test3Name, + [](){}, + false, + false, + true, + false + ); + + auto testSuite = unique_ptr(new TestSuite( + suiteName, test1, test2, test3 + )); + testSuite->disableTestPassedMessage(); + testSuite->run(); + + stringstream tsLog; + tsLog << "All '"; + tsLog << suiteName; + tsLog << "' suite tests passed!"; + + stringstream tc1Log; + tc1Log << "Test "; + tc1Log << test1Name; + tc1Log << " passed! ("; + stringstream tc2Log; + tc2Log << "Test "; + tc2Log << test2Name; + tc2Log << " passed! ("; + stringstream tc3Log; + tc3Log << "Test "; + tc3Log << test3Name; + tc3Log << " passed! ("; + + string secondsParen = "s)"; + + Assertions::assertFalse( + test1.checkLog(tc1Log.str()), + "Should not have notified on test passage! 1" + ); + Assertions::assertFalse( + test1.checkLog(tc2Log.str()), + "Should not have notified on test passage! 2" + ); + Assertions::assertFalse( + test1.checkLog(tc3Log.str()), + "Should not have notified on test passage! 3" + ); + Assertions::assertFalse( + test1.checkLog(secondsParen), + "Should not have notified on test passage! 4" + ); + Assertions::assertFalse( + test1.checkLog(tsLog.str()), + "Should not have notified on suite passage! 1" + ); + + test1 = TestCase( + test1Name, + [](){}, + true, + false, + true, + false + ); + test2 = TestCase( + test2Name, + [](){}, + true, + false, + true, + false + ); + test3 = TestCase( + test3Name, + [](){}, + true, + false, + true, + false + ); + + testSuite = unique_ptr(new TestSuite( + suiteName, test1, test2, test3 + )); + + testSuite->disableTestPassedMessage(); + testSuite->run(); + + Assertions::assertFalse( + test1.checkLog(tc1Log.str()), + "Should not have notified on test passage! 5" + ); + Assertions::assertFalse( + test1.checkLog(tc2Log.str()), + "Should not have notified on test passage! 6" + ); + Assertions::assertFalse( + test1.checkLog(tc3Log.str()), + "Should not have notified on test passage! 7" + ); + Assertions::assertFalse( + test1.checkLog(secondsParen), + "Should not have notified on test passage! 8" + ); + Assertions::assertFalse( + test1.checkLog(tsLog.str()), + "Should not have notified on suite passage! 2" + ); + } } } } From 316e501692a73a66dfed149212cb12127f48fcaa Mon Sep 17 00:00:00 2001 From: Jonathan Hyry Date: Sun, 23 Jun 2024 11:40:22 -0700 Subject: [PATCH 23/32] Remove excess whitespace, add/fix/flesh-out docs, fix private API. Excess whitespace in doc comments has been removed. Docs have been expanded/fixed. Removed unnecessary parameter from internal private API and adjust implementation to fix unnecessary reliance on the removed parameter. --- include/internal/TestCPPTestCase.h | 85 ++++++++++++++++++++++++++---- src/TestCPPTestCase.cpp | 11 ++-- 2 files changed, 79 insertions(+), 17 deletions(-) diff --git a/include/internal/TestCPPTestCase.h b/include/internal/TestCPPTestCase.h index 721062a..41ff892 100644 --- a/include/internal/TestCPPTestCase.h +++ b/include/internal/TestCPPTestCase.h @@ -69,7 +69,7 @@ namespace TestCPP { * @date 03/05/24 * @file TestCPP.h * @brief Provides an interface for implementing tests. - * + * * TestCase is one of three core types that define the TestCPP API. * TestCase provides faculties for defining and controlling tests * and the stdout/stderr/clog environment. @@ -94,6 +94,22 @@ namespace TestCPP { }; /** + * @brief Construct a test case with possibility to use all + * options. + * + * @param testName The name of the test as a TestObjName. + * @param test The implementation or pointer to the test. + * @param testPassedMessage Whether to omit a message indicating + * test passage. + * @param captureOut Whether to capture stdout output for + * analysis after the test run. + * @param captureLog Whether to capture clog output for + * analysis after the test run. + * @param captureErr Whether to capture stderr output for + * analysis after the test run. + * @param opt Technique for comparing actual output with + * expected output after the test run. + * * Instantiate and define a test case. * All parameters are optional other than the test name and the * test function. @@ -122,24 +138,32 @@ namespace TestCPP { ); /** - * Construct a TestCase by copying it from another TestCase. + * @brief Construct a TestCase by copying it from another + * TestCase. + * @param o The test case from which to make a copy. */ TestCase (TestCase& o); /** - * Construct a TestCase by moving all data from another - * TestCase. + * @brief Construct a TestCase by moving all data from another + * TestCase. + * @param o Move everything from this TestCase into the new one. */ TestCase (TestCase&& o); /** * @brief Copy a TestCase into another TestCase. * @param rhs The test case to copy from. + * @return A reference to the new TestCase copy. */ TestCase& operator= (TestCase& rhs); /** * @brief Move a TestCase into another TestCase. + * @param rhs Move everything from this TestCase into the new + * one. + * @return A reference to the TestCase that everything from the + * old TestCase was moved into. */ TestCase& operator= (TestCase&& rhs); @@ -157,7 +181,7 @@ namespace TestCPP { /** * @brief Set the output comparison mode. * @param opt Accepts specified mode from the referenced enum. - * + * * If this is called with an option specified that is different * from what is set for this test case already, subsequent * calls to any of the output check functions will use the @@ -167,7 +191,7 @@ namespace TestCPP { /** * @brief Clears the captured output from stdout. - * + * * This can be used for checking sections of output based on * test configuration. */ @@ -175,7 +199,7 @@ namespace TestCPP { /** * @brief Clears the captured output from std::clog. - * + * * This can be used for checking sections of output based on * test configuration. */ @@ -183,7 +207,7 @@ namespace TestCPP { /** * @brief Clears the captured output from stderr. - * + * * This can be used for checking sections of output based on * test configuration. */ @@ -225,7 +249,8 @@ namespace TestCPP { /** * @brief Returns the duration of the last run in nanoseconds. - * @return The duration of the last run in nanoseconds. + * @return The duration of the last run of this TestCase in + * nanoseconds. */ long long getLastRuntime (); @@ -243,13 +268,44 @@ namespace TestCPP { TestCaseOutCompareOptions option = TestCaseOutCompareOptions::CONTAINS; + /** + * @brief If instructed, capture stdout while this test is + * active. + */ void captureStdout (); + /** + * @brief If instructed, capture clog while this test is + * active. + */ void captureClog (); + /** + * @brief If instructed, capture stderr while this test is + * active. + */ void captureStdErr (); + /** + * @brief If a test encounters an error while running, this + * function will be called to log the test error. + * @param failureMessage The error message from the test that + * should be logged. + */ void logTestFailure (string failureMessage); + /** + * @brief Internal test run controller. + */ void runTest (); - bool checkOutput (TestCaseOutCompareOptions opt, string source, - string against); + /** + * @brief Handles the internal logic for calls to checkStdout, + * checkLog, and checkStderr based on the selected + * comparison technique for this test. + * @param source The actual output + * @param against The expected output, or a portion of the + * expected output. + * @return True if the argument results in a successful check + * using the configured comparison mode, false + * otherwise. + */ + bool checkOutput (string source, string against); static atomic_int stdoutCaptureCasesConstructed; static atomic_int logCaptureCasesConstructed; @@ -265,6 +321,13 @@ namespace TestCPP { static unique_ptr clogOriginal; static unique_ptr stderrOriginal; + /** + * @brief Measure the duration of a function when run. + * @param func Measure the duration of this function when run. + * @param args A template pack of arguments to apply to the + * given function of which to measure the duration. + * @return The duration of the function run, in nanoseconds. + */ template static nanoseconds duration (F func, Args&&... args) { diff --git a/src/TestCPPTestCase.cpp b/src/TestCPPTestCase.cpp index a76f091..c0bbb96 100644 --- a/src/TestCPPTestCase.cpp +++ b/src/TestCPPTestCase.cpp @@ -392,24 +392,23 @@ namespace TestCPP { } bool TestCase::checkStdout (string against) { - return checkOutput(this->option, TestCase::stdoutBuffer->str(), + return checkOutput(TestCase::stdoutBuffer->str(), against); } bool TestCase::checkLog (string against) { - return checkOutput(this->option, TestCase::clogBuffer->str(), + return checkOutput(TestCase::clogBuffer->str(), against); } bool TestCase::checkStderr (string against) { - return checkOutput(this->option, TestCase::stderrBuffer->str(), + return checkOutput(TestCase::stderrBuffer->str(), against); } - bool TestCase::checkOutput (TestCase::TestCaseOutCompareOptions opt, - string source, string against) + bool TestCase::checkOutput (string source, string against) { - switch (opt) { + switch (this->option) { case EXACT: if (source == against) { return true; From ef8dd1786955669668fd8d8f38a919098b1ae669 Mon Sep 17 00:00:00 2001 From: Jonathan Hyry Date: Sun, 23 Jun 2024 11:45:07 -0700 Subject: [PATCH 24/32] Finish removing reliance on the parameter Whoopsies --- src/TestCPPTestCase.cpp | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/TestCPPTestCase.cpp b/src/TestCPPTestCase.cpp index c0bbb96..4e4a607 100644 --- a/src/TestCPPTestCase.cpp +++ b/src/TestCPPTestCase.cpp @@ -455,7 +455,7 @@ namespace TestCPP { default: stringstream re; - re << TCPPStr::UNK_CMP_OPT_ << opt; + re << TCPPStr::UNK_CMP_OPT_ << this->option; throw TestCPPException(re.str()); } } From 4424d63095b402e16cbb6ee461a0c54064a19bc4 Mon Sep 17 00:00:00 2001 From: Jonathan Hyry Date: Sun, 23 Jun 2024 12:12:09 -0700 Subject: [PATCH 25/32] Remove unused function I don't know why I put this in there in the first place. It has been unused ever since I can remember. I think I was just messing with templates back in 2014 and this seemed like a cool function to implement. --- include/internal/TestCPPTestSuite.h | 10 ---------- 1 file changed, 10 deletions(-) diff --git a/include/internal/TestCPPTestSuite.h b/include/internal/TestCPPTestSuite.h index 4277174..e47717b 100644 --- a/include/internal/TestCPPTestSuite.h +++ b/include/internal/TestCPPTestSuite.h @@ -128,16 +128,6 @@ namespace TestCPP { addTests(tests...); } - /** - * @brief Get the TestCase object that is associated with the - * given test case construction arguments. - * @return The TestCase with the given construction arguments. - */ - template - static T getTestObject (ConstructionArgs ...args) { - return T(args...); - } - /** * @brief Sets the name of this test suite. */ From cbcb1339706944052ce005941d72b063cf93daaa Mon Sep 17 00:00:00 2001 From: Jonathan Hyry Date: Thu, 27 Jun 2024 16:11:29 -0700 Subject: [PATCH 26/32] Fix multiple frees on streams, make sure test failures aren't silenced Make sure stream capture does not affect logging of test failures. ----------------------------------------------------------------------- Created a subroutine for logging test failures where the output stream is injected instead of just using std::clog. This allows the TestCase::logTestFailure function to log to the real clog stream and the captured (if captured) clog stream to ensure any expected output is present for analysis. This ensures test failures are logged even if the stream being logged to is captured for analysis for the given test. This was an oversight on my part, and there are more than likely other instances where this will need to be corrected. Fix multiple frees on streams (stringstreams and streambufs) ----------------------------------------------------------------------- I've been working on this periodically since last month to figure out what is happening. It turns out, std::unique_ptr seems to add an exit handler to the program (this is a possibility, I haven't looked at the STL implementation code to confirm) that calls the deleter when it is a static object. The default deleter just calls delete on the pointed-to object without checking that and with no context around the object. The solution ended up being constructing each std::unique_ptr with a custom no-op deleter to allow the TestCase destructor to properly handle desctruction, since proper functionality of the TestCase code depends on the semaphore behavior defined in the construction and destruction of TestCases depending on the stream capture arguments, so it knows when to free the buffers: only when nothing is using them. The std::unique_ptr template arguments are adjusted accordingly, and the buffers are now freed at the proper time and place. The streambufs are also left alone with custom no-op deleters, as we have no business freeing streambufs that are not ours to delete (the underlying streams for std::cout, std::clog, and std::cerr). --- include/internal/TestCPPTestCase.h | 25 ++++-- src/TestCPPTestCase.cpp | 126 +++++++++++++++++++++-------- 2 files changed, 111 insertions(+), 40 deletions(-) diff --git a/include/internal/TestCPPTestCase.h b/include/internal/TestCPPTestCase.h index 41ff892..0d39113 100644 --- a/include/internal/TestCPPTestCase.h +++ b/include/internal/TestCPPTestCase.h @@ -51,6 +51,7 @@ using std::endl; using std::forward; using std::function; using std::move; +using std::ostream; using std::runtime_error; using std::string; using std::streambuf; @@ -283,6 +284,12 @@ namespace TestCPP { * active. */ void captureStdErr (); + /** + * @brief Write a test failure reason to the specified stream. + * @param out The stream to write the test failure reason to. + * @param reason The test failure reason to write. + */ + void logFailure (ostream& out, string& reason); /** * @brief If a test encounters an error while running, this * function will be called to log the test error. @@ -314,12 +321,18 @@ namespace TestCPP { static atomic_int logCaptureCasesDestroyed; static atomic_int stderrCaptureCasesDestroyed; - static unique_ptr stdoutBuffer; - static unique_ptr clogBuffer; - static unique_ptr stderrBuffer; - static unique_ptr stdoutOriginal; - static unique_ptr clogOriginal; - static unique_ptr stderrOriginal; + static unique_ptr + stdoutBuffer; + static unique_ptr + clogBuffer; + static unique_ptr + stderrBuffer; + static unique_ptr + stdoutOriginal; + static unique_ptr + clogOriginal; + static unique_ptr + stderrOriginal; /** * @brief Measure the duration of a function when run. diff --git a/src/TestCPPTestCase.cpp b/src/TestCPPTestCase.cpp index 4e4a607..16c4072 100644 --- a/src/TestCPPTestCase.cpp +++ b/src/TestCPPTestCase.cpp @@ -49,7 +49,6 @@ using std::fixed; using std::function; using std::invalid_argument; using std::move; -using std::ostream; using std::rethrow_exception; using std::runtime_error; using std::setprecision; @@ -68,12 +67,36 @@ namespace TestCPP { atomic_int TestCase::logCaptureCasesDestroyed; atomic_int TestCase::stderrCaptureCasesDestroyed; - unique_ptr TestCase::stdoutBuffer = nullptr; - unique_ptr TestCase::clogBuffer = nullptr; - unique_ptr TestCase::stderrBuffer = nullptr; - unique_ptr TestCase::stdoutOriginal = nullptr; - unique_ptr TestCase::clogOriginal = nullptr; - unique_ptr TestCase::stderrOriginal = nullptr; + unique_ptr + TestCase::stdoutBuffer = + unique_ptr( + nullptr, [](stringstream*){} + ); + unique_ptr + TestCase::clogBuffer = + unique_ptr( + nullptr, [](stringstream*){} + ); + unique_ptr + TestCase::stderrBuffer = + unique_ptr( + nullptr, [](stringstream*){} + ); + unique_ptr + TestCase::stdoutOriginal = + unique_ptr( + nullptr, [](streambuf*){} + ); + unique_ptr + TestCase::clogOriginal = + unique_ptr( + nullptr, [](streambuf*){} + ); + unique_ptr + TestCase::stderrOriginal = + unique_ptr( + nullptr, [](streambuf*){} + ); TestCase::TestCase (TestObjName&& name, function test, @@ -161,6 +184,7 @@ namespace TestCPP { TestCase::stdoutCaptureCasesConstructed - 1) { cout.rdbuf(TestCase::stdoutOriginal.release()); + delete TestCase::stdoutBuffer.get(); TestCase::stdoutBuffer = nullptr; } @@ -172,6 +196,7 @@ namespace TestCPP { TestCase::logCaptureCasesConstructed - 1) { clog.rdbuf(TestCase::clogOriginal.release()); + delete TestCase::clogBuffer.get(); TestCase::clogBuffer = nullptr; } @@ -183,6 +208,7 @@ namespace TestCPP { TestCase::stderrCaptureCasesConstructed - 1) { cerr.rdbuf(TestCase::stderrOriginal.release()); + delete TestCase::stderrBuffer.get(); TestCase::stderrBuffer = nullptr; } @@ -248,16 +274,42 @@ namespace TestCPP { return this->lastRunTime; } + void TestCase::logFailure(ostream& out, string& reason) { + out << fixed; + out << setprecision(TCPPNum::TIME_PRECISION); + out << TCPPStr::TEST_ << this->testName << TCPPStr::_FAIL_ + << TCPPStr::PARENL + << static_cast(this->lastRunTime)/ + TCPPNum::NANOS_IN_SEC + << TCPPStr::SEC << TCPPStr::PARENR + << endl; + out << TCPPStr::REASON_ << reason << endl; + } + void TestCase::logTestFailure (string reason) { - clog << fixed; - clog << setprecision(TCPPNum::TIME_PRECISION); - clog << TCPPStr::TEST_ << this->testName << TCPPStr::_FAIL_ - << TCPPStr::PARENL - << static_cast(this->lastRunTime)/ - TCPPNum::NANOS_IN_SEC - << TCPPStr::SEC << TCPPStr::PARENR - << endl; - clog << TCPPStr::REASON_ << reason << endl; + unique_ptr logStream = nullptr; + + if (this->clogOriginal != nullptr) { + logStream = unique_ptr( + new ostream(this->clogOriginal.get()) + ); + } + else { + logStream = unique_ptr(&clog); + } + + logFailure(*logStream, reason); + + if (this->clogOriginal != nullptr) { + logStream->flush(); + + // If someone is looking for something in the message, + // and it's captured, make sure it's there. + logFailure(clog, reason); + } + + logStream.release(); + logStream.reset(); } void TestCase::runTest () { @@ -310,12 +362,14 @@ namespace TestCPP { TestCase::stdoutCaptureCasesDestroyed) { TestCase::stdoutCaptureCasesConstructed += 1; - TestCase::stdoutBuffer = unique_ptr( - new stringstream() - ); - TestCase::stdoutOriginal = unique_ptr( - cout.rdbuf() - ); + TestCase::stdoutBuffer = + unique_ptr( + new stringstream(), [](stringstream *) {} + ); + TestCase::stdoutOriginal = + unique_ptr( + cout.rdbuf(), [](streambuf *) {} + ); cout.rdbuf(TestCase::stdoutBuffer->rdbuf()); } else { @@ -328,12 +382,14 @@ namespace TestCPP { TestCase::logCaptureCasesDestroyed) { TestCase::logCaptureCasesConstructed += 1; - TestCase::clogBuffer = unique_ptr( - new stringstream() - ); - TestCase::clogOriginal = unique_ptr( - clog.rdbuf() - ); + TestCase::clogBuffer = + unique_ptr( + new stringstream(), [](stringstream *) {} + ); + TestCase::clogOriginal = + unique_ptr( + cout.rdbuf(), [](streambuf *) {} + ); clog.rdbuf(TestCase::clogBuffer->rdbuf()); } else { @@ -346,12 +402,14 @@ namespace TestCPP { TestCase::stderrCaptureCasesDestroyed) { TestCase::stderrCaptureCasesConstructed += 1; - TestCase::stderrBuffer = unique_ptr( - new stringstream() - ); - TestCase::stderrOriginal = unique_ptr( - cerr.rdbuf() - ); + TestCase::stderrBuffer = + unique_ptr( + new stringstream(), [](stringstream *) {} + ); + TestCase::stderrOriginal = + unique_ptr( + cout.rdbuf(), [](streambuf *) {} + ); cerr.rdbuf(TestCase::stderrBuffer->rdbuf()); } else { From 62e852561abf0d8465c63aeb640e16551036f8db Mon Sep 17 00:00:00 2001 From: Jonathan Hyry Date: Thu, 27 Jun 2024 16:17:58 -0700 Subject: [PATCH 27/32] Clean up how test failures in a suite are recorded; Fixed failing tests There is now only one place for incrementing, and the conditions are now logically split and actions clearer. Tests in TestSuiteTests are no longer failing. The output buffer was not being cleared and there was lingering buffer content from previous tests. The tests were checking that nothing was logged related to test passage, so the checks for lack of output were failing. All tests pass! :) --- src/TestCPPTestSuite.cpp | 10 +++++----- test/src/TestSuite/TestSuiteTests.cpp | 9 +++++++++ 2 files changed, 14 insertions(+), 5 deletions(-) diff --git a/src/TestCPPTestSuite.cpp b/src/TestCPPTestSuite.cpp index 89736a7..c5b04fa 100644 --- a/src/TestCPPTestSuite.cpp +++ b/src/TestCPPTestSuite.cpp @@ -106,12 +106,12 @@ namespace TestCPP { << endl; } - if (!testPassed && this->lastRunSucceeded) { - this->lastRunFailCount++; - this->lastRunSucceeded = false; - } - else if (!testPassed) { + if (!testPassed) { this->lastRunFailCount++; + + if (this->lastRunSucceeded) { + this->lastRunSucceeded = false; + } } else { this->lastRunSuccessCount++; diff --git a/test/src/TestSuite/TestSuiteTests.cpp b/test/src/TestSuite/TestSuiteTests.cpp index 19dc164..52189a1 100644 --- a/test/src/TestSuite/TestSuiteTests.cpp +++ b/test/src/TestSuite/TestSuiteTests.cpp @@ -336,6 +336,8 @@ namespace TestCPP { suiteName, test )); + test.clearLogCapture(); + testSuite->disableTestPassedMessage(); testSuite->run(); @@ -377,6 +379,8 @@ namespace TestCPP { suiteName, test )); + test.clearLogCapture(); + testSuite->disableTestPassedMessage(); testSuite->run(); @@ -431,6 +435,9 @@ namespace TestCPP { auto testSuite = unique_ptr(new TestSuite( suiteName, test1, test2, test3 )); + + test1.clearLogCapture(); + testSuite->disableTestPassedMessage(); testSuite->run(); @@ -504,6 +511,8 @@ namespace TestCPP { suiteName, test1, test2, test3 )); + test1.clearLogCapture(); + testSuite->disableTestPassedMessage(); testSuite->run(); From 4c57775e3563a006b0bf991ffcb41f76cb30d50b Mon Sep 17 00:00:00 2001 From: Jonathan Hyry Date: Thu, 27 Jun 2024 16:39:38 -0700 Subject: [PATCH 28/32] Another windows-only segfault Try to figure out where this is happening since I have no windows box to test this on. --- test/src/TestCase/TestCaseTestChunks.cpp | 8 ++++++++ 1 file changed, 8 insertions(+) diff --git a/test/src/TestCase/TestCaseTestChunks.cpp b/test/src/TestCase/TestCaseTestChunks.cpp index 661cd48..f6854f9 100644 --- a/test/src/TestCase/TestCaseTestChunks.cpp +++ b/test/src/TestCase/TestCaseTestChunks.cpp @@ -71,48 +71,56 @@ namespace TestCPP { } void varyingCaptureLog () { + debugLog("CaptureLog param ctor test chunk FFF"); auto test = unique_ptr(new TestCase( "ConstructCase Test - w/NF,COF,CLF", function([](){}), false, false, false )); + debugLog("CaptureLog param ctor test chunk FFT"); test = unique_ptr(new TestCase( "ConstructCase Test - w/NF,COF,CLT", function([](){}), false, false, true )); + debugLog("CaptureLog param ctor test chunk FTF"); test = unique_ptr(new TestCase( "ConstructCase Test - w/NF,COT,CLF", function([](){}), false, true, false )); + debugLog("CaptureLog param ctor test chunk FTT"); test = unique_ptr(new TestCase( "ConstructCase Test - w/NF,COT,CLT", function([](){}), false, true, true )); + debugLog("CaptureLog param ctor test chunk TFF"); test = unique_ptr(new TestCase( "ConstructCase Test - w/NT,COF,CLF", function([](){}), true, false, false )); + debugLog("CaptureLog param ctor test chunk TFT"); test = unique_ptr(new TestCase( "ConstructCase Test - w/NT,COF,CLT", function([](){}), true, false, true )); + debugLog("CaptureLog param ctor test chunk TTF"); test = unique_ptr(new TestCase( "ConstructCase Test - w/NT,COT,CLF", function([](){}), true, true, false )); + debugLog("CaptureLog param ctor test chunk TTT"); test = unique_ptr(new TestCase( "ConstructCase Test - w/NT,COT,CLT", function([](){}), From aee613697de85ce45cdc9b4cfd9f5f6ad9c6e7ec Mon Sep 17 00:00:00 2001 From: Jonathan Hyry Date: Thu, 27 Jun 2024 16:49:08 -0700 Subject: [PATCH 29/32] Windows segfault - deeper debug logging more debug logging --- src/TestCPPTestCase.cpp | 14 ++++++++++++++ 1 file changed, 14 insertions(+) diff --git a/src/TestCPPTestCase.cpp b/src/TestCPPTestCase.cpp index 16c4072..30205f1 100644 --- a/src/TestCPPTestCase.cpp +++ b/src/TestCPPTestCase.cpp @@ -105,26 +105,40 @@ namespace TestCPP { bool captureErr, TestCase::TestCaseOutCompareOptions opt) { + debugLog("CaptureLog windows segfault check - open"); this->notifyTestPassed = msg; + debugLog("CaptureLog windows segfault check - ntp bool"); this->test = test; + debugLog("CaptureLog windows segfault check - test fn"); this->testName = name; + debugLog("CaptureLog windows segfault check - test name"); if (captureOut) { + debugLog("CaptureLog windows segfault check - stdout cap"); captureStdout(); + debugLog("CaptureLog windows segfault check - stdout end"); } if (captureLog) { + debugLog("CaptureLog windows segfault check - clog cap"); captureClog(); + debugLog("CaptureLog windows segfault check - clog end"); } if (captureErr) { + debugLog("CaptureLog windows segfault check - stderr cap"); captureStdErr(); + debugLog("CaptureLog windows segfault check - stderr end"); } this->stdoutCaptured = captureOut; + debugLog("CaptureLog windows segfault check - stdout bool"); this->clogCaptured = captureLog; + debugLog("CaptureLog windows segfault check - clog bool"); this->stderrCaptured = captureErr; + debugLog("CaptureLog windows segfault check - stderr bool"); this->option = opt; + debugLog("CaptureLog windows segfault check - cmp opt"); } TestCase::TestCase (TestCase& o) { From ad69700168cde071514b902fcb0058ab46b8abe6 Mon Sep 17 00:00:00 2001 From: Jonathan Hyry Date: Thu, 27 Jun 2024 17:02:56 -0700 Subject: [PATCH 30/32] Windows segfault debugging - remove some dbg logging, add deeper logging debug logging for windows segfault issue --- src/TestCPPTestCase.cpp | 21 +++++++++------------ test/src/TestCase/TestCaseTestChunks.cpp | 4 ---- 2 files changed, 9 insertions(+), 16 deletions(-) diff --git a/src/TestCPPTestCase.cpp b/src/TestCPPTestCase.cpp index 30205f1..96ed883 100644 --- a/src/TestCPPTestCase.cpp +++ b/src/TestCPPTestCase.cpp @@ -105,19 +105,13 @@ namespace TestCPP { bool captureErr, TestCase::TestCaseOutCompareOptions opt) { - debugLog("CaptureLog windows segfault check - open"); this->notifyTestPassed = msg; - debugLog("CaptureLog windows segfault check - ntp bool"); this->test = test; - debugLog("CaptureLog windows segfault check - test fn"); this->testName = name; - debugLog("CaptureLog windows segfault check - test name"); if (captureOut) { - debugLog("CaptureLog windows segfault check - stdout cap"); captureStdout(); - debugLog("CaptureLog windows segfault check - stdout end"); } if (captureLog) { debugLog("CaptureLog windows segfault check - clog cap"); @@ -125,20 +119,14 @@ namespace TestCPP { debugLog("CaptureLog windows segfault check - clog end"); } if (captureErr) { - debugLog("CaptureLog windows segfault check - stderr cap"); captureStdErr(); - debugLog("CaptureLog windows segfault check - stderr end"); } this->stdoutCaptured = captureOut; - debugLog("CaptureLog windows segfault check - stdout bool"); this->clogCaptured = captureLog; - debugLog("CaptureLog windows segfault check - clog bool"); this->stderrCaptured = captureErr; - debugLog("CaptureLog windows segfault check - stderr bool"); this->option = opt; - debugLog("CaptureLog windows segfault check - cmp opt"); } TestCase::TestCase (TestCase& o) { @@ -392,23 +380,32 @@ namespace TestCPP { } void TestCase::captureClog () { + debugLog("captureClog winseg check - open"); if (TestCase::logCaptureCasesConstructed == TestCase::logCaptureCasesDestroyed) { + debugLog("captureClog winseg check - clogConstruct"); TestCase::logCaptureCasesConstructed += 1; + debugLog("captureClog winseg check - atomic++"); TestCase::clogBuffer = unique_ptr( new stringstream(), [](stringstream *) {} ); + debugLog("captureClog winseg check - clogBuffer"); TestCase::clogOriginal = unique_ptr( cout.rdbuf(), [](streambuf *) {} ); + debugLog("captureClog winseg check - clogStream"); clog.rdbuf(TestCase::clogBuffer->rdbuf()); + debugLog("captureClog winseg check - clogSet"); } else { + debugLog("captureClog winseg check - clogNoConstruct"); TestCase::logCaptureCasesConstructed += 1; + debugLog("captureClog winseg check - atomic++"); } + debugLog("captureClog winseg check - close"); } void TestCase::captureStdErr () { diff --git a/test/src/TestCase/TestCaseTestChunks.cpp b/test/src/TestCase/TestCaseTestChunks.cpp index f6854f9..6fa4776 100644 --- a/test/src/TestCase/TestCaseTestChunks.cpp +++ b/test/src/TestCase/TestCaseTestChunks.cpp @@ -71,21 +71,17 @@ namespace TestCPP { } void varyingCaptureLog () { - debugLog("CaptureLog param ctor test chunk FFF"); auto test = unique_ptr(new TestCase( "ConstructCase Test - w/NF,COF,CLF", function([](){}), false, false, false )); - debugLog("CaptureLog param ctor test chunk FFT"); test = unique_ptr(new TestCase( "ConstructCase Test - w/NF,COF,CLT", function([](){}), false, false, true )); - - debugLog("CaptureLog param ctor test chunk FTF"); test = unique_ptr(new TestCase( "ConstructCase Test - w/NF,COT,CLF", function([](){}), From 4f337a167ff3c833be6c19169f3f8a06737e136b Mon Sep 17 00:00:00 2001 From: Jonathan Hyry Date: Thu, 27 Jun 2024 17:16:16 -0700 Subject: [PATCH 31/32] Fixed a bug where cout buffer was being used instead of clog and cerr --- src/TestCPPTestCase.cpp | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/src/TestCPPTestCase.cpp b/src/TestCPPTestCase.cpp index 96ed883..7b5588d 100644 --- a/src/TestCPPTestCase.cpp +++ b/src/TestCPPTestCase.cpp @@ -394,7 +394,7 @@ namespace TestCPP { debugLog("captureClog winseg check - clogBuffer"); TestCase::clogOriginal = unique_ptr( - cout.rdbuf(), [](streambuf *) {} + clog.rdbuf(), [](streambuf *) {} ); debugLog("captureClog winseg check - clogStream"); clog.rdbuf(TestCase::clogBuffer->rdbuf()); @@ -419,7 +419,7 @@ namespace TestCPP { ); TestCase::stderrOriginal = unique_ptr( - cout.rdbuf(), [](streambuf *) {} + cerr.rdbuf(), [](streambuf *) {} ); cerr.rdbuf(TestCase::stderrBuffer->rdbuf()); } From b408120f6384dbb6106caec69b594ddb38a4148b Mon Sep 17 00:00:00 2001 From: Jonathan Hyry Date: Thu, 27 Jun 2024 17:59:34 -0700 Subject: [PATCH 32/32] It looks like that bug fix fixed the windows segfault in the TC tests Removing the debug logs. Since the std::streambuf pointer returned by cout.rdbuf() result was already assigned to a std::unique_ptr, it must have been what was causing the segfault (really an Access Violation, this is Windows not *nix/BSD). The weird thing is that the same code was not causing the issue on any other platform with any other compiler. The only time the issue popped up was on Windows with the MSVC++ cl compiler, compiling with debug options, where there were no compile warnings or errors! Very strange, but the good news is it pointed out 2 really severe but subtle bugs of the same kind in 2 different places, where the wrong std::streambuf was being stored for output capturing, which would have caused an inability to revert the capture for tests that don't capture output after the capturing test(s) have finished running. All because of a copy-paste error 2 times over. Yeesh. --- src/TestCPPTestCase.cpp | 9 --------- test/src/TestCase/TestCaseTestChunks.cpp | 5 ----- 2 files changed, 14 deletions(-) diff --git a/src/TestCPPTestCase.cpp b/src/TestCPPTestCase.cpp index 7b5588d..475aae4 100644 --- a/src/TestCPPTestCase.cpp +++ b/src/TestCPPTestCase.cpp @@ -380,32 +380,23 @@ namespace TestCPP { } void TestCase::captureClog () { - debugLog("captureClog winseg check - open"); if (TestCase::logCaptureCasesConstructed == TestCase::logCaptureCasesDestroyed) { - debugLog("captureClog winseg check - clogConstruct"); TestCase::logCaptureCasesConstructed += 1; - debugLog("captureClog winseg check - atomic++"); TestCase::clogBuffer = unique_ptr( new stringstream(), [](stringstream *) {} ); - debugLog("captureClog winseg check - clogBuffer"); TestCase::clogOriginal = unique_ptr( clog.rdbuf(), [](streambuf *) {} ); - debugLog("captureClog winseg check - clogStream"); clog.rdbuf(TestCase::clogBuffer->rdbuf()); - debugLog("captureClog winseg check - clogSet"); } else { - debugLog("captureClog winseg check - clogNoConstruct"); TestCase::logCaptureCasesConstructed += 1; - debugLog("captureClog winseg check - atomic++"); } - debugLog("captureClog winseg check - close"); } void TestCase::captureStdErr () { diff --git a/test/src/TestCase/TestCaseTestChunks.cpp b/test/src/TestCase/TestCaseTestChunks.cpp index 6fa4776..a022227 100644 --- a/test/src/TestCase/TestCaseTestChunks.cpp +++ b/test/src/TestCase/TestCaseTestChunks.cpp @@ -88,35 +88,30 @@ namespace TestCPP { false, true, false )); - debugLog("CaptureLog param ctor test chunk FTT"); test = unique_ptr(new TestCase( "ConstructCase Test - w/NF,COT,CLT", function([](){}), false, true, true )); - debugLog("CaptureLog param ctor test chunk TFF"); test = unique_ptr(new TestCase( "ConstructCase Test - w/NT,COF,CLF", function([](){}), true, false, false )); - debugLog("CaptureLog param ctor test chunk TFT"); test = unique_ptr(new TestCase( "ConstructCase Test - w/NT,COF,CLT", function([](){}), true, false, true )); - debugLog("CaptureLog param ctor test chunk TTF"); test = unique_ptr(new TestCase( "ConstructCase Test - w/NT,COT,CLF", function([](){}), true, true, false )); - debugLog("CaptureLog param ctor test chunk TTT"); test = unique_ptr(new TestCase( "ConstructCase Test - w/NT,COT,CLT", function([](){}),