diff --git a/.gitignore b/.gitignore index db19af5..f5c0f37 100644 --- a/.gitignore +++ b/.gitignore @@ -10,3 +10,4 @@ compile_commands.json notes.txt *.exe .cache +.clang-format diff --git a/README.md b/README.md index c364f54..d119c0c 100644 --- a/README.md +++ b/README.md @@ -14,6 +14,7 @@ Quick description, and links to the documentation of all the libraries of the pr - [Text](./zero/ifc/text/README.md) - Offers utilities for text manipulation, string splitting, formatting, styling, and print to console functionalities. - [Types](./zero/ifc/types/README.md) - Information about types and traits for types - [Math](./zero/ifc/math/README.md) - Mathematical operations and functions +- [Test Suite](./zero/ifc/test-suite/README.md) - A flexible and simple test-suite. ## Sponsorship @@ -48,16 +49,6 @@ system in a real world environment, so people can profit by taking examples of h in a big scale project. Also, we introduce the changes of the latest releases available here, so it serves as well as a kind of latest up-to-date guide. -## Testing tools - -We are using [`catch2`](https://github.com/catchorg/Catch2) as the testing suite for `Zero`, in its *header-only* version. - -We made a try to replace it with `boost::ut`, that spots that is compatible with modules, but we are getting all kind of errors in the process of convert the provided header to a precompiled module, so we will stay -with `catch2` for now. - -Also, we are looking for generate a precompiled header of the Catch's suite in order to decrement -the tests compilation time. - ## General guidelines and API design This entry is a collection of both the things that we're looking for, or focusing on diff --git a/zero/assets/tsuite_results.png b/zero/assets/tsuite_results.png deleted file mode 100644 index 0e43d4f..0000000 Binary files a/zero/assets/tsuite_results.png and /dev/null differ diff --git a/zero/assets/tsuite_results_ABORT_ALL_ON_FAIL_mode.png b/zero/assets/tsuite_results_ABORT_ALL_ON_FAIL_mode.png new file mode 100644 index 0000000..68f9eec Binary files /dev/null and b/zero/assets/tsuite_results_ABORT_ALL_ON_FAIL_mode.png differ diff --git a/zero/assets/tsuite_results_CONTINUE_ON_ERROR_mode.png b/zero/assets/tsuite_results_CONTINUE_ON_ERROR_mode.png new file mode 100644 index 0000000..44c4e91 Binary files /dev/null and b/zero/assets/tsuite_results_CONTINUE_ON_ERROR_mode.png differ diff --git a/zero/assets/tsuite_results_HALT_SUITE_ON_FAIL_mode.png b/zero/assets/tsuite_results_HALT_SUITE_ON_FAIL_mode.png new file mode 100644 index 0000000..7964bf6 Binary files /dev/null and b/zero/assets/tsuite_results_HALT_SUITE_ON_FAIL_mode.png differ diff --git a/zero/ifc/test-suite/README.md b/zero/ifc/test-suite/README.md index 02b698d..f3ad231 100644 --- a/zero/ifc/test-suite/README.md +++ b/zero/ifc/test-suite/README.md @@ -65,6 +65,21 @@ Test cases within a suite can be registered using the `TEST_CASE(...)` function, just like standalone tests, but choosing the overload that receives as its first parameter a reference to the test suite. +### **Test Modes** +In the pursuit of flexibility and control over the test execution process, our testing framework offers different modes of operation. These modes determine the test runner's behavior in response to test failures, allowing users to tailor the testing process according to their specific needs or the nature of the tests being run. + +1. **CONTINUE_ON_ERROR** + - **Description**: This mode ensures that all tests are executed regardless of any failures encountered. It's ideal for comprehensive error analysis or understanding the full extent of issues in the codebase. + - **Use Case**: Opt for this mode when a complete analysis of the system's state is required, particularly to identify all potential problems in a single test run. + +2. **HALT_SUITE_ON_FAIL** + - **Description**: In this mode, the test suite halts the execution of the current suite or free tests upon encountering a failure, then proceeds to the next suite or free test. It allows for a quick bypass of problematic tests while still performing remaining tests. + - **Use Case**: Useful for scenarios where quickly identifying and addressing failures is important, without getting bogged down by tests in a problematic suite. + +3. **ABORT_ALL_ON_FAIL (Default mode)** + - **Description**: This mode adopts a zero-tolerance approach towards test failures. As soon as any test fails, it immediately halts all further testing activities. + - **Use Case**: Use this mode to avoid wasting time on further tests when there's already a known issue that needs fixing. + ## **Example of usage** Here's an example of how to use the custom test-suite to write and run test cases: @@ -76,52 +91,104 @@ import std; // Should be ready on all the major compilers for C++23. But until // with the `Zork++` out of the box solution based on Clang modulemaps. import tsuite; -// Define test functions. Should be void functions that later w'd be registered in a suite. +// Let's define some example test functions using the assertion function void testAddition() { int result = 2 + 2; assertEquals(4, result); + assertEquals(4, result); + assertEquals(4, result); +} + +// Let's define some more example test functions using the assertion function +void testSubtraction() { + int result = 3 - 2; + assertEquals(1, result); + assertEquals(23, result); + assertEquals(1, result); +} + + +// Let's define even more example test functions using the assertion function +void testMultiplication() { + int result = 2 * 2; + assertEquals(4, result); + assertEquals(4, result); + assertEquals(4, result); } + // Passing two pointers to compare if the values that they point to are equals void testPtrsAddition() { int result = 2 + 2; int expected = 4; + int wrongExpected = 16; assertEquals(&expected, &result); + assertEquals(&wrongExpected, &result); } // Driver code int main() { - // Free tests cases registration examples - + + TEST_CASE("Multiplication Test", []() { + int result = 5 * 3; + assertEquals(15, result); + assertEquals(15, result); + }); + + // Register a new test case using a function pointer. TEST_CASE("Addition Test With Pointers", testPtrsAddition); - - // Users can register a new test case using lambdas, avoiding to write standalone functions + + // Users can register a new test case using lambdas, avoiding writing standalone functions TEST_CASE("Subtraction Test", []() { int result = 5 - 3; - assertEquals(122435, result); + assertEquals(2, result); + assertEquals(2, result); }); - + // Registering test cases into test suites, to group and relate tests that makes sense to exists // as a part of a whole - + // Instantiate a new test suite, giving it a unique identifier. TestSuite suite {"My Suite"}; // Register test cases using function pointers into a test suite TEST_CASE(suite, "Addition Test", testAddition); - // Force a warning that alerts the user that the test will be discarded, since already + // Forces a warning that alerts the user that the test will be discarded, since already // exists one with the same identifier in the given suite TEST_CASE(suite, "Addition Test", testAddition); - + // Register a test case designed to fail, useful for testing the behavior + // of RUN_TESTS with different failure modes. + TEST_CASE(suite, "Subtraction Test", testSubtraction); + + // Register additional test cases to verify the functionality of RUN_TESTS + // under different conditions. + TEST_CASE(suite, "Multiplication Test", testMultiplication); + + // Create another test suite to further validate the behavior of RUN_TESTS + // with multiple suites, especially under different failure modes. + TestSuite anotherSuite {"Another Suite"}; + TEST_CASE(anotherSuite, "Addition Test", testAddition); + TEST_CASE(anotherSuite, "Subtraction Test", testSubtraction); + TEST_CASE(anotherSuite, "Multiplication Test", testMultiplication); + // Don't forget to call this free function, to run all the tests written! + // Options are: CONTINUE_ON_ERROR, HALT_SUITE_ON_FAIL, ABORT_ALL_ON_FAIL (default) RUN_TESTS(); return 0; } ``` -With these example, you will see this result: -![img.png](../../assets/tsuite_results.png) +With these examples, you will see different results depending on the mode used: + +1. **ABORT_ALL_ON_FAIL** mode (Default): + ![Abort All on Fail Mode](../../assets/tsuite_results_ABORT_ALL_ON_FAIL_mode.png) + +2. **HALT_SUITE_ON_FAIL** mode: + ![Halt Suite on Fail Mode](../../assets/tsuite_results_HALT_SUITE_ON_FAIL_mode.png) + +3. **CONTINUE_ON_ERROR** mode: + ![Continue on Error Mode](../../assets/tsuite_results_CONTINUE_ON_ERROR_mode.png) ## Funny facts diff --git a/zero/ifc/test-suite/suite.cppm b/zero/ifc/test-suite/suite.cppm index f4abfae..01039e8 100644 --- a/zero/ifc/test-suite/suite.cppm +++ b/zero/ifc/test-suite/suite.cppm @@ -6,161 +6,384 @@ * with confidence, without depending on some third-party * changing standards, implementations while avoiding the * typical dependency pitfalls -*/ + */ export module tsuite; export import :assertions; import std; + /** + * @struct TestResults + * @brief Holds the results of test execution. + * + * This structure is used to track the outcomes of a series of tests, + * including the number of passed and failed tests, along with any warnings + * that might have been generated during test execution. + * + * @var passed + * The count of tests that have successfully passed. * + * @var failed + * The count of tests that have failed. + * + * @var warnings + * A list of warning messages generated during test execution. Warnings are + * used to notify about non-critical issues or potential problems in the tests, + * which do not necessarily constitute test failures. For example, a warning + * can be used to indicate the duplication of a test case. */ struct TestResults { - int passed = 0; - int failed = 0; - std::vector warnings {}; + int passed = 0; + int failed = 0; + std::vector warnings{}; }; // Forward declarations export struct TestSuite; export struct TestCase; -void runTest(const TestCase* testCase, TestResults& testResults); -void runFreeTestCases(); +/** + * @enum TestRunBehavior + * @brief Defines the behavior of the test runner upon encountering test + * failures. + * + * This enum allows selecting different strategies for test execution, + * useful for various testing scenarios ranging from comprehensive error + * analysis to immediate failure response. + */ +export enum TestRunBehavior { + /** + * @brief Execute all tests regardless of failures. + * + * Use this mode when a complete test run is needed to gather full + * information about the system's state or to understand the full extent of + * the issues. + */ + CONTINUE_ON_ERROR, + + /** + * @brief Halt the execution of the current test suite or free tests upon a + * failure and proceed to the next suite or free test. + * + * This mode stops the execution of the current suite or set of free tests + * immediately upon encountering a failure, then proceeds to the next suite + * or free test. It's beneficial for quickly bypassing problematic tests + * while still executing the remaining tests. + */ + HALT_SUITE_ON_FAIL, + + /** + * @brief Abort all testing activities immediately upon any failure. + * + * Choose this mode when a single failure indicates a critical system issue, + * necessitating immediate attention and halting further tests until + * resolution. + */ + ABORT_ALL_ON_FAIL +}; + +/** + * @brief Executes a single test case and updates the test results. + * + * This function runs an individual test case and captures its success or failure. + * It updates the passed and failed count in the provided TestResults object. + * If the test case throws an exception, it is caught and treated as a test failure. + * + * @param testCase Pointer to the @ref TestCase to be executed. + * @param testResults Reference to a @ref TestResults object where the outcome (pass/fail) + * of the test will be recorded. + * @return Returns true if the test case passed, false if it failed. + */ +bool runTest(const TestCase *testCase, TestResults &testResults); + +/** + * @brief Executes all free-standing test cases based on the specified behavior. + * + * A "free-standing test" refers to a test case that is not part of any test suite. + * These are individual tests executed independently, without being grouped in a suite. + * This function iterates over and executes all such free-standing test cases. The behavior + * of the function upon encountering a failed test is determined by the @ref TestRunBehavior + * parameter. It can either continue running the remaining tests or halt/abort execution. + * + * @param behavior The @ref TestRunBehavior (e.g., CONTINUE_ON_ERROR, HALT_SUITE_ON_FAIL, + * ABORT_ALL_ON_FAIL) that determines the function's response to test + * failures. + * @return Returns true if any test case failed, false otherwise. + */ +bool runFreeTestCases(const TestRunBehavior behavior); + +/** + * @brief Executes all test cases within test suites based on the specified behavior. + * + * This function iterates over all registered test suites, executing the test cases + * within each suite. The execution behavior upon encountering a test failure is + * governed by the @ref TestRunBehavior parameter. Depending on this parameter, the function + * can continue with the next tests/suites, halt the current suite, or abort all tests. + * + * @param behavior The @ref TestRunBehavior (e.g., CONTINUE_ON_ERROR, HALT_SUITE_ON_FAIL, + * ABORT_ALL_ON_FAIL) that influences the function's handling of test + * failures. + */ +void runSuiteTestCases(const TestRunBehavior behavior); + +/** + * @brief Checks for errors post test execution based on test run behavior. + * + * This function is designed to be called after all tests have been executed under + * certain TestRunBehaviors (CONTINUE_ON_ERROR, HALT_SUITE_ON_FAIL). It determines if + * any errors occurred during the test runs. + * + * In scenarios where tests are allowed to continue despite failures (CONTINUE_ON_ERROR) + * or where test execution is halted only for the current suite upon failure + * (HALT_SUITE_ON_FAIL), this function provides a final check to ascertain if any errors + * were encountered during the entire testing process. + * + * The function evaluates two sources of potential errors: + * 1. Free test errors, indicated by the boolean parameter 'freeTestsErrors'. + * 2. Suite test errors, determined by examining all test suites for any failures. + * + * If errors are found in either free tests or test suites, the function terminates the + * program with an exit code of 1, signaling an error condition. + * + * @param freeTestsErrors Boolean indicating if there were errors in the free tests. + */ +void checkForTestErrors(const bool freeTestsErrors); // Top-level containers. They hold pointers to the types to avoid: // `arithmetic on a pointer to an incomplete type` -std::vector testSuites; -std::vector freeTestCases; +std::vector testSuites; +std::vector freeTestCases; export { - /** - * Common group of related test cases, identified by a unique string - */ - struct TestSuite { - std::string uuid; - std::vector cases {}; - TestResults results {}; - - TestSuite() = delete; - /** - * @short This ctr shouldn't exist, since {@link std::string} has a constructor - * for convert cstr to std::string - * @bug Clang16 under windows linking against libc++ with mingw, - * when the TestSuite new instance receives a const char* in a different file - * than the main file (in particular from a module), Clang refuses to compile - * saying that there's no viable ctr for TestSuite receiving a const char* - * - * For further @details @see https://github.com/llvm/llvm-project/issues/64211 - */ - constexpr explicit TestSuite(const char* uuid) - : uuid(std::move(uuid)) {} - constexpr explicit TestSuite(std::string uuid) - : uuid(std::move(uuid)) {} - - TestSuite(const TestSuite& rhs) = delete; - TestSuite (TestSuite&& rhs) = delete; - - friend bool operator==(const TestSuite& lhs, const TestSuite& rhs) { - return lhs.uuid == rhs.uuid; - } - }; - - /** - * @struct Holds the data for a particular user's test case. - */ - struct TestCase { - std::string name; - std::function fn; - - /** - * @note waiting for Clang to implement the {@link std::function] constructors as *constexpr*, - * so we can make the {@link TestCase] constexpr-constructible - * - * @details constexpr constructor's 2nd parameter type 'std::function' is not a literal type - * constexpr TestCase(std::string name, std::function fn) - * - * 'function' is not literal because it is not an aggregate and has no constexpr constructors other than copy or move constructors - * class _LIBCPP_TEMPLATE_VIS function<_Rp(_ArgTypes...)> - */ - TestCase(std::string name, std::function fn) - : name(std::move(name)), fn(std::move(fn)) {} - }; - - void TEST_CASE(const std::string& tname, const std::function tfunc) { - freeTestCases.push_back(new TestCase(tname, tfunc)); - } - - void TEST_CASE(TestSuite& tsuite, const std::string& tname, const std::function tfunc) { - auto it = std::find_if(tsuite.cases.begin(), tsuite.cases.end(), [&](const TestCase* tcase) { - return tcase->name == tname; - }); - - /// Check that the user didn't registered a test case with the same identifier - if (it == tsuite.cases.end()) - tsuite.cases.emplace_back(new TestCase(tname, tfunc)); - else - tsuite.results.warnings.emplace_back( - "\033[38;5;220m[Warning\033[0m in suite: \033[38;5;165m" + - tsuite.uuid + "\033[0m\033[38;5;220m]\033[0m " - "Already exists a test case with the name: \033[38;5;117m" - + tname + "\033[0m. Skipping test case." - ); - /// If this is the first time that the suite is being registered - auto suites_it = std::find_if(testSuites.begin(), testSuites.end(), [&](const TestSuite* suite) { - return suite->uuid == tsuite.uuid; - }); - if (suites_it == testSuites.end()) - testSuites.push_back(&tsuite); - } - - // Function to run all the test cases and suites - void RUN_TESTS() { - if (!freeTestCases.empty()) - runFreeTestCases(); - std::cout - << "\nRunning test suites. Total suites found: " << testSuites.size() - << std::endl; - - for (const auto& test_suite : testSuites) { - std::cout << "Running test suite: \033[38;5;165m" << test_suite->uuid << "\033[m"; - - for (const auto& warning : test_suite->results.warnings) - std::cout << "\n " << warning << std::endl; - for (const auto& test_case : test_suite->cases) - runTest(test_case, test_suite->results); - - std::cout << "\nTest suite [" << test_suite->uuid << "] summary:" << std::endl; - std::cout << " \033[32mPassed:\033[0m " << test_suite->results.passed << std::endl; - std::cout << " \033[31mFailed:\033[0m " << test_suite->results.failed << std::endl; - } - } + /** + * Common group of related test cases, identified by a unique string + */ + struct TestSuite { + std::string uuid; + std::vector cases{}; + TestResults results{}; + + TestSuite() = delete; + /** + * @short This ctr shouldn't exist, since {@link std::string} has a + * constructor for convert cstr to std::string + * @bug Clang16 under windows linking against libc++ with mingw, + * when the TestSuite new instance receives a const char* in a different + * file than the main file (in particular from a module), Clang refuses + * to compile saying that there's no viable ctr for TestSuite receiving + * a const char* + * + * For further @details @see + * https://github.com/llvm/llvm-project/issues/64211 + */ + constexpr explicit TestSuite(const char *uuid) + : uuid(std::move(uuid)) {} + constexpr explicit TestSuite(std::string uuid) + : uuid(std::move(uuid)) {} + + TestSuite(const TestSuite &rhs) = delete; + TestSuite(TestSuite &&rhs) = delete; + + friend bool operator==(const TestSuite &lhs, const TestSuite &rhs) { + return lhs.uuid == rhs.uuid; + } + }; + + /** + * @struct Holds the data for a particular user's test case. + */ + struct TestCase { + std::string name; + std::function fn; + + /** + * @note waiting for Clang to implement the {@link std::function] + * constructors as *constexpr*, so we can make the {@link TestCase] + * constexpr-constructible + * + * @details constexpr constructor's 2nd parameter type + * 'std::function' is not a literal type constexpr TestCase(std::string name, + * std::function fn) + * + * 'function' is not literal because it is not an aggregate and + * has no constexpr constructors other than copy or move constructors + * class _LIBCPP_TEMPLATE_VIS function<_Rp(_ArgTypes...)> + */ + TestCase(std::string name, std::function fn) + : name(std::move(name)), fn(std::move(fn)) {} + }; + + void TEST_CASE(const std::string &tname, + const std::function tfunc) { + freeTestCases.push_back(new TestCase(tname, tfunc)); + } + + void TEST_CASE(TestSuite & tsuite, const std::string &tname, + const std::function tfunc) { + auto it = std::find_if( + tsuite.cases.begin(), tsuite.cases.end(), + [&](const TestCase *tcase) { return tcase->name == tname; }); + + /// Check that the user didn't registered a test case with the same + /// identifier + if (it == tsuite.cases.end()) + tsuite.cases.emplace_back(new TestCase(tname, tfunc)); + else + tsuite.results.warnings.emplace_back( + "\033[38;5;220m[Warning\033[0m in suite: \033[38;5;165m" + + tsuite.uuid + + "\033[0m\033[38;5;220m]\033[0m " + "Already exists a test case with the name: \033[38;5;117m" + + tname + "\033[0m. Skipping test case."); + /// If this is the first time that the suite is being registered + auto suites_it = std::find_if( + testSuites.begin(), testSuites.end(), + [&](const TestSuite *suite) { return suite->uuid == tsuite.uuid; }); + if (suites_it == testSuites.end()) + testSuites.push_back(&tsuite); + } + + // Function to run all the test cases and suites + void RUN_TESTS(const TestRunBehavior behavior = ABORT_ALL_ON_FAIL) { + bool freeTestsErrors = false; + if (!freeTestCases.empty()) { + freeTestsErrors = runFreeTestCases(behavior); + } + runSuiteTestCases(behavior); + + checkForTestErrors(freeTestsErrors); + } } -void runFreeTestCases() { - TestResults freeTestsResults; - std::cout << "Running free tests: " << std::endl; - for (const auto& testCase : freeTestCases) { - runTest(testCase, freeTestsResults); - std::cout << std::endl; - } +void runSuiteTestCases(const TestRunBehavior behavior) { + std::cout << "\nRunning test suites. Total suites found: " + << testSuites.size() << std::endl; - std::cout << "Free tests summary:" << std::endl; - std::cout << " \033[32mPassed:\033[0m " << freeTestsResults.passed << std::endl; - std::cout << " \033[31mFailed:\033[0m " << freeTestsResults.failed << std::endl; + for (const auto &test_suite : testSuites) { + std::cout << "Running test suite: \033[38;5;165m" << test_suite->uuid + << "\033[m"; + for (const auto &warning : test_suite->results.warnings) + std::cout << "\n " << warning << std::endl; + for (const auto &test_case : test_suite->cases) { + if (!runTest(test_case, test_suite->results)) { + + if (behavior == HALT_SUITE_ON_FAIL) { + std::cout << "\n\033[1;38;5;214m===========================" + "==============" + "=======\n"; + std::cout << "[Halt Suite Tests] Stopping further tests of " + "the suite " + "\033[38;5;165m" + << test_suite->uuid + << "\033[0m\033[1;38;5;214m due to a failure.\n"; + std::cout << "=============================================" + "===\033[0m\n"; + break; + } + + if (behavior == ABORT_ALL_ON_FAIL) { + std::cout << "\nTest suite [" << test_suite->uuid + << "] summary:" << std::endl; + std::cout << " \033[32mPassed:\033[0m " + << test_suite->results.passed << std::endl; + std::cout << " \033[31mFailed:\033[0m " + << test_suite->results.failed << std::endl; + + std::cout << "\n\033[1;38;5;196m===========================" + "==============" + "=======\n"; + std::cout << "[Abort] All further tests are aborted due to " + "a failure in " + "a test in this suite.\n"; + std::cout << "=============================================" + "===\033[0m\n"; + return; + } + } + } + + std::cout << "\nTest suite [" << test_suite->uuid + << "] summary:" << std::endl; + std::cout << " \033[32mPassed:\033[0m " << test_suite->results.passed + << std::endl; + std::cout << " \033[31mFailed:\033[0m " << test_suite->results.failed + << std::endl; + } +} + +bool runFreeTestCases(const TestRunBehavior behavior) { + bool anyFailed = false; + TestResults freeTestsResults; + std::cout << "Running free tests: " << std::endl; + + for (const auto &testCase : freeTestCases) { + if (!runTest(testCase, freeTestsResults)) { + anyFailed = true; + if (behavior == ABORT_ALL_ON_FAIL || + behavior == HALT_SUITE_ON_FAIL) { + break; + } + } + } + + std::cout << "\nFree tests summary:" << std::endl; + std::cout << " \033[32mPassed:\033[0m " << freeTestsResults.passed + << std::endl; + std::cout << " \033[31mFailed:\033[0m " << freeTestsResults.failed + << std::endl; + + if (anyFailed) { + if (behavior == HALT_SUITE_ON_FAIL) { + std::cout + << "\n\033[1;38;5;214m=========================================" + "=======\n"; + std::cout + << "[Halt Free Tests] Stopping further free tests due to a " + "failure.\n"; + std::cout + << "================================================\033[0m\n"; + } else if (behavior == ABORT_ALL_ON_FAIL) { + std::cout + << "\n\033[1;38;5;196m=========================================" + "=======\n"; + std::cout + << "[Abort] All further tests are aborted due to a failure in " + "free tests.\n"; + std::cout + << "================================================\033[0m\n"; + std::exit(1); + } + } + + return anyFailed; } -void runTest(const TestCase* const testCase, TestResults& results) { - std::cout << "\n Running test: \033[38;5;117m" << testCase->name << "\033[0m"; - - try { - // Call the test function - testCase->fn(); - std::cout << " ... Result => \033[32mPassed!\033[0m"; - results.passed++; - } catch (const std::exception& ex) { - std::cout << " ... Result => \033[31mFailed\033[0m: " << ex.what(); - results.failed++; - } +bool runTest(const TestCase *const testCase, TestResults &results) { + std::cout << "\n Running test: \033[38;5;117m" << testCase->name + << "\033[0m"; + + try { + // Call the test function + testCase->fn(); + std::cout << " ... Result => \033[32mPassed!\033[0m"; + results.passed++; + return true; + } catch (const std::exception &ex) { + std::cout << " ... Result => \033[31mFailed\033[0m: " << ex.what(); + results.failed++; + return false; + } +} + + +void checkForTestErrors(const bool freeTestsErrors) { + + bool suiteTestsErrors = std::any_of( + testSuites.begin(), testSuites.end(), + [](const TestSuite *suite) { return suite->results.failed > 0; }); + std::cout << freeTestsErrors << " " << suiteTestsErrors; + if (suiteTestsErrors || freeTestsErrors) std::exit(1); } \ No newline at end of file diff --git a/zero/main.cpp b/zero/main.cpp index f65ec2d..77585ce 100644 --- a/zero/main.cpp +++ b/zero/main.cpp @@ -22,13 +22,35 @@ void run_print_examples(); void testAddition() { int result = 2 + 2; assertEquals(4, result); + assertEquals(4, result); + assertEquals(4, result); +} + +// Let's define some more example test functions using the assertion function +void testSubtraction() { + int result = 3 - 2; + assertEquals(1, result); + assertEquals(23, result); + assertEquals(1, result); +} + + +// Let's define even more example test functions using the assertion function +void testMultiplication() { + int result = 2 * 2; + assertEquals(4, result); + assertEquals(4, result); + assertEquals(4, result); } + // Passing two pointers to compare if the values that they point to are equals void testPtrsAddition() { int result = 2 + 2; int expected = 4; + int wrongExpected = 16; assertEquals(&expected, &result); + assertEquals(&wrongExpected, &result); } // Driver code @@ -36,16 +58,25 @@ int main() { // run_containers_examples(); run_output_iterator_examples(); // run_quantities_examples(); - run_formatter_and_stylize_examples(); - run_print_examples(); + // run_formatter_and_stylize_examples(); + // run_print_examples(); + + TEST_CASE("Multiplication Test", []() { + int result = 5 * 3; + assertEquals(15, result); + assertEquals(15, result); + }); + // Register a new test case using a function pointer. - TEST_CASE("Addition Test With Pointers", testPtrsAddition); + // Comment this line if you don't want failed tests in the freetests + // TEST_CASE("Addition Test With Pointers", testPtrsAddition); // Users can register a new test case using lambdas, avoiding writing standalone functions TEST_CASE("Subtraction Test", []() { int result = 5 - 3; - assertEquals(122435, result); + assertEquals(2, result); + assertEquals(2, result); }); // Registering test cases into test suites, to group and relate tests that makes sense to exists @@ -58,8 +89,23 @@ int main() { // Forces a warning that alerts the user that the test will be discarded, since already // exists one with the same identifier in the given suite TEST_CASE(suite, "Addition Test", testAddition); + // Register a test case designed to fail, useful for testing the behavior + // of RUN_TESTS with different failure modes. + TEST_CASE(suite, "Subtraction Test", testSubtraction); + + // Register additional test cases to verify the functionality of RUN_TESTS + // under different conditions. + TEST_CASE(suite, "Multiplication Test", testMultiplication); + + // Create another test suite to further validate the behavior of RUN_TESTS + // with multiple suites, especially under different failure modes. + TestSuite anotherSuite {"Another Suite"}; + TEST_CASE(anotherSuite, "Addition Test", testAddition); + TEST_CASE(anotherSuite, "Subtraction Test", testSubtraction); + TEST_CASE(anotherSuite, "Multiplication Test", testMultiplication); // Don't forget to call this free function, to run all the tests written! + // Options are: CONTINUE_ON_ERROR, HALT_SUITE_ON_FAIL, ABORT_ALL_ON_FAIL RUN_TESTS(); return 0;