Compare commits
3 Commits
36fed9b926
...
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