Compare commits
	
		
			3 Commits
		
	
	
		
			main
			...
			14960909cf
		
	
	| Author | SHA1 | Date | |
|---|---|---|---|
|   | 14960909cf | ||
|   | e75c0ed9cb | ||
|   | 40121ff39a | 
							
								
								
									
										4
									
								
								.gitignore
									
									
									
									
										vendored
									
									
								
							
							
						
						
									
										4
									
								
								.gitignore
									
									
									
									
										vendored
									
									
								
							| @@ -1,3 +1,3 @@ | ||||
| .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