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/TestFramework.project b/TestFramework.project index d6badc7..bab697a 100644 --- a/TestFramework.project +++ b/TestFramework.project @@ -3,12 +3,22 @@ - + + + + - + + + + + + + + @@ -48,6 +58,10 @@ + + + + @@ -59,6 +73,10 @@ + + + + diff --git a/cmake/BuildTypeHandling.cmake b/cmake/BuildTypeHandling.cmake index eba34cd..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 @@ -135,6 +145,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 +180,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 +217,11 @@ else () PUBLIC DEBUG_LOG ) + target_compile_definitions ( + ${PROJECT_NAME}_Assertions_test + PUBLIC + DEBUG_LOG + ) endif () if (MSVC) @@ -224,6 +251,12 @@ else () PUBLIC ${MSVC_DEBUG_BUILD_OPTS} ) + + target_compile_options ( + ${PROJECT_NAME}_Assertions_test + PUBLIC + ${MSVC_DEBUG_BUILD_OPTS} + ) endif () else () @@ -256,6 +289,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..7606865 100644 --- a/cmake/Installing.cmake +++ b/cmake/Installing.cmake @@ -1,8 +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 + "${TESTCPP_PRIVATE_HEADERS}" ) include (GNUInstallDirs) install ( @@ -10,6 +33,8 @@ install ( 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 () diff --git a/demo/src/main.cpp b/demo/src/main.cpp index eb65b50..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; @@ -12,7 +11,7 @@ int main(void) { try { TestSuite suite( - string("Demo Test Suite"), + "Demo Test Suite", make_tuple( "simpleTest", 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/include/TestCPP.h b/include/TestCPP.h index 0567063..74f6ad2 100644 --- a/include/TestCPP.h +++ b/include/TestCPP.h @@ -25,313 +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 TestCaseName { - public: - TestCaseName () = default; - TestCaseName (const char* name); - - const string& getTestName (); - - friend std::ostream& operator<< ( - std::ostream& s, - TestCaseName& tcName - ) - { - s << tcName.getTestName(); - return s; - } - - private: - string testCaseName; - - }; - - class TestCase { - - public: - enum TestCaseOutCompareOptions { - CONTAINS, - EXACT - }; - - TestCase ( - TestCaseName&& 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; - - TestCaseName 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 - ); - } - }; - - class TestSuite { - - public: - template - TestSuite (string suiteName, - typename enable_if::type) - { - this->testPassedMessage = true; - this->setSuiteName(suiteName); - this->tests = vector(); - } - - template - TestSuite (string suiteName, TestType ...tests) { - this->testPassedMessage = true; - this->setSuiteName(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 (string 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; - - string 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..8032643 --- /dev/null +++ b/include/internal/TestCPPAssertions.h @@ -0,0 +1,260 @@ +/* +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 +#include +#include +#include + +#include "TestCPPExceptions.h" + +using std::endl; +using std::function; +using std::string; +using std::stringstream; + +/** + * 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..0d39113 --- /dev/null +++ b/include/internal/TestCPPTestCase.h @@ -0,0 +1,356 @@ +/* +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::ostream; +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 + }; + + /** + * @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. + * 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 + ); + + /** + * @brief Construct a TestCase by copying it from another + * TestCase. + * @param o The test case from which to make a copy. + */ + TestCase (TestCase& o); + + /** + * @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); + + /** + * @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 of this TestCase in + * nanoseconds. + */ + long long getLastRuntime (); + + private: + 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::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 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. + * @param failureMessage The error message from the test that + * should be logged. + */ + void logTestFailure (string failureMessage); + /** + * @brief Internal test run controller. + */ + void runTest (); + /** + * @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; + 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; + + /** + * @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) + { + 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..e47717b --- /dev/null +++ b/include/internal/TestCPPTestSuite.h @@ -0,0 +1,173 @@ +/* +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->testSuitePassedMessage = 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->testSuitePassedMessage = true; + this->setSuiteName(move(suiteName)); + this->tests = vector(); + + this->addTests(tests...); + } + + /** + * @brief Add a test to this test suite. + * + * Appropriate specializations defined in the source file. + */ + template + void addTest (T&& test); + + /** + * @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. + * @param tests The rest of the tests to add. + */ + template + void addTests (Test test, OtherTests ...tests) { + addTest(move(test)); + addTests(tests...); + } + + /** + * @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 testSuitePassedMessage; + 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..045def4 --- /dev/null +++ b/include/internal/TestCPPUtil.h @@ -0,0 +1,133 @@ +/* +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 + ); + + 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..f689234 --- /dev/null +++ b/src/TestCPPAssertions.cpp @@ -0,0 +1,124 @@ +/* +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" + +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 53% rename from src/TestCPP.cpp rename to src/TestCPPTestCase.cpp index aa287e5..475aae4 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,71 +37,28 @@ 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; using std::move; -using std::ostream; using std::rethrow_exception; using std::runtime_error; 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 - } +using TCPPNum = TestCPP::TestCPPCommon::Nums; +using TCPPStr = TestCPP::TestCPPCommon::Strings; - TestCaseName::TestCaseName (const char* name) { - if (name) { - this->testCaseName = name; - } - else { - throw TestCPPException("Not a valid test name!"); - } - } - - const string& TestCaseName::getTestName () { - return this->testCaseName; - } +namespace TestCPP { atomic_int TestCase::stdoutCaptureCasesConstructed; atomic_int TestCase::logCaptureCasesConstructed; @@ -108,14 +67,38 @@ 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; - - TestCase::TestCase (TestCaseName&& name, + 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, bool msg, bool captureOut, bool captureLog, @@ -131,12 +114,18 @@ namespace TestCPP { captureStdout(); } if (captureLog) { + debugLog("CaptureLog windows segfault check - clog cap"); captureClog(); + debugLog("CaptureLog windows segfault check - clog end"); } if (captureErr) { captureStdErr(); } + this->stdoutCaptured = captureOut; + this->clogCaptured = captureLog; + this->stderrCaptured = captureErr; + this->option = opt; } @@ -147,6 +136,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; } @@ -158,6 +161,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); } @@ -169,6 +186,7 @@ namespace TestCPP { TestCase::stdoutCaptureCasesConstructed - 1) { cout.rdbuf(TestCase::stdoutOriginal.release()); + delete TestCase::stdoutBuffer.get(); TestCase::stdoutBuffer = nullptr; } @@ -180,6 +198,7 @@ namespace TestCPP { TestCase::logCaptureCasesConstructed - 1) { clog.rdbuf(TestCase::clogOriginal.release()); + delete TestCase::clogBuffer.get(); TestCase::clogBuffer = nullptr; } @@ -191,6 +210,7 @@ namespace TestCPP { TestCase::stderrCaptureCasesConstructed - 1) { cerr.rdbuf(TestCase::stderrOriginal.release()); + delete TestCase::stderrBuffer.get(); TestCase::stderrBuffer = nullptr; } @@ -205,6 +225,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; @@ -218,6 +252,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); @@ -228,24 +276,56 @@ 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(4); - clog << "Test " << this->testName << " failed! (" - << static_cast(this->lastRunTime) - /1000000000.0 << "s)" << endl; - clog << "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 () { - clog << "Starting run of test " << 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 << "Test " << this->testName << " passed! (" - << static_cast(this->lastRunTime) - /1000000000.0 << "s)" << 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; } @@ -259,7 +339,7 @@ namespace TestCPP { this->pass = false; logTestFailure(errorMessage); } - catch(string errorMessage) { + catch(string& errorMessage) { this->pass = false; logTestFailure(errorMessage); } @@ -269,7 +349,7 @@ namespace TestCPP { } catch (...) { this->pass = false; - logTestFailure("Unknown error occurred in test!"); + logTestFailure(TCPPStr::UNK_EXC); } return false; @@ -284,12 +364,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 { @@ -302,12 +384,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( + clog.rdbuf(), [](streambuf *) {} + ); clog.rdbuf(TestCase::clogBuffer->rdbuf()); } else { @@ -320,12 +404,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( + cerr.rdbuf(), [](streambuf *) {} + ); cerr.rdbuf(TestCase::stderrBuffer->rdbuf()); } else { @@ -342,7 +428,7 @@ namespace TestCPP { default: stringstream error; - error << "Unknown option " << opt; + error << TCPPStr::UNK_OPT_ << opt; throw TestCPPException(error.str()); } } @@ -366,32 +452,32 @@ 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; } else { stringstream nomatch; - nomatch << "'" << source << "' is not equivalent to '"; - nomatch << against << "'"; + nomatch << TCPPStr::APOS << source << TCPPStr::APOS; + nomatch << TCPPStr::_NEQUIV_ << TCPPStr::APOS; + nomatch << against << TCPPStr::APOS; if (this->clogOriginal != nullptr) { ostream tmp(this->clogOriginal.get()); @@ -411,8 +497,9 @@ namespace TestCPP { } else { stringstream nomatch; - nomatch << "'" << source << "' does not contain '"; - nomatch << against << "'"; + nomatch << TCPPStr::APOS << source << TCPPStr::APOS; + nomatch << TCPPStr::_NCONTAIN_ << TCPPStr::APOS; + nomatch << against << TCPPStr::APOS; if (this->clogOriginal != nullptr) { ostream tmp(this->clogOriginal.get()); @@ -428,185 +515,8 @@ namespace TestCPP { default: stringstream re; - re << "Unknown comparison option! " << opt; + re << TCPPStr::UNK_CMP_OPT_ << this->option; 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 (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()); - } - } - - 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..c5b04fa --- /dev/null +++ b/src/TestCPPTestSuite.cpp @@ -0,0 +1,172 @@ +/* +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->testSuitePassedMessage = true; + for (unsigned i = 0; i < this->tests.size(); i += 1) { + this->tests[i].setNotifyPassed(true); + } + } + void TestSuite::disableTestPassedMessage () { + this->testSuitePassedMessage = false; + for (unsigned i = 0; i < this->tests.size(); i += 1) { + this->tests[i].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->lastRunFailCount++; + + if (this->lastRunSucceeded) { + this->lastRunSucceeded = false; + } + } + else { + this->lastRunSuccessCount++; + } + + this->totalRuntime += test.getLastRuntime(); + } + + clog << endl; + + if (this->testSuitePassedMessage && + 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; + } + + /** + * @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 + ); + } +} diff --git a/src/TestCPPUtil.cpp b/src/TestCPPUtil.cpp index b48693e..671d8ea 100644 --- a/src/TestCPPUtil.cpp +++ b/src/TestCPPUtil.cpp @@ -28,10 +28,14 @@ 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 +#else +#include #endif #ifdef DEBUG_LOG @@ -39,8 +43,34 @@ 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; + } + + std::ostream& operator<< ( + std::ostream& s, + TestObjName& tcName + ) + { + s << tcName.getName(); + return s; + } + namespace Util { + void debugLog(const string& message, bool omitNewline) { #ifdef DEBUG_LOG clog << message; 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/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/include/TestSuite/TestSuiteSuite.h b/test/include/TestSuite/TestSuiteSuite.h index ca195ec..68cef2c 100644 --- a/test/include/TestSuite/TestSuiteSuite.h +++ b/test/include/TestSuite/TestSuiteSuite.h @@ -10,24 +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( - "assertNull Test", - function(TestSuiteTests::TestAssertNull) + "Suite construction Test - TestCases", + function( + TestSuiteTests::TestConstructSuiteTestCases + ) ), make_tuple( - "assertNotNull Test", - function(TestSuiteTests::TestAssertNotNull) + "Suite construction Test - tuples", + function( + TestSuiteTests::TestConstructSuiteTuples + ) ), make_tuple( - "assertTrue Test", - function(TestSuiteTests::TestAssertTrue) + "Suite construction Test - mixed", + function( + TestSuiteTests::TestConstructSuiteMixed + ) ), make_tuple( - "assertFalse Test", - function(TestSuiteTests::TestAssertFalse) + "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 22aeb86..878c6a6 100644 --- a/test/include/TestSuite/TestSuiteTests.h +++ b/test/include/TestSuite/TestSuiteTests.h @@ -4,11 +4,16 @@ namespace TestCPP { namespace Testing { namespace TestSuiteTests { - void TestConstructSuite (); - void TestAssertTrue (); - void TestAssertFalse (); - void TestAssertNull (); - void TestAssertNotNull (); + 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/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..a022227 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; @@ -83,7 +82,6 @@ namespace TestCPP { function([](){}), false, false, true )); - test = unique_ptr(new TestCase( "ConstructCase Test - w/NF,COT,CLF", function([](){}), 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" ); diff --git a/test/src/TestSuite/TestSuiteTests.cpp b/test/src/TestSuite/TestSuiteTests.cpp index 6ee58d7..52189a1 100644 --- a/test/src/TestSuite/TestSuiteTests.cpp +++ b/test/src/TestSuite/TestSuiteTests.cpp @@ -4,50 +4,538 @@ 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", [](){}); - void TestAssertTrue () { - int lower = 5; - int higher = 9; + 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 + )); + } - TestSuite::assertTrue(higher > lower, - "Negated condtion!"); + 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(); - void TestAssertFalse () { - int lower = 5; - int higher = 9; + test = TestCase( + testName, + [](){}, + true, + false, + true, + false + ); + + testSuite = unique_ptr(new TestSuite( + suiteName, test + )); + + testSuite->enableTestPassedMessage(); + testSuite->run(); - TestSuite::assertFalse(higher < lower, - "Negated condtion!"); + 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 + )); - void TestAssertNull () { - string * nullString = nullptr; + testSuite->enableTestPassedMessage(); + testSuite->run(); - TestSuite::assertNull(nullString, "nullptr is null!"); + 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 TestAssertNotNull () { - string notNull("non-null"); + 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"; - TestSuite::assertNotNull( - ¬Null, - "A constructed std::string is not null!" + TestCase test( + testName, + [](){}, + false, + false, + true, + false ); - TestSuite::assertNotNull( - "non-null", - "A const char * is not null!" + + auto testSuite = unique_ptr(new TestSuite( + suiteName, test + )); + + test.clearLogCapture(); + + 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 + )); + + test.clearLogCapture(); - int testInt = 5; + testSuite->disableTestPassedMessage(); + testSuite->run(); - TestSuite::assertNotNull(&testInt, - "An int pointer is not null!"); + 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 + )); + + test1.clearLogCapture(); + + 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 + )); + + test1.clearLogCapture(); + + 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" + ); } } }