From 40121ff39acf5080f9ee2d45141b39d07e5f00f3 Mon Sep 17 00:00:00 2001 From: Tom Hicks Date: Tue, 8 Apr 2025 15:05:58 -0700 Subject: [PATCH] Migrates the project from bazel to cmake and copies tinytest into the project temporarily. --- .gitignore | 2 +- .gitmodules | 0 BUILD | 116 -- CMakeLists.txt | 26 + WORKSPACE | 55 - docs/migrate-to-cmake.md | 85 ++ src/CMakeLists.txt | 29 + ansi_escapes.cpp => src/ansi_escapes.cpp | 0 ansi_escapes.h => src/ansi_escapes.h | 0 console_logger.cpp => src/console_logger.cpp | 0 console_logger.h => src/console_logger.h | 0 logger.cpp => src/logger.cpp | 0 logger.h => src/logger.h | 0 pretty_print.cpp => src/pretty_print.cpp | 0 pretty_print.h => src/pretty_print.h | 0 src/tinytest.cpp | 246 ++++ src/tinytest.h | 587 ++++++++ windows_logger.cpp => src/windows_logger.cpp | 0 windows_logger.h => src/windows_logger.h | 0 tests/CMakeLists.txt | 34 + .../ansi_escapes_test.cpp | 0 .../console_logger_test.cpp | 0 logger_test.cpp => tests/logger_test.cpp | 0 .../pretty_print_test.cpp | 0 tests/tinytest_test.cpp | 1251 +++++++++++++++++ .../windows_logger_test.cpp | 0 26 files changed, 2259 insertions(+), 172 deletions(-) create mode 100644 .gitmodules delete mode 100644 BUILD create mode 100644 CMakeLists.txt delete mode 100644 WORKSPACE create mode 100644 docs/migrate-to-cmake.md create mode 100644 src/CMakeLists.txt rename ansi_escapes.cpp => src/ansi_escapes.cpp (100%) rename ansi_escapes.h => src/ansi_escapes.h (100%) rename console_logger.cpp => src/console_logger.cpp (100%) rename console_logger.h => src/console_logger.h (100%) rename logger.cpp => src/logger.cpp (100%) rename logger.h => src/logger.h (100%) rename pretty_print.cpp => src/pretty_print.cpp (100%) rename pretty_print.h => src/pretty_print.h (100%) create mode 100644 src/tinytest.cpp create mode 100644 src/tinytest.h rename windows_logger.cpp => src/windows_logger.cpp (100%) rename windows_logger.h => src/windows_logger.h (100%) create mode 100644 tests/CMakeLists.txt rename ansi_escapes_test.cpp => tests/ansi_escapes_test.cpp (100%) rename console_logger_test.cpp => tests/console_logger_test.cpp (100%) rename logger_test.cpp => tests/logger_test.cpp (100%) rename pretty_print_test.cpp => tests/pretty_print_test.cpp (100%) create mode 100644 tests/tinytest_test.cpp rename windows_logger_test.cpp => tests/windows_logger_test.cpp (100%) diff --git a/.gitignore b/.gitignore index 4fa90e1..992ce82 100644 --- a/.gitignore +++ b/.gitignore @@ -1,3 +1,3 @@ .vscode/ -bazel-* docs/ +build/ diff --git a/.gitmodules b/.gitmodules new file mode 100644 index 0000000..e69de29 diff --git a/BUILD b/BUILD deleted file mode 100644 index f2c5d44..0000000 --- a/BUILD +++ /dev/null @@ -1,116 +0,0 @@ -######################################################################################################################## -# # -# @file BUILD # -# # -# @brief Defines build rules including tests for the top level utilities. # -# @copyright Copyright (C) 2023 by Tom Hicks # -# # -# Permission is hereby granted, free of charge, to any person obtaining a copy of this software and associated # -# documentation files (the "Software"), to deal in the Software without restriction, including without limitation the # -# rights to use, copy, modify, merge, publish, distribute, sublicense, and/or sell copies of the Software, and to # -# permit persons to whom the Software is furnished to do so, subject to the following conditions: # -# # -# The above copyright notice and this permission notice shall be included in all copies or substantial portions of the # -# Software. # -# # -# 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 # -# OR COPYRIGHT HOLDERS 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. # -# # -######################################################################################################################## -load("@rules_cc//cc:defs.bzl", "cc_library", "cc_test") - -package( - default_visibility = ["//visibility:public"], -) - -cc_library( - name = "ansi_escapes", - srcs = ["ansi_escapes.cpp"], - hdrs = ["ansi_escapes.h"], - includes = ["ansi_escapes.h"], - deps = [":pretty_print"], -) - -cc_test( - name = "ansi_escapes_test", - srcs = ["ansi_escapes_test.cpp"], - deps = [ - ":ansi_escapes", - "@TinyTest//:tinytest", - ], -) - -cc_library( - name = "console_logger", - srcs = ["console_logger.cpp"], - hdrs = ["console_logger.h"], - deps = [":logger"], -) - -cc_test( - name = "console_logger_test", - srcs = ["console_logger_test.cpp"], - deps = [ - ":console_logger", - "@TinyTest//:tinytest", - ], -) - -cc_library( - name = "logger", - srcs = ["logger.cpp"], - hdrs = ["logger.h"], -) - -cc_test( - name = "logger_test", - srcs = ["logger_test.cpp"], - deps = [ - ":logger", - "@TinyTest//:tinytest", - ], -) - -cc_library( - name = "pretty_print", - srcs = ["pretty_print.cpp"], - hdrs = ["pretty_print.h"], - includes = ["pretty_print.h"], -) - -cc_test( - name = "pretty_print_test", - srcs = ["pretty_print_test.cpp"], - deps = [ - ":pretty_print", - "@TinyTest//:tinytest", - ], -) - -cc_library( - name = "windows_logger", - srcs = ["windows_logger.cpp"], - hdrs = ["windows_logger.h"], - target_compatible_with = ["@platforms//os:windows"], - deps = [":logger"], -) - -cc_test( - name = "windows_logger_test", - srcs = ["windows_logger_test.cpp"], - target_compatible_with = ["@platforms//os:windows"], - deps = [ - ":windows_logger", - "@TinyTest//:tinytest", - ], -) diff --git a/CMakeLists.txt b/CMakeLists.txt new file mode 100644 index 0000000..82c73e6 --- /dev/null +++ b/CMakeLists.txt @@ -0,0 +1,26 @@ +cmake_minimum_required(VERSION 3.10) + +project(CPPUtils VERSION 1.0 LANGUAGES CXX) +enable_testing() + +# Set the C++ standard +set(CMAKE_CXX_STANDARD 17) +set(CMAKE_CXX_STANDARD_REQUIRED True) + +# Add subdirectories for each component +add_subdirectory(src) +add_subdirectory(tests) + +# Include FetchContent module +include(FetchContent) + +# Add Google Test as an external dependency +FetchContent_Declare( + googletest + URL https://github.com/google/googletest/archive/refs/tags/release-1.12.1.zip + DOWNLOAD_EXTRACT_TIMESTAMP true +) +# For Windows: Prevent overriding the parent project's compiler/linker settings +set(gtest_force_shared_crt ON CACHE BOOL "" FORCE) + +FetchContent_MakeAvailable(googletest) \ No newline at end of file diff --git a/WORKSPACE b/WORKSPACE deleted file mode 100644 index 48430e5..0000000 --- a/WORKSPACE +++ /dev/null @@ -1,55 +0,0 @@ -######################################################################################################################## -# # -# @file WORKSPACE # -# # -# @brief Defines external modules available to all BUILD files in this workspace. This also marks the root folder of # -# the workspace # -# @copyright Copyright (C) 2023 by Tom Hicks # -# # -# Permission is hereby granted, free of charge, to any person obtaining a copy of this software and associated # -# documentation files (the "Software"), to deal in the Software without restriction, including without limitation the # -# rights to use, copy, modify, merge, publish, distribute, sublicense, and/or sell copies of the Software, and to # -# permit persons to whom the Software is furnished to do so, subject to the following conditions: # -# # -# The above copyright notice and this permission notice shall be included in all copies or substantial portions of the # -# Software. # -# # -# 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 # -# OR COPYRIGHT HOLDERS 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. # -# # -######################################################################################################################## -workspace(name = "CPPUtils") - -load("@bazel_tools//tools/build_defs/repo:http.bzl", "http_archive") - -# Hedron's Compile Commands Extractor for Bazel -# https://github.com/hedronvision/bazel-compile-commands-extractor -# To update config run `bazel run @hedron_compile_commands//:refresh_all` -http_archive( - name = "hedron_compile_commands", - sha256 = "99bc3106eb6ce5ffab3c31de8501d4d628de5f1acd74b8b563a876bd39a2e32f", - # Replace the commit hash in both places (below) with the latest, rather than using the stale one here. - strip_prefix = "bazel-compile-commands-extractor-b33a4b05c2287372c8e932c55ff4d3a37e6761ed", - url = "https://github.com/hedronvision/bazel-compile-commands-extractor/archive/b33a4b05c2287372c8e932c55ff4d3a37e6761ed.tar.gz", -) - -load("@hedron_compile_commands//:workspace_setup.bzl", "hedron_compile_commands_setup") - -hedron_compile_commands_setup() - -http_archive( - name = "TinyTest", - sha256 = "d6729abbec6ac167635be7463d8c37ca54b08e506e61553236b50c5ad30e8736", - strip_prefix = "TinyTest-a4109d2f494fd3fd3cc47069239fd3c79f728d5a", - urls = ["https://github.com/headhunter45/TinyTest/archive/a4109d2f494fd3fd3cc47069239fd3c79f728d5a.zip"], -) diff --git a/docs/migrate-to-cmake.md b/docs/migrate-to-cmake.md new file mode 100644 index 0000000..46391b1 --- /dev/null +++ b/docs/migrate-to-cmake.md @@ -0,0 +1,85 @@ +# Migration Plan: Bazel to CMake + +## 1. Understand the Current Build Configuration +### Review Bazel Files +- Identify all Bazel build files (`BUILD`, `WORKSPACE`, etc.) and understand their structure and dependencies. +- **Specific Files**: + - `BUILD` + - `WORKSPACE` + +### Identify Targets +- List all the targets, libraries, and executables defined in the Bazel files. + +#### Libraries +- `ansi_escapes` +- `console_logger` +- `logger` +- `pretty_print` +- `windows_logger` + +#### Tests +- `ansi_escapes_test` +- `console_logger_test` +- `logger_test` +- `pretty_print_test` +- `windows_logger_test` + +### Dependencies +- Note all external dependencies, including JTest, and how they are currently managed. + +#### External Dependencies +##### Hedron's Compile Commands Extractor for Bazel +- **Name**: `hedron_compile_commands` +- **URL**: [Hedron Compile Commands Extractor](https://github.com/hedronvision/bazel-compile-commands-extractor) +- **SHA256**: `99bc3106eb6ce5ffab3c31de8501d4d628de5f1acd74b8b563a876bd39a2e32f` + +###### How to Generate `compile_commands.json` with CMake +- **Configure CMake to Output `compile_commands.json`**: + - When running CMake, specify the `CMAKE_EXPORT_COMPILE_COMMANDS` option to generate the `compile_commands.json` file. + - Add the following line to your `CMakeLists.txt` or pass it as a command-line argument: + + ```bash + cmake -DCMAKE_EXPORT_COMPILE_COMMANDS=ON .. + ``` + +- **Build the Project**: + - After configuring CMake with the above option, build the project. The `compile_commands.json` file will be generated in the build directory. + +##### TinyTest +- **Name**: `TinyTest` +- **URL**: [TinyTest](https://github.com/headhunter45/TinyTest) +- **SHA256**: `d6729abbec6ac167635be7463d8c37ca54b08e506e61553236b50c5ad30e8736` + +**Note**: TinyTest is now a git submodule under `external/TinyTest`. We will replace the external dependency with a dependency on this folder and migrate TinyTest to use CMake within that folder. + +## 2. Set Up CMake Structure +- **Create CMakeLists.txt**: For each Bazel `BUILD` file, create a corresponding `CMakeLists.txt` file. +- **Define Project**: Use `project()` to define the project name and version in the root `CMakeLists.txt`. +- **Set CMake Version**: Specify the minimum required CMake version using `cmake_minimum_required()`. + +## 3. Translate Build Rules +- **Targets**: Convert Bazel targets to CMake targets using `add_executable()` and `add_library()`. +- **Include Directories**: Use `include_directories()` to specify include paths. +- **Link Libraries**: Use `target_link_libraries()` to link against necessary libraries. + +## 4. Handle Dependencies +- **External Libraries**: Use `find_package()` or `FetchContent` to manage external dependencies. +- **JTest Migration**: Since JTest will also be migrated to CMake, ensure it is included as a dependency using CMake's dependency management. + +## 5. Configure Build Options +- **Compiler Flags**: Translate any Bazel-specific compiler flags to CMake using `set()` and `add_compile_options()`. +- **Build Types**: Define build types (e.g., Debug, Release) using `set(CMAKE_BUILD_TYPE)`. + +## 6. Testing and Validation +- **Build the Project**: Use CMake to configure and build the project, ensuring all targets are correctly built. +- **Run Tests**: If there are tests, ensure they are correctly configured and run using CMake's testing support (`enable_testing()` and `add_test()`). + +## 7. Documentation and Cleanup +- **Document Changes**: Update any project documentation to reflect the new build system. +- **Remove Bazel Files**: Once the migration is complete and verified, remove Bazel-related files. + +## 8. Continuous Integration +- **Update CI/CD**: Modify any CI/CD pipelines to use CMake instead of Bazel for building and testing. + +## 9. Iterate and Refine +- **Feedback Loop**: Gather feedback from developers and refine the CMake setup as needed. \ No newline at end of file diff --git a/src/CMakeLists.txt b/src/CMakeLists.txt new file mode 100644 index 0000000..2d81033 --- /dev/null +++ b/src/CMakeLists.txt @@ -0,0 +1,29 @@ +# Define the ansi_escapes library +add_library(ansi_escapes ansi_escapes.cpp) +target_include_directories(ansi_escapes PUBLIC ${CMAKE_CURRENT_SOURCE_DIR}) + +# Define the console_logger library +add_library(console_logger console_logger.cpp) +target_include_directories(console_logger PUBLIC ${CMAKE_CURRENT_SOURCE_DIR}) +target_link_libraries(console_logger PRIVATE logger) + +# Define the logger library +add_library(logger logger.cpp) +target_include_directories(logger PUBLIC ${CMAKE_CURRENT_SOURCE_DIR}) + +# Define the pretty_print library +add_library(pretty_print pretty_print.cpp) +target_include_directories(pretty_print PUBLIC ${CMAKE_CURRENT_SOURCE_DIR}) + +# Define the windows_logger library only for Windows +if(WIN32) + add_library(windows_logger windows_logger.cpp) + target_include_directories(windows_logger PUBLIC ${CMAKE_CURRENT_SOURCE_DIR}) + target_link_libraries(windows_logger PRIVATE logger) + set_target_properties(windows_logger PROPERTIES COMPILE_DEFINITIONS "_WIN32") +endif() + +# Define the tinytest library +add_library(tinytest tinytest.cpp) +target_include_directories(tinytest PUBLIC ${CMAKE_CURRENT_SOURCE_DIR}) +target_link_libraries(tinytest PRIVATE pretty_print) \ No newline at end of file diff --git a/ansi_escapes.cpp b/src/ansi_escapes.cpp similarity index 100% rename from ansi_escapes.cpp rename to src/ansi_escapes.cpp diff --git a/ansi_escapes.h b/src/ansi_escapes.h similarity index 100% rename from ansi_escapes.h rename to src/ansi_escapes.h diff --git a/console_logger.cpp b/src/console_logger.cpp similarity index 100% rename from console_logger.cpp rename to src/console_logger.cpp diff --git a/console_logger.h b/src/console_logger.h similarity index 100% rename from console_logger.h rename to src/console_logger.h diff --git a/logger.cpp b/src/logger.cpp similarity index 100% rename from logger.cpp rename to src/logger.cpp diff --git a/logger.h b/src/logger.h similarity index 100% rename from logger.h rename to src/logger.h diff --git a/pretty_print.cpp b/src/pretty_print.cpp similarity index 100% rename from pretty_print.cpp rename to src/pretty_print.cpp diff --git a/pretty_print.h b/src/pretty_print.h similarity index 100% rename from pretty_print.h rename to src/pretty_print.h diff --git a/src/tinytest.cpp b/src/tinytest.cpp new file mode 100644 index 0000000..daad35d --- /dev/null +++ b/src/tinytest.cpp @@ -0,0 +1,246 @@ +/*************************************************************************************** + * @file tinytest.cpp * + * * + * @brief Defines structs and functions for implementing TinyTest. * + * @copyright Copyright 2023 Tom Hicks * + * Licensed under the MIT license see the LICENSE file for details. * + ***************************************************************************************/ + +#define _XOPEN_SOURCE_EXTENDED +#include "tinytest.h" + +#include +#include +#include +#include +#include + +namespace TinyTest { +namespace { +using std::endl; +using std::string; +using std::vector; +} // End namespace + +// TODO: Add TShared(*)(string /*test_name*/, UUID /*testRunId*/) +// allocateSharedData to the test tuple to make some shared data that can be +// used in a thread safe way by setup, teardown, and evaluate steps of the test. +// TODO: Add TShared to be returned by the setup functions, and consumed by the +// evaluate and teardown functions. +// Suite setup/teardown functions should allocate/free. +// Test setup/teardown functions should consume the data allocated by Suite +// setup. Test setup functions may allocate additional resources. If they do +// then the allocated resources they should be freed by test teardown +// function. Suite and/or Test compare functions may consume this shared data, +// but it will not be shared with the execution of function_to_test. + +// Begin TestResults methods +TestResults::TestResults() : errors_(0), failed_(0), passed_(0), skipped_(0), total_(0) {} + +TestResults::TestResults(const TestResults& other) + : error_messages_(other.error_messages_), + errors_(other.errors_), + failed_(other.failed_), + failure_messages_(other.failure_messages_), + passed_(other.passed_), + skip_messages_(other.skip_messages_), + skipped_(other.skipped_), + total_(other.total_) {} + +TestResults::TestResults(uint32_t errors, + uint32_t failed, + uint32_t passed, + uint32_t skipped, + uint32_t total, + vector error_messages, + vector failure_messages, + vector skip_messages) + : error_messages_(error_messages), + errors_(errors), + failed_(failed), + failure_messages_(failure_messages), + passed_(passed), + skip_messages_(skip_messages), + skipped_(skipped), + total_(total) {} + +TestResults& TestResults::Error() { + errors_++; + return *this; +} + +TestResults& TestResults::Error(string message) { + errors_++; + error_messages_.push_back(message); + return *this; +} + +TestResults& TestResults::Fail() { + total_++; + failed_++; + return *this; +} + +TestResults& TestResults::Fail(const string& message) { + total_++; + failed_++; + failure_messages_.push_back(message); + return *this; +} + +vector TestResults::FailureMessages() const { + return failure_messages_; +} + +TestResults& TestResults::Pass() { + total_++; + passed_++; + return *this; +} + +TestResults& TestResults::Skip() { + total_++; + skipped_++; + return *this; +} + +TestResults& TestResults::Skip(const string& message) { + total_++; + skipped_++; + skip_messages_.push_back(message); + return *this; +} + +vector TestResults::SkipMessages() const { + return skip_messages_; +} + +vector TestResults::ErrorMessages() const { + return error_messages_; +} + +uint32_t TestResults::Errors() const { + return errors_; +} + +uint32_t TestResults::Failed() const { + return failed_; +} + +uint32_t TestResults::Passed() const { + return passed_; +} + +uint32_t TestResults::Skipped() const { + return skipped_; +} + +uint32_t TestResults::Total() const { + return total_; +} + +TestResults TestResults::operator+(const TestResults& other) const { + vector error_messages; + error_messages.insert(error_messages.end(), error_messages_.begin(), error_messages_.end()); + error_messages.insert(error_messages.end(), other.error_messages_.begin(), other.error_messages_.end()); + vector failure_messages; + failure_messages.insert(failure_messages.end(), failure_messages_.begin(), failure_messages_.end()); + failure_messages.insert(failure_messages.end(), other.failure_messages_.begin(), other.failure_messages_.end()); + vector skip_messages; + skip_messages.insert(skip_messages.end(), skip_messages_.begin(), skip_messages_.end()); + skip_messages.insert(skip_messages.end(), other.skip_messages_.begin(), other.skip_messages_.end()); + + return TestResults(errors_ + other.errors_, + failed_ + other.failed_, + passed_ + other.passed_, + skipped_ + other.skipped_, + total_ + other.total_, + error_messages, + failure_messages, + skip_messages); +} + +TestResults& TestResults::operator+=(const TestResults& other) { + error_messages_.insert(error_messages_.end(), other.error_messages_.begin(), other.error_messages_.end()); + errors_ += other.errors_; + failed_ += other.failed_; + failure_messages_.insert(failure_messages_.end(), other.failure_messages_.begin(), other.failure_messages_.end()); + passed_ += other.passed_; + skip_messages_.insert(skip_messages_.end(), other.skip_messages_.begin(), other.skip_messages_.end()); + skipped_ += other.skipped_; + total_ += other.total_; + return *this; +} + +void PrintResults(std::ostream& os, TestResults results) { + auto skip_messages = results.SkipMessages(); + if (skip_messages.size() > 0) { + os << "Skipped:" << endl; + for_each(skip_messages.begin(), skip_messages.end(), [&os](const string& message) { + os << "🚧Skipped: " << message << endl; + }); + } + auto failure_messages = results.FailureMessages(); + if (failure_messages.size() > 0) { + os << "Failures:" << endl; + for_each(failure_messages.begin(), failure_messages.end(), [&os](const string& message) { + os << "❌FAILED: " << message << endl; + }); + } + auto error_messages = results.ErrorMessages(); + if (error_messages.size() > 0) { + os << "Errors:" << endl; + for_each(error_messages.begin(), error_messages.end(), [&os](const string& message) { + os << "🔥ERROR: " << message << endl; + }); + } + os << "Total tests: " << results.Total() << endl; + os << "Passed: " << results.Passed() << " ✅" << endl; + os << "Failed: " << results.Failed() << " ❌" << endl; + os << "Skipped: " << results.Skipped() << " 🚧" << endl; + os << "Errors: " << results.Errors() << " 🔥" << endl; +} + +// End TestResults methods. + +MaybeTestConfigureFunction DefaultTestConfigureFunction() { + return std::nullopt; +} + +MaybeTestConfigureFunction Coalesce(MaybeTestConfigureFunction first, MaybeTestConfigureFunction second) { + if (first.has_value()) { + if (second.has_value()) { + // This is the only place we actually need to combine them. + return [&first, &second]() { + first.value()(); + second.value()(); + }; + } else { + return first; + } + } else { + return second; + } +} + +// Utility functions. +TestResults& SkipTest(TestResults& results, + const std::string& suite_label, + const std::string& test_label, + std::optional reason) { + std::string qualified_test_label = suite_label + "::" + test_label; + std::cout << " 🚧Skipping Test: " << test_label; + if (reason.has_value()) { + std::cout << " because " << reason.value(); + } + std::cout << std::endl; + results.Skip(qualified_test_label + (reason.has_value() ? " because " + reason.value() : "")); + return results; +} + +// TODO: Factor out the pretty printing into a separate module so it can be tested separately. +// TODO: Consider making separate files for test suite, tests, test cases, and test results. +// TODO: Come up with a way to autogenerat a main function that runs all tests in a *_test.cpp file. +// TODO: Come up with a way to aggregate TestResults over multiple c++ files when running under bazel. +// TODO: Create a Makefile to build as a library. +} // namespace TinyTest diff --git a/src/tinytest.h b/src/tinytest.h new file mode 100644 index 0000000..2fed249 --- /dev/null +++ b/src/tinytest.h @@ -0,0 +1,587 @@ +#ifndef TinyTest__tinytest_h__ +#define TinyTest__tinytest_h__ +/*************************************************************************************** + * @file tinytest.h * + * * + * @brief Defines structs and functions for implementing TinyTest. * + * @copyright Copyright 2023 Tom Hicks * + * Licensed under the MIT license see the LICENSE file for details. * + ***************************************************************************************/ + +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include + +#include "pretty_print.h" + +namespace TinyTest { + +/// @defgroup tests Tests +/// @defgroup test_suites Test Suites +/// @defgroup test_results Test Results +/// @defgroup test_execution Test Execution +/// @defgroup configure_functions Configure Functions +/// @defgroup compare_functions Compare Functions +/// @defgroup helpers Helpers + +/// @addtogroup configure_functions +/// @{ + +/// @brief This is a type that represents a setup or teardown function for tests. +using TestConfigureFunction = std::function; + +/// @brief This is a type that represents an optional setup or teardown function for tests. +using MaybeTestConfigureFunction = std::optional; + +/// @brief This is the default configure function. +/// @return The default configure function. This is currently std::nullopt. +MaybeTestConfigureFunction DefaultTestConfigureFunction(); + +/// @brief Combines multiple test configure functions into a single one. +/// @param first The first setup function if this is nullopt it is ignored. +/// @param second The second setup function if this is nullopt it is ignored. +/// @return The resulting setup function or nullopt if both first and second are nullopt. +MaybeTestConfigureFunction Coalesce(MaybeTestConfigureFunction first, MaybeTestConfigureFunction second); +/// @} + +/// @addtogroup compare_functions +/// @{ + +/// @brief This is a type that represents a compare function for types of TResult. +/// +/// It should return true if the parameters are equal and false otherwise. +/// @tparam TResult The type of the parameters. This is the return type of the function being tested. +template +using TestCompareFunction = std::function; + +/// @brief This is a type that represents an optional compare function for types of TResult. +/// @tparam TResult The type of the parameters. This is the return type of the function being tested. +template +using MaybeTestCompareFunction = std::optional>; + +/// @brief This is the default compare function. +/// @tparam TResult The return type of the test function. +/// @return The default compare function. Currently this is std::nullopt. +template +MaybeTestCompareFunction DefaultTestCompareFunction(); +/// @} + +// TODO: For some reason all hell breaks loose if test_name or expected output +// are const&. Figure out why. Probably need to use decay and make const& where we want it explicitly. +/// @addtogroup tests +/// @{ + +/// @brief This is a type that represents an individual test. +/// @tparam TResult The return type of the test function. +/// @tparam ...TInputParams The parameters to pass to the test function +template +using TestTuple = std::tuple< + /// test_name + std::string, + /// expected_output + TResult, + /// input_params - The input parameters for this test. These will be used when calling std::apply with + /// function_to_test to execute the test. + std::tuple, + /// test_compare_function - If this is not nullptr then this function will be called instead of + /// suite_compare_function to determine if the test passes. Use this to check for side effects of the test. Return + /// true if the test passes and false otherwise. + MaybeTestCompareFunction, + /// test_setup_function - If this is not nullptr this function is called before each test to setup the environment. + /// It is called with std::apply and input_params so you can use them to mock records with specific IDs or calculate + /// an expected result. + MaybeTestConfigureFunction, + /// test_teardown_function - If this is not nullptr this function is called after each test to cleanup any + /// allocated/shared resources. + MaybeTestConfigureFunction, + /// is_enabled - If this is false the test, setup, and teardown functions are not run. + bool>; + +/// @brief Executes a TestSuite. +/// @tparam TResult The result type of the test. +/// @tparam ...TInputParams The types of parameters sent to the test function. +/// @param test_name The label for this test. For example "should calculate the +/// interest". +/// @param expected The expected output of calling the test function with these +/// input parameters. +/// @param input_params The input parameters to use when calling the test +/// function. +/// @param test_compare_function An optional function that can be used to Compare the +/// expected and actual return values. This is good for when you only care about +/// certain fields being equal. +/// @param before_each This is called to setup the environment before running +/// the test. This is where you should build mocks, setup spies, and set any +/// other values you need before calling the test function. +/// @param after_each This is called after each test run to cleanup anything +/// allocated in before_each. +/// @param is_enabled If false this test run is not executed and considered +/// skipped for reporting purposes. +/// @return A TestTuple suitable for use as a test run when calling test_fn. +template +TestTuple MakeTest(const std::string& test_name, + const TResult& expected, + std::tuple input_params, + MaybeTestCompareFunction test_compare_function = std::nullopt, + MaybeTestConfigureFunction before_each = std::nullopt, + MaybeTestConfigureFunction after_each = std::nullopt, + bool is_enabled = true); +/// @} + +/// @addtogroup test_suites +/// @{ + +/// @brief This type represents a test suite. +/// @tparam TResult The return type of the function to test. +/// @tparam TFunctionToTest The type of the function to test. +/// @tparam ...TInputParams The types of the input parameters to the function to test. +template +using TestSuite = std::tuple< + /// test_name - The name of the test. + std::string, + /// function_to_test - The function to test. It will be executed once for each item in the tests initializer_list. + std::function, + /// tests - This is an initializer list of @link TestTuple @endlink that represent the test runs to execute. + std::initializer_list>, + /// test_compare_function - This is an optional function that overrides how test results are compared. + MaybeTestCompareFunction, + /// before_each - This is an optional function that is executed before each test. + MaybeTestConfigureFunction, + /// after_each - This is an optional function that is executed after each test. + MaybeTestConfigureFunction, + // is_enabled - If true the suite is executed. If false all test runs are reported as skipped and none are run. + bool>; + +/// @brief Makes a TestSuite tuple from the given parameters. +/// @tparam TResult The return type of function_to_test. +/// @tparam TFunctionToTest The type of function_to_test. +/// @tparam ...TInputParams The parameter types of function_to_test. +/// @param suite_name The label for this test suite. +/// @param function_to_test The function to test. +/// @param test_data The configuration for the test runs. +/// @param compare An optional compare function to use when evaluating test results. +/// @param before_each An optional function to run before each test. +/// @param after_each An optional function to run after each test. +/// @param is_enabled If false the test suite is skipped. All tests in the suite will be reported as skipped. +/// @return The results of the test suite. +template +TestSuite MakeTestSuite(const std::string& suite_name, + TFunctionToTest function_to_test, + std::initializer_list> test_data, + MaybeTestCompareFunction compare = std::nullopt, + MaybeTestConfigureFunction before_each = std::nullopt, + MaybeTestConfigureFunction after_each = std::nullopt, + bool is_enabled = true); +/// @} + +/// @addtogroup helpers +/// @{ + +/// @brief Intercepts cout, executes the function and returns a string with all tesxt sent to cout while running the +/// function. +/// @tparam TResult The return type of function_to_execute. +/// @tparam ...TParameters The parameter types of function_to_execute. +/// @param function_to_execute The function to execute. +/// @param maybe_args The args to call function_to_execute with or nullopt if it takes no parameters. +/// @return A string containing all text written to cout by function_to_execute. +template +std::string InterceptCout(std::function function_to_execute, + std::optional> maybe_args = std::nullopt); + +/// @brief This function compares two vectors. +/// @tparam TChar The character type of the stream to write to. +/// @tparam TTraits The character_traits type of the stream to write to. +/// @tparam TItem The type of item in the vectors. +/// @param error_message The stream to write error messages to. +/// @param expected The expected vector. +/// @param actual The actual vector. +/// @return The error_message stream. +template +auto& Compare(std::basic_ostream& error_message, + std::vector expected, + std::vector actual); + +/// @} + +/// @addtogroup test_results + +/// @brief Represents the results of running some number of tests. +/// +/// This type may evolve over time, but currently it tracks: +/// * The total number of tests run. +/// * The number of failures and any messages sent with the failures. +/// * The number of tests skipped and any messages sent with the skips. +/// * The number of tests with errors and any error messages sent with the errors. +/// * __Note:__ Errors do not count as test runs. Errored tests will be recorded as a passed/failed/skipped test +/// separately. +/// * The number of passed tests. +class TestResults { + public: + /// @brief Creates an empty TestResults instance representing no tests run. + TestResults(); + + /// @brief Creates a new TestResults instance that is a copy of other. + /// @param other + TestResults(const TestResults& other); + + /// @brief Creates a new TestResults instance with specific counts. + /// @param errors The number of errors while running the tests. + /// @param failed The number of failed tests. + /// @param passed The number of passed tests. + /// @param skipped The number of skipped tests. + /// @param total The total number of tests run. This should equal the sum of failed, passed, and skipped tests. + /// @param error_messages The list of error messages. + /// @param failure_messages The list of failure messages. + /// @param skip_messages The list of skip messages. + TestResults(uint32_t errors, + uint32_t failed, + uint32_t passed, + uint32_t skipped, + uint32_t total, + std::vector error_messages, + std::vector failure_messages, + std::vector skip_messages); + + /// @brief Adds an error. This increments errors. + /// @return A reference to this instance. Used for chaining. + TestResults& Error(); + + /// @brief Adds an error with a message. This increments errors as well as + /// saving the error message. + /// @param message The error message. + /// @return A reference to this instance. Used for chaining. + TestResults& Error(std::string message); + + /// @brief Adds a failed test. This increments total and failed. + /// @return A reference to this instance. Used for chaining. + TestResults& Fail(); + + /// @brief Adds a failed test with a message. This increments total and failed + /// as well as saving the failure message. + /// @param message The reason the test failed. + /// @return A reference to this instance. Used for chaining. + TestResults& Fail(const std::string& message); + + /// @brief Adds a passed test. This increments total and passed. + /// @return A reference to this instance. Used for chaining. + TestResults& Pass(); + + /// @brief Adds a skipped test. This increments total and skipped. + /// @return A reference to this instance. Used for chaining. + TestResults& Skip(); + + /// @brief Adds a skipped test with a message. This increments total and + /// skipped as well as saving the skip message. + /// @param message The reason the test was skipped. + /// @return A reference to this instance. Used for chaining. + TestResults& Skip(const std::string& message); + + /// @brief Getter for the list of error messages. + /// @return + std::vector ErrorMessages() const; + + /// @brief Getter for the count of errors. + /// @return + uint32_t Errors() const; + + /// @brief Getter for the count of failed tests. + /// @return The count of failed tests. + uint32_t Failed() const; + + /// @brief Getter for the list of failure messages. + /// @return The list of failure messages. + std::vector FailureMessages() const; + + /// @brief Getter for the count of passed tests. + /// @return The count of passed tests. + uint32_t Passed() const; + + /// @brief Getter for the count of skipped tests. + /// @return The count of skipped tests. + uint32_t Skipped() const; + + /// @brief Getter for the list of skip messages. + /// @return The list of skip messages. + std::vector SkipMessages() const; + + /// @brief Getter for the count of total tests. + /// @return The count of total tests run. + uint32_t Total() const; + + /// @brief Returns the combination of this and another TestResults instance. + /// @param other The other TestResults instance to add to this one. + /// @return The combination of the two TestResults instances. + TestResults operator+(const TestResults& other) const; + + /// @brief Adds another TestResults to this one and returns a reference to + /// this instance. + /// @param other The other TestResults instance to add to this one. + /// @return A reference to this instance. + TestResults& operator+=(const TestResults& other); + + private: + std::vector error_messages_; + uint32_t errors_; + uint32_t failed_; + std::vector failure_messages_; + uint32_t passed_; + std::vector skip_messages_; + uint32_t skipped_; + uint32_t total_; +}; + +/// @brief Writes a friendly version of results to the provided stream. +/// @param os The stream to write to. +/// @param results The TestResults to write. +void PrintResults(std::ostream& os, TestResults results); + +/// @addtogroup test_execution +/// @{ + +/// @brief This function marks a test as skipped with an optional reason. +/// @param results The TestResults to update. +/// @param suite_label The label for the test suite. +/// @param test_label The label for the test. +/// @param reason The optional reason the test is being skipped. +/// @return The TestResults for chaining. +TestResults& SkipTest(TestResults& results, + const std::string& suite_label, + const std::string& test_label, + std::optional reason = std::nullopt); + +/// @brief Executes a TestSuite. +/// @tparam TResult The result type of the test. +/// @tparam TInputParams... The types of parameters sent to the test function. +/// @param suite_label The label for this test suite. For example a class name +/// such as "MortgageCalculator". +/// @param function_to_test The function to be tested. It will be called with +/// std::apply and a std::tuple made from each item in tests. +/// @param tests An std::initializer_list of test runs. +/// @param suite_Compare A function used to Compare the expected and actual test +/// results. This can be overridden per test by setting test_Compare. +/// @param after_all This is called before each suite is started to setup the +/// environment. This is where you should build mocks, setup spies, and test +/// fixtures. +/// @param before_all This is called after each suite has completed to cleanup +/// anything allocated in suite_before_each. +/// @param is_enabled If false the test is reported as skipped. If true the test +/// is run as normal. +template +TestResults ExecuteSuite(std::string suite_label, + std::function function_to_test, + std::initializer_list> tests, + MaybeTestCompareFunction suite_Compare = std::nullopt, + MaybeTestConfigureFunction before_all = std::nullopt, + MaybeTestConfigureFunction after_all = std::nullopt, + bool is_enabled = true); + +/// @brief +/// @tparam TResult The result type of the test. +/// @tparam TInputParams... The types of parameters sent to the test function. +/// @param test_suite A tuple representing the test suite configuration. +template +TestResults ExecuteSuite(const TestSuite& test_suite); + +/// @} + +template +MaybeTestCompareFunction DefaultTestCompareFunction() { + return std::nullopt; +} + +template +TestTuple MakeTest(const std::string& test_name, + const TResult& expected, + std::tuple input_params, + MaybeTestCompareFunction test_compare_function, + MaybeTestConfigureFunction before_each, + MaybeTestConfigureFunction after_each, + bool is_enabled) { + return make_tuple(test_name, expected, input_params, test_compare_function, before_each, after_each, is_enabled); +} + +template +TestSuite MakeTestSuite(const std::string& suite_name, + TFunctionToTest function_to_test, + std::initializer_list> test_data, + MaybeTestCompareFunction compare, + MaybeTestConfigureFunction before_each, + MaybeTestConfigureFunction after_each, + bool is_enabled) { + return make_tuple(suite_name, function_to_test, test_data, compare, before_each, after_each, is_enabled); +} + +template +std::string InterceptCout(std::function function_to_execute, + std::optional> maybe_args) { + std::ostringstream os; + auto saved_buffer = std::cout.rdbuf(); + std::cout.rdbuf(os.rdbuf()); + + if (maybe_args.has_value()) { + std::apply(function_to_execute, maybe_args.value()); + } else { + std::invoke(function_to_execute); + } + std::cout.rdbuf(saved_buffer); + return os.str(); +} + +template +auto& Compare(std::basic_ostream& error_message, + std::vector expected, + std::vector actual) { + if (expected.size() != actual.size()) { + error_message << "size mismatch expected: " << expected.size() << ", actual: " << actual.size(); + return error_message; + } + + for (size_t index = 0; index < expected.size(); index++) { + if (expected[index] != actual[index]) { + error_message << "vectors differ at index " << index << ", "; + CPPUtils::PrettyPrint(error_message, expected[index]) << " != "; + CPPUtils::PrettyPrint(error_message, actual[index]) << ", expected: "; + CPPUtils::PrettyPrint(error_message, expected) << ", actual: "; + CPPUtils::PrettyPrint(error_message, actual); + return error_message; + } + } + return error_message; +} + +template +TestResults ExecuteSuite(std::string suite_label, + std::function function_to_test, + std::initializer_list> tests, + MaybeTestCompareFunction suite_Compare, + MaybeTestConfigureFunction before_all, + MaybeTestConfigureFunction after_all, + bool is_enabled) { + TestResults results; + if (!is_enabled) { + std::cout << "🚧Skipping suite: " << suite_label << " because it is disabled." << std::endl; + for (auto test : tests) { + std::string test_label = std::get<0>(test); + SkipTest(results, suite_label, test_label, "the suite is disabled."); + } + return results; + } + if (tests.size() == 0) { + std::cout << "🚧Skipping suite: " << suite_label << " because it is empty." << std::endl; + return results; + } + std::cout << "🚀Beginning Suite: " << suite_label << std::endl; + + // Step 1: Suite Setup + + if (before_all.has_value()) { + (*before_all)(); + } + + // Step 2: Execute Tests + for_each(tests.begin(), + tests.end(), + [&suite_label, &function_to_test, &results, &suite_Compare](TestTuple test_data) { + // Step 2a: Extract our variables from the TestTuple. + const std::string& test_label = std::get<0>(test_data); + const std::string qualified_test_label = suite_label + "::" + test_label; + const TResult& expected_output = std::get<1>(test_data); + std::tuple input_params = std::get<2>(test_data); + MaybeTestCompareFunction maybe_Compare_function = std::get<3>(test_data); + TestCompareFunction Compare_function = + maybe_Compare_function.has_value() ? *maybe_Compare_function + : suite_Compare.has_value() ? *suite_Compare + : [](const TResult& l, const TResult& r) { return l == r; }; + MaybeTestConfigureFunction before_each = std::get<4>(test_data); + MaybeTestConfigureFunction after_each = std::get<5>(test_data); + bool is_enabled = std::get<6>(test_data); + + if (!is_enabled) { + SkipTest(results, suite_label, test_label); + return; + } + + // Step 2b: Test Setup + std::cout << " Beginning Test: " << test_label << std::endl; + if (before_each.has_value()) { + (*before_each)(); + } + + TResult actual; + try { + // Step 2c: Execute the test method. + actual = std::apply(function_to_test, input_params); + } catch (const std::exception& ex) { + std::ostringstream os; + os << "Caught exception \"" << ex.what() << "\"."; + results.Error(qualified_test_label + " " + os.str()); + std::cout << " 🔥ERROR: " << os.str() << std::endl; + } catch (const std::string& message) { + std::ostringstream os; + os << "Caught string \"" << message << "\"."; + results.Error(qualified_test_label + " " + os.str()); + std::cout << " 🔥ERROR: " << os.str() << std::endl; + } catch (const char* message) { + std::ostringstream os; + os << "Caught c-string \"" << message << "\"."; + results.Error(qualified_test_label + " " + os.str()); + std::cout << " 🔥ERROR: " << os.str() << std::endl; + } catch (...) { + std::string message = + "Caught something that is neither an std::exception " + "nor an std::string."; + results.Error(qualified_test_label + " " + message); + std::cout << " 🔥ERROR: " << message << std::endl; + } + + // Step 2d: Pass or fail. + if (Compare_function(expected_output, actual)) { + results.Pass(); + std::cout << " ✅PASSED" << std::endl; + } else { + std::ostringstream os; + os << "expected: "; + CPPUtils::PrettyPrint(os, expected_output) << ", actual: "; + CPPUtils::PrettyPrint(os, actual); + results.Fail(qualified_test_label + " " + os.str()); + std::cout << " ❌FAILED: " << os.str() << std::endl; + } + + // Step 2e: Test Teardown + if (after_each.has_value()) { + (*after_each)(); + } + std::cout << " Ending Test: " << test_label << std::endl; + }); + + // Step 3: Suite Teardown + if (after_all.has_value()) { + (*after_all)(); + } + std::cout << "Ending Suite: " << suite_label << std::endl; + return results; +} + +template +TestResults ExecuteSuite(const TestSuite& test_suite) { + std::string suite_label = std::get<0>(test_suite); + std::function function_to_test = std::get<1>(test_suite); + std::initializer_list> tests = std::get<2>(test_suite); + MaybeTestCompareFunction suite_Compare = sizeof(test_suite) > 3 ? std::get<3>(test_suite) : std::nullopt; + MaybeTestConfigureFunction before_all = sizeof(test_suite) > 4 ? std::get<4>(test_suite) : std::nullopt; + MaybeTestConfigureFunction after_all = sizeof(test_suite) > 5 ? std::get<5>(test_suite) : std::nullopt; + bool is_enabled = sizeof(test_suite) > 6 ? std::get<6>(test_suite) : true; + return ExecuteSuite(suite_label, function_to_test, tests, suite_Compare, before_all, after_all, is_enabled); +} + +} // End namespace TinyTest + +#endif // End !defined(TinyTest__tinytest_h__) diff --git a/windows_logger.cpp b/src/windows_logger.cpp similarity index 100% rename from windows_logger.cpp rename to src/windows_logger.cpp diff --git a/windows_logger.h b/src/windows_logger.h similarity index 100% rename from windows_logger.h rename to src/windows_logger.h diff --git a/tests/CMakeLists.txt b/tests/CMakeLists.txt new file mode 100644 index 0000000..1e6ec07 --- /dev/null +++ b/tests/CMakeLists.txt @@ -0,0 +1,34 @@ +# Enable testing +enable_testing() + +# Define the ansi_escapes_test executable +add_executable(ansi_escapes_test ansi_escapes_test.cpp) +target_link_libraries(ansi_escapes_test PRIVATE ansi_escapes tinytest) +add_test(NAME ansi_escapes_test COMMAND ansi_escapes_test) + +# Define the console_logger_test executable +add_executable(console_logger_test console_logger_test.cpp) +target_link_libraries(console_logger_test PRIVATE console_logger tinytest) +add_test(NAME console_logger_test COMMAND console_logger_test) + +# Define the logger_test executable +add_executable(logger_test logger_test.cpp) +target_link_libraries(logger_test PRIVATE logger tinytest) +add_test(NAME logger_test COMMAND logger_test) + +# Define the pretty_print_test executable +add_executable(pretty_print_test pretty_print_test.cpp) +target_link_libraries(pretty_print_test PRIVATE pretty_print tinytest) +add_test(NAME pretty_print_test COMMAND pretty_print_test) + +# Define the windows_logger_test executable only for Windows +if(WIN32) + add_executable(windows_logger_test windows_logger_test.cpp) + target_link_libraries(windows_logger_test PRIVATE windows_logger tinytest) + add_test(NAME windows_logger_test COMMAND windows_logger_test) +endif() + +# Define the tinytest_test executable +add_executable(tinytest_test tinytest_test.cpp) +target_link_libraries(tinytest_test PRIVATE tinytest gtest_main gtest gmock) +add_test(NAME tinytest_test COMMAND tinytest_test) \ No newline at end of file diff --git a/ansi_escapes_test.cpp b/tests/ansi_escapes_test.cpp similarity index 100% rename from ansi_escapes_test.cpp rename to tests/ansi_escapes_test.cpp diff --git a/console_logger_test.cpp b/tests/console_logger_test.cpp similarity index 100% rename from console_logger_test.cpp rename to tests/console_logger_test.cpp diff --git a/logger_test.cpp b/tests/logger_test.cpp similarity index 100% rename from logger_test.cpp rename to tests/logger_test.cpp diff --git a/pretty_print_test.cpp b/tests/pretty_print_test.cpp similarity index 100% rename from pretty_print_test.cpp rename to tests/pretty_print_test.cpp diff --git a/tests/tinytest_test.cpp b/tests/tinytest_test.cpp new file mode 100644 index 0000000..0046216 --- /dev/null +++ b/tests/tinytest_test.cpp @@ -0,0 +1,1251 @@ +/*************************************************************************************** + * @file tinytest.h * + * * + * @brief Defines structs and functions for implementing TinyTest. * + * @copyright Copyright 2023 Tom Hicks * + * Licensed under the MIT license see the LICENSE file for details. * + ***************************************************************************************/ + +#include "tinytest.h" + +#include +#include +#include +#include +#include + +#include "gmock/gmock.h" +#include "gtest/gtest.h" + +namespace { +using std::function; +using std::get; +using std::make_tuple; +using std::nullopt; +using std::ostringstream; +using std::string; +using std::tuple; +using std::vector; +using testing::Eq; +using testing::Ne; +using TinyTest::Coalesce; +using TinyTest::Compare; +using TinyTest::DefaultTestCompareFunction; +using TinyTest::DefaultTestConfigureFunction; +using TinyTest::ExecuteSuite; +using TinyTest::InterceptCout; +using TinyTest::MakeTest; +using TinyTest::MakeTestSuite; +using TinyTest::MaybeTestCompareFunction; +using TinyTest::MaybeTestConfigureFunction; +using TinyTest::PrintResults; +using TinyTest::TestResults; +using TinyTest::TestSuite; +using TinyTest::TestTuple; + +TEST(VectorCompare, ShouldPrintSizeMismatch) { + ostringstream os; + vector first = vector({1, 2, 3, 4}); + vector second = vector({1, 2, 3}); + Compare(os, first, second); + EXPECT_THAT(os.str(), Eq("size mismatch expected: 4, actual: 3")); +} + +TEST(VectorCompare, ShouldPrintVectorsDifferAtIndexZero) { + ostringstream os; + vector first = vector({1, 2, 3, 4}); + vector second = vector({0, 1, 2, 3}); + Compare(os, first, second); + EXPECT_THAT(os.str(), + Eq((string) "vectors differ at index 0, 1 != 0, expected: [ 1, 2, 3, 4 ], actual: [ 0, 1, 2, 3 ]")); +} + +TEST(VectorCompare, ShouldPrintVectorsDifferAtEnd) { + ostringstream os; + vector first = vector({1, 2, 3, 4}); + vector second = vector({1, 2, 3, 0}); + Compare(os, first, second); + EXPECT_THAT(os.str(), Eq("vectors differ at index 3, 4 != 0, expected: [ 1, 2, 3, 4 ], actual: [ 1, 2, 3, 0 ]")); +} + +TEST(VectorCompare, ShouldPrintNothingWhenVectorsAreEqual) { + ostringstream os; + vector first = vector({1, 2, 3, 4}); + vector second = vector({1, 2, 3, 4}); + Compare(os, first, second); + EXPECT_THAT(os.str(), Eq("")); +} + +TEST(TestResults, ShouldConstructTheDefaultInstance) { + TestResults actual; + EXPECT_THAT(actual.ErrorMessages().size(), Eq(0)); + EXPECT_THAT(actual.Errors(), Eq(0)); + EXPECT_THAT(actual.FailureMessages().size(), Eq(0)); + EXPECT_THAT(actual.Failed(), Eq(0)); + EXPECT_THAT(actual.SkipMessages().size(), Eq(0)); + EXPECT_THAT(actual.Skipped(), Eq(0)); + EXPECT_THAT(actual.Passed(), Eq(0)); + EXPECT_THAT(actual.Total(), Eq(0)); +} + +TEST(TestResults, ShouldCopyAnotherInstance) { + TestResults original; + original.Pass().Skip().Skip().Fail().Fail().Fail().Error().Error().Error().Error(); + TestResults actual(original); + EXPECT_THAT(actual.ErrorMessages().size(), Eq(0)); + EXPECT_THAT(actual.Errors(), Eq(4)); + EXPECT_THAT(actual.FailureMessages().size(), Eq(0)); + EXPECT_THAT(actual.Failed(), Eq(3)); + EXPECT_THAT(actual.SkipMessages().size(), Eq(0)); + EXPECT_THAT(actual.Skipped(), Eq(2)); + EXPECT_THAT(actual.Passed(), Eq(1)); + EXPECT_THAT(actual.Total(), Eq(6)); +} + +TEST(TestResults, ShouldCreateASpecificInstance) { + TestResults actual(1, 2, 3, 4, 5, {"hello"}, {"first", "second"}, {"third", "fourth", "fifth"}); + EXPECT_THAT(actual.ErrorMessages().size(), Eq(1)); + EXPECT_THAT(actual.Errors(), Eq(1)); + EXPECT_THAT(actual.FailureMessages().size(), Eq(2)); + EXPECT_THAT(actual.Failed(), Eq(2)); + EXPECT_THAT(actual.SkipMessages().size(), Eq(3)); + EXPECT_THAT(actual.Skipped(), Eq(4)); + EXPECT_THAT(actual.Passed(), Eq(3)); + EXPECT_THAT(actual.Total(), Eq(5)); +} + +TEST(TestResults, ShouldReportAnErrorWithoutAMessage) { + TestResults actual; + actual.Error(); + EXPECT_THAT(actual.ErrorMessages().size(), Eq(0)); + EXPECT_THAT(actual.Errors(), Eq(1)); + EXPECT_THAT(actual.FailureMessages().size(), Eq(0)); + EXPECT_THAT(actual.Failed(), Eq(0)); + EXPECT_THAT(actual.SkipMessages().size(), Eq(0)); + EXPECT_THAT(actual.Skipped(), Eq(0)); + EXPECT_THAT(actual.Passed(), Eq(0)); + EXPECT_THAT(actual.Total(), Eq(0)); +} + +TEST(TestResults, ShouldReportAnErrorWithAMessage) { + TestResults actual; + actual.Error("my error message"); + EXPECT_THAT(actual.ErrorMessages().size(), Eq(1)); + EXPECT_THAT(actual.ErrorMessages()[0], Eq("my error message")); + EXPECT_THAT(actual.Errors(), Eq(1)); + EXPECT_THAT(actual.FailureMessages().size(), Eq(0)); + EXPECT_THAT(actual.Failed(), Eq(0)); + EXPECT_THAT(actual.SkipMessages().size(), Eq(0)); + EXPECT_THAT(actual.Skipped(), Eq(0)); + EXPECT_THAT(actual.Passed(), Eq(0)); + EXPECT_THAT(actual.Total(), Eq(0)); +} + +TEST(TestResults, ShouldReportAFailureWithoutAMessage) { + TestResults actual; + actual.Fail(); + EXPECT_THAT(actual.ErrorMessages().size(), Eq(0)); + EXPECT_THAT(actual.Errors(), Eq(0)); + EXPECT_THAT(actual.FailureMessages().size(), Eq(0)); + EXPECT_THAT(actual.Failed(), Eq(1)); + EXPECT_THAT(actual.SkipMessages().size(), Eq(0)); + EXPECT_THAT(actual.Skipped(), Eq(0)); + EXPECT_THAT(actual.Passed(), Eq(0)); + EXPECT_THAT(actual.Total(), Eq(1)); +} + +TEST(TestResults, ShouldReportAFailureWithAMessage) { + TestResults actual; + actual.Fail("this test failed"); + EXPECT_THAT(actual.ErrorMessages().size(), Eq(0)); + EXPECT_THAT(actual.Errors(), Eq(0)); + EXPECT_THAT(actual.FailureMessages().size(), Eq(1)); + EXPECT_THAT(actual.FailureMessages()[0], Eq("this test failed")); + EXPECT_THAT(actual.Failed(), Eq(1)); + EXPECT_THAT(actual.SkipMessages().size(), Eq(0)); + EXPECT_THAT(actual.Skipped(), Eq(0)); + EXPECT_THAT(actual.Passed(), Eq(0)); + EXPECT_THAT(actual.Total(), Eq(1)); +} + +// You can't currently pass with a message. +TEST(TestResults, ShouldReportAPassWithoutAMessage) { + TestResults actual; + actual.Pass(); + EXPECT_THAT(actual.ErrorMessages().size(), Eq(0)); + EXPECT_THAT(actual.Errors(), Eq(0)); + EXPECT_THAT(actual.FailureMessages().size(), Eq(0)); + EXPECT_THAT(actual.Failed(), Eq(0)); + EXPECT_THAT(actual.SkipMessages().size(), Eq(0)); + EXPECT_THAT(actual.Skipped(), Eq(0)); + EXPECT_THAT(actual.Passed(), Eq(1)); + EXPECT_THAT(actual.Total(), Eq(1)); +} + +TEST(TestResults, ShouldReportASkipWithoutAMessage) { + TestResults actual; + actual.Skip(); + EXPECT_THAT(actual.ErrorMessages().size(), Eq(0)); + EXPECT_THAT(actual.Errors(), Eq(0)); + EXPECT_THAT(actual.FailureMessages().size(), Eq(0)); + EXPECT_THAT(actual.Failed(), Eq(0)); + EXPECT_THAT(actual.SkipMessages().size(), Eq(0)); + EXPECT_THAT(actual.Skipped(), Eq(1)); + EXPECT_THAT(actual.Passed(), Eq(0)); + EXPECT_THAT(actual.Total(), Eq(1)); +} + +TEST(TestResults, ShouldReportASkipWithAMessage) { + TestResults actual; + actual.Skip("not ready yet"); + EXPECT_THAT(actual.ErrorMessages().size(), Eq(0)); + EXPECT_THAT(actual.Errors(), Eq(0)); + EXPECT_THAT(actual.FailureMessages().size(), Eq(0)); + EXPECT_THAT(actual.Failed(), Eq(0)); + EXPECT_THAT(actual.SkipMessages().size(), Eq(1)); + EXPECT_THAT(actual.SkipMessages()[0], Eq("not ready yet")); + EXPECT_THAT(actual.Skipped(), Eq(1)); + EXPECT_THAT(actual.Passed(), Eq(0)); + EXPECT_THAT(actual.Total(), Eq(1)); +} + +TEST(TestResults, ShouldGetACombinationOfTwoInstances) { + const TestResults first = TestResults().Pass().Skip("S").Fail("F"); + const TestResults second = TestResults().Skip().Error("A").Error("B").Fail("D"); + const TestResults third = first + second; + + // Expect first was unchanged. + EXPECT_THAT(first.ErrorMessages().size(), Eq(0)); + EXPECT_THAT(first.Errors(), Eq(0)); + EXPECT_THAT(first.FailureMessages().size(), Eq(1)); + EXPECT_THAT(first.FailureMessages()[0], Eq("F")); + EXPECT_THAT(first.Failed(), Eq(1)); + EXPECT_THAT(first.SkipMessages().size(), Eq(1)); + EXPECT_THAT(first.SkipMessages()[0], Eq("S")); + EXPECT_THAT(first.Skipped(), Eq(1)); + EXPECT_THAT(first.Passed(), Eq(1)); + EXPECT_THAT(first.Total(), Eq(3)); + + // Expect second was unchanged + EXPECT_THAT(second.ErrorMessages().size(), Eq(2)); + EXPECT_THAT(second.ErrorMessages()[0], Eq("A")); + EXPECT_THAT(second.ErrorMessages()[1], Eq("B")); + EXPECT_THAT(second.Errors(), Eq(2)); + EXPECT_THAT(second.FailureMessages().size(), Eq(1)); + EXPECT_THAT(second.FailureMessages()[0], Eq("D")); + EXPECT_THAT(second.Failed(), Eq(1)); + EXPECT_THAT(second.SkipMessages().size(), Eq(0)); + EXPECT_THAT(second.Skipped(), Eq(1)); + EXPECT_THAT(second.Passed(), Eq(0)); + EXPECT_THAT(second.Total(), Eq(2)); + + // Expect third was the combination of the two. + EXPECT_THAT(third.ErrorMessages().size(), Eq(2)); + EXPECT_THAT(third.ErrorMessages()[0], Eq("A")); + EXPECT_THAT(third.ErrorMessages()[1], Eq("B")); + EXPECT_THAT(third.Errors(), Eq(2)); + EXPECT_THAT(third.FailureMessages().size(), Eq(2)); + EXPECT_THAT(third.FailureMessages()[0], Eq("F")); + EXPECT_THAT(third.FailureMessages()[1], Eq("D")); + EXPECT_THAT(third.Failed(), Eq(2)); + EXPECT_THAT(third.SkipMessages().size(), Eq(1)); + EXPECT_THAT(third.SkipMessages()[0], Eq("S")); + EXPECT_THAT(third.Skipped(), Eq(2)); + EXPECT_THAT(third.Passed(), Eq(1)); + EXPECT_THAT(third.Total(), Eq(5)); +} + +TEST(TestResults, ShouldCombineAnotherInstanceIntoThisOne) { + TestResults first = TestResults().Pass().Skip("S").Fail("F"); + const TestResults second = TestResults().Skip().Error("A").Error("B").Fail("D"); + first += second; + + // Expect second was unchanged + EXPECT_THAT(second.ErrorMessages().size(), Eq(2)); + EXPECT_THAT(second.ErrorMessages()[0], Eq("A")); + EXPECT_THAT(second.ErrorMessages()[1], Eq("B")); + EXPECT_THAT(second.Errors(), Eq(2)); + EXPECT_THAT(second.FailureMessages().size(), Eq(1)); + EXPECT_THAT(second.FailureMessages()[0], Eq("D")); + EXPECT_THAT(second.Failed(), Eq(1)); + EXPECT_THAT(second.SkipMessages().size(), Eq(0)); + EXPECT_THAT(second.Skipped(), Eq(1)); + EXPECT_THAT(second.Passed(), Eq(0)); + EXPECT_THAT(second.Total(), Eq(2)); + + // Expect first is the combination of the two. + EXPECT_THAT(first.ErrorMessages().size(), Eq(2)); + EXPECT_THAT(first.ErrorMessages()[0], Eq("A")); + EXPECT_THAT(first.ErrorMessages()[1], Eq("B")); + EXPECT_THAT(first.Errors(), Eq(2)); + EXPECT_THAT(first.FailureMessages().size(), Eq(2)); + EXPECT_THAT(first.FailureMessages()[0], Eq("F")); + EXPECT_THAT(first.FailureMessages()[1], Eq("D")); + EXPECT_THAT(first.Failed(), Eq(2)); + EXPECT_THAT(first.SkipMessages().size(), Eq(1)); + EXPECT_THAT(first.SkipMessages()[0], Eq("S")); + EXPECT_THAT(first.Skipped(), Eq(2)); + EXPECT_THAT(first.Passed(), Eq(1)); + EXPECT_THAT(first.Total(), Eq(5)); +} + +TEST(TestResults, ShouldCombineAnInstanceWithItself) { + TestResults actual = TestResults().Pass().Fail("A").Fail("B").Skip().Error("Bad").Skip(); + actual += actual; + EXPECT_THAT(actual.ErrorMessages().size(), Eq(2)); + EXPECT_THAT(actual.ErrorMessages().at(0), Eq("Bad")); + EXPECT_THAT(actual.ErrorMessages().at(1), Eq("Bad")); + EXPECT_THAT(actual.Errors(), Eq(2)); + EXPECT_THAT(actual.FailureMessages().size(), Eq(4)); + EXPECT_THAT(actual.FailureMessages().at(0), Eq("A")); + EXPECT_THAT(actual.FailureMessages().at(1), Eq("B")); + EXPECT_THAT(actual.FailureMessages().at(2), Eq("A")); + EXPECT_THAT(actual.FailureMessages().at(3), Eq("B")); + EXPECT_THAT(actual.Failed(), Eq(4)); + EXPECT_THAT(actual.SkipMessages().size(), Eq(0)); + EXPECT_THAT(actual.Skipped(), Eq(4)); + EXPECT_THAT(actual.Passed(), Eq(2)); + EXPECT_THAT(actual.Total(), Eq(10)); +} + +TEST(DefaultTestCompareFunction, ShouldBeNullOpt) { + auto actual = DefaultTestCompareFunction(); + EXPECT_THAT(actual, Eq(nullopt)); +} + +TEST(DefaultTestConfigureFunction, ShouldBeNullOpt) { + auto actual = DefaultTestConfigureFunction(); + EXPECT_THAT(actual, Eq(nullopt)); +} + +// Test that TestTuple makes the right kind of tuple. +TEST(TestTuple, ShouldDoTheThing) { + TestTuple test = { + (string) "ASDF", + 0, + {(string) "first", 2, "third"}, + (MaybeTestCompareFunction)nullopt, + (MaybeTestConfigureFunction)nullopt, + (MaybeTestConfigureFunction)nullopt, + true, + }; + tuple arguments = get<2>(test); + EXPECT_THAT(get<0>(test), Eq("ASDF")); + EXPECT_THAT(get<1>(test), Eq(0)); + EXPECT_THAT(get<2>(test), Eq(make_tuple((string) "first", 2, "third"))); + EXPECT_THAT(get<3>(test), Eq(nullopt)); + EXPECT_THAT(get<4>(test), Eq(nullopt)); + EXPECT_THAT(get<5>(test), Eq(nullopt)); + EXPECT_THAT(get<6>(test), Eq(true)); +} + +TEST(TestTuple, ShouldCoerceValuesToTheCorrectTypes) { + TestTuple test = { + "A", + 0, + {"B", 1, "C"}, + nullopt, + nullopt, + nullopt, + true, + }; + tuple arguments = get<2>(test); + EXPECT_THAT(get<0>(test), Eq((string) "A")); + EXPECT_THAT(get<1>(test), Eq(0)); + EXPECT_THAT(get<2>(test), Eq(make_tuple((string) "B", 1, "C"))); + EXPECT_THAT(get<3>(test), Eq(nullopt)); + EXPECT_THAT(get<4>(test), Eq(nullopt)); + EXPECT_THAT(get<5>(test), Eq(nullopt)); + EXPECT_THAT(get<6>(test), Eq(true)); +} + +TEST(MakeTest, ShouldMakeTests) { + MaybeTestCompareFunction test_Compare = [](const string& left, const string& right) -> bool { return false; }; + MaybeTestConfigureFunction after_each = []() {}; + MaybeTestConfigureFunction before_each = []() {}; + + tuple first = MakeTest( + (string) "A Test", (string) "A", make_tuple((string) "ABCDEFG", 0), test_Compare, before_each, after_each, false); + + TestTuple second = + MakeTest("Another Test", "B", make_tuple((string) "ABCDEF", 1)); + TestTuple third = first; + + EXPECT_THAT(get<0>(first), Eq("A Test")); + EXPECT_THAT(get<0>(second), Eq("Another Test")); + EXPECT_THAT(get<0>(third), Eq("A Test")); + + EXPECT_THAT(get<1>(first), Eq("A")); + EXPECT_THAT(get<1>(second), Eq("B")); + EXPECT_THAT(get<1>(third), Eq("A")); + + EXPECT_THAT(get<2>(first), Eq(make_tuple((string) "ABCDEFG", 0))); + EXPECT_THAT(get<2>(second), Eq(make_tuple((string) "ABCDEF", 1))); + EXPECT_THAT(get<2>(third), Eq(make_tuple((string) "ABCDEFG", 0))); + + // TODO: We can only test Eq(nullopt) or not. + EXPECT_THAT(get<3>(first), Ne(nullopt)); + EXPECT_THAT(get<3>(second), Eq(nullopt)); + EXPECT_THAT(get<3>(third), Ne(nullopt)); + + EXPECT_THAT(get<4>(first), Ne(nullopt)); + EXPECT_THAT(get<4>(second), Eq(nullopt)); + EXPECT_THAT(get<4>(third), Ne(nullopt)); + + EXPECT_THAT(get<5>(first), Ne(nullopt)); + EXPECT_THAT(get<5>(second), Eq(nullopt)); + EXPECT_THAT(get<5>(third), Ne(nullopt)); + + EXPECT_THAT(get<6>(first), Eq(false)); + EXPECT_THAT(get<6>(second), Eq(true)); + EXPECT_THAT(get<6>(third), Eq(false)); + + // TODO: Create make_inputs and TestInputParams to reduce the amount of type casting in these lines. +} + +TEST(TestSuite, ShouldCoerceValuesToTheCorrectTypes) { + auto fnToTest = [](const string& text, int position) -> string { + if (position >= 0 && position < text.size()) { + return &text.at(position); + } + return ""; + }; + MaybeTestCompareFunction test_Compare = [](const string& left, const string& right) -> bool { return false; }; + MaybeTestCompareFunction suite_Compare = [](const string& left, const string& right) -> bool { return true; }; + MaybeTestConfigureFunction after_all = []() {}; + MaybeTestConfigureFunction after_each = []() {}; + MaybeTestConfigureFunction before_all = []() {}; + MaybeTestConfigureFunction before_each = []() {}; + TestTuple test_run = MakeTest( + "Test Name", "Expected", make_tuple((string) "text", 0), test_Compare, before_each, after_each, false); + TestSuite first = { + "Suite Name", + fnToTest, + { + test_run, + }, + suite_Compare, + before_all, + after_all, + true, + }; + EXPECT_THAT(get<0>(first), Eq("Suite Name")); + // EXPECT_THAT(get<1>(first), Eq(fnToTest)); + EXPECT_THAT(get<2>(first).size(), Eq(1)); + EXPECT_THAT(get<3>(first), Ne(nullopt)); + EXPECT_THAT(get<4>(first), Ne(nullopt)); + EXPECT_THAT(get<5>(first), Ne(nullopt)); + EXPECT_THAT(get<6>(first), Eq(true)); + + auto test_data = *get<2>(first).begin(); + EXPECT_THAT(get<0>(test_data), Eq("Test Name")); + EXPECT_THAT(get<1>(test_data), Eq("Expected")); + // Item 2 is checked below as inputs. + EXPECT_THAT(get<3>(test_data), Ne(nullopt)); + EXPECT_THAT(get<4>(test_data), Ne(nullopt)); + EXPECT_THAT(get<5>(test_data), Ne(nullopt)); + EXPECT_THAT(get<6>(test_data), Eq(false)); + + auto inputs = get<2>(test_data); + EXPECT_THAT(get<0>(inputs), Eq("text")); + EXPECT_THAT(get<1>(inputs), Eq(0)); +} + +TEST(MakeTestSuite, ShouldMakeATestSuiteWithAVectorOfTestRuns) { + auto fnToTest = [](const string& text, int position) -> string { + if (position >= 0 && position < text.size()) { + return &text.at(position); + } + return ""; + }; + MaybeTestCompareFunction test_Compare = [](const string& left, const string& right) -> bool { return false; }; + MaybeTestCompareFunction suite_Compare = [](const string& left, const string& right) -> bool { return true; }; + MaybeTestConfigureFunction after_all = []() {}; + MaybeTestConfigureFunction after_each = []() {}; + MaybeTestConfigureFunction before_all = []() {}; + MaybeTestConfigureFunction before_each = []() {}; + TestTuple test_run = MakeTest( + "Test Name", "Expected", make_tuple((string) "text", 0), test_Compare, before_each, after_each, false); + TestSuite first = + MakeTestSuite("Suite Name", fnToTest, {test_run}, suite_Compare, before_all, after_all, false); + + EXPECT_THAT(get<0>(first), Eq("Suite Name")); + // EXPECT_THAT(get<1>(first), Eq(fnToTest)); + EXPECT_THAT(get<2>(first).size(), Eq(1)); + EXPECT_THAT(get<3>(first), Ne(nullopt)); + EXPECT_THAT(get<4>(first), Ne(nullopt)); + EXPECT_THAT(get<5>(first), Ne(nullopt)); + EXPECT_THAT(get<6>(first), Eq(false)); + + auto test_data = *get<2>(first).begin(); + EXPECT_THAT(get<0>(test_data), Eq("Test Name")); + EXPECT_THAT(get<1>(test_data), Eq("Expected")); + // Item 2 is checked below as inputs. + EXPECT_THAT(get<3>(test_data), Ne(nullopt)); + EXPECT_THAT(get<4>(test_data), Ne(nullopt)); + EXPECT_THAT(get<5>(test_data), Ne(nullopt)); + EXPECT_THAT(get<6>(test_data), Eq(false)); + + auto inputs = get<2>(test_data); + EXPECT_THAT(get<0>(inputs), Eq("text")); + EXPECT_THAT(get<1>(inputs), Eq(0)); +} + +TEST(MakeTestSuite, ShouldMakeATestSuiteWithAnInitializerListOfTestRuns) { + auto fnToTest = [](const string& text, int position) -> string { + if (position >= 0 && position < text.size()) { + return &text.at(position); + } + return ""; + }; + MaybeTestCompareFunction test_Compare = [](const string& left, const string& right) -> bool { return false; }; + MaybeTestCompareFunction suite_Compare = [](const string& left, const string& right) -> bool { return true; }; + MaybeTestConfigureFunction after_all = []() {}; + MaybeTestConfigureFunction after_each = []() {}; + MaybeTestConfigureFunction before_all = []() {}; + MaybeTestConfigureFunction before_each = []() {}; + TestTuple test_run = MakeTest( + "Test Name", "Expected", make_tuple((string) "text", 0), test_Compare, before_each, after_each, false); + TestSuite first = + MakeTestSuite("Suite Two", fnToTest, {test_run}, suite_Compare, before_all, after_all, true); + + EXPECT_THAT(get<0>(first), Eq("Suite Two")); + // EXPECT_THAT(get<1>(first), Eq(fnToTest)); + EXPECT_THAT(get<2>(first).size(), Eq(1)); + EXPECT_THAT(get<3>(first), Ne(nullopt)); + EXPECT_THAT(get<4>(first), Ne(nullopt)); + EXPECT_THAT(get<5>(first), Ne(nullopt)); + EXPECT_THAT(get<6>(first), Eq(true)); + + auto test_data = *get<2>(first).begin(); + EXPECT_THAT(get<0>(test_data), Eq("Test Name")); + EXPECT_THAT(get<1>(test_data), Eq("Expected")); + // Item 2 is checked below as inputs. + EXPECT_THAT(get<3>(test_data), Ne(nullopt)); + EXPECT_THAT(get<4>(test_data), Ne(nullopt)); + EXPECT_THAT(get<5>(test_data), Ne(nullopt)); + EXPECT_THAT(get<6>(test_data), Eq(false)); + + auto inputs = get<2>(test_data); + EXPECT_THAT(get<0>(inputs), Eq("text")); + EXPECT_THAT(get<1>(inputs), Eq(0)); +} + +TEST(PrintResults, ShouldDoTheThing) { + TestResults results; + results.Error() + .Fail() + .Fail() + .Skip() + .Skip() + .Skip() + .Pass() + .Pass() + .Pass() + .Pass() + .Pass() + .Skip("skip with a message") + .Fail("fail with a message") + .Error("error with a message"); + ostringstream os; + PrintResults(os, results); + EXPECT_THAT(os.str(), Eq(R"test(Skipped: +🚧Skipped: skip with a message +Failures: +❌FAILED: fail with a message +Errors: +🔥ERROR: error with a message +Total tests: 12 +Passed: 5 ✅ +Failed: 3 ❌ +Skipped: 4 🚧 +Errors: 2 🔥 +)test")); +} + +TEST(Coalesce, ShouldCombineTwoNulls) { + MaybeTestConfigureFunction fn1 = nullopt; + MaybeTestConfigureFunction fn2 = nullopt; + MaybeTestConfigureFunction actual = Coalesce(fn1, fn2); + EXPECT_THAT(actual, Eq(nullopt)); + EXPECT_THAT(actual.has_value(), Eq(false)); +} + +TEST(Coalesce, ShouldCombineAFunctionWithANull) { + bool this_starts_false; + MaybeTestConfigureFunction fn1 = [&this_starts_false]() { this_starts_false = true; }; + MaybeTestConfigureFunction fn2 = nullopt; + MaybeTestConfigureFunction actual = Coalesce(fn1, fn2); + EXPECT_THAT(actual, Ne(nullopt)); + EXPECT_THAT(actual.has_value(), Eq(true)); + actual.value()(); + EXPECT_THAT(this_starts_false, true); +} + +TEST(Coalesce, ShouldCombineANullWithAFunction) { + bool this_starts_false; + MaybeTestConfigureFunction fn1 = nullopt; + MaybeTestConfigureFunction fn2 = [&this_starts_false]() { this_starts_false = true; }; + MaybeTestConfigureFunction actual = Coalesce(fn1, fn2); + EXPECT_THAT(actual, Ne(nullopt)); + EXPECT_THAT(actual.has_value(), Eq(true)); + actual.value()(); + EXPECT_THAT(this_starts_false, true); +} + +TEST(Coalesce, ShouldCombineTwoFunctions) { + bool red_flag = false; + bool blue_flag = false; + MaybeTestConfigureFunction fn1 = [&red_flag]() { red_flag = true; }; + MaybeTestConfigureFunction fn2 = [&blue_flag]() { blue_flag = true; }; + MaybeTestConfigureFunction actual = Coalesce(fn1, fn2); + EXPECT_THAT(actual, Ne(nullopt)); + EXPECT_THAT(actual.has_value(), Eq(true)); + actual.value()(); + EXPECT_THAT(red_flag, Eq(true)); + EXPECT_THAT(blue_flag, Eq(true)); +} + +TEST(Coalesce, ShouldExecuteInTheCorrectOrder) { + vector lines; + MaybeTestConfigureFunction fn1 = [&lines]() { lines.push_back("Line 1"); }; + MaybeTestConfigureFunction fn2 = [&lines]() { lines.push_back("Line 2"); }; + MaybeTestConfigureFunction actual = Coalesce(fn1, fn2); + EXPECT_THAT(actual, Ne(nullopt)); + EXPECT_THAT(actual.has_value(), Eq(true)); + actual.value()(); + EXPECT_THAT(lines.size(), Eq(2)); + EXPECT_THAT(lines.at(0), Eq("Line 1")); + EXPECT_THAT(lines.at(1), Eq("Line 2")); +} + +TEST(ExecuteSuiteWithParams, ShouldNotExecuteADisabledSuite) { + bool suite_Compare_called = false; + MaybeTestCompareFunction suite_Compare = [&suite_Compare_called](bool left, bool right) { + suite_Compare_called = true; + return left == right; + }; + bool before_all_called = false; + MaybeTestConfigureFunction before_all = [&before_all_called]() { before_all_called = true; }; + bool after_all_called = false; + MaybeTestConfigureFunction after_all = [&after_all_called]() { after_all_called = true; }; + bool test_Compare_called = false; + MaybeTestCompareFunction test_Compare = [&test_Compare_called](bool left, bool right) { + test_Compare_called = true; + return left == right; + }; + bool before_each_called = false; + MaybeTestConfigureFunction before_each = [&before_each_called]() { before_each_called = true; }; + bool after_each_called = false; + MaybeTestConfigureFunction after_each = [&after_each_called]() { after_each_called = true; }; + bool test_function_called = false; + function test_function = [&test_function_called]() { + test_function_called = true; + return true; + }; + + // TODO: Remove this wrapper function once InterceptCout works properly with parameters. + function wrapper = [&]() { + ExecuteSuite("My Suite", + test_function, + { + MakeTest("Test Name", true, make_tuple(), test_Compare, before_each, after_each, true), + }, + suite_Compare, + before_all, + after_all, + false); + }; + + string output = InterceptCout(wrapper); + EXPECT_THAT(output, Eq(R"test(🚧Skipping suite: My Suite because it is disabled. + 🚧Skipping Test: Test Name because the suite is disabled. +)test")); + EXPECT_THAT(test_function_called, Eq(false)); + EXPECT_THAT(suite_Compare_called, Eq(false)); + EXPECT_THAT(before_all_called, Eq(false)); + EXPECT_THAT(after_all_called, Eq(false)); + EXPECT_THAT(test_Compare_called, Eq(false)); + EXPECT_THAT(before_each_called, Eq(false)); + EXPECT_THAT(after_each_called, Eq(false)); +} + +TEST(ExecuteSuiteWithParams, ShouldNotExecuteASuiteWithNoTests) { + bool suite_Compare_called = false; + MaybeTestCompareFunction suite_Compare = [&suite_Compare_called](bool left, bool right) { + suite_Compare_called = true; + return left == right; + }; + bool before_all_called = false; + MaybeTestConfigureFunction before_all = [&before_all_called]() { before_all_called = true; }; + bool after_all_called = false; + MaybeTestConfigureFunction after_all = [&after_all_called]() { after_all_called = true; }; + bool test_Compare_called = false; + MaybeTestCompareFunction test_Compare = [&test_Compare_called](bool left, bool right) { + test_Compare_called = true; + return left == right; + }; + bool before_each_called = false; + MaybeTestConfigureFunction before_each = [&before_each_called]() { before_each_called = true; }; + bool after_each_called = false; + MaybeTestConfigureFunction after_each = [&after_each_called]() { after_each_called = true; }; + bool test_function_called = false; + function test_function = [&test_function_called]() { + test_function_called = true; + return true; + }; + + // TODO: Remove this wrapper function once InterceptCout works properly with parameters. + function wrapper = [&]() { + ExecuteSuite("My Suite", test_function, {}, suite_Compare, before_all, after_all, true); + }; + + string output = InterceptCout(wrapper); + EXPECT_THAT(output, Eq("🚧Skipping suite: My Suite because it is empty.\n")); + EXPECT_THAT(test_function_called, Eq(false)); + EXPECT_THAT(suite_Compare_called, Eq(false)); + EXPECT_THAT(before_all_called, Eq(false)); + EXPECT_THAT(after_all_called, Eq(false)); + EXPECT_THAT(test_Compare_called, Eq(false)); + EXPECT_THAT(before_each_called, Eq(false)); + EXPECT_THAT(after_each_called, Eq(false)); +} + +TEST(ExecuteSuiteWithParams, ShouldExecuteASuiteWithASinglePass) { + int suite_Compare_call_count = 0; + MaybeTestCompareFunction suite_Compare = [&](bool left, bool right) { + suite_Compare_call_count++; + return left == right; + }; + int before_all_call_count = 0; + MaybeTestConfigureFunction before_all = [&]() { before_all_call_count++; }; + int after_all_call_count = 0; + MaybeTestConfigureFunction after_all = [&]() { after_all_call_count++; }; + int test_Compare_call_count = 0; + MaybeTestCompareFunction test_Compare = [&test_Compare_call_count](bool left, bool right) { + test_Compare_call_count++; + return left == right; + }; + int before_each_call_count = 0; + MaybeTestConfigureFunction before_each = [&before_each_call_count]() { before_each_call_count++; }; + int after_each_call_count = 0; + MaybeTestConfigureFunction after_each = [&after_each_call_count]() { after_each_call_count++; }; + int test_function_call_count = 0; + function test_function = [&test_function_call_count]() { + test_function_call_count++; + return true; + }; + + // TODO: Remove this wrapper function once InterceptCout works properly with parameters. + function wrapper = [&]() { + ExecuteSuite("My Suite", + test_function, + { + MakeTest("Test Name", true, make_tuple(), test_Compare, before_each, after_each, true), + }, + suite_Compare, + before_all, + after_all, + true); + }; + + string output = InterceptCout(wrapper); + EXPECT_THAT(output, + Eq( + R"test(🚀Beginning Suite: My Suite + Beginning Test: Test Name + ✅PASSED + Ending Test: Test Name +Ending Suite: My Suite +)test")); + EXPECT_THAT(test_function_call_count, Eq(1)); + EXPECT_THAT(suite_Compare_call_count, Eq(0)); + EXPECT_THAT(before_all_call_count, Eq(1)); + EXPECT_THAT(after_all_call_count, Eq(1)); + EXPECT_THAT(test_Compare_call_count, Eq(1)); + EXPECT_THAT(before_each_call_count, Eq(1)); + EXPECT_THAT(after_each_call_count, Eq(1)); +} + +TEST(ExecuteSuiteWithParams, ShouldExecuteASuiteWithASingleFailure) { + int suite_Compare_call_count = 0; + MaybeTestCompareFunction suite_Compare = [&](bool left, bool right) { + suite_Compare_call_count++; + return left == right; + }; + int before_all_call_count = 0; + MaybeTestConfigureFunction before_all = [&]() { before_all_call_count++; }; + int after_all_call_count = 0; + MaybeTestConfigureFunction after_all = [&]() { after_all_call_count++; }; + int test_Compare_call_count = 0; + MaybeTestCompareFunction test_Compare = [&test_Compare_call_count](bool left, bool right) { + test_Compare_call_count++; + return left == right; + }; + int before_each_call_count = 0; + MaybeTestConfigureFunction before_each = [&before_each_call_count]() { before_each_call_count++; }; + int after_each_call_count = 0; + MaybeTestConfigureFunction after_each = [&after_each_call_count]() { after_each_call_count++; }; + int test_function_call_count = 0; + function test_function = [&test_function_call_count]() { + test_function_call_count++; + return true; + }; + + // TODO: Remove this wrapper function once InterceptCout works properly with parameters. + function wrapper = [&]() { + ExecuteSuite("My Suite", + test_function, + { + MakeTest("Test Name", false, make_tuple(), test_Compare, before_each, after_each, true), + }, + suite_Compare, + before_all, + after_all, + true); + }; + + string output = InterceptCout(wrapper); + EXPECT_THAT(output, + Eq( + R"test(🚀Beginning Suite: My Suite + Beginning Test: Test Name + ❌FAILED: expected: 0, actual: 1 + Ending Test: Test Name +Ending Suite: My Suite +)test")); + EXPECT_THAT(test_function_call_count, Eq(1)); + EXPECT_THAT(suite_Compare_call_count, Eq(0)); + EXPECT_THAT(before_all_call_count, Eq(1)); + EXPECT_THAT(after_all_call_count, Eq(1)); + EXPECT_THAT(test_Compare_call_count, Eq(1)); + EXPECT_THAT(before_each_call_count, Eq(1)); + EXPECT_THAT(after_each_call_count, Eq(1)); +} + +TEST(ExecuteSuiteWithParams, ShouldExecuteASuiteWithASingleSkip) { + int suite_Compare_call_count = 0; + MaybeTestCompareFunction suite_Compare = [&](bool left, bool right) { + suite_Compare_call_count++; + return left == right; + }; + int before_all_call_count = 0; + MaybeTestConfigureFunction before_all = [&]() { before_all_call_count++; }; + int after_all_call_count = 0; + MaybeTestConfigureFunction after_all = [&]() { after_all_call_count++; }; + int test_Compare_call_count = 0; + MaybeTestCompareFunction test_Compare = [&test_Compare_call_count](bool left, bool right) { + test_Compare_call_count++; + return left == right; + }; + int before_each_call_count = 0; + MaybeTestConfigureFunction before_each = [&before_each_call_count]() { before_each_call_count++; }; + int after_each_call_count = 0; + MaybeTestConfigureFunction after_each = [&after_each_call_count]() { after_each_call_count++; }; + int test_function_call_count = 0; + function test_function = [&test_function_call_count]() { + test_function_call_count++; + return true; + }; + + // TODO: Remove this wrapper function once InterceptCout works properly with parameters. + function wrapper = [&]() { + ExecuteSuite("My Suite", + test_function, + { + MakeTest("Test Name", true, make_tuple(), test_Compare, before_each, after_each, false), + }, + suite_Compare, + before_all, + after_all, + true); + }; + + string output = InterceptCout(wrapper); + EXPECT_THAT(output, + Eq( + R"test(🚀Beginning Suite: My Suite + 🚧Skipping Test: Test Name +Ending Suite: My Suite +)test")); + EXPECT_THAT(test_function_call_count, Eq(0)); + EXPECT_THAT(suite_Compare_call_count, Eq(0)); + EXPECT_THAT(before_all_call_count, Eq(1)); + EXPECT_THAT(after_all_call_count, Eq(1)); + EXPECT_THAT(test_Compare_call_count, Eq(0)); + EXPECT_THAT(before_each_call_count, Eq(0)); + EXPECT_THAT(after_each_call_count, Eq(0)); +} + +TEST(ExecuteSuiteWithParams, ShouldExecuteASuiteWithASinglePassAndADisabledTest) { + int suite_Compare_call_count = 0; + MaybeTestCompareFunction suite_Compare = [&](bool left, bool right) { + suite_Compare_call_count++; + return left == right; + }; + int before_all_call_count = 0; + MaybeTestConfigureFunction before_all = [&]() { before_all_call_count++; }; + int after_all_call_count = 0; + MaybeTestConfigureFunction after_all = [&]() { after_all_call_count++; }; + int test_Compare_call_count = 0; + MaybeTestCompareFunction test_Compare = [&test_Compare_call_count](bool left, bool right) { + test_Compare_call_count++; + return left == right; + }; + int before_each_call_count = 0; + MaybeTestConfigureFunction before_each = [&before_each_call_count]() { before_each_call_count++; }; + int after_each_call_count = 0; + MaybeTestConfigureFunction after_each = [&after_each_call_count]() { after_each_call_count++; }; + int test_function_call_count = 0; + function test_function = [&test_function_call_count]() { + test_function_call_count++; + return true; + }; + + // TODO: Remove this wrapper function once InterceptCout works properly with parameters. + function wrapper = [&]() { + ExecuteSuite("My Suite", + test_function, + { + MakeTest("Test Name", true, make_tuple(), test_Compare, before_each, after_each, true), + MakeTest("Second Test", false, make_tuple(), test_Compare, before_each, after_each, false), + }, + suite_Compare, + before_all, + after_all, + true); + }; + + string output = InterceptCout(wrapper); + EXPECT_THAT(output, + Eq( + R"test(🚀Beginning Suite: My Suite + Beginning Test: Test Name + ✅PASSED + Ending Test: Test Name + 🚧Skipping Test: Second Test +Ending Suite: My Suite +)test")); + EXPECT_THAT(test_function_call_count, Eq(1)); + EXPECT_THAT(suite_Compare_call_count, Eq(0)); + EXPECT_THAT(before_all_call_count, Eq(1)); + EXPECT_THAT(after_all_call_count, Eq(1)); + EXPECT_THAT(test_Compare_call_count, Eq(1)); + EXPECT_THAT(before_each_call_count, Eq(1)); + EXPECT_THAT(after_each_call_count, Eq(1)); +} + +TEST(ExecuteSuiteWithParams, ShouldCatchAnExceptionThrownByATest) { + int suite_Compare_call_count = 0; + MaybeTestCompareFunction suite_Compare = [&](bool left, bool right) { + suite_Compare_call_count++; + return left == right; + }; + int before_all_call_count = 0; + MaybeTestConfigureFunction before_all = [&]() { before_all_call_count++; }; + int after_all_call_count = 0; + MaybeTestConfigureFunction after_all = [&]() { after_all_call_count++; }; + int test_Compare_call_count = 0; + MaybeTestCompareFunction test_Compare = [&test_Compare_call_count](bool left, bool right) { + test_Compare_call_count++; + return left == right; + }; + int before_each_call_count = 0; + MaybeTestConfigureFunction before_each = [&before_each_call_count]() { before_each_call_count++; }; + int after_each_call_count = 0; + MaybeTestConfigureFunction after_each = [&after_each_call_count]() { after_each_call_count++; }; + int test_function_call_count = 0; + function test_function = [&test_function_call_count]() { + test_function_call_count++; + throw(std::exception()); + return true; + }; + + // TODO: Remove this wrapper function once InterceptCout works properly with parameters. + function wrapper = [&]() { + ExecuteSuite("My Suite", + test_function, + { + MakeTest("Test Name", true, make_tuple(), test_Compare, before_each, after_each, true), + }, + suite_Compare, + before_all, + after_all, + true); + }; + + string output = InterceptCout(wrapper); + // string output = ""; + // EXPECT_THROW((output = InterceptCout(wrapper)), std::exception); + EXPECT_THAT(output, + Eq( + R"test(🚀Beginning Suite: My Suite + Beginning Test: Test Name + 🔥ERROR: Caught exception "std::exception". + ❌FAILED: expected: 1, actual: 0 + Ending Test: Test Name +Ending Suite: My Suite +)test")); + EXPECT_THAT(test_function_call_count, Eq(1)); + EXPECT_THAT(suite_Compare_call_count, Eq(0)); + EXPECT_THAT(before_all_call_count, Eq(1)); + EXPECT_THAT(after_all_call_count, Eq(1)); + EXPECT_THAT(test_Compare_call_count, Eq(1)); + EXPECT_THAT(before_each_call_count, Eq(1)); + EXPECT_THAT(after_each_call_count, Eq(1)); +} + +TEST(ExecuteSuiteWithParams, ShouldCatchAStringThrownByATest) { + int suite_Compare_call_count = 0; + MaybeTestCompareFunction suite_Compare = [&](bool left, bool right) { + suite_Compare_call_count++; + return left == right; + }; + int before_all_call_count = 0; + MaybeTestConfigureFunction before_all = [&]() { before_all_call_count++; }; + int after_all_call_count = 0; + MaybeTestConfigureFunction after_all = [&]() { after_all_call_count++; }; + int test_Compare_call_count = 0; + MaybeTestCompareFunction test_Compare = [&test_Compare_call_count](bool left, bool right) { + test_Compare_call_count++; + return left == right; + }; + int before_each_call_count = 0; + MaybeTestConfigureFunction before_each = [&before_each_call_count]() { before_each_call_count++; }; + int after_each_call_count = 0; + MaybeTestConfigureFunction after_each = [&after_each_call_count]() { after_each_call_count++; }; + int test_function_call_count = 0; + function test_function = [&test_function_call_count]() { + test_function_call_count++; + throw((string) "burp"); + return true; + }; + + // TODO: Remove this wrapper function once InterceptCout works properly with parameters. + function wrapper = [&]() { + ExecuteSuite("My Suite", + test_function, + { + MakeTest("Test Name", true, make_tuple(), test_Compare, before_each, after_each, true), + }, + suite_Compare, + before_all, + after_all, + true); + }; + + string output = InterceptCout(wrapper); + // string output = ""; + // EXPECT_THROW((output = InterceptCout(wrapper)), std::exception); + EXPECT_THAT(output, + Eq( + R"test(🚀Beginning Suite: My Suite + Beginning Test: Test Name + 🔥ERROR: Caught string "burp". + ❌FAILED: expected: 1, actual: 0 + Ending Test: Test Name +Ending Suite: My Suite +)test")); + EXPECT_THAT(test_function_call_count, Eq(1)); + EXPECT_THAT(suite_Compare_call_count, Eq(0)); + EXPECT_THAT(before_all_call_count, Eq(1)); + EXPECT_THAT(after_all_call_count, Eq(1)); + EXPECT_THAT(test_Compare_call_count, Eq(1)); + EXPECT_THAT(before_each_call_count, Eq(1)); + EXPECT_THAT(after_each_call_count, Eq(1)); +} + +TEST(ExecuteSuiteWithParams, ShouldCatchACStringThrownByATest) { + int suite_Compare_call_count = 0; + MaybeTestCompareFunction suite_Compare = [&](bool left, bool right) { + suite_Compare_call_count++; + return left == right; + }; + int before_all_call_count = 0; + MaybeTestConfigureFunction before_all = [&]() { before_all_call_count++; }; + int after_all_call_count = 0; + MaybeTestConfigureFunction after_all = [&]() { after_all_call_count++; }; + int test_Compare_call_count = 0; + MaybeTestCompareFunction test_Compare = [&test_Compare_call_count](bool left, bool right) { + test_Compare_call_count++; + return left == right; + }; + int before_each_call_count = 0; + MaybeTestConfigureFunction before_each = [&before_each_call_count]() { before_each_call_count++; }; + int after_each_call_count = 0; + MaybeTestConfigureFunction after_each = [&after_each_call_count]() { after_each_call_count++; }; + int test_function_call_count = 0; + function test_function = [&test_function_call_count]() { + test_function_call_count++; + throw "burp"; + return true; + }; + + // TODO: Remove this wrapper function once InterceptCout works properly with parameters. + function wrapper = [&]() { + ExecuteSuite("My Suite", + test_function, + { + MakeTest("Test Name", true, make_tuple(), test_Compare, before_each, after_each, true), + }, + suite_Compare, + before_all, + after_all, + true); + }; + + string output = InterceptCout(wrapper); + // string output = ""; + // EXPECT_THROW((output = InterceptCout(wrapper)), std::exception); + EXPECT_THAT(output, + Eq( + R"test(🚀Beginning Suite: My Suite + Beginning Test: Test Name + 🔥ERROR: Caught c-string "burp". + ❌FAILED: expected: 1, actual: 0 + Ending Test: Test Name +Ending Suite: My Suite +)test")); + EXPECT_THAT(test_function_call_count, Eq(1)); + EXPECT_THAT(suite_Compare_call_count, Eq(0)); + EXPECT_THAT(before_all_call_count, Eq(1)); + EXPECT_THAT(after_all_call_count, Eq(1)); + EXPECT_THAT(test_Compare_call_count, Eq(1)); + EXPECT_THAT(before_each_call_count, Eq(1)); + EXPECT_THAT(after_each_call_count, Eq(1)); +} + +TEST(ExecuteSuiteWithParams, ShouldCatchSomethingElseThrownByATest) { + int suite_Compare_call_count = 0; + MaybeTestCompareFunction suite_Compare = [&](bool left, bool right) { + suite_Compare_call_count++; + return left == right; + }; + int before_all_call_count = 0; + MaybeTestConfigureFunction before_all = [&]() { before_all_call_count++; }; + int after_all_call_count = 0; + MaybeTestConfigureFunction after_all = [&]() { after_all_call_count++; }; + int test_Compare_call_count = 0; + MaybeTestCompareFunction test_Compare = [&test_Compare_call_count](bool left, bool right) { + test_Compare_call_count++; + return left == right; + }; + int before_each_call_count = 0; + MaybeTestConfigureFunction before_each = [&before_each_call_count]() { before_each_call_count++; }; + int after_each_call_count = 0; + MaybeTestConfigureFunction after_each = [&after_each_call_count]() { after_each_call_count++; }; + int test_function_call_count = 0; + function test_function = [&test_function_call_count]() { + test_function_call_count++; + throw(42); + return true; + }; + + // TODO: Remove this wrapper function once InterceptCout works properly with parameters. + function wrapper = [&]() { + ExecuteSuite("My Suite", + test_function, + { + MakeTest("Test Name", true, make_tuple(), test_Compare, before_each, after_each, true), + }, + suite_Compare, + before_all, + after_all, + true); + }; + + string output = InterceptCout(wrapper); + // string output = ""; + // EXPECT_THROW((output = InterceptCout(wrapper)), std::exception); + EXPECT_THAT(output, + Eq( + R"test(🚀Beginning Suite: My Suite + Beginning Test: Test Name + 🔥ERROR: Caught something that is neither an std::exception nor an std::string. + ❌FAILED: expected: 1, actual: 0 + Ending Test: Test Name +Ending Suite: My Suite +)test")); + EXPECT_THAT(test_function_call_count, Eq(1)); + EXPECT_THAT(suite_Compare_call_count, Eq(0)); + EXPECT_THAT(before_all_call_count, Eq(1)); + EXPECT_THAT(after_all_call_count, Eq(1)); + EXPECT_THAT(test_Compare_call_count, Eq(1)); + EXPECT_THAT(before_each_call_count, Eq(1)); + EXPECT_THAT(after_each_call_count, Eq(1)); +} + +TEST(ExecuteSuiteWithTuple, ShouldNotExecuteADisabledSuite) { + bool suite_Compare_called = false; + MaybeTestCompareFunction suite_Compare = [&suite_Compare_called](bool left, bool right) { + suite_Compare_called = true; + return left == right; + }; + bool before_all_called = false; + MaybeTestConfigureFunction before_all = [&before_all_called]() { before_all_called = true; }; + bool after_all_called = false; + MaybeTestConfigureFunction after_all = [&after_all_called]() { after_all_called = true; }; + bool test_Compare_called = false; + MaybeTestCompareFunction test_Compare = [&test_Compare_called](bool left, bool right) { + test_Compare_called = true; + return left == right; + }; + bool before_each_called = false; + MaybeTestConfigureFunction before_each = [&before_each_called]() { before_each_called = true; }; + bool after_each_called = false; + MaybeTestConfigureFunction after_each = [&after_each_called]() { after_each_called = true; }; + bool test_function_called = false; + function test_function = [&test_function_called]() { + test_function_called = true; + return true; + }; + TestSuite test_suite = MakeTestSuite( + "My Suite", + []() { return true; }, + { + MakeTest("Test Name", true, make_tuple(), test_Compare, before_each, after_each, true), + }, + suite_Compare, + before_all, + after_all, + false); + function wrapper = [&test_suite]() { ExecuteSuite(test_suite); }; + + string output = InterceptCout(wrapper); + EXPECT_THAT(output, + Eq("🚧Skipping suite: My Suite because it is disabled.\n 🚧Skipping Test: Test Name because the suite " + "is disabled.\n")); + EXPECT_THAT(test_function_called, Eq(false)); + EXPECT_THAT(suite_Compare_called, Eq(false)); + EXPECT_THAT(before_all_called, Eq(false)); + EXPECT_THAT(after_all_called, Eq(false)); + EXPECT_THAT(test_Compare_called, Eq(false)); + EXPECT_THAT(before_each_called, Eq(false)); + EXPECT_THAT(after_each_called, Eq(false)); +} + +// TODO: Add tests for ExecuteSuite with tuple. +/* +For each ExecuteSuite variant. +- Should not execute a disabled suite. +* Should execute a suite with no tests. +* Should execute a suite with a single pass. +* Should execute a suite with a single fail. +* Should execute a suite with a single skip/disabled test. +* Should execute a suite with a pass and a disabled test. +* Should catch an exception thrown by a test and record it as an error. +* Should catch a string thrown by a test and record it as an error. +* Should catch something else thrown by a test and record it as an error. +For all tests +* Should call before_all once before the first before_each. +* Should call before_each once per test. +* Should call after_each once per test. +* Should call after_all once after the last after_each. +* Should print output. Test with one of each kind of test above to make sure all cout statements happen correctly. +Hijack cout before calling ExecuteSuite and restore it after. Split the recorded cout output into lines for easier +order checking while allowing for parallelism. +*/ +// TODO: Test InterceptCout. +// TODO: Test container printer prints initializer_lists and other non-vector containers. +// TODO: Test SkipTest +} // End namespace diff --git a/windows_logger_test.cpp b/tests/windows_logger_test.cpp similarity index 100% rename from windows_logger_test.cpp rename to tests/windows_logger_test.cpp