Compare commits
	
		
			3 Commits
		
	
	
		
			main
			...
			14960909cf
		
	
	| Author | SHA1 | Date | |
|---|---|---|---|
|   | 14960909cf | ||
|   | e75c0ed9cb | ||
|   | 40121ff39a | 
							
								
								
									
										4
									
								
								.gitignore
									
									
									
									
										vendored
									
									
								
							
							
						
						
									
										4
									
								
								.gitignore
									
									
									
									
										vendored
									
									
								
							| @@ -1,3 +1,3 @@ | |||||||
| .vscode/ | .vscode/ | ||||||
| bazel-* | /docs/ | ||||||
| docs/ | /build/ | ||||||
|   | |||||||
							
								
								
									
										0
									
								
								.gitmodules
									
									
									
									
										vendored
									
									
										Normal file
									
								
							
							
						
						
									
										0
									
								
								.gitmodules
									
									
									
									
										vendored
									
									
										Normal file
									
								
							
							
								
								
									
										116
									
								
								BUILD
									
									
									
									
									
								
							
							
						
						
									
										116
									
								
								BUILD
									
									
									
									
									
								
							| @@ -1,116 +0,0 @@ | |||||||
| ######################################################################################################################## |  | ||||||
| #                                                                                                                      # |  | ||||||
| # @file BUILD                                                                                                          # |  | ||||||
| #                                                                                                                      # |  | ||||||
| # @brief Defines build rules including tests for the top level utilities.                                              # |  | ||||||
| # @copyright Copyright (C) 2023 by Tom Hicks <headhunter3@gmail.com                                                    # |  | ||||||
| #                                                                                                                      # |  | ||||||
| # Licensed under the MIT license. See below for details.                                                               # |  | ||||||
| #                                                                                                                      # |  | ||||||
| ######################################################################################################################## |  | ||||||
| #                                                                                                                      # |  | ||||||
| #  MIT License                                                                                                         # |  | ||||||
| #                                                                                                                      # |  | ||||||
| # Copyright (c) 2023 Tom Hicks <headhunter3@gmail.com>                                                                 # |  | ||||||
| #                                                                                                                      # |  | ||||||
| # 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", |  | ||||||
|     ], |  | ||||||
| ) |  | ||||||
							
								
								
									
										26
									
								
								CMakeLists.txt
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										26
									
								
								CMakeLists.txt
									
									
									
									
									
										Normal file
									
								
							| @@ -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)  | ||||||
							
								
								
									
										55
									
								
								WORKSPACE
									
									
									
									
									
								
							
							
						
						
									
										55
									
								
								WORKSPACE
									
									
									
									
									
								
							| @@ -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 <headhunter3@gmail.com                                                    # |  | ||||||
| #                                                                                                                      # |  | ||||||
| # Licensed under the MIT license. See below for details.                                                               # |  | ||||||
| #                                                                                                                      # |  | ||||||
| ######################################################################################################################## |  | ||||||
| #                                                                                                                      # |  | ||||||
| #  MIT License                                                                                                         # |  | ||||||
| #                                                                                                                      # |  | ||||||
| # Copyright (c) 2023 Tom Hicks <headhunter3@gmail.com>                                                                 # |  | ||||||
| #                                                                                                                      # |  | ||||||
| # 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"], |  | ||||||
| ) |  | ||||||
							
								
								
									
										87
									
								
								docs/migrate-to-cmake.md
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										87
									
								
								docs/migrate-to-cmake.md
									
									
									
									
									
										Normal file
									
								
							| @@ -0,0 +1,87 @@ | |||||||
|  | # 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` | ||||||
|  | - `tinytest` | ||||||
|  |  | ||||||
|  | #### Tests | ||||||
|  | - `ansi_escapes_test` | ||||||
|  | - `console_logger_test` | ||||||
|  | - `logger_test` | ||||||
|  | - `pretty_print_test` | ||||||
|  | - `windows_logger_test` | ||||||
|  | - `tinytest_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.  | ||||||
							
								
								
									
										29
									
								
								src/CMakeLists.txt
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										29
									
								
								src/CMakeLists.txt
									
									
									
									
									
										Normal file
									
								
							| @@ -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)  | ||||||
							
								
								
									
										257
									
								
								src/tinytest.cpp
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										257
									
								
								src/tinytest.cpp
									
									
									
									
									
										Normal file
									
								
							| @@ -0,0 +1,257 @@ | |||||||
|  | /*************************************************************************************** | ||||||
|  |  * @file tinytest.cpp                                                                  * | ||||||
|  |  *                                                                                     * | ||||||
|  |  * @brief Defines structs and functions for implementing TinyTest.                     * | ||||||
|  |  * @copyright Copyright 2023 Tom Hicks <headhunter3@gmail.com>                         * | ||||||
|  |  * Licensed under the MIT license see the LICENSE file for details.                    * | ||||||
|  |  ***************************************************************************************/ | ||||||
|  |  | ||||||
|  | #define _XOPEN_SOURCE_EXTENDED | ||||||
|  | #include "tinytest.h" | ||||||
|  |  | ||||||
|  | #include <cstdint> | ||||||
|  | #include <cstdio> | ||||||
|  | #include <iostream> | ||||||
|  | #include <string> | ||||||
|  | #include <vector> | ||||||
|  |  | ||||||
|  | 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<string> error_messages, | ||||||
|  |                          vector<string> failure_messages, | ||||||
|  |                          vector<string> 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<string> 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<string> TestResults::SkipMessages() const { | ||||||
|  |   return skip_messages_; | ||||||
|  | } | ||||||
|  |  | ||||||
|  | vector<string> 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<string> 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<string> 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<string> 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) { | ||||||
|  |   if (this != &other) { | ||||||
|  |     error_messages_.insert(error_messages_.end(), other.error_messages_.begin(), other.error_messages_.end()); | ||||||
|  |     failure_messages_.insert(failure_messages_.end(), other.failure_messages_.begin(), other.failure_messages_.end()); | ||||||
|  |     skip_messages_.insert(skip_messages_.end(), other.skip_messages_.begin(), other.skip_messages_.end()); | ||||||
|  |   } else { | ||||||
|  |     const auto other_error_messages = other.error_messages_; | ||||||
|  |     error_messages_.insert(error_messages_.end(), other_error_messages.begin(), other_error_messages.end()); | ||||||
|  |     const auto other_failure_messages = other.failure_messages_; | ||||||
|  |     failure_messages_.insert(failure_messages_.end(), other_failure_messages.begin(), other_failure_messages.end()); | ||||||
|  |     const auto other_skip_messages = other.skip_messages_; | ||||||
|  |     skip_messages_.insert(skip_messages_.end(), other_skip_messages.begin(), other_skip_messages.end()); | ||||||
|  |   } | ||||||
|  |   errors_ += other.errors_; | ||||||
|  |   failed_ += other.failed_; | ||||||
|  |   passed_ += other.passed_; | ||||||
|  |   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. | ||||||
|  |       const auto& first_value = first.value(); | ||||||
|  |       const auto& second_value = second.value(); | ||||||
|  |       return [&first_value, &second_value]() { | ||||||
|  |         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<const std::string> 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 | ||||||
							
								
								
									
										587
									
								
								src/tinytest.h
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										587
									
								
								src/tinytest.h
									
									
									
									
									
										Normal file
									
								
							| @@ -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 <headhunter3@gmail.com>                         * | ||||||
|  |  * Licensed under the MIT license see the LICENSE file for details.                    * | ||||||
|  |  ***************************************************************************************/ | ||||||
|  |  | ||||||
|  | #include <cstdint> | ||||||
|  | #include <functional> | ||||||
|  | #include <initializer_list> | ||||||
|  | #include <iostream> | ||||||
|  | #include <optional> | ||||||
|  | #include <regex> | ||||||
|  | #include <sstream> | ||||||
|  | #include <string> | ||||||
|  | #include <string_view> | ||||||
|  | #include <tuple> | ||||||
|  | #include <utility> | ||||||
|  | #include <vector> | ||||||
|  |  | ||||||
|  | #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<void()>; | ||||||
|  |  | ||||||
|  | /// @brief This is a type that represents an optional setup or teardown function for tests. | ||||||
|  | using MaybeTestConfigureFunction = std::optional<TestConfigureFunction>; | ||||||
|  |  | ||||||
|  | /// @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 <typename TResult> | ||||||
|  | using TestCompareFunction = std::function<bool(const TResult& expected, const TResult& actual)>; | ||||||
|  |  | ||||||
|  | /// @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 <typename TResult> | ||||||
|  | using MaybeTestCompareFunction = std::optional<TestCompareFunction<TResult>>; | ||||||
|  |  | ||||||
|  | /// @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 <typename TResult> | ||||||
|  | MaybeTestCompareFunction<TResult> 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 <typename TResult, typename... TInputParams> | ||||||
|  | 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<TInputParams...>, | ||||||
|  |     /// 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<TResult>, | ||||||
|  |     /// 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 <typename TResult, typename... TInputParams> | ||||||
|  | TestTuple<TResult, TInputParams...> MakeTest(const std::string& test_name, | ||||||
|  |                                              const TResult& expected, | ||||||
|  |                                              std::tuple<TInputParams...> input_params, | ||||||
|  |                                              MaybeTestCompareFunction<TResult> 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 <typename TResult, typename... TInputParams> | ||||||
|  | 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<TResult(TInputParams...)>, | ||||||
|  |     /// tests - This is an initializer list of @link TestTuple @endlink that represent the test runs to execute. | ||||||
|  |     std::initializer_list<TestTuple<TResult, TInputParams...>>, | ||||||
|  |     /// test_compare_function - This is an optional function that overrides how test results are compared. | ||||||
|  |     MaybeTestCompareFunction<TResult>, | ||||||
|  |     /// 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 <typename TResult, typename TFunctionToTest, typename... TInputParams> | ||||||
|  | TestSuite<TResult, TInputParams...> MakeTestSuite(const std::string& suite_name, | ||||||
|  |                                                   TFunctionToTest function_to_test, | ||||||
|  |                                                   std::initializer_list<TestTuple<TResult, TInputParams...>> test_data, | ||||||
|  |                                                   MaybeTestCompareFunction<TResult> 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 <typename TResult, typename... TParameters> | ||||||
|  | std::string InterceptCout(std::function<TResult(TParameters...)> function_to_execute, | ||||||
|  |                           std::optional<std::tuple<TParameters...>> 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 <typename TChar, typename TTraits, typename TItem> | ||||||
|  | auto& Compare(std::basic_ostream<TChar, TTraits>& error_message, | ||||||
|  |               std::vector<TItem> expected, | ||||||
|  |               std::vector<TItem> 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<std::string> error_messages, | ||||||
|  |               std::vector<std::string> failure_messages, | ||||||
|  |               std::vector<std::string> 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<std::string> 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<std::string> 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<std::string> 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<std::string> error_messages_; | ||||||
|  |   uint32_t errors_; | ||||||
|  |   uint32_t failed_; | ||||||
|  |   std::vector<std::string> failure_messages_; | ||||||
|  |   uint32_t passed_; | ||||||
|  |   std::vector<std::string> 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<const std::string> 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<TInputParams...> 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 <typename TResult, typename... TInputParams> | ||||||
|  | TestResults ExecuteSuite(std::string suite_label, | ||||||
|  |                          std::function<TResult(TInputParams...)> function_to_test, | ||||||
|  |                          std::initializer_list<TestTuple<TResult, TInputParams...>> tests, | ||||||
|  |                          MaybeTestCompareFunction<TResult> 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 <typename TResult, typename... TInputParams> | ||||||
|  | TestResults ExecuteSuite(const TestSuite<TResult, TInputParams...>& test_suite); | ||||||
|  |  | ||||||
|  | /// @} | ||||||
|  |  | ||||||
|  | template <typename TResult> | ||||||
|  | MaybeTestCompareFunction<TResult> DefaultTestCompareFunction() { | ||||||
|  |   return std::nullopt; | ||||||
|  | } | ||||||
|  |  | ||||||
|  | template <typename TResult, typename... TInputParams> | ||||||
|  | TestTuple<TResult, TInputParams...> MakeTest(const std::string& test_name, | ||||||
|  |                                              const TResult& expected, | ||||||
|  |                                              std::tuple<TInputParams...> input_params, | ||||||
|  |                                              MaybeTestCompareFunction<TResult> 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 <typename TResult, typename TFunctionToTest, typename... TInputParams> | ||||||
|  | TestSuite<TResult, TInputParams...> MakeTestSuite(const std::string& suite_name, | ||||||
|  |                                                   TFunctionToTest function_to_test, | ||||||
|  |                                                   std::initializer_list<TestTuple<TResult, TInputParams...>> test_data, | ||||||
|  |                                                   MaybeTestCompareFunction<TResult> 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 <typename TResult, typename... TParameters> | ||||||
|  | std::string InterceptCout(std::function<TResult(TParameters...)> function_to_execute, | ||||||
|  |                           std::optional<std::tuple<TParameters...>> 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 <typename TChar, typename TTraits, typename TItem> | ||||||
|  | auto& Compare(std::basic_ostream<TChar, TTraits>& error_message, | ||||||
|  |               std::vector<TItem> expected, | ||||||
|  |               std::vector<TItem> 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 <typename TResult, typename... TInputParams> | ||||||
|  | TestResults ExecuteSuite(std::string suite_label, | ||||||
|  |                          std::function<TResult(TInputParams...)> function_to_test, | ||||||
|  |                          std::initializer_list<TestTuple<TResult, TInputParams...>> tests, | ||||||
|  |                          MaybeTestCompareFunction<TResult> 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<TResult, TInputParams...> 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<TInputParams...> input_params = std::get<2>(test_data); | ||||||
|  |              MaybeTestCompareFunction<TResult> maybe_Compare_function = std::get<3>(test_data); | ||||||
|  |              TestCompareFunction<TResult> 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 <typename TResult, typename... TInputParams> | ||||||
|  | TestResults ExecuteSuite(const TestSuite<TResult, TInputParams...>& test_suite) { | ||||||
|  |   std::string suite_label = std::get<0>(test_suite); | ||||||
|  |   std::function<TResult(TInputParams...)> function_to_test = std::get<1>(test_suite); | ||||||
|  |   std::initializer_list<TestTuple<TResult, TInputParams...>> tests = std::get<2>(test_suite); | ||||||
|  |   MaybeTestCompareFunction<TResult> 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__) | ||||||
							
								
								
									
										34
									
								
								tests/CMakeLists.txt
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										34
									
								
								tests/CMakeLists.txt
									
									
									
									
									
										Normal file
									
								
							| @@ -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)  | ||||||
							
								
								
									
										1251
									
								
								tests/tinytest_test.cpp
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										1251
									
								
								tests/tinytest_test.cpp
									
									
									
									
									
										Normal file
									
								
							
										
											
												File diff suppressed because it is too large
												Load Diff
											
										
									
								
							
		Reference in New Issue
	
	Block a user