From acb70996f612b497b8cb3319ae6fd029aaf10866 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Matthias=20M=C3=B6ller?= Date: Tue, 27 Feb 2024 12:49:12 +0100 Subject: [PATCH] initial commit --- .clang-format | 95 + .github/workflows/build.yml | 72 + .gitignore | 76 + CMakeLists.txt | 65 + LICENSE | 21 + Readme.md | 137 + assets/logo.png | Bin 0 -> 142590 bytes assets/logo_128.png | Bin 0 -> 19614 bytes assets/logo_256.png | Bin 0 -> 47107 bytes assets/logo_64.png | Bin 0 -> 11235 bytes cmake/CPM.cmake | 1161 +++++ doctest/doctest.h | 7106 ++++++++++++++++++++++++++++++ src/AnyCamera.hpp | 173 + src/CMakeLists.txt | 63 + src/Config.cmake.in | 5 + src/DirectoryCamera.hpp | 85 + src/DirectoryTriggerCamera.cpp | 104 + src/DirectoryTriggerCamera.hpp | 47 + src/FileCamera.cpp | 43 + src/FileCamera.hpp | 66 + src/HttpCamera.cpp | 174 + src/HttpCamera.hpp | 48 + src/IsAnyCamera.hpp | 40 + tests/AnyCamera.cpp | 33 + tests/CMakeLists.txt | 24 + tests/DirectoryCamera.cpp | 57 + tests/DirectoryTriggerCamera.cpp | 86 + tests/FileCamera.cpp | 85 + tests/HttpCamera.cpp | 217 + tests/data/test.png | Bin 0 -> 8949 bytes tests/main.cpp | 2 + 31 files changed, 10085 insertions(+) create mode 100644 .clang-format create mode 100644 .github/workflows/build.yml create mode 100644 .gitignore create mode 100644 CMakeLists.txt create mode 100644 LICENSE create mode 100644 Readme.md create mode 100644 assets/logo.png create mode 100644 assets/logo_128.png create mode 100644 assets/logo_256.png create mode 100644 assets/logo_64.png create mode 100644 cmake/CPM.cmake create mode 100644 doctest/doctest.h create mode 100644 src/AnyCamera.hpp create mode 100644 src/CMakeLists.txt create mode 100644 src/Config.cmake.in create mode 100644 src/DirectoryCamera.hpp create mode 100644 src/DirectoryTriggerCamera.cpp create mode 100644 src/DirectoryTriggerCamera.hpp create mode 100644 src/FileCamera.cpp create mode 100644 src/FileCamera.hpp create mode 100644 src/HttpCamera.cpp create mode 100644 src/HttpCamera.hpp create mode 100644 src/IsAnyCamera.hpp create mode 100644 tests/AnyCamera.cpp create mode 100644 tests/CMakeLists.txt create mode 100644 tests/DirectoryCamera.cpp create mode 100644 tests/DirectoryTriggerCamera.cpp create mode 100644 tests/FileCamera.cpp create mode 100644 tests/HttpCamera.cpp create mode 100644 tests/data/test.png create mode 100644 tests/main.cpp diff --git a/.clang-format b/.clang-format new file mode 100644 index 0000000..dd61ad5 --- /dev/null +++ b/.clang-format @@ -0,0 +1,95 @@ +--- +Language: Cpp +# BasedOnStyle: LLVM +AccessModifierOffset: -2 +AlignAfterOpenBracket: Align +AlignConsecutiveAssignments: false +AlignConsecutiveDeclarations: false +AlignEscapedNewlinesLeft: false +AlignOperands: true +AlignTrailingComments: true +AllowAllParametersOfDeclarationOnNextLine: true +AllowShortBlocksOnASingleLine: false +AllowShortCaseLabelsOnASingleLine: false +AllowShortFunctionsOnASingleLine: All +AllowShortIfStatementsOnASingleLine: false +AllowShortLoopsOnASingleLine: false +AlwaysBreakAfterDefinitionReturnType: None +AlwaysBreakAfterReturnType: None +AlwaysBreakBeforeMultilineStrings: false +AlwaysBreakTemplateDeclarations: false +BinPackArguments: true +BinPackParameters: true +BraceWrapping: + AfterClass: false + AfterControlStatement: false + AfterEnum: false + AfterFunction: false + AfterNamespace: false + AfterObjCDeclaration: false + AfterStruct: false + AfterUnion: false + BeforeCatch: false + BeforeElse: false + IndentBraces: false +BreakBeforeBinaryOperators: None +BreakBeforeBraces: Allman +BreakBeforeTernaryOperators: true +BreakConstructorInitializersBeforeComma: false +BreakAfterJavaFieldAnnotations: false +BreakStringLiterals: true +ColumnLimit: 80 +CommentPragmas: '^ IWYU pragma:' +ConstructorInitializerAllOnOneLineOrOnePerLine: false +ConstructorInitializerIndentWidth: 4 +ContinuationIndentWidth: 4 +Cpp11BracedListStyle: true +DerivePointerAlignment: false +DisableFormat: false +ExperimentalAutoDetectBinPacking: false +ForEachMacros: [ foreach, Q_FOREACH, BOOST_FOREACH ] +IncludeCategories: + - Regex: '^"(llvm|llvm-c|clang|clang-c)/' + Priority: 2 + - Regex: '^(<|"(gtest|isl|json)/)' + Priority: 3 + - Regex: '.*' + Priority: 1 +IncludeIsMainRegex: '$' +IndentCaseLabels: false +IndentWidth: 4 +IndentWrappedFunctionNames: false +JavaScriptQuotes: Leave +JavaScriptWrapImports: true +KeepEmptyLinesAtTheStartOfBlocks: true +MacroBlockBegin: '' +MacroBlockEnd: '' +MaxEmptyLinesToKeep: 1 +NamespaceIndentation: None +ObjCBlockIndentWidth: 2 +ObjCSpaceAfterProperty: false +ObjCSpaceBeforeProtocolList: true +PenaltyBreakBeforeFirstCallParameter: 19 +PenaltyBreakComment: 300 +PenaltyBreakFirstLessLess: 120 +PenaltyBreakString: 1000 +PenaltyExcessCharacter: 1000000 +PenaltyReturnTypeOnItsOwnLine: 60 +PointerAlignment: Right +ReflowComments: true +SortIncludes: true +SpaceAfterCStyleCast: false +SpaceAfterTemplateKeyword: true +SpaceBeforeAssignmentOperators: true +SpaceBeforeParens: ControlStatements +SpaceInEmptyParentheses: false +SpacesBeforeTrailingComments: 1 +SpacesInAngles: false +SpacesInContainerLiterals: true +SpacesInCStyleCastParentheses: false +SpacesInParentheses: false +SpacesInSquareBrackets: false +Standard: Cpp11 +TabWidth: 8 +UseTab: Never + diff --git a/.github/workflows/build.yml b/.github/workflows/build.yml new file mode 100644 index 0000000..21f687c --- /dev/null +++ b/.github/workflows/build.yml @@ -0,0 +1,72 @@ +name: ci +on: + pull_request: + release: + types: [published] + push: + tags: + branches: + - main + - master + +jobs: + Test: + runs-on: ${{ matrix.os }} + strategy: + matrix: + os: + - ubuntu-latest + #- macos-latest + #- windows-2022 + compiler: + # you can specify the version after `-` like "llvm-15.0.2". + - llvm-17 + - gcc-13 + generator: + - "Ninja Multi-Config" + build_type: + - Release + include: + # Windows msvc builds + - os: windows-latest + compiler: msvc + generator: "Visual Studio 17 2022" + build_type: Release + + steps: + - uses: actions/checkout@v4 + + - name: Install dependencies (linux) + run: sudo apt install ninja-build libopencv-dev libpoco-dev + if: matrix.os == 'ubuntu-latest' + - name: Install dependencies (windows) + run: | + choco install opencv --package-parameters '/InstallationPath:C:\opencv /Environment' + echo "C:\opencv\opencv\build\x64\vc16\bin" | Out-File -FilePath $env:GITHUB_PATH -Encoding utf8 -Append + dir C:\opencv\opencv\build + dir C:\opencv\opencv\build\bin + if: matrix.os == 'windows-latest' + + - name: Setup Cpp + uses: aminya/setup-cpp@v1 + with: + compiler: ${{ matrix.compiler }} + vcvarsall: ${{ contains(matrix.os, 'windows' )}} + + - name: Configure CMake (windows) + run: | + cmake -S . -B ./build -G "${{matrix.generator}}" -DCMAKE_BUILD_TYPE:STRING=${{matrix.build_type}} -DOpenCV_DIR="C:/opencv/opencv/build/" + if: matrix.os == 'windows-latest' + + - name: Configure CMake (linux) + run: | + cmake -S . -B ./build -G "${{matrix.generator}}" -DCMAKE_BUILD_TYPE:STRING=${{matrix.build_type}} + if: matrix.os == 'ubuntu-latest' + + - name: Build + # Execute the build. You can specify a specific target with "--target " + run: | + cmake --build ./build --config ${{matrix.build_type}} + - name: Test + working-directory: ${{github.workspace}}/build + run: ctest -C ${{matrix.build_type}} --output-on-failure diff --git a/.gitignore b/.gitignore new file mode 100644 index 0000000..1825c9f --- /dev/null +++ b/.gitignore @@ -0,0 +1,76 @@ +# This file is used to ignore files which are generated +# ---------------------------------------------------------------------------- + +*~ +*.autosave +*.a +*.core +*.moc +*.o +*.obj +*.orig +*.rej +*.so +*.so.* +*_pch.h.cpp +*_resource.rc +*.qm +.#* +*.*# +core +!core/ +tags +.DS_Store +.directory +*.debug +Makefile* +*.prl +*.app +moc_*.cpp +ui_*.h +qrc_*.cpp +Thumbs.db +*.res +*.rc +/.qmake.cache +/.qmake.stash +build/* +.vs/* +out/* + +# qtcreator generated files +*.pro.user* +CMakeLists.txt.user* + +# xemacs temporary files +*.flc + +# Vim temporary files +.*.swp + +# Visual Studio generated files +*.ib_pdb_index +*.idb +*.ilk +*.pdb +*.sln +*.suo +*.vcproj +*vcproj.*.*.user +*.ncb +*.sdf +*.opensdf +*.vcxproj +*vcxproj.* + +# MinGW generated files +*.Debug +*.Release + +# Python byte code +*.pyc + +# Binaries +# -------- +*.dll +*.exe diff --git a/CMakeLists.txt b/CMakeLists.txt new file mode 100644 index 0000000..f35998c --- /dev/null +++ b/CMakeLists.txt @@ -0,0 +1,65 @@ +cmake_minimum_required(VERSION 3.20) + +set(PROJECT_VERSION 1.0.0) +project(FairyCam LANGUAGES CXX VERSION ${PROJECT_VERSION}) + +option (FAIRYCAM_ENABLE_TESTING ON "Create Testing targets") + +set(CMAKE_CXX_STANDARD 20) +set(CMAKE_CXX_STANDARD_REQUIRED ON) + +####### +# dependencies + +# Poco +find_package(Poco QUIET COMPONENTS Foundation Net) + +if (NOT ${Poco_FOUND}) + message("Poco not found. Download Poco") + include(cmake/CPM.cmake) + CPMAddPackage( + NAME Poco + VERSION 1.13.2 + URL https://github.com/pocoproject/poco/archive/refs/tags/poco-1.13.2-release.tar.gz + OPTIONS + "BUILD_SHARED_LIBS OFF" + "ENABLE_FOUNDATION ON" + "ENABLE_NET ON" + "ENABLE_NETSSL OFF" + "ENABLE_XML OFF" + "ENABLE_DATA_SQLITE OFF" + "ENABLE_ENCODINGS OFF" + "ENABLE_ENCODINGS_COMPILER OFF" + "ENABLE_UTIL OFF" + "ENABLE_JSON OFF" + "ENABLE_MONGODB OFF" + "ENABLE_REDIS OFF" + "ENABLE_PROMETHEUS OFF" + "ENABLE_JWT OFF" + "ENABLE_CRYPTO OFF" + "ENABLE_DATA OFF" + "ENABLE_ACTIVERECORD OFF" + "ENABLE_ACTIVERECORD_COMPILER OFF" + "ENABLE_ZIP OFF" + "ENABLE_PAGECOMPILER OFF" + "ENABLE_PAGECOMPILER_FILE2PAGE OFF" + ) +endif() + +# doctest +add_library(doctest INTERFACE) +target_sources(doctest PUBLIC + FILE_SET HEADERS + BASE_DIRS doctest + FILES doctest/doctest.h +) + +####### +add_subdirectory(src/) + +# tests +include(CTest) +enable_testing() +if(FAIRYCAM_ENABLE_TESTING) + add_subdirectory(tests/) +endif() diff --git a/LICENSE b/LICENSE new file mode 100644 index 0000000..a03beff --- /dev/null +++ b/LICENSE @@ -0,0 +1,21 @@ +MIT License + +Copyright (c) 2024 Matthias Möller + +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. diff --git a/Readme.md b/Readme.md new file mode 100644 index 0000000..ec67ce3 --- /dev/null +++ b/Readme.md @@ -0,0 +1,137 @@ +# FairyCam - OpenCV Camera Stubs + +
+ +
+
+ +----------------------- + +[![Build](https://github.com/TinyTinni/QtDuckDBDriver/actions/workflows/cmake.yml/badge.svg)](https://github.com/TinyTinni/FairyCam/actions/workflows/build.yml) + +Camera Stubs for OpenCV's camera class [`cv::VideoCapture`](https://docs.opencv.org/4.9.0/d8/dfe/classcv_1_1VideoCapture.html). + +This library provides several classes which can replace the VideoCapture class from OpenCV and provides alternative methods in providing images. You can, for example, send an image over TCP/HTTP with the `HttpCamera` class which will provide this image as a next "grabbed" image. All cameras can be replaced with another camera or the original `cv::VideoCapture` at runtime. It is similar to the `cv::CAP_IMAGES` backend, but provides more flexibility. + +The intention of this library is to make automatic testing possible without relying on a hardware camera but also to be as close as the final system as possible. + +## Camera Types + +- `AnyCamera`: can hold any `cv::VideoCapture` interface compatible camera. + +- `FileCamera`: grabs images from a provided sequence of paths. Returns an empty image and error when the sequence was reached the end, or, when the `circular` option is enabled, starts from the start of the sequence again. + +- `DirectoryCamera` : grabs images from a provided directory. Returns an empty image anderror when the all images in the directory were shown once. When the `circular` option is enabled, the images in the directory are shown endlessly. + +- `DirectoryTriggerCamera` : grabs images from a provided directory which were newly added to the directory. Every add/move of an image file into the directory is like the camera was triggered. It queues those images. + +- `HttpCamera` : opens a HTTP server where you can connect and can images (.png/.jpg) via "POST /images" \. Queues those images. "DELETE /images" empties the queue. + +## Build Requirements + +- C++20 +- [OpenCV](https://opencv.org/) +- [Poco](https://pocoproject.org/) + +## How-To Use + +FairyCam provides a new class `AnyCamera` which is an interface of `cv::VideoCapture` without relying on inheritance. It + can take any Camera which fulfills the `cv::VideoCapture`/`FairyCam::IsAnyCamera`-Concept. + +An example using dynamic polymorphism where you can change the camera at runtime: + ```cpp +#include +#include + +int startSystem(FairyCam::AnyCamera cam) +{ + bool keepRunning = cam.open(0,0,{}); + while(keepRunning && cam.isOpened()) + { + cv::Mat input; + if (cam.read(input)) + { + keepRunning = process(input); + } + } + return 0; +} + +int main() +{ + ///... load config etc. + + using namespace FairyCam; + if (config::isTestingEnabled()) + { + auto opts = FileCamera::Options{.files={"myFile1.png""differentFile.jpg"}}; + return startSystem(AnyCamera::create(std::move(opts))); + } + return startSystem(AnyCamera::create()); +} + + + + ``` + + You can also use the `IsAnyCamera` concept for compile time polymorphism. This method generates more code, but will not use any virtual calls or pointer indirections. + +```cpp +#include +#include + +// Template with concept instead of runtime polymorphism. +// It eliminates the virtual calls and indirection of AnyCamera +// and adds extra code size due to duplicating this function. +int startSystem(FairyCamera::IsAnyCamera auto cam) +{ + bool keepRunning = cam.open(0,0,{}); + while(keepRunning && cam.isOpened()) + { + cv::Mat input; + if (cam.read(input)) + { + keepRunning = process(input); + } + } + return 0; +} + +int main() +{ + ///... load config etc. + + if (config::isTestingEnabled()) + { + return startSystem(FairyCamera::HttpCamera()); + } + return startSystem(cv::VideoCapture()); +} + +``` + + + For more examples and usage, see [in the tests directory](./tests). + + +## Get Original Camera Type + +Similar to `dynamic_cast`, it should be possible to cast from `AnyCamera` to the original camera type. + +For this, the function `AnyCamera::dynamicCast` is provided. If the underlying type matches given `T`, a reference of T is returned. Otherwise, the optional is `std::nullopt` + +Example: +```cpp +using namespace FairyCam; + +AnyCamera cam = AnyCamera::create(); +if (auto file_cam_ref = cam.dynamicCast) +{ + FileCamera& file_cam = *file_cam; + //.. do something specific +} +``` + +## License + +MIT License © Matthias Möller. Made with ♥ in Germany. diff --git a/assets/logo.png b/assets/logo.png new file mode 100644 index 0000000000000000000000000000000000000000..6525554ec2bb03e0bd3193149a9f501eef05ff19 GIT binary patch literal 142590 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 literal 0 HcmV?d00001 diff --git a/assets/logo_128.png b/assets/logo_128.png new file mode 100644 index 0000000000000000000000000000000000000000..97efa735f48bbcbea45665c15d702e8c879f84e9 GIT binary patch literal 19614 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
  • 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 UBII6TfdBvi07*qoM6N<$g8ZGK@Bjb+ literal 0 HcmV?d00001 diff --git a/assets/logo_64.png b/assets/logo_64.png new file mode 100644 index 0000000000000000000000000000000000000000..7cca79d838eace472639e63d212a1769e3255d1f GIT binary patch literal 11235 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 literal 0 HcmV?d00001 diff --git a/cmake/CPM.cmake b/cmake/CPM.cmake new file mode 100644 index 0000000..f4d0eea --- /dev/null +++ b/cmake/CPM.cmake @@ -0,0 +1,1161 @@ +# CPM.cmake - CMake's missing package manager +# =========================================== +# See https://github.com/cpm-cmake/CPM.cmake for usage and update instructions. +# +# MIT License +# ----------- +#[[ + Copyright (c) 2019-2023 Lars Melchior and contributors + + 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. +]] + +cmake_minimum_required(VERSION 3.14 FATAL_ERROR) + +# Initialize logging prefix +if(NOT CPM_INDENT) + set(CPM_INDENT + "CPM:" + CACHE INTERNAL "" + ) +endif() + +if(NOT COMMAND cpm_message) + function(cpm_message) + message(${ARGV}) + endfunction() +endif() + +set(CURRENT_CPM_VERSION 1.0.0-development-version) + +get_filename_component(CPM_CURRENT_DIRECTORY "${CMAKE_CURRENT_LIST_DIR}" REALPATH) +if(CPM_DIRECTORY) + if(NOT CPM_DIRECTORY STREQUAL CPM_CURRENT_DIRECTORY) + if(CPM_VERSION VERSION_LESS CURRENT_CPM_VERSION) + message( + AUTHOR_WARNING + "${CPM_INDENT} \ +A dependency is using a more recent CPM version (${CURRENT_CPM_VERSION}) than the current project (${CPM_VERSION}). \ +It is recommended to upgrade CPM to the most recent version. \ +See https://github.com/cpm-cmake/CPM.cmake for more information." + ) + endif() + if(${CMAKE_VERSION} VERSION_LESS "3.17.0") + include(FetchContent) + endif() + return() + endif() + + get_property( + CPM_INITIALIZED GLOBAL "" + PROPERTY CPM_INITIALIZED + SET + ) + if(CPM_INITIALIZED) + return() + endif() +endif() + +if(CURRENT_CPM_VERSION MATCHES "development-version") + message( + WARNING "${CPM_INDENT} Your project is using an unstable development version of CPM.cmake. \ +Please update to a recent release if possible. \ +See https://github.com/cpm-cmake/CPM.cmake for details." + ) +endif() + +set_property(GLOBAL PROPERTY CPM_INITIALIZED true) + +macro(cpm_set_policies) + # the policy allows us to change options without caching + cmake_policy(SET CMP0077 NEW) + set(CMAKE_POLICY_DEFAULT_CMP0077 NEW) + + # the policy allows us to change set(CACHE) without caching + if(POLICY CMP0126) + cmake_policy(SET CMP0126 NEW) + set(CMAKE_POLICY_DEFAULT_CMP0126 NEW) + endif() + + # The policy uses the download time for timestamp, instead of the timestamp in the archive. This + # allows for proper rebuilds when a projects url changes + if(POLICY CMP0135) + cmake_policy(SET CMP0135 NEW) + set(CMAKE_POLICY_DEFAULT_CMP0135 NEW) + endif() + + # treat relative git repository paths as being relative to the parent project's remote + if(POLICY CMP0150) + cmake_policy(SET CMP0150 NEW) + set(CMAKE_POLICY_DEFAULT_CMP0150 NEW) + endif() +endmacro() +cpm_set_policies() + +option(CPM_USE_LOCAL_PACKAGES "Always try to use `find_package` to get dependencies" + $ENV{CPM_USE_LOCAL_PACKAGES} +) +option(CPM_LOCAL_PACKAGES_ONLY "Only use `find_package` to get dependencies" + $ENV{CPM_LOCAL_PACKAGES_ONLY} +) +option(CPM_DOWNLOAD_ALL "Always download dependencies from source" $ENV{CPM_DOWNLOAD_ALL}) +option(CPM_DONT_UPDATE_MODULE_PATH "Don't update the module path to allow using find_package" + $ENV{CPM_DONT_UPDATE_MODULE_PATH} +) +option(CPM_DONT_CREATE_PACKAGE_LOCK "Don't create a package lock file in the binary path" + $ENV{CPM_DONT_CREATE_PACKAGE_LOCK} +) +option(CPM_INCLUDE_ALL_IN_PACKAGE_LOCK + "Add all packages added through CPM.cmake to the package lock" + $ENV{CPM_INCLUDE_ALL_IN_PACKAGE_LOCK} +) +option(CPM_USE_NAMED_CACHE_DIRECTORIES + "Use additional directory of package name in cache on the most nested level." + $ENV{CPM_USE_NAMED_CACHE_DIRECTORIES} +) + +set(CPM_VERSION + ${CURRENT_CPM_VERSION} + CACHE INTERNAL "" +) +set(CPM_DIRECTORY + ${CPM_CURRENT_DIRECTORY} + CACHE INTERNAL "" +) +set(CPM_FILE + ${CMAKE_CURRENT_LIST_FILE} + CACHE INTERNAL "" +) +set(CPM_PACKAGES + "" + CACHE INTERNAL "" +) +set(CPM_DRY_RUN + OFF + CACHE INTERNAL "Don't download or configure dependencies (for testing)" +) + +if(DEFINED ENV{CPM_SOURCE_CACHE}) + set(CPM_SOURCE_CACHE_DEFAULT $ENV{CPM_SOURCE_CACHE}) +else() + set(CPM_SOURCE_CACHE_DEFAULT OFF) +endif() + +set(CPM_SOURCE_CACHE + ${CPM_SOURCE_CACHE_DEFAULT} + CACHE PATH "Directory to download CPM dependencies" +) + +if(NOT CPM_DONT_UPDATE_MODULE_PATH) + set(CPM_MODULE_PATH + "${CMAKE_BINARY_DIR}/CPM_modules" + CACHE INTERNAL "" + ) + # remove old modules + file(REMOVE_RECURSE ${CPM_MODULE_PATH}) + file(MAKE_DIRECTORY ${CPM_MODULE_PATH}) + # locally added CPM modules should override global packages + set(CMAKE_MODULE_PATH "${CPM_MODULE_PATH};${CMAKE_MODULE_PATH}") +endif() + +if(NOT CPM_DONT_CREATE_PACKAGE_LOCK) + set(CPM_PACKAGE_LOCK_FILE + "${CMAKE_BINARY_DIR}/cpm-package-lock.cmake" + CACHE INTERNAL "" + ) + file(WRITE ${CPM_PACKAGE_LOCK_FILE} + "# CPM Package Lock\n# This file should be committed to version control\n\n" + ) +endif() + +include(FetchContent) + +# Try to infer package name from git repository uri (path or url) +function(cpm_package_name_from_git_uri URI RESULT) + if("${URI}" MATCHES "([^/:]+)/?.git/?$") + set(${RESULT} + ${CMAKE_MATCH_1} + PARENT_SCOPE + ) + else() + unset(${RESULT} PARENT_SCOPE) + endif() +endfunction() + +# Try to infer package name and version from a url +function(cpm_package_name_and_ver_from_url url outName outVer) + if(url MATCHES "[/\\?]([a-zA-Z0-9_\\.-]+)\\.(tar|tar\\.gz|tar\\.bz2|zip|ZIP)(\\?|/|$)") + # We matched an archive + set(filename "${CMAKE_MATCH_1}") + + if(filename MATCHES "([a-zA-Z0-9_\\.-]+)[_-]v?(([0-9]+\\.)*[0-9]+[a-zA-Z0-9]*)") + # We matched - (ie foo-1.2.3) + set(${outName} + "${CMAKE_MATCH_1}" + PARENT_SCOPE + ) + set(${outVer} + "${CMAKE_MATCH_2}" + PARENT_SCOPE + ) + elseif(filename MATCHES "(([0-9]+\\.)+[0-9]+[a-zA-Z0-9]*)") + # We couldn't find a name, but we found a version + # + # In many cases (which we don't handle here) the url would look something like + # `irrelevant/ACTUAL_PACKAGE_NAME/irrelevant/1.2.3.zip`. In such a case we can't possibly + # distinguish the package name from the irrelevant bits. Moreover if we try to match the + # package name from the filename, we'd get bogus at best. + unset(${outName} PARENT_SCOPE) + set(${outVer} + "${CMAKE_MATCH_1}" + PARENT_SCOPE + ) + else() + # Boldly assume that the file name is the package name. + # + # Yes, something like `irrelevant/ACTUAL_NAME/irrelevant/download.zip` will ruin our day, but + # such cases should be quite rare. No popular service does this... we think. + set(${outName} + "${filename}" + PARENT_SCOPE + ) + unset(${outVer} PARENT_SCOPE) + endif() + else() + # No ideas yet what to do with non-archives + unset(${outName} PARENT_SCOPE) + unset(${outVer} PARENT_SCOPE) + endif() +endfunction() + +function(cpm_find_package NAME VERSION) + string(REPLACE " " ";" EXTRA_ARGS "${ARGN}") + find_package(${NAME} ${VERSION} ${EXTRA_ARGS} QUIET) + if(${CPM_ARGS_NAME}_FOUND) + if(DEFINED ${CPM_ARGS_NAME}_VERSION) + set(VERSION ${${CPM_ARGS_NAME}_VERSION}) + endif() + cpm_message(STATUS "${CPM_INDENT} Using local package ${CPM_ARGS_NAME}@${VERSION}") + CPMRegisterPackage(${CPM_ARGS_NAME} "${VERSION}") + set(CPM_PACKAGE_FOUND + YES + PARENT_SCOPE + ) + else() + set(CPM_PACKAGE_FOUND + NO + PARENT_SCOPE + ) + endif() +endfunction() + +# Create a custom FindXXX.cmake module for a CPM package This prevents `find_package(NAME)` from +# finding the system library +function(cpm_create_module_file Name) + if(NOT CPM_DONT_UPDATE_MODULE_PATH) + # erase any previous modules + file(WRITE ${CPM_MODULE_PATH}/Find${Name}.cmake + "include(\"${CPM_FILE}\")\n${ARGN}\nset(${Name}_FOUND TRUE)" + ) + endif() +endfunction() + +# Find a package locally or fallback to CPMAddPackage +function(CPMFindPackage) + set(oneValueArgs NAME VERSION GIT_TAG FIND_PACKAGE_ARGUMENTS) + + cmake_parse_arguments(CPM_ARGS "" "${oneValueArgs}" "" ${ARGN}) + + if(NOT DEFINED CPM_ARGS_VERSION) + if(DEFINED CPM_ARGS_GIT_TAG) + cpm_get_version_from_git_tag("${CPM_ARGS_GIT_TAG}" CPM_ARGS_VERSION) + endif() + endif() + + set(downloadPackage ${CPM_DOWNLOAD_ALL}) + if(DEFINED CPM_DOWNLOAD_${CPM_ARGS_NAME}) + set(downloadPackage ${CPM_DOWNLOAD_${CPM_ARGS_NAME}}) + elseif(DEFINED ENV{CPM_DOWNLOAD_${CPM_ARGS_NAME}}) + set(downloadPackage $ENV{CPM_DOWNLOAD_${CPM_ARGS_NAME}}) + endif() + if(downloadPackage) + CPMAddPackage(${ARGN}) + cpm_export_variables(${CPM_ARGS_NAME}) + return() + endif() + + cpm_check_if_package_already_added(${CPM_ARGS_NAME} "${CPM_ARGS_VERSION}") + if(CPM_PACKAGE_ALREADY_ADDED) + cpm_export_variables(${CPM_ARGS_NAME}) + return() + endif() + + cpm_find_package(${CPM_ARGS_NAME} "${CPM_ARGS_VERSION}" ${CPM_ARGS_FIND_PACKAGE_ARGUMENTS}) + + if(NOT CPM_PACKAGE_FOUND) + CPMAddPackage(${ARGN}) + cpm_export_variables(${CPM_ARGS_NAME}) + endif() + +endfunction() + +# checks if a package has been added before +function(cpm_check_if_package_already_added CPM_ARGS_NAME CPM_ARGS_VERSION) + if("${CPM_ARGS_NAME}" IN_LIST CPM_PACKAGES) + CPMGetPackageVersion(${CPM_ARGS_NAME} CPM_PACKAGE_VERSION) + if("${CPM_PACKAGE_VERSION}" VERSION_LESS "${CPM_ARGS_VERSION}") + message( + WARNING + "${CPM_INDENT} Requires a newer version of ${CPM_ARGS_NAME} (${CPM_ARGS_VERSION}) than currently included (${CPM_PACKAGE_VERSION})." + ) + endif() + cpm_get_fetch_properties(${CPM_ARGS_NAME}) + set(${CPM_ARGS_NAME}_ADDED NO) + set(CPM_PACKAGE_ALREADY_ADDED + YES + PARENT_SCOPE + ) + cpm_export_variables(${CPM_ARGS_NAME}) + else() + set(CPM_PACKAGE_ALREADY_ADDED + NO + PARENT_SCOPE + ) + endif() +endfunction() + +# Parse the argument of CPMAddPackage in case a single one was provided and convert it to a list of +# arguments which can then be parsed idiomatically. For example gh:foo/bar@1.2.3 will be converted +# to: GITHUB_REPOSITORY;foo/bar;VERSION;1.2.3 +function(cpm_parse_add_package_single_arg arg outArgs) + # Look for a scheme + if("${arg}" MATCHES "^([a-zA-Z]+):(.+)$") + string(TOLOWER "${CMAKE_MATCH_1}" scheme) + set(uri "${CMAKE_MATCH_2}") + + # Check for CPM-specific schemes + if(scheme STREQUAL "gh") + set(out "GITHUB_REPOSITORY;${uri}") + set(packageType "git") + elseif(scheme STREQUAL "gl") + set(out "GITLAB_REPOSITORY;${uri}") + set(packageType "git") + elseif(scheme STREQUAL "bb") + set(out "BITBUCKET_REPOSITORY;${uri}") + set(packageType "git") + # A CPM-specific scheme was not found. Looks like this is a generic URL so try to determine + # type + elseif(arg MATCHES ".git/?(@|#|$)") + set(out "GIT_REPOSITORY;${arg}") + set(packageType "git") + else() + # Fall back to a URL + set(out "URL;${arg}") + set(packageType "archive") + + # We could also check for SVN since FetchContent supports it, but SVN is so rare these days. + # We just won't bother with the additional complexity it will induce in this function. SVN is + # done by multi-arg + endif() + else() + if(arg MATCHES ".git/?(@|#|$)") + set(out "GIT_REPOSITORY;${arg}") + set(packageType "git") + else() + # Give up + message(FATAL_ERROR "${CPM_INDENT} Can't determine package type of '${arg}'") + endif() + endif() + + # For all packages we interpret @... as version. Only replace the last occurrence. Thus URIs + # containing '@' can be used + string(REGEX REPLACE "@([^@]+)$" ";VERSION;\\1" out "${out}") + + # Parse the rest according to package type + if(packageType STREQUAL "git") + # For git repos we interpret #... as a tag or branch or commit hash + string(REGEX REPLACE "#([^#]+)$" ";GIT_TAG;\\1" out "${out}") + elseif(packageType STREQUAL "archive") + # For archives we interpret #... as a URL hash. + string(REGEX REPLACE "#([^#]+)$" ";URL_HASH;\\1" out "${out}") + # We don't try to parse the version if it's not provided explicitly. cpm_get_version_from_url + # should do this at a later point + else() + # We should never get here. This is an assertion and hitting it means there's a bug in the code + # above. A packageType was set, but not handled by this if-else. + message(FATAL_ERROR "${CPM_INDENT} Unsupported package type '${packageType}' of '${arg}'") + endif() + + set(${outArgs} + ${out} + PARENT_SCOPE + ) +endfunction() + +# Check that the working directory for a git repo is clean +function(cpm_check_git_working_dir_is_clean repoPath gitTag isClean) + + find_package(Git REQUIRED) + + if(NOT GIT_EXECUTABLE) + # No git executable, assume directory is clean + set(${isClean} + TRUE + PARENT_SCOPE + ) + return() + endif() + + # check for uncommitted changes + execute_process( + COMMAND ${GIT_EXECUTABLE} status --porcelain + RESULT_VARIABLE resultGitStatus + OUTPUT_VARIABLE repoStatus + OUTPUT_STRIP_TRAILING_WHITESPACE ERROR_QUIET + WORKING_DIRECTORY ${repoPath} + ) + if(resultGitStatus) + # not supposed to happen, assume clean anyway + message(WARNING "${CPM_INDENT} Calling git status on folder ${repoPath} failed") + set(${isClean} + TRUE + PARENT_SCOPE + ) + return() + endif() + + if(NOT "${repoStatus}" STREQUAL "") + set(${isClean} + FALSE + PARENT_SCOPE + ) + return() + endif() + + # check for committed changes + execute_process( + COMMAND ${GIT_EXECUTABLE} diff -s --exit-code ${gitTag} + RESULT_VARIABLE resultGitDiff + OUTPUT_STRIP_TRAILING_WHITESPACE OUTPUT_QUIET + WORKING_DIRECTORY ${repoPath} + ) + + if(${resultGitDiff} EQUAL 0) + set(${isClean} + TRUE + PARENT_SCOPE + ) + else() + set(${isClean} + FALSE + PARENT_SCOPE + ) + endif() + +endfunction() + +# method to overwrite internal FetchContent properties, to allow using CPM.cmake to overload +# FetchContent calls. As these are internal cmake properties, this method should be used carefully +# and may need modification in future CMake versions. Source: +# https://github.com/Kitware/CMake/blob/dc3d0b5a0a7d26d43d6cfeb511e224533b5d188f/Modules/FetchContent.cmake#L1152 +function(cpm_override_fetchcontent contentName) + cmake_parse_arguments(PARSE_ARGV 1 arg "" "SOURCE_DIR;BINARY_DIR" "") + if(NOT "${arg_UNPARSED_ARGUMENTS}" STREQUAL "") + message(FATAL_ERROR "${CPM_INDENT} Unsupported arguments: ${arg_UNPARSED_ARGUMENTS}") + endif() + + string(TOLOWER ${contentName} contentNameLower) + set(prefix "_FetchContent_${contentNameLower}") + + set(propertyName "${prefix}_sourceDir") + define_property( + GLOBAL + PROPERTY ${propertyName} + BRIEF_DOCS "Internal implementation detail of FetchContent_Populate()" + FULL_DOCS "Details used by FetchContent_Populate() for ${contentName}" + ) + set_property(GLOBAL PROPERTY ${propertyName} "${arg_SOURCE_DIR}") + + set(propertyName "${prefix}_binaryDir") + define_property( + GLOBAL + PROPERTY ${propertyName} + BRIEF_DOCS "Internal implementation detail of FetchContent_Populate()" + FULL_DOCS "Details used by FetchContent_Populate() for ${contentName}" + ) + set_property(GLOBAL PROPERTY ${propertyName} "${arg_BINARY_DIR}") + + set(propertyName "${prefix}_populated") + define_property( + GLOBAL + PROPERTY ${propertyName} + BRIEF_DOCS "Internal implementation detail of FetchContent_Populate()" + FULL_DOCS "Details used by FetchContent_Populate() for ${contentName}" + ) + set_property(GLOBAL PROPERTY ${propertyName} TRUE) +endfunction() + +# Download and add a package from source +function(CPMAddPackage) + cpm_set_policies() + + list(LENGTH ARGN argnLength) + if(argnLength EQUAL 1) + cpm_parse_add_package_single_arg("${ARGN}" ARGN) + + # The shorthand syntax implies EXCLUDE_FROM_ALL and SYSTEM + set(ARGN "${ARGN};EXCLUDE_FROM_ALL;YES;SYSTEM;YES;") + endif() + + set(oneValueArgs + NAME + FORCE + VERSION + GIT_TAG + DOWNLOAD_ONLY + GITHUB_REPOSITORY + GITLAB_REPOSITORY + BITBUCKET_REPOSITORY + GIT_REPOSITORY + SOURCE_DIR + FIND_PACKAGE_ARGUMENTS + NO_CACHE + SYSTEM + GIT_SHALLOW + EXCLUDE_FROM_ALL + SOURCE_SUBDIR + ) + + set(multiValueArgs URL OPTIONS DOWNLOAD_COMMAND) + + cmake_parse_arguments(CPM_ARGS "" "${oneValueArgs}" "${multiValueArgs}" "${ARGN}") + + # Set default values for arguments + + if(NOT DEFINED CPM_ARGS_VERSION) + if(DEFINED CPM_ARGS_GIT_TAG) + cpm_get_version_from_git_tag("${CPM_ARGS_GIT_TAG}" CPM_ARGS_VERSION) + endif() + endif() + + if(CPM_ARGS_DOWNLOAD_ONLY) + set(DOWNLOAD_ONLY ${CPM_ARGS_DOWNLOAD_ONLY}) + else() + set(DOWNLOAD_ONLY NO) + endif() + + if(DEFINED CPM_ARGS_GITHUB_REPOSITORY) + set(CPM_ARGS_GIT_REPOSITORY "https://github.com/${CPM_ARGS_GITHUB_REPOSITORY}.git") + elseif(DEFINED CPM_ARGS_GITLAB_REPOSITORY) + set(CPM_ARGS_GIT_REPOSITORY "https://gitlab.com/${CPM_ARGS_GITLAB_REPOSITORY}.git") + elseif(DEFINED CPM_ARGS_BITBUCKET_REPOSITORY) + set(CPM_ARGS_GIT_REPOSITORY "https://bitbucket.org/${CPM_ARGS_BITBUCKET_REPOSITORY}.git") + endif() + + if(DEFINED CPM_ARGS_GIT_REPOSITORY) + list(APPEND CPM_ARGS_UNPARSED_ARGUMENTS GIT_REPOSITORY ${CPM_ARGS_GIT_REPOSITORY}) + if(NOT DEFINED CPM_ARGS_GIT_TAG) + set(CPM_ARGS_GIT_TAG v${CPM_ARGS_VERSION}) + endif() + + # If a name wasn't provided, try to infer it from the git repo + if(NOT DEFINED CPM_ARGS_NAME) + cpm_package_name_from_git_uri(${CPM_ARGS_GIT_REPOSITORY} CPM_ARGS_NAME) + endif() + endif() + + set(CPM_SKIP_FETCH FALSE) + + if(DEFINED CPM_ARGS_GIT_TAG) + list(APPEND CPM_ARGS_UNPARSED_ARGUMENTS GIT_TAG ${CPM_ARGS_GIT_TAG}) + # If GIT_SHALLOW is explicitly specified, honor the value. + if(DEFINED CPM_ARGS_GIT_SHALLOW) + list(APPEND CPM_ARGS_UNPARSED_ARGUMENTS GIT_SHALLOW ${CPM_ARGS_GIT_SHALLOW}) + endif() + endif() + + if(DEFINED CPM_ARGS_URL) + # If a name or version aren't provided, try to infer them from the URL + list(GET CPM_ARGS_URL 0 firstUrl) + cpm_package_name_and_ver_from_url(${firstUrl} nameFromUrl verFromUrl) + # If we fail to obtain name and version from the first URL, we could try other URLs if any. + # However multiple URLs are expected to be quite rare, so for now we won't bother. + + # If the caller provided their own name and version, they trump the inferred ones. + if(NOT DEFINED CPM_ARGS_NAME) + set(CPM_ARGS_NAME ${nameFromUrl}) + endif() + if(NOT DEFINED CPM_ARGS_VERSION) + set(CPM_ARGS_VERSION ${verFromUrl}) + endif() + + list(APPEND CPM_ARGS_UNPARSED_ARGUMENTS URL "${CPM_ARGS_URL}") + endif() + + # Check for required arguments + + if(NOT DEFINED CPM_ARGS_NAME) + message( + FATAL_ERROR + "${CPM_INDENT} 'NAME' was not provided and couldn't be automatically inferred for package added with arguments: '${ARGN}'" + ) + endif() + + # Check if package has been added before + cpm_check_if_package_already_added(${CPM_ARGS_NAME} "${CPM_ARGS_VERSION}") + if(CPM_PACKAGE_ALREADY_ADDED) + cpm_export_variables(${CPM_ARGS_NAME}) + return() + endif() + + # Check for manual overrides + if(NOT CPM_ARGS_FORCE AND NOT "${CPM_${CPM_ARGS_NAME}_SOURCE}" STREQUAL "") + set(PACKAGE_SOURCE ${CPM_${CPM_ARGS_NAME}_SOURCE}) + set(CPM_${CPM_ARGS_NAME}_SOURCE "") + CPMAddPackage( + NAME "${CPM_ARGS_NAME}" + SOURCE_DIR "${PACKAGE_SOURCE}" + EXCLUDE_FROM_ALL "${CPM_ARGS_EXCLUDE_FROM_ALL}" + SYSTEM "${CPM_ARGS_SYSTEM}" + OPTIONS "${CPM_ARGS_OPTIONS}" + SOURCE_SUBDIR "${CPM_ARGS_SOURCE_SUBDIR}" + DOWNLOAD_ONLY "${DOWNLOAD_ONLY}" + FORCE True + ) + cpm_export_variables(${CPM_ARGS_NAME}) + return() + endif() + + # Check for available declaration + if(NOT CPM_ARGS_FORCE AND NOT "${CPM_DECLARATION_${CPM_ARGS_NAME}}" STREQUAL "") + set(declaration ${CPM_DECLARATION_${CPM_ARGS_NAME}}) + set(CPM_DECLARATION_${CPM_ARGS_NAME} "") + CPMAddPackage(${declaration}) + cpm_export_variables(${CPM_ARGS_NAME}) + # checking again to ensure version and option compatibility + cpm_check_if_package_already_added(${CPM_ARGS_NAME} "${CPM_ARGS_VERSION}") + return() + endif() + + if(NOT CPM_ARGS_FORCE) + if(CPM_USE_LOCAL_PACKAGES OR CPM_LOCAL_PACKAGES_ONLY) + cpm_find_package(${CPM_ARGS_NAME} "${CPM_ARGS_VERSION}" ${CPM_ARGS_FIND_PACKAGE_ARGUMENTS}) + + if(CPM_PACKAGE_FOUND) + cpm_export_variables(${CPM_ARGS_NAME}) + return() + endif() + + if(CPM_LOCAL_PACKAGES_ONLY) + message( + SEND_ERROR + "${CPM_INDENT} ${CPM_ARGS_NAME} not found via find_package(${CPM_ARGS_NAME} ${CPM_ARGS_VERSION})" + ) + endif() + endif() + endif() + + CPMRegisterPackage("${CPM_ARGS_NAME}" "${CPM_ARGS_VERSION}") + + if(DEFINED CPM_ARGS_GIT_TAG) + set(PACKAGE_INFO "${CPM_ARGS_GIT_TAG}") + elseif(DEFINED CPM_ARGS_SOURCE_DIR) + set(PACKAGE_INFO "${CPM_ARGS_SOURCE_DIR}") + else() + set(PACKAGE_INFO "${CPM_ARGS_VERSION}") + endif() + + if(DEFINED FETCHCONTENT_BASE_DIR) + # respect user's FETCHCONTENT_BASE_DIR if set + set(CPM_FETCHCONTENT_BASE_DIR ${FETCHCONTENT_BASE_DIR}) + else() + set(CPM_FETCHCONTENT_BASE_DIR ${CMAKE_BINARY_DIR}/_deps) + endif() + + if(DEFINED CPM_ARGS_DOWNLOAD_COMMAND) + list(APPEND CPM_ARGS_UNPARSED_ARGUMENTS DOWNLOAD_COMMAND ${CPM_ARGS_DOWNLOAD_COMMAND}) + elseif(DEFINED CPM_ARGS_SOURCE_DIR) + list(APPEND CPM_ARGS_UNPARSED_ARGUMENTS SOURCE_DIR ${CPM_ARGS_SOURCE_DIR}) + if(NOT IS_ABSOLUTE ${CPM_ARGS_SOURCE_DIR}) + # Expand `CPM_ARGS_SOURCE_DIR` relative path. This is important because EXISTS doesn't work + # for relative paths. + get_filename_component( + source_directory ${CPM_ARGS_SOURCE_DIR} REALPATH BASE_DIR ${CMAKE_CURRENT_BINARY_DIR} + ) + else() + set(source_directory ${CPM_ARGS_SOURCE_DIR}) + endif() + if(NOT EXISTS ${source_directory}) + string(TOLOWER ${CPM_ARGS_NAME} lower_case_name) + # remove timestamps so CMake will re-download the dependency + file(REMOVE_RECURSE "${CPM_FETCHCONTENT_BASE_DIR}/${lower_case_name}-subbuild") + endif() + elseif(CPM_SOURCE_CACHE AND NOT CPM_ARGS_NO_CACHE) + string(TOLOWER ${CPM_ARGS_NAME} lower_case_name) + set(origin_parameters ${CPM_ARGS_UNPARSED_ARGUMENTS}) + list(SORT origin_parameters) + if(CPM_USE_NAMED_CACHE_DIRECTORIES) + string(SHA1 origin_hash "${origin_parameters};NEW_CACHE_STRUCTURE_TAG") + set(download_directory ${CPM_SOURCE_CACHE}/${lower_case_name}/${origin_hash}/${CPM_ARGS_NAME}) + else() + string(SHA1 origin_hash "${origin_parameters}") + set(download_directory ${CPM_SOURCE_CACHE}/${lower_case_name}/${origin_hash}) + endif() + # Expand `download_directory` relative path. This is important because EXISTS doesn't work for + # relative paths. + get_filename_component(download_directory ${download_directory} ABSOLUTE) + list(APPEND CPM_ARGS_UNPARSED_ARGUMENTS SOURCE_DIR ${download_directory}) + + if(CPM_SOURCE_CACHE) + file(LOCK ${download_directory}/../cmake.lock) + endif() + + if(EXISTS ${download_directory}) + if(CPM_SOURCE_CACHE) + file(LOCK ${download_directory}/../cmake.lock RELEASE) + endif() + + cpm_store_fetch_properties( + ${CPM_ARGS_NAME} "${download_directory}" + "${CPM_FETCHCONTENT_BASE_DIR}/${lower_case_name}-build" + ) + cpm_get_fetch_properties("${CPM_ARGS_NAME}") + + if(DEFINED CPM_ARGS_GIT_TAG AND NOT (PATCH_COMMAND IN_LIST CPM_ARGS_UNPARSED_ARGUMENTS)) + # warn if cache has been changed since checkout + cpm_check_git_working_dir_is_clean(${download_directory} ${CPM_ARGS_GIT_TAG} IS_CLEAN) + if(NOT ${IS_CLEAN}) + message( + WARNING "${CPM_INDENT} Cache for ${CPM_ARGS_NAME} (${download_directory}) is dirty" + ) + endif() + endif() + + cpm_add_subdirectory( + "${CPM_ARGS_NAME}" + "${DOWNLOAD_ONLY}" + "${${CPM_ARGS_NAME}_SOURCE_DIR}/${CPM_ARGS_SOURCE_SUBDIR}" + "${${CPM_ARGS_NAME}_BINARY_DIR}" + "${CPM_ARGS_EXCLUDE_FROM_ALL}" + "${CPM_ARGS_SYSTEM}" + "${CPM_ARGS_OPTIONS}" + ) + set(PACKAGE_INFO "${PACKAGE_INFO} at ${download_directory}") + + # As the source dir is already cached/populated, we override the call to FetchContent. + set(CPM_SKIP_FETCH TRUE) + cpm_override_fetchcontent( + "${lower_case_name}" SOURCE_DIR "${${CPM_ARGS_NAME}_SOURCE_DIR}/${CPM_ARGS_SOURCE_SUBDIR}" + BINARY_DIR "${${CPM_ARGS_NAME}_BINARY_DIR}" + ) + + else() + # Enable shallow clone when GIT_TAG is not a commit hash. Our guess may not be accurate, but + # it should guarantee no commit hash get mis-detected. + if(NOT DEFINED CPM_ARGS_GIT_SHALLOW) + cpm_is_git_tag_commit_hash("${CPM_ARGS_GIT_TAG}" IS_HASH) + if(NOT ${IS_HASH}) + list(APPEND CPM_ARGS_UNPARSED_ARGUMENTS GIT_SHALLOW TRUE) + endif() + endif() + + # remove timestamps so CMake will re-download the dependency + file(REMOVE_RECURSE ${CPM_FETCHCONTENT_BASE_DIR}/${lower_case_name}-subbuild) + set(PACKAGE_INFO "${PACKAGE_INFO} to ${download_directory}") + endif() + endif() + + cpm_create_module_file(${CPM_ARGS_NAME} "CPMAddPackage(\"${ARGN}\")") + + if(CPM_PACKAGE_LOCK_ENABLED) + if((CPM_ARGS_VERSION AND NOT CPM_ARGS_SOURCE_DIR) OR CPM_INCLUDE_ALL_IN_PACKAGE_LOCK) + cpm_add_to_package_lock(${CPM_ARGS_NAME} "${ARGN}") + elseif(CPM_ARGS_SOURCE_DIR) + cpm_add_comment_to_package_lock(${CPM_ARGS_NAME} "local directory") + else() + cpm_add_comment_to_package_lock(${CPM_ARGS_NAME} "${ARGN}") + endif() + endif() + + cpm_message( + STATUS "${CPM_INDENT} Adding package ${CPM_ARGS_NAME}@${CPM_ARGS_VERSION} (${PACKAGE_INFO})" + ) + + if(NOT CPM_SKIP_FETCH) + cpm_declare_fetch( + "${CPM_ARGS_NAME}" "${CPM_ARGS_VERSION}" "${PACKAGE_INFO}" "${CPM_ARGS_UNPARSED_ARGUMENTS}" + ) + cpm_fetch_package("${CPM_ARGS_NAME}" populated) + if(CPM_SOURCE_CACHE AND download_directory) + file(LOCK ${download_directory}/../cmake.lock RELEASE) + endif() + if(${populated}) + cpm_add_subdirectory( + "${CPM_ARGS_NAME}" + "${DOWNLOAD_ONLY}" + "${${CPM_ARGS_NAME}_SOURCE_DIR}/${CPM_ARGS_SOURCE_SUBDIR}" + "${${CPM_ARGS_NAME}_BINARY_DIR}" + "${CPM_ARGS_EXCLUDE_FROM_ALL}" + "${CPM_ARGS_SYSTEM}" + "${CPM_ARGS_OPTIONS}" + ) + endif() + cpm_get_fetch_properties("${CPM_ARGS_NAME}") + endif() + + set(${CPM_ARGS_NAME}_ADDED YES) + cpm_export_variables("${CPM_ARGS_NAME}") +endfunction() + +# Fetch a previously declared package +macro(CPMGetPackage Name) + if(DEFINED "CPM_DECLARATION_${Name}") + CPMAddPackage(NAME ${Name}) + else() + message(SEND_ERROR "${CPM_INDENT} Cannot retrieve package ${Name}: no declaration available") + endif() +endmacro() + +# export variables available to the caller to the parent scope expects ${CPM_ARGS_NAME} to be set +macro(cpm_export_variables name) + set(${name}_SOURCE_DIR + "${${name}_SOURCE_DIR}" + PARENT_SCOPE + ) + set(${name}_BINARY_DIR + "${${name}_BINARY_DIR}" + PARENT_SCOPE + ) + set(${name}_ADDED + "${${name}_ADDED}" + PARENT_SCOPE + ) + set(CPM_LAST_PACKAGE_NAME + "${name}" + PARENT_SCOPE + ) +endmacro() + +# declares a package, so that any call to CPMAddPackage for the package name will use these +# arguments instead. Previous declarations will not be overridden. +macro(CPMDeclarePackage Name) + if(NOT DEFINED "CPM_DECLARATION_${Name}") + set("CPM_DECLARATION_${Name}" "${ARGN}") + endif() +endmacro() + +function(cpm_add_to_package_lock Name) + if(NOT CPM_DONT_CREATE_PACKAGE_LOCK) + cpm_prettify_package_arguments(PRETTY_ARGN false ${ARGN}) + file(APPEND ${CPM_PACKAGE_LOCK_FILE} "# ${Name}\nCPMDeclarePackage(${Name}\n${PRETTY_ARGN})\n") + endif() +endfunction() + +function(cpm_add_comment_to_package_lock Name) + if(NOT CPM_DONT_CREATE_PACKAGE_LOCK) + cpm_prettify_package_arguments(PRETTY_ARGN true ${ARGN}) + file(APPEND ${CPM_PACKAGE_LOCK_FILE} + "# ${Name} (unversioned)\n# CPMDeclarePackage(${Name}\n${PRETTY_ARGN}#)\n" + ) + endif() +endfunction() + +# includes the package lock file if it exists and creates a target `cpm-update-package-lock` to +# update it +macro(CPMUsePackageLock file) + if(NOT CPM_DONT_CREATE_PACKAGE_LOCK) + get_filename_component(CPM_ABSOLUTE_PACKAGE_LOCK_PATH ${file} ABSOLUTE) + if(EXISTS ${CPM_ABSOLUTE_PACKAGE_LOCK_PATH}) + include(${CPM_ABSOLUTE_PACKAGE_LOCK_PATH}) + endif() + if(NOT TARGET cpm-update-package-lock) + add_custom_target( + cpm-update-package-lock COMMAND ${CMAKE_COMMAND} -E copy ${CPM_PACKAGE_LOCK_FILE} + ${CPM_ABSOLUTE_PACKAGE_LOCK_PATH} + ) + endif() + set(CPM_PACKAGE_LOCK_ENABLED true) + endif() +endmacro() + +# registers a package that has been added to CPM +function(CPMRegisterPackage PACKAGE VERSION) + list(APPEND CPM_PACKAGES ${PACKAGE}) + set(CPM_PACKAGES + ${CPM_PACKAGES} + CACHE INTERNAL "" + ) + set("CPM_PACKAGE_${PACKAGE}_VERSION" + ${VERSION} + CACHE INTERNAL "" + ) +endfunction() + +# retrieve the current version of the package to ${OUTPUT} +function(CPMGetPackageVersion PACKAGE OUTPUT) + set(${OUTPUT} + "${CPM_PACKAGE_${PACKAGE}_VERSION}" + PARENT_SCOPE + ) +endfunction() + +# declares a package in FetchContent_Declare +function(cpm_declare_fetch PACKAGE VERSION INFO) + if(${CPM_DRY_RUN}) + cpm_message(STATUS "${CPM_INDENT} Package not declared (dry run)") + return() + endif() + + FetchContent_Declare(${PACKAGE} ${ARGN}) +endfunction() + +# returns properties for a package previously defined by cpm_declare_fetch +function(cpm_get_fetch_properties PACKAGE) + if(${CPM_DRY_RUN}) + return() + endif() + + set(${PACKAGE}_SOURCE_DIR + "${CPM_PACKAGE_${PACKAGE}_SOURCE_DIR}" + PARENT_SCOPE + ) + set(${PACKAGE}_BINARY_DIR + "${CPM_PACKAGE_${PACKAGE}_BINARY_DIR}" + PARENT_SCOPE + ) +endfunction() + +function(cpm_store_fetch_properties PACKAGE source_dir binary_dir) + if(${CPM_DRY_RUN}) + return() + endif() + + set(CPM_PACKAGE_${PACKAGE}_SOURCE_DIR + "${source_dir}" + CACHE INTERNAL "" + ) + set(CPM_PACKAGE_${PACKAGE}_BINARY_DIR + "${binary_dir}" + CACHE INTERNAL "" + ) +endfunction() + +# adds a package as a subdirectory if viable, according to provided options +function( + cpm_add_subdirectory + PACKAGE + DOWNLOAD_ONLY + SOURCE_DIR + BINARY_DIR + EXCLUDE + SYSTEM + OPTIONS +) + + if(NOT DOWNLOAD_ONLY AND EXISTS ${SOURCE_DIR}/CMakeLists.txt) + set(addSubdirectoryExtraArgs "") + if(EXCLUDE) + list(APPEND addSubdirectoryExtraArgs EXCLUDE_FROM_ALL) + endif() + if("${SYSTEM}" AND "${CMAKE_VERSION}" VERSION_GREATER_EQUAL "3.25") + # https://cmake.org/cmake/help/latest/prop_dir/SYSTEM.html#prop_dir:SYSTEM + list(APPEND addSubdirectoryExtraArgs SYSTEM) + endif() + if(OPTIONS) + foreach(OPTION ${OPTIONS}) + cpm_parse_option("${OPTION}") + set(${OPTION_KEY} "${OPTION_VALUE}") + endforeach() + endif() + set(CPM_OLD_INDENT "${CPM_INDENT}") + set(CPM_INDENT "${CPM_INDENT} ${PACKAGE}:") + add_subdirectory(${SOURCE_DIR} ${BINARY_DIR} ${addSubdirectoryExtraArgs}) + set(CPM_INDENT "${CPM_OLD_INDENT}") + endif() +endfunction() + +# downloads a previously declared package via FetchContent and exports the variables +# `${PACKAGE}_SOURCE_DIR` and `${PACKAGE}_BINARY_DIR` to the parent scope +function(cpm_fetch_package PACKAGE populated) + set(${populated} + FALSE + PARENT_SCOPE + ) + if(${CPM_DRY_RUN}) + cpm_message(STATUS "${CPM_INDENT} Package ${PACKAGE} not fetched (dry run)") + return() + endif() + + FetchContent_GetProperties(${PACKAGE}) + + string(TOLOWER "${PACKAGE}" lower_case_name) + + if(NOT ${lower_case_name}_POPULATED) + FetchContent_Populate(${PACKAGE}) + set(${populated} + TRUE + PARENT_SCOPE + ) + endif() + + cpm_store_fetch_properties( + ${CPM_ARGS_NAME} ${${lower_case_name}_SOURCE_DIR} ${${lower_case_name}_BINARY_DIR} + ) + + set(${PACKAGE}_SOURCE_DIR + ${${lower_case_name}_SOURCE_DIR} + PARENT_SCOPE + ) + set(${PACKAGE}_BINARY_DIR + ${${lower_case_name}_BINARY_DIR} + PARENT_SCOPE + ) +endfunction() + +# splits a package option +function(cpm_parse_option OPTION) + string(REGEX MATCH "^[^ ]+" OPTION_KEY "${OPTION}") + string(LENGTH "${OPTION}" OPTION_LENGTH) + string(LENGTH "${OPTION_KEY}" OPTION_KEY_LENGTH) + if(OPTION_KEY_LENGTH STREQUAL OPTION_LENGTH) + # no value for key provided, assume user wants to set option to "ON" + set(OPTION_VALUE "ON") + else() + math(EXPR OPTION_KEY_LENGTH "${OPTION_KEY_LENGTH}+1") + string(SUBSTRING "${OPTION}" "${OPTION_KEY_LENGTH}" "-1" OPTION_VALUE) + endif() + set(OPTION_KEY + "${OPTION_KEY}" + PARENT_SCOPE + ) + set(OPTION_VALUE + "${OPTION_VALUE}" + PARENT_SCOPE + ) +endfunction() + +# guesses the package version from a git tag +function(cpm_get_version_from_git_tag GIT_TAG RESULT) + string(LENGTH ${GIT_TAG} length) + if(length EQUAL 40) + # GIT_TAG is probably a git hash + set(${RESULT} + 0 + PARENT_SCOPE + ) + else() + string(REGEX MATCH "v?([0123456789.]*).*" _ ${GIT_TAG}) + set(${RESULT} + ${CMAKE_MATCH_1} + PARENT_SCOPE + ) + endif() +endfunction() + +# guesses if the git tag is a commit hash or an actual tag or a branch name. +function(cpm_is_git_tag_commit_hash GIT_TAG RESULT) + string(LENGTH "${GIT_TAG}" length) + # full hash has 40 characters, and short hash has at least 7 characters. + if(length LESS 7 OR length GREATER 40) + set(${RESULT} + 0 + PARENT_SCOPE + ) + else() + if(${GIT_TAG} MATCHES "^[a-fA-F0-9]+$") + set(${RESULT} + 1 + PARENT_SCOPE + ) + else() + set(${RESULT} + 0 + PARENT_SCOPE + ) + endif() + endif() +endfunction() + +function(cpm_prettify_package_arguments OUT_VAR IS_IN_COMMENT) + set(oneValueArgs + NAME + FORCE + VERSION + GIT_TAG + DOWNLOAD_ONLY + GITHUB_REPOSITORY + GITLAB_REPOSITORY + BITBUCKET_REPOSITORY + GIT_REPOSITORY + SOURCE_DIR + FIND_PACKAGE_ARGUMENTS + NO_CACHE + SYSTEM + GIT_SHALLOW + EXCLUDE_FROM_ALL + SOURCE_SUBDIR + ) + set(multiValueArgs URL OPTIONS DOWNLOAD_COMMAND) + cmake_parse_arguments(CPM_ARGS "" "${oneValueArgs}" "${multiValueArgs}" ${ARGN}) + + foreach(oneArgName ${oneValueArgs}) + if(DEFINED CPM_ARGS_${oneArgName}) + if(${IS_IN_COMMENT}) + string(APPEND PRETTY_OUT_VAR "#") + endif() + if(${oneArgName} STREQUAL "SOURCE_DIR") + string(REPLACE ${CMAKE_SOURCE_DIR} "\${CMAKE_SOURCE_DIR}" CPM_ARGS_${oneArgName} + ${CPM_ARGS_${oneArgName}} + ) + endif() + string(APPEND PRETTY_OUT_VAR " ${oneArgName} ${CPM_ARGS_${oneArgName}}\n") + endif() + endforeach() + foreach(multiArgName ${multiValueArgs}) + if(DEFINED CPM_ARGS_${multiArgName}) + if(${IS_IN_COMMENT}) + string(APPEND PRETTY_OUT_VAR "#") + endif() + string(APPEND PRETTY_OUT_VAR " ${multiArgName}\n") + foreach(singleOption ${CPM_ARGS_${multiArgName}}) + if(${IS_IN_COMMENT}) + string(APPEND PRETTY_OUT_VAR "#") + endif() + string(APPEND PRETTY_OUT_VAR " \"${singleOption}\"\n") + endforeach() + endif() + endforeach() + + if(NOT "${CPM_ARGS_UNPARSED_ARGUMENTS}" STREQUAL "") + if(${IS_IN_COMMENT}) + string(APPEND PRETTY_OUT_VAR "#") + endif() + string(APPEND PRETTY_OUT_VAR " ") + foreach(CPM_ARGS_UNPARSED_ARGUMENT ${CPM_ARGS_UNPARSED_ARGUMENTS}) + string(APPEND PRETTY_OUT_VAR " ${CPM_ARGS_UNPARSED_ARGUMENT}") + endforeach() + string(APPEND PRETTY_OUT_VAR "\n") + endif() + + set(${OUT_VAR} + ${PRETTY_OUT_VAR} + PARENT_SCOPE + ) + +endfunction() diff --git a/doctest/doctest.h b/doctest/doctest.h new file mode 100644 index 0000000..5c754cd --- /dev/null +++ b/doctest/doctest.h @@ -0,0 +1,7106 @@ +// ====================================================================== lgtm [cpp/missing-header-guard] +// == DO NOT MODIFY THIS FILE BY HAND - IT IS AUTO GENERATED BY CMAKE! == +// ====================================================================== +// +// doctest.h - the lightest feature-rich C++ single-header testing framework for unit tests and TDD +// +// Copyright (c) 2016-2023 Viktor Kirilov +// +// Distributed under the MIT Software License +// See accompanying file LICENSE.txt or copy at +// https://opensource.org/licenses/MIT +// +// The documentation can be found at the library's page: +// https://github.com/doctest/doctest/blob/master/doc/markdown/readme.md +// +// ================================================================================================= +// ================================================================================================= +// ================================================================================================= +// +// The library is heavily influenced by Catch - https://github.com/catchorg/Catch2 +// which uses the Boost Software License - Version 1.0 +// see here - https://github.com/catchorg/Catch2/blob/master/LICENSE.txt +// +// The concept of subcases (sections in Catch) and expression decomposition are from there. +// Some parts of the code are taken directly: +// - stringification - the detection of "ostream& operator<<(ostream&, const T&)" and StringMaker<> +// - the Approx() helper class for floating point comparison +// - colors in the console +// - breaking into a debugger +// - signal / SEH handling +// - timer +// - XmlWriter class - thanks to Phil Nash for allowing the direct reuse (AKA copy/paste) +// +// The expression decomposing templates are taken from lest - https://github.com/martinmoene/lest +// which uses the Boost Software License - Version 1.0 +// see here - https://github.com/martinmoene/lest/blob/master/LICENSE.txt +// +// ================================================================================================= +// ================================================================================================= +// ================================================================================================= + +#ifndef DOCTEST_LIBRARY_INCLUDED +#define DOCTEST_LIBRARY_INCLUDED + +// ================================================================================================= +// == VERSION ====================================================================================== +// ================================================================================================= + +#define DOCTEST_VERSION_MAJOR 2 +#define DOCTEST_VERSION_MINOR 4 +#define DOCTEST_VERSION_PATCH 11 + +// util we need here +#define DOCTEST_TOSTR_IMPL(x) #x +#define DOCTEST_TOSTR(x) DOCTEST_TOSTR_IMPL(x) + +#define DOCTEST_VERSION_STR \ + DOCTEST_TOSTR(DOCTEST_VERSION_MAJOR) "." \ + DOCTEST_TOSTR(DOCTEST_VERSION_MINOR) "." \ + DOCTEST_TOSTR(DOCTEST_VERSION_PATCH) + +#define DOCTEST_VERSION \ + (DOCTEST_VERSION_MAJOR * 10000 + DOCTEST_VERSION_MINOR * 100 + DOCTEST_VERSION_PATCH) + +// ================================================================================================= +// == COMPILER VERSION ============================================================================= +// ================================================================================================= + +// ideas for the version stuff are taken from here: https://github.com/cxxstuff/cxx_detect + +#ifdef _MSC_VER +#define DOCTEST_CPLUSPLUS _MSVC_LANG +#else +#define DOCTEST_CPLUSPLUS __cplusplus +#endif + +#define DOCTEST_COMPILER(MAJOR, MINOR, PATCH) ((MAJOR)*10000000 + (MINOR)*100000 + (PATCH)) + +// GCC/Clang and GCC/MSVC are mutually exclusive, but Clang/MSVC are not because of clang-cl... +#if defined(_MSC_VER) && defined(_MSC_FULL_VER) +#if _MSC_VER == _MSC_FULL_VER / 10000 +#define DOCTEST_MSVC DOCTEST_COMPILER(_MSC_VER / 100, _MSC_VER % 100, _MSC_FULL_VER % 10000) +#else // MSVC +#define DOCTEST_MSVC \ + DOCTEST_COMPILER(_MSC_VER / 100, (_MSC_FULL_VER / 100000) % 100, _MSC_FULL_VER % 100000) +#endif // MSVC +#endif // MSVC +#if defined(__clang__) && defined(__clang_minor__) && defined(__clang_patchlevel__) +#define DOCTEST_CLANG DOCTEST_COMPILER(__clang_major__, __clang_minor__, __clang_patchlevel__) +#elif defined(__GNUC__) && defined(__GNUC_MINOR__) && defined(__GNUC_PATCHLEVEL__) && \ + !defined(__INTEL_COMPILER) +#define DOCTEST_GCC DOCTEST_COMPILER(__GNUC__, __GNUC_MINOR__, __GNUC_PATCHLEVEL__) +#endif // GCC +#if defined(__INTEL_COMPILER) +#define DOCTEST_ICC DOCTEST_COMPILER(__INTEL_COMPILER / 100, __INTEL_COMPILER % 100, 0) +#endif // ICC + +#ifndef DOCTEST_MSVC +#define DOCTEST_MSVC 0 +#endif // DOCTEST_MSVC +#ifndef DOCTEST_CLANG +#define DOCTEST_CLANG 0 +#endif // DOCTEST_CLANG +#ifndef DOCTEST_GCC +#define DOCTEST_GCC 0 +#endif // DOCTEST_GCC +#ifndef DOCTEST_ICC +#define DOCTEST_ICC 0 +#endif // DOCTEST_ICC + +// ================================================================================================= +// == COMPILER WARNINGS HELPERS ==================================================================== +// ================================================================================================= + +#if DOCTEST_CLANG && !DOCTEST_ICC +#define DOCTEST_PRAGMA_TO_STR(x) _Pragma(#x) +#define DOCTEST_CLANG_SUPPRESS_WARNING_PUSH _Pragma("clang diagnostic push") +#define DOCTEST_CLANG_SUPPRESS_WARNING(w) DOCTEST_PRAGMA_TO_STR(clang diagnostic ignored w) +#define DOCTEST_CLANG_SUPPRESS_WARNING_POP _Pragma("clang diagnostic pop") +#define DOCTEST_CLANG_SUPPRESS_WARNING_WITH_PUSH(w) \ + DOCTEST_CLANG_SUPPRESS_WARNING_PUSH DOCTEST_CLANG_SUPPRESS_WARNING(w) +#else // DOCTEST_CLANG +#define DOCTEST_CLANG_SUPPRESS_WARNING_PUSH +#define DOCTEST_CLANG_SUPPRESS_WARNING(w) +#define DOCTEST_CLANG_SUPPRESS_WARNING_POP +#define DOCTEST_CLANG_SUPPRESS_WARNING_WITH_PUSH(w) +#endif // DOCTEST_CLANG + +#if DOCTEST_GCC +#define DOCTEST_PRAGMA_TO_STR(x) _Pragma(#x) +#define DOCTEST_GCC_SUPPRESS_WARNING_PUSH _Pragma("GCC diagnostic push") +#define DOCTEST_GCC_SUPPRESS_WARNING(w) DOCTEST_PRAGMA_TO_STR(GCC diagnostic ignored w) +#define DOCTEST_GCC_SUPPRESS_WARNING_POP _Pragma("GCC diagnostic pop") +#define DOCTEST_GCC_SUPPRESS_WARNING_WITH_PUSH(w) \ + DOCTEST_GCC_SUPPRESS_WARNING_PUSH DOCTEST_GCC_SUPPRESS_WARNING(w) +#else // DOCTEST_GCC +#define DOCTEST_GCC_SUPPRESS_WARNING_PUSH +#define DOCTEST_GCC_SUPPRESS_WARNING(w) +#define DOCTEST_GCC_SUPPRESS_WARNING_POP +#define DOCTEST_GCC_SUPPRESS_WARNING_WITH_PUSH(w) +#endif // DOCTEST_GCC + +#if DOCTEST_MSVC +#define DOCTEST_MSVC_SUPPRESS_WARNING_PUSH __pragma(warning(push)) +#define DOCTEST_MSVC_SUPPRESS_WARNING(w) __pragma(warning(disable : w)) +#define DOCTEST_MSVC_SUPPRESS_WARNING_POP __pragma(warning(pop)) +#define DOCTEST_MSVC_SUPPRESS_WARNING_WITH_PUSH(w) \ + DOCTEST_MSVC_SUPPRESS_WARNING_PUSH DOCTEST_MSVC_SUPPRESS_WARNING(w) +#else // DOCTEST_MSVC +#define DOCTEST_MSVC_SUPPRESS_WARNING_PUSH +#define DOCTEST_MSVC_SUPPRESS_WARNING(w) +#define DOCTEST_MSVC_SUPPRESS_WARNING_POP +#define DOCTEST_MSVC_SUPPRESS_WARNING_WITH_PUSH(w) +#endif // DOCTEST_MSVC + +// ================================================================================================= +// == COMPILER WARNINGS ============================================================================ +// ================================================================================================= + +// both the header and the implementation suppress all of these, +// so it only makes sense to aggregate them like so +#define DOCTEST_SUPPRESS_COMMON_WARNINGS_PUSH \ + DOCTEST_CLANG_SUPPRESS_WARNING_PUSH \ + DOCTEST_CLANG_SUPPRESS_WARNING("-Wunknown-pragmas") \ + DOCTEST_CLANG_SUPPRESS_WARNING("-Wweak-vtables") \ + DOCTEST_CLANG_SUPPRESS_WARNING("-Wpadded") \ + DOCTEST_CLANG_SUPPRESS_WARNING("-Wmissing-prototypes") \ + DOCTEST_CLANG_SUPPRESS_WARNING("-Wc++98-compat") \ + DOCTEST_CLANG_SUPPRESS_WARNING("-Wc++98-compat-pedantic") \ + \ + DOCTEST_GCC_SUPPRESS_WARNING_PUSH \ + DOCTEST_GCC_SUPPRESS_WARNING("-Wunknown-pragmas") \ + DOCTEST_GCC_SUPPRESS_WARNING("-Wpragmas") \ + DOCTEST_GCC_SUPPRESS_WARNING("-Weffc++") \ + DOCTEST_GCC_SUPPRESS_WARNING("-Wstrict-overflow") \ + DOCTEST_GCC_SUPPRESS_WARNING("-Wstrict-aliasing") \ + DOCTEST_GCC_SUPPRESS_WARNING("-Wmissing-declarations") \ + DOCTEST_GCC_SUPPRESS_WARNING("-Wuseless-cast") \ + DOCTEST_GCC_SUPPRESS_WARNING("-Wnoexcept") \ + \ + DOCTEST_MSVC_SUPPRESS_WARNING_PUSH \ + /* these 4 also disabled globally via cmake: */ \ + DOCTEST_MSVC_SUPPRESS_WARNING(4514) /* unreferenced inline function has been removed */ \ + DOCTEST_MSVC_SUPPRESS_WARNING(4571) /* SEH related */ \ + DOCTEST_MSVC_SUPPRESS_WARNING(4710) /* function not inlined */ \ + DOCTEST_MSVC_SUPPRESS_WARNING(4711) /* function selected for inline expansion*/ \ + /* common ones */ \ + DOCTEST_MSVC_SUPPRESS_WARNING(4616) /* invalid compiler warning */ \ + DOCTEST_MSVC_SUPPRESS_WARNING(4619) /* invalid compiler warning */ \ + DOCTEST_MSVC_SUPPRESS_WARNING(4996) /* The compiler encountered a deprecated declaration */ \ + DOCTEST_MSVC_SUPPRESS_WARNING(4706) /* assignment within conditional expression */ \ + DOCTEST_MSVC_SUPPRESS_WARNING(4512) /* 'class' : assignment operator could not be generated */ \ + DOCTEST_MSVC_SUPPRESS_WARNING(4127) /* conditional expression is constant */ \ + DOCTEST_MSVC_SUPPRESS_WARNING(4820) /* padding */ \ + DOCTEST_MSVC_SUPPRESS_WARNING(4625) /* copy constructor was implicitly deleted */ \ + DOCTEST_MSVC_SUPPRESS_WARNING(4626) /* assignment operator was implicitly deleted */ \ + DOCTEST_MSVC_SUPPRESS_WARNING(5027) /* move assignment operator implicitly deleted */ \ + DOCTEST_MSVC_SUPPRESS_WARNING(5026) /* move constructor was implicitly deleted */ \ + DOCTEST_MSVC_SUPPRESS_WARNING(4640) /* construction of local static object not thread-safe */ \ + DOCTEST_MSVC_SUPPRESS_WARNING(5045) /* Spectre mitigation for memory load */ \ + DOCTEST_MSVC_SUPPRESS_WARNING(5264) /* 'variable-name': 'const' variable is not used */ \ + /* static analysis */ \ + DOCTEST_MSVC_SUPPRESS_WARNING(26439) /* Function may not throw. Declare it 'noexcept' */ \ + DOCTEST_MSVC_SUPPRESS_WARNING(26495) /* Always initialize a member variable */ \ + DOCTEST_MSVC_SUPPRESS_WARNING(26451) /* Arithmetic overflow ... */ \ + DOCTEST_MSVC_SUPPRESS_WARNING(26444) /* Avoid unnamed objects with custom ctor and dtor... */ \ + DOCTEST_MSVC_SUPPRESS_WARNING(26812) /* Prefer 'enum class' over 'enum' */ + +#define DOCTEST_SUPPRESS_COMMON_WARNINGS_POP \ + DOCTEST_CLANG_SUPPRESS_WARNING_POP \ + DOCTEST_GCC_SUPPRESS_WARNING_POP \ + DOCTEST_MSVC_SUPPRESS_WARNING_POP + +DOCTEST_SUPPRESS_COMMON_WARNINGS_PUSH + +DOCTEST_CLANG_SUPPRESS_WARNING_PUSH +DOCTEST_CLANG_SUPPRESS_WARNING("-Wnon-virtual-dtor") +DOCTEST_CLANG_SUPPRESS_WARNING("-Wdeprecated") + +DOCTEST_GCC_SUPPRESS_WARNING_PUSH +DOCTEST_GCC_SUPPRESS_WARNING("-Wctor-dtor-privacy") +DOCTEST_GCC_SUPPRESS_WARNING("-Wnon-virtual-dtor") +DOCTEST_GCC_SUPPRESS_WARNING("-Wsign-promo") + +DOCTEST_MSVC_SUPPRESS_WARNING_PUSH +DOCTEST_MSVC_SUPPRESS_WARNING(4623) // default constructor was implicitly defined as deleted + +#define DOCTEST_MAKE_STD_HEADERS_CLEAN_FROM_WARNINGS_ON_WALL_BEGIN \ + DOCTEST_MSVC_SUPPRESS_WARNING_PUSH \ + DOCTEST_MSVC_SUPPRESS_WARNING(4548) /* before comma no effect; expected side - effect */ \ + DOCTEST_MSVC_SUPPRESS_WARNING(4265) /* virtual functions, but destructor is not virtual */ \ + DOCTEST_MSVC_SUPPRESS_WARNING(4986) /* exception specification does not match previous */ \ + DOCTEST_MSVC_SUPPRESS_WARNING(4350) /* 'member1' called instead of 'member2' */ \ + DOCTEST_MSVC_SUPPRESS_WARNING(4668) /* not defined as a preprocessor macro */ \ + DOCTEST_MSVC_SUPPRESS_WARNING(4365) /* signed/unsigned mismatch */ \ + DOCTEST_MSVC_SUPPRESS_WARNING(4774) /* format string not a string literal */ \ + DOCTEST_MSVC_SUPPRESS_WARNING(4820) /* padding */ \ + DOCTEST_MSVC_SUPPRESS_WARNING(4625) /* copy constructor was implicitly deleted */ \ + DOCTEST_MSVC_SUPPRESS_WARNING(4626) /* assignment operator was implicitly deleted */ \ + DOCTEST_MSVC_SUPPRESS_WARNING(5027) /* move assignment operator implicitly deleted */ \ + DOCTEST_MSVC_SUPPRESS_WARNING(5026) /* move constructor was implicitly deleted */ \ + DOCTEST_MSVC_SUPPRESS_WARNING(4623) /* default constructor was implicitly deleted */ \ + DOCTEST_MSVC_SUPPRESS_WARNING(5039) /* pointer to pot. throwing function passed to extern C */ \ + DOCTEST_MSVC_SUPPRESS_WARNING(5045) /* Spectre mitigation for memory load */ \ + DOCTEST_MSVC_SUPPRESS_WARNING(5105) /* macro producing 'defined' has undefined behavior */ \ + DOCTEST_MSVC_SUPPRESS_WARNING(4738) /* storing float result in memory, loss of performance */ \ + DOCTEST_MSVC_SUPPRESS_WARNING(5262) /* implicit fall-through */ + +#define DOCTEST_MAKE_STD_HEADERS_CLEAN_FROM_WARNINGS_ON_WALL_END DOCTEST_MSVC_SUPPRESS_WARNING_POP + +// ================================================================================================= +// == FEATURE DETECTION ============================================================================ +// ================================================================================================= + +// general compiler feature support table: https://en.cppreference.com/w/cpp/compiler_support +// MSVC C++11 feature support table: https://msdn.microsoft.com/en-us/library/hh567368.aspx +// GCC C++11 feature support table: https://gcc.gnu.org/projects/cxx-status.html +// MSVC version table: +// https://en.wikipedia.org/wiki/Microsoft_Visual_C%2B%2B#Internal_version_numbering +// MSVC++ 14.3 (17) _MSC_VER == 1930 (Visual Studio 2022) +// MSVC++ 14.2 (16) _MSC_VER == 1920 (Visual Studio 2019) +// MSVC++ 14.1 (15) _MSC_VER == 1910 (Visual Studio 2017) +// MSVC++ 14.0 _MSC_VER == 1900 (Visual Studio 2015) +// MSVC++ 12.0 _MSC_VER == 1800 (Visual Studio 2013) +// MSVC++ 11.0 _MSC_VER == 1700 (Visual Studio 2012) +// MSVC++ 10.0 _MSC_VER == 1600 (Visual Studio 2010) +// MSVC++ 9.0 _MSC_VER == 1500 (Visual Studio 2008) +// MSVC++ 8.0 _MSC_VER == 1400 (Visual Studio 2005) + +// Universal Windows Platform support +#if defined(WINAPI_FAMILY) && (WINAPI_FAMILY == WINAPI_FAMILY_APP) +#define DOCTEST_CONFIG_NO_WINDOWS_SEH +#endif // WINAPI_FAMILY +#if DOCTEST_MSVC && !defined(DOCTEST_CONFIG_WINDOWS_SEH) +#define DOCTEST_CONFIG_WINDOWS_SEH +#endif // MSVC +#if defined(DOCTEST_CONFIG_NO_WINDOWS_SEH) && defined(DOCTEST_CONFIG_WINDOWS_SEH) +#undef DOCTEST_CONFIG_WINDOWS_SEH +#endif // DOCTEST_CONFIG_NO_WINDOWS_SEH + +#if !defined(_WIN32) && !defined(__QNX__) && !defined(DOCTEST_CONFIG_POSIX_SIGNALS) && \ + !defined(__EMSCRIPTEN__) && !defined(__wasi__) +#define DOCTEST_CONFIG_POSIX_SIGNALS +#endif // _WIN32 +#if defined(DOCTEST_CONFIG_NO_POSIX_SIGNALS) && defined(DOCTEST_CONFIG_POSIX_SIGNALS) +#undef DOCTEST_CONFIG_POSIX_SIGNALS +#endif // DOCTEST_CONFIG_NO_POSIX_SIGNALS + +#ifndef DOCTEST_CONFIG_NO_EXCEPTIONS +#if !defined(__cpp_exceptions) && !defined(__EXCEPTIONS) && !defined(_CPPUNWIND) \ + || defined(__wasi__) +#define DOCTEST_CONFIG_NO_EXCEPTIONS +#endif // no exceptions +#endif // DOCTEST_CONFIG_NO_EXCEPTIONS + +#ifdef DOCTEST_CONFIG_NO_EXCEPTIONS_BUT_WITH_ALL_ASSERTS +#ifndef DOCTEST_CONFIG_NO_EXCEPTIONS +#define DOCTEST_CONFIG_NO_EXCEPTIONS +#endif // DOCTEST_CONFIG_NO_EXCEPTIONS +#endif // DOCTEST_CONFIG_NO_EXCEPTIONS_BUT_WITH_ALL_ASSERTS + +#if defined(DOCTEST_CONFIG_NO_EXCEPTIONS) && !defined(DOCTEST_CONFIG_NO_TRY_CATCH_IN_ASSERTS) +#define DOCTEST_CONFIG_NO_TRY_CATCH_IN_ASSERTS +#endif // DOCTEST_CONFIG_NO_EXCEPTIONS && !DOCTEST_CONFIG_NO_TRY_CATCH_IN_ASSERTS + +#ifdef __wasi__ +#define DOCTEST_CONFIG_NO_MULTITHREADING +#endif + +#if defined(DOCTEST_CONFIG_IMPLEMENT_WITH_MAIN) && !defined(DOCTEST_CONFIG_IMPLEMENT) +#define DOCTEST_CONFIG_IMPLEMENT +#endif // DOCTEST_CONFIG_IMPLEMENT_WITH_MAIN + +#if defined(_WIN32) || defined(__CYGWIN__) +#if DOCTEST_MSVC +#define DOCTEST_SYMBOL_EXPORT __declspec(dllexport) +#define DOCTEST_SYMBOL_IMPORT __declspec(dllimport) +#else // MSVC +#define DOCTEST_SYMBOL_EXPORT __attribute__((dllexport)) +#define DOCTEST_SYMBOL_IMPORT __attribute__((dllimport)) +#endif // MSVC +#else // _WIN32 +#define DOCTEST_SYMBOL_EXPORT __attribute__((visibility("default"))) +#define DOCTEST_SYMBOL_IMPORT +#endif // _WIN32 + +#ifdef DOCTEST_CONFIG_IMPLEMENTATION_IN_DLL +#ifdef DOCTEST_CONFIG_IMPLEMENT +#define DOCTEST_INTERFACE DOCTEST_SYMBOL_EXPORT +#else // DOCTEST_CONFIG_IMPLEMENT +#define DOCTEST_INTERFACE DOCTEST_SYMBOL_IMPORT +#endif // DOCTEST_CONFIG_IMPLEMENT +#else // DOCTEST_CONFIG_IMPLEMENTATION_IN_DLL +#define DOCTEST_INTERFACE +#endif // DOCTEST_CONFIG_IMPLEMENTATION_IN_DLL + +// needed for extern template instantiations +// see https://github.com/fmtlib/fmt/issues/2228 +#if DOCTEST_MSVC +#define DOCTEST_INTERFACE_DECL +#define DOCTEST_INTERFACE_DEF DOCTEST_INTERFACE +#else // DOCTEST_MSVC +#define DOCTEST_INTERFACE_DECL DOCTEST_INTERFACE +#define DOCTEST_INTERFACE_DEF +#endif // DOCTEST_MSVC + +#define DOCTEST_EMPTY + +#if DOCTEST_MSVC +#define DOCTEST_NOINLINE __declspec(noinline) +#define DOCTEST_UNUSED +#define DOCTEST_ALIGNMENT(x) +#elif DOCTEST_CLANG && DOCTEST_CLANG < DOCTEST_COMPILER(3, 5, 0) +#define DOCTEST_NOINLINE +#define DOCTEST_UNUSED +#define DOCTEST_ALIGNMENT(x) +#else +#define DOCTEST_NOINLINE __attribute__((noinline)) +#define DOCTEST_UNUSED __attribute__((unused)) +#define DOCTEST_ALIGNMENT(x) __attribute__((aligned(x))) +#endif + +#ifdef DOCTEST_CONFIG_NO_CONTRADICTING_INLINE +#define DOCTEST_INLINE_NOINLINE inline +#else +#define DOCTEST_INLINE_NOINLINE inline DOCTEST_NOINLINE +#endif + +#ifndef DOCTEST_NORETURN +#if DOCTEST_MSVC && (DOCTEST_MSVC < DOCTEST_COMPILER(19, 0, 0)) +#define DOCTEST_NORETURN +#else // DOCTEST_MSVC +#define DOCTEST_NORETURN [[noreturn]] +#endif // DOCTEST_MSVC +#endif // DOCTEST_NORETURN + +#ifndef DOCTEST_NOEXCEPT +#if DOCTEST_MSVC && (DOCTEST_MSVC < DOCTEST_COMPILER(19, 0, 0)) +#define DOCTEST_NOEXCEPT +#else // DOCTEST_MSVC +#define DOCTEST_NOEXCEPT noexcept +#endif // DOCTEST_MSVC +#endif // DOCTEST_NOEXCEPT + +#ifndef DOCTEST_CONSTEXPR +#if DOCTEST_MSVC && (DOCTEST_MSVC < DOCTEST_COMPILER(19, 0, 0)) +#define DOCTEST_CONSTEXPR const +#define DOCTEST_CONSTEXPR_FUNC inline +#else // DOCTEST_MSVC +#define DOCTEST_CONSTEXPR constexpr +#define DOCTEST_CONSTEXPR_FUNC constexpr +#endif // DOCTEST_MSVC +#endif // DOCTEST_CONSTEXPR + +#ifndef DOCTEST_NO_SANITIZE_INTEGER +#if DOCTEST_CLANG >= DOCTEST_COMPILER(3, 7, 0) +#define DOCTEST_NO_SANITIZE_INTEGER __attribute__((no_sanitize("integer"))) +#else +#define DOCTEST_NO_SANITIZE_INTEGER +#endif +#endif // DOCTEST_NO_SANITIZE_INTEGER + +// ================================================================================================= +// == FEATURE DETECTION END ======================================================================== +// ================================================================================================= + +#define DOCTEST_DECLARE_INTERFACE(name) \ + virtual ~name(); \ + name() = default; \ + name(const name&) = delete; \ + name(name&&) = delete; \ + name& operator=(const name&) = delete; \ + name& operator=(name&&) = delete; + +#define DOCTEST_DEFINE_INTERFACE(name) \ + name::~name() = default; + +// internal macros for string concatenation and anonymous variable name generation +#define DOCTEST_CAT_IMPL(s1, s2) s1##s2 +#define DOCTEST_CAT(s1, s2) DOCTEST_CAT_IMPL(s1, s2) +#ifdef __COUNTER__ // not standard and may be missing for some compilers +#define DOCTEST_ANONYMOUS(x) DOCTEST_CAT(x, __COUNTER__) +#else // __COUNTER__ +#define DOCTEST_ANONYMOUS(x) DOCTEST_CAT(x, __LINE__) +#endif // __COUNTER__ + +#ifndef DOCTEST_CONFIG_ASSERTION_PARAMETERS_BY_VALUE +#define DOCTEST_REF_WRAP(x) x& +#else // DOCTEST_CONFIG_ASSERTION_PARAMETERS_BY_VALUE +#define DOCTEST_REF_WRAP(x) x +#endif // DOCTEST_CONFIG_ASSERTION_PARAMETERS_BY_VALUE + +// not using __APPLE__ because... this is how Catch does it +#ifdef __MAC_OS_X_VERSION_MIN_REQUIRED +#define DOCTEST_PLATFORM_MAC +#elif defined(__IPHONE_OS_VERSION_MIN_REQUIRED) +#define DOCTEST_PLATFORM_IPHONE +#elif defined(_WIN32) +#define DOCTEST_PLATFORM_WINDOWS +#elif defined(__wasi__) +#define DOCTEST_PLATFORM_WASI +#else // DOCTEST_PLATFORM +#define DOCTEST_PLATFORM_LINUX +#endif // DOCTEST_PLATFORM + +namespace doctest { namespace detail { + static DOCTEST_CONSTEXPR int consume(const int*, int) noexcept { return 0; } +}} + +#define DOCTEST_GLOBAL_NO_WARNINGS(var, ...) \ + DOCTEST_CLANG_SUPPRESS_WARNING_WITH_PUSH("-Wglobal-constructors") \ + static const int var = doctest::detail::consume(&var, __VA_ARGS__); \ + DOCTEST_CLANG_SUPPRESS_WARNING_POP + +#ifndef DOCTEST_BREAK_INTO_DEBUGGER +// should probably take a look at https://github.com/scottt/debugbreak +#ifdef DOCTEST_PLATFORM_LINUX +#if defined(__GNUC__) && (defined(__i386) || defined(__x86_64)) +// Break at the location of the failing check if possible +#define DOCTEST_BREAK_INTO_DEBUGGER() __asm__("int $3\n" : :) // NOLINT(hicpp-no-assembler) +#else +#include +#define DOCTEST_BREAK_INTO_DEBUGGER() raise(SIGTRAP) +#endif +#elif defined(DOCTEST_PLATFORM_MAC) +#if defined(__x86_64) || defined(__x86_64__) || defined(__amd64__) || defined(__i386) +#define DOCTEST_BREAK_INTO_DEBUGGER() __asm__("int $3\n" : :) // NOLINT(hicpp-no-assembler) +#elif defined(__ppc__) || defined(__ppc64__) +// https://www.cocoawithlove.com/2008/03/break-into-debugger.html +#define DOCTEST_BREAK_INTO_DEBUGGER() __asm__("li r0, 20\nsc\nnop\nli r0, 37\nli r4, 2\nsc\nnop\n": : : "memory","r0","r3","r4") // NOLINT(hicpp-no-assembler) +#else +#define DOCTEST_BREAK_INTO_DEBUGGER() __asm__("brk #0"); // NOLINT(hicpp-no-assembler) +#endif +#elif DOCTEST_MSVC +#define DOCTEST_BREAK_INTO_DEBUGGER() __debugbreak() +#elif defined(__MINGW32__) +DOCTEST_GCC_SUPPRESS_WARNING_WITH_PUSH("-Wredundant-decls") +extern "C" __declspec(dllimport) void __stdcall DebugBreak(); +DOCTEST_GCC_SUPPRESS_WARNING_POP +#define DOCTEST_BREAK_INTO_DEBUGGER() ::DebugBreak() +#else // linux +#define DOCTEST_BREAK_INTO_DEBUGGER() (static_cast(0)) +#endif // linux +#endif // DOCTEST_BREAK_INTO_DEBUGGER + +// this is kept here for backwards compatibility since the config option was changed +#ifdef DOCTEST_CONFIG_USE_IOSFWD +#ifndef DOCTEST_CONFIG_USE_STD_HEADERS +#define DOCTEST_CONFIG_USE_STD_HEADERS +#endif +#endif // DOCTEST_CONFIG_USE_IOSFWD + +// for clang - always include ciso646 (which drags some std stuff) because +// we want to check if we are using libc++ with the _LIBCPP_VERSION macro in +// which case we don't want to forward declare stuff from std - for reference: +// https://github.com/doctest/doctest/issues/126 +// https://github.com/doctest/doctest/issues/356 +#if DOCTEST_CLANG +#include +#endif // clang + +#ifdef _LIBCPP_VERSION +#ifndef DOCTEST_CONFIG_USE_STD_HEADERS +#define DOCTEST_CONFIG_USE_STD_HEADERS +#endif +#endif // _LIBCPP_VERSION + +#ifdef DOCTEST_CONFIG_USE_STD_HEADERS +#ifndef DOCTEST_CONFIG_INCLUDE_TYPE_TRAITS +#define DOCTEST_CONFIG_INCLUDE_TYPE_TRAITS +#endif // DOCTEST_CONFIG_INCLUDE_TYPE_TRAITS +DOCTEST_MAKE_STD_HEADERS_CLEAN_FROM_WARNINGS_ON_WALL_BEGIN +#include +#include +#include +DOCTEST_MAKE_STD_HEADERS_CLEAN_FROM_WARNINGS_ON_WALL_END +#else // DOCTEST_CONFIG_USE_STD_HEADERS + +// Forward declaring 'X' in namespace std is not permitted by the C++ Standard. +DOCTEST_MSVC_SUPPRESS_WARNING_WITH_PUSH(4643) + +namespace std { // NOLINT(cert-dcl58-cpp) +typedef decltype(nullptr) nullptr_t; // NOLINT(modernize-use-using) +typedef decltype(sizeof(void*)) size_t; // NOLINT(modernize-use-using) +template +struct char_traits; +template <> +struct char_traits; +template +class basic_ostream; // NOLINT(fuchsia-virtual-inheritance) +typedef basic_ostream> ostream; // NOLINT(modernize-use-using) +template +// NOLINTNEXTLINE +basic_ostream& operator<<(basic_ostream&, const char*); +template +class basic_istream; +typedef basic_istream> istream; // NOLINT(modernize-use-using) +template +class tuple; +#if DOCTEST_MSVC >= DOCTEST_COMPILER(19, 20, 0) +// see this issue on why this is needed: https://github.com/doctest/doctest/issues/183 +template +class allocator; +template +class basic_string; +using string = basic_string, allocator>; +#endif // VS 2019 +} // namespace std + +DOCTEST_MSVC_SUPPRESS_WARNING_POP + +#endif // DOCTEST_CONFIG_USE_STD_HEADERS + +#ifdef DOCTEST_CONFIG_INCLUDE_TYPE_TRAITS +#include +#endif // DOCTEST_CONFIG_INCLUDE_TYPE_TRAITS + +namespace doctest { + +using std::size_t; + +DOCTEST_INTERFACE extern bool is_running_in_test; + +#ifndef DOCTEST_CONFIG_STRING_SIZE_TYPE +#define DOCTEST_CONFIG_STRING_SIZE_TYPE unsigned +#endif + +// A 24 byte string class (can be as small as 17 for x64 and 13 for x86) that can hold strings with length +// of up to 23 chars on the stack before going on the heap - the last byte of the buffer is used for: +// - "is small" bit - the highest bit - if "0" then it is small - otherwise its "1" (128) +// - if small - capacity left before going on the heap - using the lowest 5 bits +// - if small - 2 bits are left unused - the second and third highest ones +// - if small - acts as a null terminator if strlen() is 23 (24 including the null terminator) +// and the "is small" bit remains "0" ("as well as the capacity left") so its OK +// Idea taken from this lecture about the string implementation of facebook/folly - fbstring +// https://www.youtube.com/watch?v=kPR8h4-qZdk +// TODO: +// - optimizations - like not deleting memory unnecessarily in operator= and etc. +// - resize/reserve/clear +// - replace +// - back/front +// - iterator stuff +// - find & friends +// - push_back/pop_back +// - assign/insert/erase +// - relational operators as free functions - taking const char* as one of the params +class DOCTEST_INTERFACE String +{ +public: + using size_type = DOCTEST_CONFIG_STRING_SIZE_TYPE; + +private: + static DOCTEST_CONSTEXPR size_type len = 24; //!OCLINT avoid private static members + static DOCTEST_CONSTEXPR size_type last = len - 1; //!OCLINT avoid private static members + + struct view // len should be more than sizeof(view) - because of the final byte for flags + { + char* ptr; + size_type size; + size_type capacity; + }; + + union + { + char buf[len]; // NOLINT(*-avoid-c-arrays) + view data; + }; + + char* allocate(size_type sz); + + bool isOnStack() const noexcept { return (buf[last] & 128) == 0; } + void setOnHeap() noexcept; + void setLast(size_type in = last) noexcept; + void setSize(size_type sz) noexcept; + + void copy(const String& other); + +public: + static DOCTEST_CONSTEXPR size_type npos = static_cast(-1); + + String() noexcept; + ~String(); + + // cppcheck-suppress noExplicitConstructor + String(const char* in); + String(const char* in, size_type in_size); + + String(std::istream& in, size_type in_size); + + String(const String& other); + String& operator=(const String& other); + + String& operator+=(const String& other); + + String(String&& other) noexcept; + String& operator=(String&& other) noexcept; + + char operator[](size_type i) const; + char& operator[](size_type i); + + // the only functions I'm willing to leave in the interface - available for inlining + const char* c_str() const { return const_cast(this)->c_str(); } // NOLINT + char* c_str() { + if (isOnStack()) { + return reinterpret_cast(buf); + } + return data.ptr; + } + + size_type size() const; + size_type capacity() const; + + String substr(size_type pos, size_type cnt = npos) &&; + String substr(size_type pos, size_type cnt = npos) const &; + + size_type find(char ch, size_type pos = 0) const; + size_type rfind(char ch, size_type pos = npos) const; + + int compare(const char* other, bool no_case = false) const; + int compare(const String& other, bool no_case = false) const; + +friend DOCTEST_INTERFACE std::ostream& operator<<(std::ostream& s, const String& in); +}; + +DOCTEST_INTERFACE String operator+(const String& lhs, const String& rhs); + +DOCTEST_INTERFACE bool operator==(const String& lhs, const String& rhs); +DOCTEST_INTERFACE bool operator!=(const String& lhs, const String& rhs); +DOCTEST_INTERFACE bool operator<(const String& lhs, const String& rhs); +DOCTEST_INTERFACE bool operator>(const String& lhs, const String& rhs); +DOCTEST_INTERFACE bool operator<=(const String& lhs, const String& rhs); +DOCTEST_INTERFACE bool operator>=(const String& lhs, const String& rhs); + +class DOCTEST_INTERFACE Contains { +public: + explicit Contains(const String& string); + + bool checkWith(const String& other) const; + + String string; +}; + +DOCTEST_INTERFACE String toString(const Contains& in); + +DOCTEST_INTERFACE bool operator==(const String& lhs, const Contains& rhs); +DOCTEST_INTERFACE bool operator==(const Contains& lhs, const String& rhs); +DOCTEST_INTERFACE bool operator!=(const String& lhs, const Contains& rhs); +DOCTEST_INTERFACE bool operator!=(const Contains& lhs, const String& rhs); + +namespace Color { + enum Enum + { + None = 0, + White, + Red, + Green, + Blue, + Cyan, + Yellow, + Grey, + + Bright = 0x10, + + BrightRed = Bright | Red, + BrightGreen = Bright | Green, + LightGrey = Bright | Grey, + BrightWhite = Bright | White + }; + + DOCTEST_INTERFACE std::ostream& operator<<(std::ostream& s, Color::Enum code); +} // namespace Color + +namespace assertType { + enum Enum + { + // macro traits + + is_warn = 1, + is_check = 2 * is_warn, + is_require = 2 * is_check, + + is_normal = 2 * is_require, + is_throws = 2 * is_normal, + is_throws_as = 2 * is_throws, + is_throws_with = 2 * is_throws_as, + is_nothrow = 2 * is_throws_with, + + is_false = 2 * is_nothrow, + is_unary = 2 * is_false, // not checked anywhere - used just to distinguish the types + + is_eq = 2 * is_unary, + is_ne = 2 * is_eq, + + is_lt = 2 * is_ne, + is_gt = 2 * is_lt, + + is_ge = 2 * is_gt, + is_le = 2 * is_ge, + + // macro types + + DT_WARN = is_normal | is_warn, + DT_CHECK = is_normal | is_check, + DT_REQUIRE = is_normal | is_require, + + DT_WARN_FALSE = is_normal | is_false | is_warn, + DT_CHECK_FALSE = is_normal | is_false | is_check, + DT_REQUIRE_FALSE = is_normal | is_false | is_require, + + DT_WARN_THROWS = is_throws | is_warn, + DT_CHECK_THROWS = is_throws | is_check, + DT_REQUIRE_THROWS = is_throws | is_require, + + DT_WARN_THROWS_AS = is_throws_as | is_warn, + DT_CHECK_THROWS_AS = is_throws_as | is_check, + DT_REQUIRE_THROWS_AS = is_throws_as | is_require, + + DT_WARN_THROWS_WITH = is_throws_with | is_warn, + DT_CHECK_THROWS_WITH = is_throws_with | is_check, + DT_REQUIRE_THROWS_WITH = is_throws_with | is_require, + + DT_WARN_THROWS_WITH_AS = is_throws_with | is_throws_as | is_warn, + DT_CHECK_THROWS_WITH_AS = is_throws_with | is_throws_as | is_check, + DT_REQUIRE_THROWS_WITH_AS = is_throws_with | is_throws_as | is_require, + + DT_WARN_NOTHROW = is_nothrow | is_warn, + DT_CHECK_NOTHROW = is_nothrow | is_check, + DT_REQUIRE_NOTHROW = is_nothrow | is_require, + + DT_WARN_EQ = is_normal | is_eq | is_warn, + DT_CHECK_EQ = is_normal | is_eq | is_check, + DT_REQUIRE_EQ = is_normal | is_eq | is_require, + + DT_WARN_NE = is_normal | is_ne | is_warn, + DT_CHECK_NE = is_normal | is_ne | is_check, + DT_REQUIRE_NE = is_normal | is_ne | is_require, + + DT_WARN_GT = is_normal | is_gt | is_warn, + DT_CHECK_GT = is_normal | is_gt | is_check, + DT_REQUIRE_GT = is_normal | is_gt | is_require, + + DT_WARN_LT = is_normal | is_lt | is_warn, + DT_CHECK_LT = is_normal | is_lt | is_check, + DT_REQUIRE_LT = is_normal | is_lt | is_require, + + DT_WARN_GE = is_normal | is_ge | is_warn, + DT_CHECK_GE = is_normal | is_ge | is_check, + DT_REQUIRE_GE = is_normal | is_ge | is_require, + + DT_WARN_LE = is_normal | is_le | is_warn, + DT_CHECK_LE = is_normal | is_le | is_check, + DT_REQUIRE_LE = is_normal | is_le | is_require, + + DT_WARN_UNARY = is_normal | is_unary | is_warn, + DT_CHECK_UNARY = is_normal | is_unary | is_check, + DT_REQUIRE_UNARY = is_normal | is_unary | is_require, + + DT_WARN_UNARY_FALSE = is_normal | is_false | is_unary | is_warn, + DT_CHECK_UNARY_FALSE = is_normal | is_false | is_unary | is_check, + DT_REQUIRE_UNARY_FALSE = is_normal | is_false | is_unary | is_require, + }; +} // namespace assertType + +DOCTEST_INTERFACE const char* assertString(assertType::Enum at); +DOCTEST_INTERFACE const char* failureString(assertType::Enum at); +DOCTEST_INTERFACE const char* skipPathFromFilename(const char* file); + +struct DOCTEST_INTERFACE TestCaseData +{ + String m_file; // the file in which the test was registered (using String - see #350) + unsigned m_line; // the line where the test was registered + const char* m_name; // name of the test case + const char* m_test_suite; // the test suite in which the test was added + const char* m_description; + bool m_skip; + bool m_no_breaks; + bool m_no_output; + bool m_may_fail; + bool m_should_fail; + int m_expected_failures; + double m_timeout; +}; + +struct DOCTEST_INTERFACE AssertData +{ + // common - for all asserts + const TestCaseData* m_test_case; + assertType::Enum m_at; + const char* m_file; + int m_line; + const char* m_expr; + bool m_failed; + + // exception-related - for all asserts + bool m_threw; + String m_exception; + + // for normal asserts + String m_decomp; + + // for specific exception-related asserts + bool m_threw_as; + const char* m_exception_type; + + class DOCTEST_INTERFACE StringContains { + private: + Contains content; + bool isContains; + + public: + StringContains(const String& str) : content(str), isContains(false) { } + StringContains(Contains cntn) : content(static_cast(cntn)), isContains(true) { } + + bool check(const String& str) { return isContains ? (content == str) : (content.string == str); } + + operator const String&() const { return content.string; } + + const char* c_str() const { return content.string.c_str(); } + } m_exception_string; + + AssertData(assertType::Enum at, const char* file, int line, const char* expr, + const char* exception_type, const StringContains& exception_string); +}; + +struct DOCTEST_INTERFACE MessageData +{ + String m_string; + const char* m_file; + int m_line; + assertType::Enum m_severity; +}; + +struct DOCTEST_INTERFACE SubcaseSignature +{ + String m_name; + const char* m_file; + int m_line; + + bool operator==(const SubcaseSignature& other) const; + bool operator<(const SubcaseSignature& other) const; +}; + +struct DOCTEST_INTERFACE IContextScope +{ + DOCTEST_DECLARE_INTERFACE(IContextScope) + virtual void stringify(std::ostream*) const = 0; +}; + +namespace detail { + struct DOCTEST_INTERFACE TestCase; +} // namespace detail + +struct ContextOptions //!OCLINT too many fields +{ + std::ostream* cout = nullptr; // stdout stream + String binary_name; // the test binary name + + const detail::TestCase* currentTest = nullptr; + + // == parameters from the command line + String out; // output filename + String order_by; // how tests should be ordered + unsigned rand_seed; // the seed for rand ordering + + unsigned first; // the first (matching) test to be executed + unsigned last; // the last (matching) test to be executed + + int abort_after; // stop tests after this many failed assertions + int subcase_filter_levels; // apply the subcase filters for the first N levels + + bool success; // include successful assertions in output + bool case_sensitive; // if filtering should be case sensitive + bool exit; // if the program should be exited after the tests are ran/whatever + bool duration; // print the time duration of each test case + bool minimal; // minimal console output (only test failures) + bool quiet; // no console output + bool no_throw; // to skip exceptions-related assertion macros + bool no_exitcode; // if the framework should return 0 as the exitcode + bool no_run; // to not run the tests at all (can be done with an "*" exclude) + bool no_intro; // to not print the intro of the framework + bool no_version; // to not print the version of the framework + bool no_colors; // if output to the console should be colorized + bool force_colors; // forces the use of colors even when a tty cannot be detected + bool no_breaks; // to not break into the debugger + bool no_skip; // don't skip test cases which are marked to be skipped + bool gnu_file_line; // if line numbers should be surrounded with :x: and not (x): + bool no_path_in_filenames; // if the path to files should be removed from the output + bool no_line_numbers; // if source code line numbers should be omitted from the output + bool no_debug_output; // no output in the debug console when a debugger is attached + bool no_skipped_summary; // don't print "skipped" in the summary !!! UNDOCUMENTED !!! + bool no_time_in_output; // omit any time/timestamps from output !!! UNDOCUMENTED !!! + + bool help; // to print the help + bool version; // to print the version + bool count; // if only the count of matching tests is to be retrieved + bool list_test_cases; // to list all tests matching the filters + bool list_test_suites; // to list all suites matching the filters + bool list_reporters; // lists all registered reporters +}; + +namespace detail { + namespace types { +#ifdef DOCTEST_CONFIG_INCLUDE_TYPE_TRAITS + using namespace std; +#else + template + struct enable_if { }; + + template + struct enable_if { using type = T; }; + + struct true_type { static DOCTEST_CONSTEXPR bool value = true; }; + struct false_type { static DOCTEST_CONSTEXPR bool value = false; }; + + template struct remove_reference { using type = T; }; + template struct remove_reference { using type = T; }; + template struct remove_reference { using type = T; }; + + template struct is_rvalue_reference : false_type { }; + template struct is_rvalue_reference : true_type { }; + + template struct remove_const { using type = T; }; + template struct remove_const { using type = T; }; + + // Compiler intrinsics + template struct is_enum { static DOCTEST_CONSTEXPR bool value = __is_enum(T); }; + template struct underlying_type { using type = __underlying_type(T); }; + + template struct is_pointer : false_type { }; + template struct is_pointer : true_type { }; + + template struct is_array : false_type { }; + // NOLINTNEXTLINE(*-avoid-c-arrays) + template struct is_array : true_type { }; +#endif + } + + // + template + T&& declval(); + + template + DOCTEST_CONSTEXPR_FUNC T&& forward(typename types::remove_reference::type& t) DOCTEST_NOEXCEPT { + return static_cast(t); + } + + template + DOCTEST_CONSTEXPR_FUNC T&& forward(typename types::remove_reference::type&& t) DOCTEST_NOEXCEPT { + return static_cast(t); + } + + template + struct deferred_false : types::false_type { }; + +// MSVS 2015 :( +#if !DOCTEST_CLANG && defined(_MSC_VER) && _MSC_VER <= 1900 + template + struct has_global_insertion_operator : types::false_type { }; + + template + struct has_global_insertion_operator(), declval()), void())> : types::true_type { }; + + template + struct has_insertion_operator { static DOCTEST_CONSTEXPR bool value = has_global_insertion_operator::value; }; + + template + struct insert_hack; + + template + struct insert_hack { + static void insert(std::ostream& os, const T& t) { ::operator<<(os, t); } + }; + + template + struct insert_hack { + static void insert(std::ostream& os, const T& t) { operator<<(os, t); } + }; + + template + using insert_hack_t = insert_hack::value>; +#else + template + struct has_insertion_operator : types::false_type { }; +#endif + + template + struct has_insertion_operator(), declval()), void())> : types::true_type { }; + + template + struct should_stringify_as_underlying_type { + static DOCTEST_CONSTEXPR bool value = detail::types::is_enum::value && !doctest::detail::has_insertion_operator::value; + }; + + DOCTEST_INTERFACE std::ostream* tlssPush(); + DOCTEST_INTERFACE String tlssPop(); + + template + struct StringMakerBase { + template + static String convert(const DOCTEST_REF_WRAP(T)) { +#ifdef DOCTEST_CONFIG_REQUIRE_STRINGIFICATION_FOR_ALL_USED_TYPES + static_assert(deferred_false::value, "No stringification detected for type T. See string conversion manual"); +#endif + return "{?}"; + } + }; + + template + struct filldata; + + template + void filloss(std::ostream* stream, const T& in) { + filldata::fill(stream, in); + } + + template + void filloss(std::ostream* stream, const T (&in)[N]) { // NOLINT(*-avoid-c-arrays) + // T[N], T(&)[N], T(&&)[N] have same behaviour. + // Hence remove reference. + filloss::type>(stream, in); + } + + template + String toStream(const T& in) { + std::ostream* stream = tlssPush(); + filloss(stream, in); + return tlssPop(); + } + + template <> + struct StringMakerBase { + template + static String convert(const DOCTEST_REF_WRAP(T) in) { + return toStream(in); + } + }; +} // namespace detail + +template +struct StringMaker : public detail::StringMakerBase< + detail::has_insertion_operator::value || detail::types::is_pointer::value || detail::types::is_array::value> +{}; + +#ifndef DOCTEST_STRINGIFY +#ifdef DOCTEST_CONFIG_DOUBLE_STRINGIFY +#define DOCTEST_STRINGIFY(...) toString(toString(__VA_ARGS__)) +#else +#define DOCTEST_STRINGIFY(...) toString(__VA_ARGS__) +#endif +#endif + +template +String toString() { +#if DOCTEST_CLANG == 0 && DOCTEST_GCC == 0 && DOCTEST_ICC == 0 + String ret = __FUNCSIG__; // class doctest::String __cdecl doctest::toString(void) + String::size_type beginPos = ret.find('<'); + return ret.substr(beginPos + 1, ret.size() - beginPos - static_cast(sizeof(">(void)"))); +#else + String ret = __PRETTY_FUNCTION__; // doctest::String toString() [with T = TYPE] + String::size_type begin = ret.find('=') + 2; + return ret.substr(begin, ret.size() - begin - 1); +#endif +} + +template ::value, bool>::type = true> +String toString(const DOCTEST_REF_WRAP(T) value) { + return StringMaker::convert(value); +} + +#ifdef DOCTEST_CONFIG_TREAT_CHAR_STAR_AS_STRING +DOCTEST_INTERFACE String toString(const char* in); +#endif // DOCTEST_CONFIG_TREAT_CHAR_STAR_AS_STRING + +#if DOCTEST_MSVC >= DOCTEST_COMPILER(19, 20, 0) +// see this issue on why this is needed: https://github.com/doctest/doctest/issues/183 +DOCTEST_INTERFACE String toString(const std::string& in); +#endif // VS 2019 + +DOCTEST_INTERFACE String toString(String in); + +DOCTEST_INTERFACE String toString(std::nullptr_t); + +DOCTEST_INTERFACE String toString(bool in); + +DOCTEST_INTERFACE String toString(float in); +DOCTEST_INTERFACE String toString(double in); +DOCTEST_INTERFACE String toString(double long in); + +DOCTEST_INTERFACE String toString(char in); +DOCTEST_INTERFACE String toString(char signed in); +DOCTEST_INTERFACE String toString(char unsigned in); +DOCTEST_INTERFACE String toString(short in); +DOCTEST_INTERFACE String toString(short unsigned in); +DOCTEST_INTERFACE String toString(signed in); +DOCTEST_INTERFACE String toString(unsigned in); +DOCTEST_INTERFACE String toString(long in); +DOCTEST_INTERFACE String toString(long unsigned in); +DOCTEST_INTERFACE String toString(long long in); +DOCTEST_INTERFACE String toString(long long unsigned in); + +template ::value, bool>::type = true> +String toString(const DOCTEST_REF_WRAP(T) value) { + using UT = typename detail::types::underlying_type::type; + return (DOCTEST_STRINGIFY(static_cast(value))); +} + +namespace detail { + template + struct filldata + { + static void fill(std::ostream* stream, const T& in) { +#if defined(_MSC_VER) && _MSC_VER <= 1900 + insert_hack_t::insert(*stream, in); +#else + operator<<(*stream, in); +#endif + } + }; + +DOCTEST_MSVC_SUPPRESS_WARNING_WITH_PUSH(4866) +// NOLINTBEGIN(*-avoid-c-arrays) + template + struct filldata { + static void fill(std::ostream* stream, const T(&in)[N]) { + *stream << "["; + for (size_t i = 0; i < N; i++) { + if (i != 0) { *stream << ", "; } + *stream << (DOCTEST_STRINGIFY(in[i])); + } + *stream << "]"; + } + }; +// NOLINTEND(*-avoid-c-arrays) +DOCTEST_MSVC_SUPPRESS_WARNING_POP + + // Specialized since we don't want the terminating null byte! +// NOLINTBEGIN(*-avoid-c-arrays) + template + struct filldata { + static void fill(std::ostream* stream, const char (&in)[N]) { + *stream << String(in, in[N - 1] ? N : N - 1); + } // NOLINT(clang-analyzer-cplusplus.NewDeleteLeaks) + }; +// NOLINTEND(*-avoid-c-arrays) + + template <> + struct filldata { + static void fill(std::ostream* stream, const void* in); + }; + + template + struct filldata { +DOCTEST_MSVC_SUPPRESS_WARNING_WITH_PUSH(4180) + static void fill(std::ostream* stream, const T* in) { +DOCTEST_MSVC_SUPPRESS_WARNING_POP +DOCTEST_CLANG_SUPPRESS_WARNING_WITH_PUSH("-Wmicrosoft-cast") + filldata::fill(stream, +#if DOCTEST_GCC == 0 || DOCTEST_GCC >= DOCTEST_COMPILER(4, 9, 0) + reinterpret_cast(in) +#else + *reinterpret_cast(&in) +#endif + ); +DOCTEST_CLANG_SUPPRESS_WARNING_POP + } + }; +} + +struct DOCTEST_INTERFACE Approx +{ + Approx(double value); + + Approx operator()(double value) const; + +#ifdef DOCTEST_CONFIG_INCLUDE_TYPE_TRAITS + template + explicit Approx(const T& value, + typename detail::types::enable_if::value>::type* = + static_cast(nullptr)) { + *this = static_cast(value); + } +#endif // DOCTEST_CONFIG_INCLUDE_TYPE_TRAITS + + Approx& epsilon(double newEpsilon); + +#ifdef DOCTEST_CONFIG_INCLUDE_TYPE_TRAITS + template + typename std::enable_if::value, Approx&>::type epsilon( + const T& newEpsilon) { + m_epsilon = static_cast(newEpsilon); + return *this; + } +#endif // DOCTEST_CONFIG_INCLUDE_TYPE_TRAITS + + Approx& scale(double newScale); + +#ifdef DOCTEST_CONFIG_INCLUDE_TYPE_TRAITS + template + typename std::enable_if::value, Approx&>::type scale( + const T& newScale) { + m_scale = static_cast(newScale); + return *this; + } +#endif // DOCTEST_CONFIG_INCLUDE_TYPE_TRAITS + + // clang-format off + DOCTEST_INTERFACE friend bool operator==(double lhs, const Approx & rhs); + DOCTEST_INTERFACE friend bool operator==(const Approx & lhs, double rhs); + DOCTEST_INTERFACE friend bool operator!=(double lhs, const Approx & rhs); + DOCTEST_INTERFACE friend bool operator!=(const Approx & lhs, double rhs); + DOCTEST_INTERFACE friend bool operator<=(double lhs, const Approx & rhs); + DOCTEST_INTERFACE friend bool operator<=(const Approx & lhs, double rhs); + DOCTEST_INTERFACE friend bool operator>=(double lhs, const Approx & rhs); + DOCTEST_INTERFACE friend bool operator>=(const Approx & lhs, double rhs); + DOCTEST_INTERFACE friend bool operator< (double lhs, const Approx & rhs); + DOCTEST_INTERFACE friend bool operator< (const Approx & lhs, double rhs); + DOCTEST_INTERFACE friend bool operator> (double lhs, const Approx & rhs); + DOCTEST_INTERFACE friend bool operator> (const Approx & lhs, double rhs); + +#ifdef DOCTEST_CONFIG_INCLUDE_TYPE_TRAITS +#define DOCTEST_APPROX_PREFIX \ + template friend typename std::enable_if::value, bool>::type + + DOCTEST_APPROX_PREFIX operator==(const T& lhs, const Approx& rhs) { return operator==(static_cast(lhs), rhs); } + DOCTEST_APPROX_PREFIX operator==(const Approx& lhs, const T& rhs) { return operator==(rhs, lhs); } + DOCTEST_APPROX_PREFIX operator!=(const T& lhs, const Approx& rhs) { return !operator==(lhs, rhs); } + DOCTEST_APPROX_PREFIX operator!=(const Approx& lhs, const T& rhs) { return !operator==(rhs, lhs); } + DOCTEST_APPROX_PREFIX operator<=(const T& lhs, const Approx& rhs) { return static_cast(lhs) < rhs.m_value || lhs == rhs; } + DOCTEST_APPROX_PREFIX operator<=(const Approx& lhs, const T& rhs) { return lhs.m_value < static_cast(rhs) || lhs == rhs; } + DOCTEST_APPROX_PREFIX operator>=(const T& lhs, const Approx& rhs) { return static_cast(lhs) > rhs.m_value || lhs == rhs; } + DOCTEST_APPROX_PREFIX operator>=(const Approx& lhs, const T& rhs) { return lhs.m_value > static_cast(rhs) || lhs == rhs; } + DOCTEST_APPROX_PREFIX operator< (const T& lhs, const Approx& rhs) { return static_cast(lhs) < rhs.m_value && lhs != rhs; } + DOCTEST_APPROX_PREFIX operator< (const Approx& lhs, const T& rhs) { return lhs.m_value < static_cast(rhs) && lhs != rhs; } + DOCTEST_APPROX_PREFIX operator> (const T& lhs, const Approx& rhs) { return static_cast(lhs) > rhs.m_value && lhs != rhs; } + DOCTEST_APPROX_PREFIX operator> (const Approx& lhs, const T& rhs) { return lhs.m_value > static_cast(rhs) && lhs != rhs; } +#undef DOCTEST_APPROX_PREFIX +#endif // DOCTEST_CONFIG_INCLUDE_TYPE_TRAITS + + // clang-format on + + double m_epsilon; + double m_scale; + double m_value; +}; + +DOCTEST_INTERFACE String toString(const Approx& in); + +DOCTEST_INTERFACE const ContextOptions* getContextOptions(); + +template +struct DOCTEST_INTERFACE_DECL IsNaN +{ + F value; bool flipped; + IsNaN(F f, bool flip = false) : value(f), flipped(flip) { } + IsNaN operator!() const { return { value, !flipped }; } + operator bool() const; +}; +#ifndef __MINGW32__ +extern template struct DOCTEST_INTERFACE_DECL IsNaN; +extern template struct DOCTEST_INTERFACE_DECL IsNaN; +extern template struct DOCTEST_INTERFACE_DECL IsNaN; +#endif +DOCTEST_INTERFACE String toString(IsNaN in); +DOCTEST_INTERFACE String toString(IsNaN in); +DOCTEST_INTERFACE String toString(IsNaN in); + +#ifndef DOCTEST_CONFIG_DISABLE + +namespace detail { + // clang-format off +#ifdef DOCTEST_CONFIG_TREAT_CHAR_STAR_AS_STRING + template struct decay_array { using type = T; }; + template struct decay_array { using type = T*; }; + template struct decay_array { using type = T*; }; + + template struct not_char_pointer { static DOCTEST_CONSTEXPR int value = 1; }; + template<> struct not_char_pointer { static DOCTEST_CONSTEXPR int value = 0; }; + template<> struct not_char_pointer { static DOCTEST_CONSTEXPR int value = 0; }; + + template struct can_use_op : public not_char_pointer::type> {}; +#endif // DOCTEST_CONFIG_TREAT_CHAR_STAR_AS_STRING + // clang-format on + + struct DOCTEST_INTERFACE TestFailureException + { + }; + + DOCTEST_INTERFACE bool checkIfShouldThrow(assertType::Enum at); + +#ifndef DOCTEST_CONFIG_NO_EXCEPTIONS + DOCTEST_NORETURN +#endif // DOCTEST_CONFIG_NO_EXCEPTIONS + DOCTEST_INTERFACE void throwException(); + + struct DOCTEST_INTERFACE Subcase + { + SubcaseSignature m_signature; + bool m_entered = false; + + Subcase(const String& name, const char* file, int line); + Subcase(const Subcase&) = delete; + Subcase(Subcase&&) = delete; + Subcase& operator=(const Subcase&) = delete; + Subcase& operator=(Subcase&&) = delete; + ~Subcase(); + + operator bool() const; + + private: + bool checkFilters(); + }; + + template + String stringifyBinaryExpr(const DOCTEST_REF_WRAP(L) lhs, const char* op, + const DOCTEST_REF_WRAP(R) rhs) { + return (DOCTEST_STRINGIFY(lhs)) + op + (DOCTEST_STRINGIFY(rhs)); + } + +#if DOCTEST_CLANG && DOCTEST_CLANG < DOCTEST_COMPILER(3, 6, 0) +DOCTEST_CLANG_SUPPRESS_WARNING_WITH_PUSH("-Wunused-comparison") +#endif + +// This will check if there is any way it could find a operator like member or friend and uses it. +// If not it doesn't find the operator or if the operator at global scope is defined after +// this template, the template won't be instantiated due to SFINAE. Once the template is not +// instantiated it can look for global operator using normal conversions. +#ifdef __NVCC__ +#define SFINAE_OP(ret,op) ret +#else +#define SFINAE_OP(ret,op) decltype((void)(doctest::detail::declval() op doctest::detail::declval()),ret{}) +#endif + +#define DOCTEST_DO_BINARY_EXPRESSION_COMPARISON(op, op_str, op_macro) \ + template \ + DOCTEST_NOINLINE SFINAE_OP(Result,op) operator op(R&& rhs) { \ + bool res = op_macro(doctest::detail::forward(lhs), doctest::detail::forward(rhs)); \ + if(m_at & assertType::is_false) \ + res = !res; \ + if(!res || doctest::getContextOptions()->success) \ + return Result(res, stringifyBinaryExpr(lhs, op_str, rhs)); \ + return Result(res); \ + } + + // more checks could be added - like in Catch: + // https://github.com/catchorg/Catch2/pull/1480/files + // https://github.com/catchorg/Catch2/pull/1481/files +#define DOCTEST_FORBIT_EXPRESSION(rt, op) \ + template \ + rt& operator op(const R&) { \ + static_assert(deferred_false::value, \ + "Expression Too Complex Please Rewrite As Binary Comparison!"); \ + return *this; \ + } + + struct DOCTEST_INTERFACE Result // NOLINT(*-member-init) + { + bool m_passed; + String m_decomp; + + Result() = default; // TODO: Why do we need this? (To remove NOLINT) + Result(bool passed, const String& decomposition = String()); + + // forbidding some expressions based on this table: https://en.cppreference.com/w/cpp/language/operator_precedence + DOCTEST_FORBIT_EXPRESSION(Result, &) + DOCTEST_FORBIT_EXPRESSION(Result, ^) + DOCTEST_FORBIT_EXPRESSION(Result, |) + DOCTEST_FORBIT_EXPRESSION(Result, &&) + DOCTEST_FORBIT_EXPRESSION(Result, ||) + DOCTEST_FORBIT_EXPRESSION(Result, ==) + DOCTEST_FORBIT_EXPRESSION(Result, !=) + DOCTEST_FORBIT_EXPRESSION(Result, <) + DOCTEST_FORBIT_EXPRESSION(Result, >) + DOCTEST_FORBIT_EXPRESSION(Result, <=) + DOCTEST_FORBIT_EXPRESSION(Result, >=) + DOCTEST_FORBIT_EXPRESSION(Result, =) + DOCTEST_FORBIT_EXPRESSION(Result, +=) + DOCTEST_FORBIT_EXPRESSION(Result, -=) + DOCTEST_FORBIT_EXPRESSION(Result, *=) + DOCTEST_FORBIT_EXPRESSION(Result, /=) + DOCTEST_FORBIT_EXPRESSION(Result, %=) + DOCTEST_FORBIT_EXPRESSION(Result, <<=) + DOCTEST_FORBIT_EXPRESSION(Result, >>=) + DOCTEST_FORBIT_EXPRESSION(Result, &=) + DOCTEST_FORBIT_EXPRESSION(Result, ^=) + DOCTEST_FORBIT_EXPRESSION(Result, |=) + }; + +#ifndef DOCTEST_CONFIG_NO_COMPARISON_WARNING_SUPPRESSION + + DOCTEST_CLANG_SUPPRESS_WARNING_PUSH + DOCTEST_CLANG_SUPPRESS_WARNING("-Wsign-conversion") + DOCTEST_CLANG_SUPPRESS_WARNING("-Wsign-compare") + //DOCTEST_CLANG_SUPPRESS_WARNING("-Wdouble-promotion") + //DOCTEST_CLANG_SUPPRESS_WARNING("-Wconversion") + //DOCTEST_CLANG_SUPPRESS_WARNING("-Wfloat-equal") + + DOCTEST_GCC_SUPPRESS_WARNING_PUSH + DOCTEST_GCC_SUPPRESS_WARNING("-Wsign-conversion") + DOCTEST_GCC_SUPPRESS_WARNING("-Wsign-compare") + //DOCTEST_GCC_SUPPRESS_WARNING("-Wdouble-promotion") + //DOCTEST_GCC_SUPPRESS_WARNING("-Wconversion") + //DOCTEST_GCC_SUPPRESS_WARNING("-Wfloat-equal") + + DOCTEST_MSVC_SUPPRESS_WARNING_PUSH + // https://stackoverflow.com/questions/39479163 what's the difference between 4018 and 4389 + DOCTEST_MSVC_SUPPRESS_WARNING(4388) // signed/unsigned mismatch + DOCTEST_MSVC_SUPPRESS_WARNING(4389) // 'operator' : signed/unsigned mismatch + DOCTEST_MSVC_SUPPRESS_WARNING(4018) // 'expression' : signed/unsigned mismatch + //DOCTEST_MSVC_SUPPRESS_WARNING(4805) // 'operation' : unsafe mix of type 'type' and type 'type' in operation + +#endif // DOCTEST_CONFIG_NO_COMPARISON_WARNING_SUPPRESSION + + // clang-format off +#ifndef DOCTEST_CONFIG_TREAT_CHAR_STAR_AS_STRING +#define DOCTEST_COMPARISON_RETURN_TYPE bool +#else // DOCTEST_CONFIG_TREAT_CHAR_STAR_AS_STRING +#define DOCTEST_COMPARISON_RETURN_TYPE typename types::enable_if::value || can_use_op::value, bool>::type + inline bool eq(const char* lhs, const char* rhs) { return String(lhs) == String(rhs); } + inline bool ne(const char* lhs, const char* rhs) { return String(lhs) != String(rhs); } + inline bool lt(const char* lhs, const char* rhs) { return String(lhs) < String(rhs); } + inline bool gt(const char* lhs, const char* rhs) { return String(lhs) > String(rhs); } + inline bool le(const char* lhs, const char* rhs) { return String(lhs) <= String(rhs); } + inline bool ge(const char* lhs, const char* rhs) { return String(lhs) >= String(rhs); } +#endif // DOCTEST_CONFIG_TREAT_CHAR_STAR_AS_STRING + // clang-format on + +#define DOCTEST_RELATIONAL_OP(name, op) \ + template \ + DOCTEST_COMPARISON_RETURN_TYPE name(const DOCTEST_REF_WRAP(L) lhs, \ + const DOCTEST_REF_WRAP(R) rhs) { \ + return lhs op rhs; \ + } + + DOCTEST_RELATIONAL_OP(eq, ==) + DOCTEST_RELATIONAL_OP(ne, !=) + DOCTEST_RELATIONAL_OP(lt, <) + DOCTEST_RELATIONAL_OP(gt, >) + DOCTEST_RELATIONAL_OP(le, <=) + DOCTEST_RELATIONAL_OP(ge, >=) + +#ifndef DOCTEST_CONFIG_TREAT_CHAR_STAR_AS_STRING +#define DOCTEST_CMP_EQ(l, r) l == r +#define DOCTEST_CMP_NE(l, r) l != r +#define DOCTEST_CMP_GT(l, r) l > r +#define DOCTEST_CMP_LT(l, r) l < r +#define DOCTEST_CMP_GE(l, r) l >= r +#define DOCTEST_CMP_LE(l, r) l <= r +#else // DOCTEST_CONFIG_TREAT_CHAR_STAR_AS_STRING +#define DOCTEST_CMP_EQ(l, r) eq(l, r) +#define DOCTEST_CMP_NE(l, r) ne(l, r) +#define DOCTEST_CMP_GT(l, r) gt(l, r) +#define DOCTEST_CMP_LT(l, r) lt(l, r) +#define DOCTEST_CMP_GE(l, r) ge(l, r) +#define DOCTEST_CMP_LE(l, r) le(l, r) +#endif // DOCTEST_CONFIG_TREAT_CHAR_STAR_AS_STRING + + template + // cppcheck-suppress copyCtorAndEqOperator + struct Expression_lhs + { + L lhs; + assertType::Enum m_at; + + explicit Expression_lhs(L&& in, assertType::Enum at) + : lhs(static_cast(in)) + , m_at(at) {} + + DOCTEST_NOINLINE operator Result() { +// this is needed only for MSVC 2015 +DOCTEST_MSVC_SUPPRESS_WARNING_WITH_PUSH(4800) // 'int': forcing value to bool + bool res = static_cast(lhs); +DOCTEST_MSVC_SUPPRESS_WARNING_POP + if(m_at & assertType::is_false) { //!OCLINT bitwise operator in conditional + res = !res; + } + + if(!res || getContextOptions()->success) { + return { res, (DOCTEST_STRINGIFY(lhs)) }; + } + return { res }; + } + + /* This is required for user-defined conversions from Expression_lhs to L */ + operator L() const { return lhs; } + + // clang-format off + DOCTEST_DO_BINARY_EXPRESSION_COMPARISON(==, " == ", DOCTEST_CMP_EQ) //!OCLINT bitwise operator in conditional + DOCTEST_DO_BINARY_EXPRESSION_COMPARISON(!=, " != ", DOCTEST_CMP_NE) //!OCLINT bitwise operator in conditional + DOCTEST_DO_BINARY_EXPRESSION_COMPARISON(>, " > ", DOCTEST_CMP_GT) //!OCLINT bitwise operator in conditional + DOCTEST_DO_BINARY_EXPRESSION_COMPARISON(<, " < ", DOCTEST_CMP_LT) //!OCLINT bitwise operator in conditional + DOCTEST_DO_BINARY_EXPRESSION_COMPARISON(>=, " >= ", DOCTEST_CMP_GE) //!OCLINT bitwise operator in conditional + DOCTEST_DO_BINARY_EXPRESSION_COMPARISON(<=, " <= ", DOCTEST_CMP_LE) //!OCLINT bitwise operator in conditional + // clang-format on + + // forbidding some expressions based on this table: https://en.cppreference.com/w/cpp/language/operator_precedence + DOCTEST_FORBIT_EXPRESSION(Expression_lhs, &) + DOCTEST_FORBIT_EXPRESSION(Expression_lhs, ^) + DOCTEST_FORBIT_EXPRESSION(Expression_lhs, |) + DOCTEST_FORBIT_EXPRESSION(Expression_lhs, &&) + DOCTEST_FORBIT_EXPRESSION(Expression_lhs, ||) + DOCTEST_FORBIT_EXPRESSION(Expression_lhs, =) + DOCTEST_FORBIT_EXPRESSION(Expression_lhs, +=) + DOCTEST_FORBIT_EXPRESSION(Expression_lhs, -=) + DOCTEST_FORBIT_EXPRESSION(Expression_lhs, *=) + DOCTEST_FORBIT_EXPRESSION(Expression_lhs, /=) + DOCTEST_FORBIT_EXPRESSION(Expression_lhs, %=) + DOCTEST_FORBIT_EXPRESSION(Expression_lhs, <<=) + DOCTEST_FORBIT_EXPRESSION(Expression_lhs, >>=) + DOCTEST_FORBIT_EXPRESSION(Expression_lhs, &=) + DOCTEST_FORBIT_EXPRESSION(Expression_lhs, ^=) + DOCTEST_FORBIT_EXPRESSION(Expression_lhs, |=) + // these 2 are unfortunate because they should be allowed - they have higher precedence over the comparisons, but the + // ExpressionDecomposer class uses the left shift operator to capture the left operand of the binary expression... + DOCTEST_FORBIT_EXPRESSION(Expression_lhs, <<) + DOCTEST_FORBIT_EXPRESSION(Expression_lhs, >>) + }; + +#ifndef DOCTEST_CONFIG_NO_COMPARISON_WARNING_SUPPRESSION + + DOCTEST_CLANG_SUPPRESS_WARNING_POP + DOCTEST_MSVC_SUPPRESS_WARNING_POP + DOCTEST_GCC_SUPPRESS_WARNING_POP + +#endif // DOCTEST_CONFIG_NO_COMPARISON_WARNING_SUPPRESSION + +#if DOCTEST_CLANG && DOCTEST_CLANG < DOCTEST_COMPILER(3, 6, 0) +DOCTEST_CLANG_SUPPRESS_WARNING_POP +#endif + + struct DOCTEST_INTERFACE ExpressionDecomposer + { + assertType::Enum m_at; + + ExpressionDecomposer(assertType::Enum at); + + // The right operator for capturing expressions is "<=" instead of "<<" (based on the operator precedence table) + // but then there will be warnings from GCC about "-Wparentheses" and since "_Pragma()" is problematic this will stay for now... + // https://github.com/catchorg/Catch2/issues/870 + // https://github.com/catchorg/Catch2/issues/565 + template + Expression_lhs operator<<(L&& operand) { + return Expression_lhs(static_cast(operand), m_at); + } + + template ::value,void >::type* = nullptr> + Expression_lhs operator<<(const L &operand) { + return Expression_lhs(operand, m_at); + } + }; + + struct DOCTEST_INTERFACE TestSuite + { + const char* m_test_suite = nullptr; + const char* m_description = nullptr; + bool m_skip = false; + bool m_no_breaks = false; + bool m_no_output = false; + bool m_may_fail = false; + bool m_should_fail = false; + int m_expected_failures = 0; + double m_timeout = 0; + + TestSuite& operator*(const char* in); + + template + TestSuite& operator*(const T& in) { + in.fill(*this); + return *this; + } + }; + + using funcType = void (*)(); + + struct DOCTEST_INTERFACE TestCase : public TestCaseData + { + funcType m_test; // a function pointer to the test case + + String m_type; // for templated test cases - gets appended to the real name + int m_template_id; // an ID used to distinguish between the different versions of a templated test case + String m_full_name; // contains the name (only for templated test cases!) + the template type + + TestCase(funcType test, const char* file, unsigned line, const TestSuite& test_suite, + const String& type = String(), int template_id = -1); + + TestCase(const TestCase& other); + TestCase(TestCase&&) = delete; + + DOCTEST_MSVC_SUPPRESS_WARNING_WITH_PUSH(26434) // hides a non-virtual function + TestCase& operator=(const TestCase& other); + DOCTEST_MSVC_SUPPRESS_WARNING_POP + + TestCase& operator=(TestCase&&) = delete; + + TestCase& operator*(const char* in); + + template + TestCase& operator*(const T& in) { + in.fill(*this); + return *this; + } + + bool operator<(const TestCase& other) const; + + ~TestCase() = default; + }; + + // forward declarations of functions used by the macros + DOCTEST_INTERFACE int regTest(const TestCase& tc); + DOCTEST_INTERFACE int setTestSuite(const TestSuite& ts); + DOCTEST_INTERFACE bool isDebuggerActive(); + + template + int instantiationHelper(const T&) { return 0; } + + namespace binaryAssertComparison { + enum Enum + { + eq = 0, + ne, + gt, + lt, + ge, + le + }; + } // namespace binaryAssertComparison + + // clang-format off + template struct RelationalComparator { bool operator()(const DOCTEST_REF_WRAP(L), const DOCTEST_REF_WRAP(R) ) const { return false; } }; + +#define DOCTEST_BINARY_RELATIONAL_OP(n, op) \ + template struct RelationalComparator { bool operator()(const DOCTEST_REF_WRAP(L) lhs, const DOCTEST_REF_WRAP(R) rhs) const { return op(lhs, rhs); } }; + // clang-format on + + DOCTEST_BINARY_RELATIONAL_OP(0, doctest::detail::eq) + DOCTEST_BINARY_RELATIONAL_OP(1, doctest::detail::ne) + DOCTEST_BINARY_RELATIONAL_OP(2, doctest::detail::gt) + DOCTEST_BINARY_RELATIONAL_OP(3, doctest::detail::lt) + DOCTEST_BINARY_RELATIONAL_OP(4, doctest::detail::ge) + DOCTEST_BINARY_RELATIONAL_OP(5, doctest::detail::le) + + struct DOCTEST_INTERFACE ResultBuilder : public AssertData + { + ResultBuilder(assertType::Enum at, const char* file, int line, const char* expr, + const char* exception_type = "", const String& exception_string = ""); + + ResultBuilder(assertType::Enum at, const char* file, int line, const char* expr, + const char* exception_type, const Contains& exception_string); + + void setResult(const Result& res); + + template + DOCTEST_NOINLINE bool binary_assert(const DOCTEST_REF_WRAP(L) lhs, + const DOCTEST_REF_WRAP(R) rhs) { + m_failed = !RelationalComparator()(lhs, rhs); + if (m_failed || getContextOptions()->success) { + m_decomp = stringifyBinaryExpr(lhs, ", ", rhs); + } + return !m_failed; + } + + template + DOCTEST_NOINLINE bool unary_assert(const DOCTEST_REF_WRAP(L) val) { + m_failed = !val; + + if (m_at & assertType::is_false) { //!OCLINT bitwise operator in conditional + m_failed = !m_failed; + } + + if (m_failed || getContextOptions()->success) { + m_decomp = (DOCTEST_STRINGIFY(val)); + } + + return !m_failed; + } + + void translateException(); + + bool log(); + void react() const; + }; + + namespace assertAction { + enum Enum + { + nothing = 0, + dbgbreak = 1, + shouldthrow = 2 + }; + } // namespace assertAction + + DOCTEST_INTERFACE void failed_out_of_a_testing_context(const AssertData& ad); + + DOCTEST_INTERFACE bool decomp_assert(assertType::Enum at, const char* file, int line, + const char* expr, const Result& result); + +#define DOCTEST_ASSERT_OUT_OF_TESTS(decomp) \ + do { \ + if(!is_running_in_test) { \ + if(failed) { \ + ResultBuilder rb(at, file, line, expr); \ + rb.m_failed = failed; \ + rb.m_decomp = decomp; \ + failed_out_of_a_testing_context(rb); \ + if(isDebuggerActive() && !getContextOptions()->no_breaks) \ + DOCTEST_BREAK_INTO_DEBUGGER(); \ + if(checkIfShouldThrow(at)) \ + throwException(); \ + } \ + return !failed; \ + } \ + } while(false) + +#define DOCTEST_ASSERT_IN_TESTS(decomp) \ + ResultBuilder rb(at, file, line, expr); \ + rb.m_failed = failed; \ + if(rb.m_failed || getContextOptions()->success) \ + rb.m_decomp = decomp; \ + if(rb.log()) \ + DOCTEST_BREAK_INTO_DEBUGGER(); \ + if(rb.m_failed && checkIfShouldThrow(at)) \ + throwException() + + template + DOCTEST_NOINLINE bool binary_assert(assertType::Enum at, const char* file, int line, + const char* expr, const DOCTEST_REF_WRAP(L) lhs, + const DOCTEST_REF_WRAP(R) rhs) { + bool failed = !RelationalComparator()(lhs, rhs); + + // ################################################################################### + // IF THE DEBUGGER BREAKS HERE - GO 1 LEVEL UP IN THE CALLSTACK FOR THE FAILING ASSERT + // THIS IS THE EFFECT OF HAVING 'DOCTEST_CONFIG_SUPER_FAST_ASSERTS' DEFINED + // ################################################################################### + DOCTEST_ASSERT_OUT_OF_TESTS(stringifyBinaryExpr(lhs, ", ", rhs)); + DOCTEST_ASSERT_IN_TESTS(stringifyBinaryExpr(lhs, ", ", rhs)); + return !failed; + } + + template + DOCTEST_NOINLINE bool unary_assert(assertType::Enum at, const char* file, int line, + const char* expr, const DOCTEST_REF_WRAP(L) val) { + bool failed = !val; + + if(at & assertType::is_false) //!OCLINT bitwise operator in conditional + failed = !failed; + + // ################################################################################### + // IF THE DEBUGGER BREAKS HERE - GO 1 LEVEL UP IN THE CALLSTACK FOR THE FAILING ASSERT + // THIS IS THE EFFECT OF HAVING 'DOCTEST_CONFIG_SUPER_FAST_ASSERTS' DEFINED + // ################################################################################### + DOCTEST_ASSERT_OUT_OF_TESTS((DOCTEST_STRINGIFY(val))); + DOCTEST_ASSERT_IN_TESTS((DOCTEST_STRINGIFY(val))); + return !failed; + } + + struct DOCTEST_INTERFACE IExceptionTranslator + { + DOCTEST_DECLARE_INTERFACE(IExceptionTranslator) + virtual bool translate(String&) const = 0; + }; + + template + class ExceptionTranslator : public IExceptionTranslator //!OCLINT destructor of virtual class + { + public: + explicit ExceptionTranslator(String (*translateFunction)(T)) + : m_translateFunction(translateFunction) {} + + bool translate(String& res) const override { +#ifndef DOCTEST_CONFIG_NO_EXCEPTIONS + try { + throw; // lgtm [cpp/rethrow-no-exception] + // cppcheck-suppress catchExceptionByValue + } catch(const T& ex) { + res = m_translateFunction(ex); //!OCLINT parameter reassignment + return true; + } catch(...) {} //!OCLINT - empty catch statement +#endif // DOCTEST_CONFIG_NO_EXCEPTIONS + static_cast(res); // to silence -Wunused-parameter + return false; + } + + private: + String (*m_translateFunction)(T); + }; + + DOCTEST_INTERFACE void registerExceptionTranslatorImpl(const IExceptionTranslator* et); + + // ContextScope base class used to allow implementing methods of ContextScope + // that don't depend on the template parameter in doctest.cpp. + struct DOCTEST_INTERFACE ContextScopeBase : public IContextScope { + ContextScopeBase(const ContextScopeBase&) = delete; + + ContextScopeBase& operator=(const ContextScopeBase&) = delete; + ContextScopeBase& operator=(ContextScopeBase&&) = delete; + + ~ContextScopeBase() override = default; + + protected: + ContextScopeBase(); + ContextScopeBase(ContextScopeBase&& other) noexcept; + + void destroy(); + bool need_to_destroy{true}; + }; + + template class ContextScope : public ContextScopeBase + { + L lambda_; + + public: + explicit ContextScope(const L &lambda) : lambda_(lambda) {} + explicit ContextScope(L&& lambda) : lambda_(static_cast(lambda)) { } + + ContextScope(const ContextScope&) = delete; + ContextScope(ContextScope&&) noexcept = default; + + ContextScope& operator=(const ContextScope&) = delete; + ContextScope& operator=(ContextScope&&) = delete; + + void stringify(std::ostream* s) const override { lambda_(s); } + + ~ContextScope() override { + if (need_to_destroy) { + destroy(); + } + } + }; + + struct DOCTEST_INTERFACE MessageBuilder : public MessageData + { + std::ostream* m_stream; + bool logged = false; + + MessageBuilder(const char* file, int line, assertType::Enum severity); + + MessageBuilder(const MessageBuilder&) = delete; + MessageBuilder(MessageBuilder&&) = delete; + + MessageBuilder& operator=(const MessageBuilder&) = delete; + MessageBuilder& operator=(MessageBuilder&&) = delete; + + ~MessageBuilder(); + + // the preferred way of chaining parameters for stringification +DOCTEST_MSVC_SUPPRESS_WARNING_WITH_PUSH(4866) + template + MessageBuilder& operator,(const T& in) { + *m_stream << (DOCTEST_STRINGIFY(in)); + return *this; + } +DOCTEST_MSVC_SUPPRESS_WARNING_POP + + // kept here just for backwards-compatibility - the comma operator should be preferred now + template + MessageBuilder& operator<<(const T& in) { return this->operator,(in); } + + // the `,` operator has the lowest operator precedence - if `<<` is used by the user then + // the `,` operator will be called last which is not what we want and thus the `*` operator + // is used first (has higher operator precedence compared to `<<`) so that we guarantee that + // an operator of the MessageBuilder class is called first before the rest of the parameters + template + MessageBuilder& operator*(const T& in) { return this->operator,(in); } + + bool log(); + void react(); + }; + + template + ContextScope MakeContextScope(const L &lambda) { + return ContextScope(lambda); + } +} // namespace detail + +#define DOCTEST_DEFINE_DECORATOR(name, type, def) \ + struct name \ + { \ + type data; \ + name(type in = def) \ + : data(in) {} \ + void fill(detail::TestCase& state) const { state.DOCTEST_CAT(m_, name) = data; } \ + void fill(detail::TestSuite& state) const { state.DOCTEST_CAT(m_, name) = data; } \ + } + +DOCTEST_DEFINE_DECORATOR(test_suite, const char*, ""); +DOCTEST_DEFINE_DECORATOR(description, const char*, ""); +DOCTEST_DEFINE_DECORATOR(skip, bool, true); +DOCTEST_DEFINE_DECORATOR(no_breaks, bool, true); +DOCTEST_DEFINE_DECORATOR(no_output, bool, true); +DOCTEST_DEFINE_DECORATOR(timeout, double, 0); +DOCTEST_DEFINE_DECORATOR(may_fail, bool, true); +DOCTEST_DEFINE_DECORATOR(should_fail, bool, true); +DOCTEST_DEFINE_DECORATOR(expected_failures, int, 0); + +template +int registerExceptionTranslator(String (*translateFunction)(T)) { + DOCTEST_CLANG_SUPPRESS_WARNING_WITH_PUSH("-Wexit-time-destructors") + static detail::ExceptionTranslator exceptionTranslator(translateFunction); + DOCTEST_CLANG_SUPPRESS_WARNING_POP + detail::registerExceptionTranslatorImpl(&exceptionTranslator); + return 0; +} + +} // namespace doctest + +// in a separate namespace outside of doctest because the DOCTEST_TEST_SUITE macro +// introduces an anonymous namespace in which getCurrentTestSuite gets overridden +namespace doctest_detail_test_suite_ns { +DOCTEST_INTERFACE doctest::detail::TestSuite& getCurrentTestSuite(); +} // namespace doctest_detail_test_suite_ns + +namespace doctest { +#else // DOCTEST_CONFIG_DISABLE +template +int registerExceptionTranslator(String (*)(T)) { + return 0; +} +#endif // DOCTEST_CONFIG_DISABLE + +namespace detail { + using assert_handler = void (*)(const AssertData&); + struct ContextState; +} // namespace detail + +class DOCTEST_INTERFACE Context +{ + detail::ContextState* p; + + void parseArgs(int argc, const char* const* argv, bool withDefaults = false); + +public: + explicit Context(int argc = 0, const char* const* argv = nullptr); + + Context(const Context&) = delete; + Context(Context&&) = delete; + + Context& operator=(const Context&) = delete; + Context& operator=(Context&&) = delete; + + ~Context(); // NOLINT(performance-trivially-destructible) + + void applyCommandLine(int argc, const char* const* argv); + + void addFilter(const char* filter, const char* value); + void clearFilters(); + void setOption(const char* option, bool value); + void setOption(const char* option, int value); + void setOption(const char* option, const char* value); + + bool shouldExit(); + + void setAsDefaultForAssertsOutOfTestCases(); + + void setAssertHandler(detail::assert_handler ah); + + void setCout(std::ostream* out); + + int run(); +}; + +namespace TestCaseFailureReason { + enum Enum + { + None = 0, + AssertFailure = 1, // an assertion has failed in the test case + Exception = 2, // test case threw an exception + Crash = 4, // a crash... + TooManyFailedAsserts = 8, // the abort-after option + Timeout = 16, // see the timeout decorator + ShouldHaveFailedButDidnt = 32, // see the should_fail decorator + ShouldHaveFailedAndDid = 64, // see the should_fail decorator + DidntFailExactlyNumTimes = 128, // see the expected_failures decorator + FailedExactlyNumTimes = 256, // see the expected_failures decorator + CouldHaveFailedAndDid = 512 // see the may_fail decorator + }; +} // namespace TestCaseFailureReason + +struct DOCTEST_INTERFACE CurrentTestCaseStats +{ + int numAssertsCurrentTest; + int numAssertsFailedCurrentTest; + double seconds; + int failure_flags; // use TestCaseFailureReason::Enum + bool testCaseSuccess; +}; + +struct DOCTEST_INTERFACE TestCaseException +{ + String error_string; + bool is_crash; +}; + +struct DOCTEST_INTERFACE TestRunStats +{ + unsigned numTestCases; + unsigned numTestCasesPassingFilters; + unsigned numTestSuitesPassingFilters; + unsigned numTestCasesFailed; + int numAsserts; + int numAssertsFailed; +}; + +struct QueryData +{ + const TestRunStats* run_stats = nullptr; + const TestCaseData** data = nullptr; + unsigned num_data = 0; +}; + +struct DOCTEST_INTERFACE IReporter +{ + // The constructor has to accept "const ContextOptions&" as a single argument + // which has most of the options for the run + a pointer to the stdout stream + // Reporter(const ContextOptions& in) + + // called when a query should be reported (listing test cases, printing the version, etc.) + virtual void report_query(const QueryData&) = 0; + + // called when the whole test run starts + virtual void test_run_start() = 0; + // called when the whole test run ends (caching a pointer to the input doesn't make sense here) + virtual void test_run_end(const TestRunStats&) = 0; + + // called when a test case is started (safe to cache a pointer to the input) + virtual void test_case_start(const TestCaseData&) = 0; + // called when a test case is reentered because of unfinished subcases (safe to cache a pointer to the input) + virtual void test_case_reenter(const TestCaseData&) = 0; + // called when a test case has ended + virtual void test_case_end(const CurrentTestCaseStats&) = 0; + + // called when an exception is thrown from the test case (or it crashes) + virtual void test_case_exception(const TestCaseException&) = 0; + + // called whenever a subcase is entered (don't cache pointers to the input) + virtual void subcase_start(const SubcaseSignature&) = 0; + // called whenever a subcase is exited (don't cache pointers to the input) + virtual void subcase_end() = 0; + + // called for each assert (don't cache pointers to the input) + virtual void log_assert(const AssertData&) = 0; + // called for each message (don't cache pointers to the input) + virtual void log_message(const MessageData&) = 0; + + // called when a test case is skipped either because it doesn't pass the filters, has a skip decorator + // or isn't in the execution range (between first and last) (safe to cache a pointer to the input) + virtual void test_case_skipped(const TestCaseData&) = 0; + + DOCTEST_DECLARE_INTERFACE(IReporter) + + // can obtain all currently active contexts and stringify them if one wishes to do so + static int get_num_active_contexts(); + static const IContextScope* const* get_active_contexts(); + + // can iterate through contexts which have been stringified automatically in their destructors when an exception has been thrown + static int get_num_stringified_contexts(); + static const String* get_stringified_contexts(); +}; + +namespace detail { + using reporterCreatorFunc = IReporter* (*)(const ContextOptions&); + + DOCTEST_INTERFACE void registerReporterImpl(const char* name, int prio, reporterCreatorFunc c, bool isReporter); + + template + IReporter* reporterCreator(const ContextOptions& o) { + return new Reporter(o); + } +} // namespace detail + +template +int registerReporter(const char* name, int priority, bool isReporter) { + detail::registerReporterImpl(name, priority, detail::reporterCreator, isReporter); + return 0; +} +} // namespace doctest + +#ifdef DOCTEST_CONFIG_ASSERTS_RETURN_VALUES +#define DOCTEST_FUNC_EMPTY [] { return false; }() +#else +#define DOCTEST_FUNC_EMPTY (void)0 +#endif + +// if registering is not disabled +#ifndef DOCTEST_CONFIG_DISABLE + +#ifdef DOCTEST_CONFIG_ASSERTS_RETURN_VALUES +#define DOCTEST_FUNC_SCOPE_BEGIN [&] +#define DOCTEST_FUNC_SCOPE_END () +#define DOCTEST_FUNC_SCOPE_RET(v) return v +#else +#define DOCTEST_FUNC_SCOPE_BEGIN do +#define DOCTEST_FUNC_SCOPE_END while(false) +#define DOCTEST_FUNC_SCOPE_RET(v) (void)0 +#endif + +// common code in asserts - for convenience +#define DOCTEST_ASSERT_LOG_REACT_RETURN(b) \ + if(b.log()) DOCTEST_BREAK_INTO_DEBUGGER(); \ + b.react(); \ + DOCTEST_FUNC_SCOPE_RET(!b.m_failed) + +#ifdef DOCTEST_CONFIG_NO_TRY_CATCH_IN_ASSERTS +#define DOCTEST_WRAP_IN_TRY(x) x; +#else // DOCTEST_CONFIG_NO_TRY_CATCH_IN_ASSERTS +#define DOCTEST_WRAP_IN_TRY(x) \ + try { \ + x; \ + } catch(...) { DOCTEST_RB.translateException(); } +#endif // DOCTEST_CONFIG_NO_TRY_CATCH_IN_ASSERTS + +#ifdef DOCTEST_CONFIG_VOID_CAST_EXPRESSIONS +#define DOCTEST_CAST_TO_VOID(...) \ + DOCTEST_GCC_SUPPRESS_WARNING_WITH_PUSH("-Wuseless-cast") \ + static_cast(__VA_ARGS__); \ + DOCTEST_GCC_SUPPRESS_WARNING_POP +#else // DOCTEST_CONFIG_VOID_CAST_EXPRESSIONS +#define DOCTEST_CAST_TO_VOID(...) __VA_ARGS__; +#endif // DOCTEST_CONFIG_VOID_CAST_EXPRESSIONS + +// registers the test by initializing a dummy var with a function +#define DOCTEST_REGISTER_FUNCTION(global_prefix, f, decorators) \ + global_prefix DOCTEST_GLOBAL_NO_WARNINGS(DOCTEST_ANONYMOUS(DOCTEST_ANON_VAR_), /* NOLINT */ \ + doctest::detail::regTest( \ + doctest::detail::TestCase( \ + f, __FILE__, __LINE__, \ + doctest_detail_test_suite_ns::getCurrentTestSuite()) * \ + decorators)) + +#define DOCTEST_IMPLEMENT_FIXTURE(der, base, func, decorators) \ + namespace { /* NOLINT */ \ + struct der : public base \ + { \ + void f(); \ + }; \ + static DOCTEST_INLINE_NOINLINE void func() { \ + der v; \ + v.f(); \ + } \ + DOCTEST_REGISTER_FUNCTION(DOCTEST_EMPTY, func, decorators) \ + } \ + DOCTEST_INLINE_NOINLINE void der::f() // NOLINT(misc-definitions-in-headers) + +#define DOCTEST_CREATE_AND_REGISTER_FUNCTION(f, decorators) \ + static void f(); \ + DOCTEST_REGISTER_FUNCTION(DOCTEST_EMPTY, f, decorators) \ + static void f() + +#define DOCTEST_CREATE_AND_REGISTER_FUNCTION_IN_CLASS(f, proxy, decorators) \ + static doctest::detail::funcType proxy() { return f; } \ + DOCTEST_REGISTER_FUNCTION(inline, proxy(), decorators) \ + static void f() + +// for registering tests +#define DOCTEST_TEST_CASE(decorators) \ + DOCTEST_CREATE_AND_REGISTER_FUNCTION(DOCTEST_ANONYMOUS(DOCTEST_ANON_FUNC_), decorators) + +// for registering tests in classes - requires C++17 for inline variables! +#if DOCTEST_CPLUSPLUS >= 201703L +#define DOCTEST_TEST_CASE_CLASS(decorators) \ + DOCTEST_CREATE_AND_REGISTER_FUNCTION_IN_CLASS(DOCTEST_ANONYMOUS(DOCTEST_ANON_FUNC_), \ + DOCTEST_ANONYMOUS(DOCTEST_ANON_PROXY_), \ + decorators) +#else // DOCTEST_TEST_CASE_CLASS +#define DOCTEST_TEST_CASE_CLASS(...) \ + TEST_CASES_CAN_BE_REGISTERED_IN_CLASSES_ONLY_IN_CPP17_MODE_OR_WITH_VS_2017_OR_NEWER +#endif // DOCTEST_TEST_CASE_CLASS + +// for registering tests with a fixture +#define DOCTEST_TEST_CASE_FIXTURE(c, decorators) \ + DOCTEST_IMPLEMENT_FIXTURE(DOCTEST_ANONYMOUS(DOCTEST_ANON_CLASS_), c, \ + DOCTEST_ANONYMOUS(DOCTEST_ANON_FUNC_), decorators) + +// for converting types to strings without the header and demangling +#define DOCTEST_TYPE_TO_STRING_AS(str, ...) \ + namespace doctest { \ + template <> \ + inline String toString<__VA_ARGS__>() { \ + return str; \ + } \ + } \ + static_assert(true, "") + +#define DOCTEST_TYPE_TO_STRING(...) DOCTEST_TYPE_TO_STRING_AS(#__VA_ARGS__, __VA_ARGS__) + +#define DOCTEST_TEST_CASE_TEMPLATE_DEFINE_IMPL(dec, T, iter, func) \ + template \ + static void func(); \ + namespace { /* NOLINT */ \ + template \ + struct iter; \ + template \ + struct iter> \ + { \ + iter(const char* file, unsigned line, int index) { \ + doctest::detail::regTest(doctest::detail::TestCase(func, file, line, \ + doctest_detail_test_suite_ns::getCurrentTestSuite(), \ + doctest::toString(), \ + int(line) * 1000 + index) \ + * dec); \ + iter>(file, line, index + 1); \ + } \ + }; \ + template <> \ + struct iter> \ + { \ + iter(const char*, unsigned, int) {} \ + }; \ + } \ + template \ + static void func() + +#define DOCTEST_TEST_CASE_TEMPLATE_DEFINE(dec, T, id) \ + DOCTEST_TEST_CASE_TEMPLATE_DEFINE_IMPL(dec, T, DOCTEST_CAT(id, ITERATOR), \ + DOCTEST_ANONYMOUS(DOCTEST_ANON_TMP_)) + +#define DOCTEST_TEST_CASE_TEMPLATE_INSTANTIATE_IMPL(id, anon, ...) \ + DOCTEST_GLOBAL_NO_WARNINGS(DOCTEST_CAT(anon, DUMMY), /* NOLINT(cert-err58-cpp, fuchsia-statically-constructed-objects) */ \ + doctest::detail::instantiationHelper( \ + DOCTEST_CAT(id, ITERATOR)<__VA_ARGS__>(__FILE__, __LINE__, 0))) + +#define DOCTEST_TEST_CASE_TEMPLATE_INVOKE(id, ...) \ + DOCTEST_TEST_CASE_TEMPLATE_INSTANTIATE_IMPL(id, DOCTEST_ANONYMOUS(DOCTEST_ANON_TMP_), std::tuple<__VA_ARGS__>) \ + static_assert(true, "") + +#define DOCTEST_TEST_CASE_TEMPLATE_APPLY(id, ...) \ + DOCTEST_TEST_CASE_TEMPLATE_INSTANTIATE_IMPL(id, DOCTEST_ANONYMOUS(DOCTEST_ANON_TMP_), __VA_ARGS__) \ + static_assert(true, "") + +#define DOCTEST_TEST_CASE_TEMPLATE_IMPL(dec, T, anon, ...) \ + DOCTEST_TEST_CASE_TEMPLATE_DEFINE_IMPL(dec, T, DOCTEST_CAT(anon, ITERATOR), anon); \ + DOCTEST_TEST_CASE_TEMPLATE_INSTANTIATE_IMPL(anon, anon, std::tuple<__VA_ARGS__>) \ + template \ + static void anon() + +#define DOCTEST_TEST_CASE_TEMPLATE(dec, T, ...) \ + DOCTEST_TEST_CASE_TEMPLATE_IMPL(dec, T, DOCTEST_ANONYMOUS(DOCTEST_ANON_TMP_), __VA_ARGS__) + +// for subcases +#define DOCTEST_SUBCASE(name) \ + if(const doctest::detail::Subcase & DOCTEST_ANONYMOUS(DOCTEST_ANON_SUBCASE_) DOCTEST_UNUSED = \ + doctest::detail::Subcase(name, __FILE__, __LINE__)) + +// for grouping tests in test suites by using code blocks +#define DOCTEST_TEST_SUITE_IMPL(decorators, ns_name) \ + namespace ns_name { namespace doctest_detail_test_suite_ns { \ + static DOCTEST_NOINLINE doctest::detail::TestSuite& getCurrentTestSuite() noexcept { \ + DOCTEST_MSVC_SUPPRESS_WARNING_WITH_PUSH(4640) \ + DOCTEST_CLANG_SUPPRESS_WARNING_WITH_PUSH("-Wexit-time-destructors") \ + DOCTEST_GCC_SUPPRESS_WARNING_WITH_PUSH("-Wmissing-field-initializers") \ + static doctest::detail::TestSuite data{}; \ + static bool inited = false; \ + DOCTEST_MSVC_SUPPRESS_WARNING_POP \ + DOCTEST_CLANG_SUPPRESS_WARNING_POP \ + DOCTEST_GCC_SUPPRESS_WARNING_POP \ + if(!inited) { \ + data* decorators; \ + inited = true; \ + } \ + return data; \ + } \ + } \ + } \ + namespace ns_name + +#define DOCTEST_TEST_SUITE(decorators) \ + DOCTEST_TEST_SUITE_IMPL(decorators, DOCTEST_ANONYMOUS(DOCTEST_ANON_SUITE_)) + +// for starting a testsuite block +#define DOCTEST_TEST_SUITE_BEGIN(decorators) \ + DOCTEST_GLOBAL_NO_WARNINGS(DOCTEST_ANONYMOUS(DOCTEST_ANON_VAR_), /* NOLINT(cert-err58-cpp) */ \ + doctest::detail::setTestSuite(doctest::detail::TestSuite() * decorators)) \ + static_assert(true, "") + +// for ending a testsuite block +#define DOCTEST_TEST_SUITE_END \ + DOCTEST_GLOBAL_NO_WARNINGS(DOCTEST_ANONYMOUS(DOCTEST_ANON_VAR_), /* NOLINT(cert-err58-cpp) */ \ + doctest::detail::setTestSuite(doctest::detail::TestSuite() * "")) \ + using DOCTEST_ANONYMOUS(DOCTEST_ANON_FOR_SEMICOLON_) = int + +// for registering exception translators +#define DOCTEST_REGISTER_EXCEPTION_TRANSLATOR_IMPL(translatorName, signature) \ + inline doctest::String translatorName(signature); \ + DOCTEST_GLOBAL_NO_WARNINGS(DOCTEST_ANONYMOUS(DOCTEST_ANON_TRANSLATOR_), /* NOLINT(cert-err58-cpp) */ \ + doctest::registerExceptionTranslator(translatorName)) \ + doctest::String translatorName(signature) + +#define DOCTEST_REGISTER_EXCEPTION_TRANSLATOR(signature) \ + DOCTEST_REGISTER_EXCEPTION_TRANSLATOR_IMPL(DOCTEST_ANONYMOUS(DOCTEST_ANON_TRANSLATOR_), \ + signature) + +// for registering reporters +#define DOCTEST_REGISTER_REPORTER(name, priority, reporter) \ + DOCTEST_GLOBAL_NO_WARNINGS(DOCTEST_ANONYMOUS(DOCTEST_ANON_REPORTER_), /* NOLINT(cert-err58-cpp) */ \ + doctest::registerReporter(name, priority, true)) \ + static_assert(true, "") + +// for registering listeners +#define DOCTEST_REGISTER_LISTENER(name, priority, reporter) \ + DOCTEST_GLOBAL_NO_WARNINGS(DOCTEST_ANONYMOUS(DOCTEST_ANON_REPORTER_), /* NOLINT(cert-err58-cpp) */ \ + doctest::registerReporter(name, priority, false)) \ + static_assert(true, "") + +// clang-format off +// for logging - disabling formatting because it's important to have these on 2 separate lines - see PR #557 +#define DOCTEST_INFO(...) \ + DOCTEST_INFO_IMPL(DOCTEST_ANONYMOUS(DOCTEST_CAPTURE_), \ + DOCTEST_ANONYMOUS(DOCTEST_CAPTURE_OTHER_), \ + __VA_ARGS__) +// clang-format on + +#define DOCTEST_INFO_IMPL(mb_name, s_name, ...) \ + auto DOCTEST_ANONYMOUS(DOCTEST_CAPTURE_) = doctest::detail::MakeContextScope( \ + [&](std::ostream* s_name) { \ + doctest::detail::MessageBuilder mb_name(__FILE__, __LINE__, doctest::assertType::is_warn); \ + mb_name.m_stream = s_name; \ + mb_name * __VA_ARGS__; \ + }) + +#define DOCTEST_CAPTURE(x) DOCTEST_INFO(#x " := ", x) + +#define DOCTEST_ADD_AT_IMPL(type, file, line, mb, ...) \ + DOCTEST_FUNC_SCOPE_BEGIN { \ + doctest::detail::MessageBuilder mb(file, line, doctest::assertType::type); \ + mb * __VA_ARGS__; \ + if(mb.log()) \ + DOCTEST_BREAK_INTO_DEBUGGER(); \ + mb.react(); \ + } DOCTEST_FUNC_SCOPE_END + +// clang-format off +#define DOCTEST_ADD_MESSAGE_AT(file, line, ...) DOCTEST_ADD_AT_IMPL(is_warn, file, line, DOCTEST_ANONYMOUS(DOCTEST_MESSAGE_), __VA_ARGS__) +#define DOCTEST_ADD_FAIL_CHECK_AT(file, line, ...) DOCTEST_ADD_AT_IMPL(is_check, file, line, DOCTEST_ANONYMOUS(DOCTEST_MESSAGE_), __VA_ARGS__) +#define DOCTEST_ADD_FAIL_AT(file, line, ...) DOCTEST_ADD_AT_IMPL(is_require, file, line, DOCTEST_ANONYMOUS(DOCTEST_MESSAGE_), __VA_ARGS__) +// clang-format on + +#define DOCTEST_MESSAGE(...) DOCTEST_ADD_MESSAGE_AT(__FILE__, __LINE__, __VA_ARGS__) +#define DOCTEST_FAIL_CHECK(...) DOCTEST_ADD_FAIL_CHECK_AT(__FILE__, __LINE__, __VA_ARGS__) +#define DOCTEST_FAIL(...) DOCTEST_ADD_FAIL_AT(__FILE__, __LINE__, __VA_ARGS__) + +#define DOCTEST_TO_LVALUE(...) __VA_ARGS__ // Not removed to keep backwards compatibility. + +#ifndef DOCTEST_CONFIG_SUPER_FAST_ASSERTS + +#define DOCTEST_ASSERT_IMPLEMENT_2(assert_type, ...) \ + DOCTEST_CLANG_SUPPRESS_WARNING_WITH_PUSH("-Woverloaded-shift-op-parentheses") \ + /* NOLINTNEXTLINE(clang-analyzer-cplusplus.NewDeleteLeaks) */ \ + doctest::detail::ResultBuilder DOCTEST_RB(doctest::assertType::assert_type, __FILE__, \ + __LINE__, #__VA_ARGS__); \ + DOCTEST_WRAP_IN_TRY(DOCTEST_RB.setResult( \ + doctest::detail::ExpressionDecomposer(doctest::assertType::assert_type) \ + << __VA_ARGS__)) /* NOLINTNEXTLINE(clang-analyzer-cplusplus.NewDeleteLeaks) */ \ + DOCTEST_ASSERT_LOG_REACT_RETURN(DOCTEST_RB) \ + DOCTEST_CLANG_SUPPRESS_WARNING_POP + +#define DOCTEST_ASSERT_IMPLEMENT_1(assert_type, ...) \ + DOCTEST_FUNC_SCOPE_BEGIN { \ + DOCTEST_ASSERT_IMPLEMENT_2(assert_type, __VA_ARGS__); \ + } DOCTEST_FUNC_SCOPE_END // NOLINT(clang-analyzer-cplusplus.NewDeleteLeaks) + +#define DOCTEST_BINARY_ASSERT(assert_type, comp, ...) \ + DOCTEST_FUNC_SCOPE_BEGIN { \ + doctest::detail::ResultBuilder DOCTEST_RB(doctest::assertType::assert_type, __FILE__, \ + __LINE__, #__VA_ARGS__); \ + DOCTEST_WRAP_IN_TRY( \ + DOCTEST_RB.binary_assert( \ + __VA_ARGS__)) \ + DOCTEST_ASSERT_LOG_REACT_RETURN(DOCTEST_RB); \ + } DOCTEST_FUNC_SCOPE_END + +#define DOCTEST_UNARY_ASSERT(assert_type, ...) \ + DOCTEST_FUNC_SCOPE_BEGIN { \ + doctest::detail::ResultBuilder DOCTEST_RB(doctest::assertType::assert_type, __FILE__, \ + __LINE__, #__VA_ARGS__); \ + DOCTEST_WRAP_IN_TRY(DOCTEST_RB.unary_assert(__VA_ARGS__)) \ + DOCTEST_ASSERT_LOG_REACT_RETURN(DOCTEST_RB); \ + } DOCTEST_FUNC_SCOPE_END + +#else // DOCTEST_CONFIG_SUPER_FAST_ASSERTS + +// necessary for _MESSAGE +#define DOCTEST_ASSERT_IMPLEMENT_2 DOCTEST_ASSERT_IMPLEMENT_1 + +#define DOCTEST_ASSERT_IMPLEMENT_1(assert_type, ...) \ + DOCTEST_CLANG_SUPPRESS_WARNING_WITH_PUSH("-Woverloaded-shift-op-parentheses") \ + doctest::detail::decomp_assert( \ + doctest::assertType::assert_type, __FILE__, __LINE__, #__VA_ARGS__, \ + doctest::detail::ExpressionDecomposer(doctest::assertType::assert_type) \ + << __VA_ARGS__) DOCTEST_CLANG_SUPPRESS_WARNING_POP + +#define DOCTEST_BINARY_ASSERT(assert_type, comparison, ...) \ + doctest::detail::binary_assert( \ + doctest::assertType::assert_type, __FILE__, __LINE__, #__VA_ARGS__, __VA_ARGS__) + +#define DOCTEST_UNARY_ASSERT(assert_type, ...) \ + doctest::detail::unary_assert(doctest::assertType::assert_type, __FILE__, __LINE__, \ + #__VA_ARGS__, __VA_ARGS__) + +#endif // DOCTEST_CONFIG_SUPER_FAST_ASSERTS + +#define DOCTEST_WARN(...) DOCTEST_ASSERT_IMPLEMENT_1(DT_WARN, __VA_ARGS__) +#define DOCTEST_CHECK(...) DOCTEST_ASSERT_IMPLEMENT_1(DT_CHECK, __VA_ARGS__) +#define DOCTEST_REQUIRE(...) DOCTEST_ASSERT_IMPLEMENT_1(DT_REQUIRE, __VA_ARGS__) +#define DOCTEST_WARN_FALSE(...) DOCTEST_ASSERT_IMPLEMENT_1(DT_WARN_FALSE, __VA_ARGS__) +#define DOCTEST_CHECK_FALSE(...) DOCTEST_ASSERT_IMPLEMENT_1(DT_CHECK_FALSE, __VA_ARGS__) +#define DOCTEST_REQUIRE_FALSE(...) DOCTEST_ASSERT_IMPLEMENT_1(DT_REQUIRE_FALSE, __VA_ARGS__) + +// clang-format off +#define DOCTEST_WARN_MESSAGE(cond, ...) DOCTEST_FUNC_SCOPE_BEGIN { DOCTEST_INFO(__VA_ARGS__); DOCTEST_ASSERT_IMPLEMENT_2(DT_WARN, cond); } DOCTEST_FUNC_SCOPE_END +#define DOCTEST_CHECK_MESSAGE(cond, ...) DOCTEST_FUNC_SCOPE_BEGIN { DOCTEST_INFO(__VA_ARGS__); DOCTEST_ASSERT_IMPLEMENT_2(DT_CHECK, cond); } DOCTEST_FUNC_SCOPE_END +#define DOCTEST_REQUIRE_MESSAGE(cond, ...) DOCTEST_FUNC_SCOPE_BEGIN { DOCTEST_INFO(__VA_ARGS__); DOCTEST_ASSERT_IMPLEMENT_2(DT_REQUIRE, cond); } DOCTEST_FUNC_SCOPE_END +#define DOCTEST_WARN_FALSE_MESSAGE(cond, ...) DOCTEST_FUNC_SCOPE_BEGIN { DOCTEST_INFO(__VA_ARGS__); DOCTEST_ASSERT_IMPLEMENT_2(DT_WARN_FALSE, cond); } DOCTEST_FUNC_SCOPE_END +#define DOCTEST_CHECK_FALSE_MESSAGE(cond, ...) DOCTEST_FUNC_SCOPE_BEGIN { DOCTEST_INFO(__VA_ARGS__); DOCTEST_ASSERT_IMPLEMENT_2(DT_CHECK_FALSE, cond); } DOCTEST_FUNC_SCOPE_END +#define DOCTEST_REQUIRE_FALSE_MESSAGE(cond, ...) DOCTEST_FUNC_SCOPE_BEGIN { DOCTEST_INFO(__VA_ARGS__); DOCTEST_ASSERT_IMPLEMENT_2(DT_REQUIRE_FALSE, cond); } DOCTEST_FUNC_SCOPE_END +// clang-format on + +#define DOCTEST_WARN_EQ(...) DOCTEST_BINARY_ASSERT(DT_WARN_EQ, eq, __VA_ARGS__) +#define DOCTEST_CHECK_EQ(...) DOCTEST_BINARY_ASSERT(DT_CHECK_EQ, eq, __VA_ARGS__) +#define DOCTEST_REQUIRE_EQ(...) DOCTEST_BINARY_ASSERT(DT_REQUIRE_EQ, eq, __VA_ARGS__) +#define DOCTEST_WARN_NE(...) DOCTEST_BINARY_ASSERT(DT_WARN_NE, ne, __VA_ARGS__) +#define DOCTEST_CHECK_NE(...) DOCTEST_BINARY_ASSERT(DT_CHECK_NE, ne, __VA_ARGS__) +#define DOCTEST_REQUIRE_NE(...) DOCTEST_BINARY_ASSERT(DT_REQUIRE_NE, ne, __VA_ARGS__) +#define DOCTEST_WARN_GT(...) DOCTEST_BINARY_ASSERT(DT_WARN_GT, gt, __VA_ARGS__) +#define DOCTEST_CHECK_GT(...) DOCTEST_BINARY_ASSERT(DT_CHECK_GT, gt, __VA_ARGS__) +#define DOCTEST_REQUIRE_GT(...) DOCTEST_BINARY_ASSERT(DT_REQUIRE_GT, gt, __VA_ARGS__) +#define DOCTEST_WARN_LT(...) DOCTEST_BINARY_ASSERT(DT_WARN_LT, lt, __VA_ARGS__) +#define DOCTEST_CHECK_LT(...) DOCTEST_BINARY_ASSERT(DT_CHECK_LT, lt, __VA_ARGS__) +#define DOCTEST_REQUIRE_LT(...) DOCTEST_BINARY_ASSERT(DT_REQUIRE_LT, lt, __VA_ARGS__) +#define DOCTEST_WARN_GE(...) DOCTEST_BINARY_ASSERT(DT_WARN_GE, ge, __VA_ARGS__) +#define DOCTEST_CHECK_GE(...) DOCTEST_BINARY_ASSERT(DT_CHECK_GE, ge, __VA_ARGS__) +#define DOCTEST_REQUIRE_GE(...) DOCTEST_BINARY_ASSERT(DT_REQUIRE_GE, ge, __VA_ARGS__) +#define DOCTEST_WARN_LE(...) DOCTEST_BINARY_ASSERT(DT_WARN_LE, le, __VA_ARGS__) +#define DOCTEST_CHECK_LE(...) DOCTEST_BINARY_ASSERT(DT_CHECK_LE, le, __VA_ARGS__) +#define DOCTEST_REQUIRE_LE(...) DOCTEST_BINARY_ASSERT(DT_REQUIRE_LE, le, __VA_ARGS__) + +#define DOCTEST_WARN_UNARY(...) DOCTEST_UNARY_ASSERT(DT_WARN_UNARY, __VA_ARGS__) +#define DOCTEST_CHECK_UNARY(...) DOCTEST_UNARY_ASSERT(DT_CHECK_UNARY, __VA_ARGS__) +#define DOCTEST_REQUIRE_UNARY(...) DOCTEST_UNARY_ASSERT(DT_REQUIRE_UNARY, __VA_ARGS__) +#define DOCTEST_WARN_UNARY_FALSE(...) DOCTEST_UNARY_ASSERT(DT_WARN_UNARY_FALSE, __VA_ARGS__) +#define DOCTEST_CHECK_UNARY_FALSE(...) DOCTEST_UNARY_ASSERT(DT_CHECK_UNARY_FALSE, __VA_ARGS__) +#define DOCTEST_REQUIRE_UNARY_FALSE(...) DOCTEST_UNARY_ASSERT(DT_REQUIRE_UNARY_FALSE, __VA_ARGS__) + +#ifndef DOCTEST_CONFIG_NO_EXCEPTIONS + +#define DOCTEST_ASSERT_THROWS_AS(expr, assert_type, message, ...) \ + DOCTEST_FUNC_SCOPE_BEGIN { \ + if(!doctest::getContextOptions()->no_throw) { \ + doctest::detail::ResultBuilder DOCTEST_RB(doctest::assertType::assert_type, __FILE__, \ + __LINE__, #expr, #__VA_ARGS__, message); \ + try { \ + DOCTEST_CAST_TO_VOID(expr) \ + } catch(const typename doctest::detail::types::remove_const< \ + typename doctest::detail::types::remove_reference<__VA_ARGS__>::type>::type&) {\ + DOCTEST_RB.translateException(); \ + DOCTEST_RB.m_threw_as = true; \ + } catch(...) { DOCTEST_RB.translateException(); } \ + DOCTEST_ASSERT_LOG_REACT_RETURN(DOCTEST_RB); \ + } else { /* NOLINT(*-else-after-return) */ \ + DOCTEST_FUNC_SCOPE_RET(false); \ + } \ + } DOCTEST_FUNC_SCOPE_END + +#define DOCTEST_ASSERT_THROWS_WITH(expr, expr_str, assert_type, ...) \ + DOCTEST_FUNC_SCOPE_BEGIN { \ + if(!doctest::getContextOptions()->no_throw) { \ + doctest::detail::ResultBuilder DOCTEST_RB(doctest::assertType::assert_type, __FILE__, \ + __LINE__, expr_str, "", __VA_ARGS__); \ + try { \ + DOCTEST_CAST_TO_VOID(expr) \ + } catch(...) { DOCTEST_RB.translateException(); } \ + DOCTEST_ASSERT_LOG_REACT_RETURN(DOCTEST_RB); \ + } else { /* NOLINT(*-else-after-return) */ \ + DOCTEST_FUNC_SCOPE_RET(false); \ + } \ + } DOCTEST_FUNC_SCOPE_END + +#define DOCTEST_ASSERT_NOTHROW(assert_type, ...) \ + DOCTEST_FUNC_SCOPE_BEGIN { \ + doctest::detail::ResultBuilder DOCTEST_RB(doctest::assertType::assert_type, __FILE__, \ + __LINE__, #__VA_ARGS__); \ + try { \ + DOCTEST_CAST_TO_VOID(__VA_ARGS__) \ + } catch(...) { DOCTEST_RB.translateException(); } \ + DOCTEST_ASSERT_LOG_REACT_RETURN(DOCTEST_RB); \ + } DOCTEST_FUNC_SCOPE_END + +// clang-format off +#define DOCTEST_WARN_THROWS(...) DOCTEST_ASSERT_THROWS_WITH((__VA_ARGS__), #__VA_ARGS__, DT_WARN_THROWS, "") +#define DOCTEST_CHECK_THROWS(...) DOCTEST_ASSERT_THROWS_WITH((__VA_ARGS__), #__VA_ARGS__, DT_CHECK_THROWS, "") +#define DOCTEST_REQUIRE_THROWS(...) DOCTEST_ASSERT_THROWS_WITH((__VA_ARGS__), #__VA_ARGS__, DT_REQUIRE_THROWS, "") + +#define DOCTEST_WARN_THROWS_AS(expr, ...) DOCTEST_ASSERT_THROWS_AS(expr, DT_WARN_THROWS_AS, "", __VA_ARGS__) +#define DOCTEST_CHECK_THROWS_AS(expr, ...) DOCTEST_ASSERT_THROWS_AS(expr, DT_CHECK_THROWS_AS, "", __VA_ARGS__) +#define DOCTEST_REQUIRE_THROWS_AS(expr, ...) DOCTEST_ASSERT_THROWS_AS(expr, DT_REQUIRE_THROWS_AS, "", __VA_ARGS__) + +#define DOCTEST_WARN_THROWS_WITH(expr, ...) DOCTEST_ASSERT_THROWS_WITH(expr, #expr, DT_WARN_THROWS_WITH, __VA_ARGS__) +#define DOCTEST_CHECK_THROWS_WITH(expr, ...) DOCTEST_ASSERT_THROWS_WITH(expr, #expr, DT_CHECK_THROWS_WITH, __VA_ARGS__) +#define DOCTEST_REQUIRE_THROWS_WITH(expr, ...) DOCTEST_ASSERT_THROWS_WITH(expr, #expr, DT_REQUIRE_THROWS_WITH, __VA_ARGS__) + +#define DOCTEST_WARN_THROWS_WITH_AS(expr, message, ...) DOCTEST_ASSERT_THROWS_AS(expr, DT_WARN_THROWS_WITH_AS, message, __VA_ARGS__) +#define DOCTEST_CHECK_THROWS_WITH_AS(expr, message, ...) DOCTEST_ASSERT_THROWS_AS(expr, DT_CHECK_THROWS_WITH_AS, message, __VA_ARGS__) +#define DOCTEST_REQUIRE_THROWS_WITH_AS(expr, message, ...) DOCTEST_ASSERT_THROWS_AS(expr, DT_REQUIRE_THROWS_WITH_AS, message, __VA_ARGS__) + +#define DOCTEST_WARN_NOTHROW(...) DOCTEST_ASSERT_NOTHROW(DT_WARN_NOTHROW, __VA_ARGS__) +#define DOCTEST_CHECK_NOTHROW(...) DOCTEST_ASSERT_NOTHROW(DT_CHECK_NOTHROW, __VA_ARGS__) +#define DOCTEST_REQUIRE_NOTHROW(...) DOCTEST_ASSERT_NOTHROW(DT_REQUIRE_NOTHROW, __VA_ARGS__) + +#define DOCTEST_WARN_THROWS_MESSAGE(expr, ...) DOCTEST_FUNC_SCOPE_BEGIN { DOCTEST_INFO(__VA_ARGS__); DOCTEST_WARN_THROWS(expr); } DOCTEST_FUNC_SCOPE_END +#define DOCTEST_CHECK_THROWS_MESSAGE(expr, ...) DOCTEST_FUNC_SCOPE_BEGIN { DOCTEST_INFO(__VA_ARGS__); DOCTEST_CHECK_THROWS(expr); } DOCTEST_FUNC_SCOPE_END +#define DOCTEST_REQUIRE_THROWS_MESSAGE(expr, ...) DOCTEST_FUNC_SCOPE_BEGIN { DOCTEST_INFO(__VA_ARGS__); DOCTEST_REQUIRE_THROWS(expr); } DOCTEST_FUNC_SCOPE_END +#define DOCTEST_WARN_THROWS_AS_MESSAGE(expr, ex, ...) DOCTEST_FUNC_SCOPE_BEGIN { DOCTEST_INFO(__VA_ARGS__); DOCTEST_WARN_THROWS_AS(expr, ex); } DOCTEST_FUNC_SCOPE_END +#define DOCTEST_CHECK_THROWS_AS_MESSAGE(expr, ex, ...) DOCTEST_FUNC_SCOPE_BEGIN { DOCTEST_INFO(__VA_ARGS__); DOCTEST_CHECK_THROWS_AS(expr, ex); } DOCTEST_FUNC_SCOPE_END +#define DOCTEST_REQUIRE_THROWS_AS_MESSAGE(expr, ex, ...) DOCTEST_FUNC_SCOPE_BEGIN { DOCTEST_INFO(__VA_ARGS__); DOCTEST_REQUIRE_THROWS_AS(expr, ex); } DOCTEST_FUNC_SCOPE_END +#define DOCTEST_WARN_THROWS_WITH_MESSAGE(expr, with, ...) DOCTEST_FUNC_SCOPE_BEGIN { DOCTEST_INFO(__VA_ARGS__); DOCTEST_WARN_THROWS_WITH(expr, with); } DOCTEST_FUNC_SCOPE_END +#define DOCTEST_CHECK_THROWS_WITH_MESSAGE(expr, with, ...) DOCTEST_FUNC_SCOPE_BEGIN { DOCTEST_INFO(__VA_ARGS__); DOCTEST_CHECK_THROWS_WITH(expr, with); } DOCTEST_FUNC_SCOPE_END +#define DOCTEST_REQUIRE_THROWS_WITH_MESSAGE(expr, with, ...) DOCTEST_FUNC_SCOPE_BEGIN { DOCTEST_INFO(__VA_ARGS__); DOCTEST_REQUIRE_THROWS_WITH(expr, with); } DOCTEST_FUNC_SCOPE_END +#define DOCTEST_WARN_THROWS_WITH_AS_MESSAGE(expr, with, ex, ...) DOCTEST_FUNC_SCOPE_BEGIN { DOCTEST_INFO(__VA_ARGS__); DOCTEST_WARN_THROWS_WITH_AS(expr, with, ex); } DOCTEST_FUNC_SCOPE_END +#define DOCTEST_CHECK_THROWS_WITH_AS_MESSAGE(expr, with, ex, ...) DOCTEST_FUNC_SCOPE_BEGIN { DOCTEST_INFO(__VA_ARGS__); DOCTEST_CHECK_THROWS_WITH_AS(expr, with, ex); } DOCTEST_FUNC_SCOPE_END +#define DOCTEST_REQUIRE_THROWS_WITH_AS_MESSAGE(expr, with, ex, ...) DOCTEST_FUNC_SCOPE_BEGIN { DOCTEST_INFO(__VA_ARGS__); DOCTEST_REQUIRE_THROWS_WITH_AS(expr, with, ex); } DOCTEST_FUNC_SCOPE_END +#define DOCTEST_WARN_NOTHROW_MESSAGE(expr, ...) DOCTEST_FUNC_SCOPE_BEGIN { DOCTEST_INFO(__VA_ARGS__); DOCTEST_WARN_NOTHROW(expr); } DOCTEST_FUNC_SCOPE_END +#define DOCTEST_CHECK_NOTHROW_MESSAGE(expr, ...) DOCTEST_FUNC_SCOPE_BEGIN { DOCTEST_INFO(__VA_ARGS__); DOCTEST_CHECK_NOTHROW(expr); } DOCTEST_FUNC_SCOPE_END +#define DOCTEST_REQUIRE_NOTHROW_MESSAGE(expr, ...) DOCTEST_FUNC_SCOPE_BEGIN { DOCTEST_INFO(__VA_ARGS__); DOCTEST_REQUIRE_NOTHROW(expr); } DOCTEST_FUNC_SCOPE_END +// clang-format on + +#endif // DOCTEST_CONFIG_NO_EXCEPTIONS + +// ================================================================================================= +// == WHAT FOLLOWS IS VERSIONS OF THE MACROS THAT DO NOT DO ANY REGISTERING! == +// == THIS CAN BE ENABLED BY DEFINING DOCTEST_CONFIG_DISABLE GLOBALLY! == +// ================================================================================================= +#else // DOCTEST_CONFIG_DISABLE + +#define DOCTEST_IMPLEMENT_FIXTURE(der, base, func, name) \ + namespace /* NOLINT */ { \ + template \ + struct der : public base \ + { void f(); }; \ + } \ + template \ + inline void der::f() + +#define DOCTEST_CREATE_AND_REGISTER_FUNCTION(f, name) \ + template \ + static inline void f() + +// for registering tests +#define DOCTEST_TEST_CASE(name) \ + DOCTEST_CREATE_AND_REGISTER_FUNCTION(DOCTEST_ANONYMOUS(DOCTEST_ANON_FUNC_), name) + +// for registering tests in classes +#define DOCTEST_TEST_CASE_CLASS(name) \ + DOCTEST_CREATE_AND_REGISTER_FUNCTION(DOCTEST_ANONYMOUS(DOCTEST_ANON_FUNC_), name) + +// for registering tests with a fixture +#define DOCTEST_TEST_CASE_FIXTURE(x, name) \ + DOCTEST_IMPLEMENT_FIXTURE(DOCTEST_ANONYMOUS(DOCTEST_ANON_CLASS_), x, \ + DOCTEST_ANONYMOUS(DOCTEST_ANON_FUNC_), name) + +// for converting types to strings without the header and demangling +#define DOCTEST_TYPE_TO_STRING_AS(str, ...) static_assert(true, "") +#define DOCTEST_TYPE_TO_STRING(...) static_assert(true, "") + +// for typed tests +#define DOCTEST_TEST_CASE_TEMPLATE(name, type, ...) \ + template \ + inline void DOCTEST_ANONYMOUS(DOCTEST_ANON_TMP_)() + +#define DOCTEST_TEST_CASE_TEMPLATE_DEFINE(name, type, id) \ + template \ + inline void DOCTEST_ANONYMOUS(DOCTEST_ANON_TMP_)() + +#define DOCTEST_TEST_CASE_TEMPLATE_INVOKE(id, ...) static_assert(true, "") +#define DOCTEST_TEST_CASE_TEMPLATE_APPLY(id, ...) static_assert(true, "") + +// for subcases +#define DOCTEST_SUBCASE(name) + +// for a testsuite block +#define DOCTEST_TEST_SUITE(name) namespace // NOLINT + +// for starting a testsuite block +#define DOCTEST_TEST_SUITE_BEGIN(name) static_assert(true, "") + +// for ending a testsuite block +#define DOCTEST_TEST_SUITE_END using DOCTEST_ANONYMOUS(DOCTEST_ANON_FOR_SEMICOLON_) = int + +#define DOCTEST_REGISTER_EXCEPTION_TRANSLATOR(signature) \ + template \ + static inline doctest::String DOCTEST_ANONYMOUS(DOCTEST_ANON_TRANSLATOR_)(signature) + +#define DOCTEST_REGISTER_REPORTER(name, priority, reporter) +#define DOCTEST_REGISTER_LISTENER(name, priority, reporter) + +#define DOCTEST_INFO(...) (static_cast(0)) +#define DOCTEST_CAPTURE(x) (static_cast(0)) +#define DOCTEST_ADD_MESSAGE_AT(file, line, ...) (static_cast(0)) +#define DOCTEST_ADD_FAIL_CHECK_AT(file, line, ...) (static_cast(0)) +#define DOCTEST_ADD_FAIL_AT(file, line, ...) (static_cast(0)) +#define DOCTEST_MESSAGE(...) (static_cast(0)) +#define DOCTEST_FAIL_CHECK(...) (static_cast(0)) +#define DOCTEST_FAIL(...) (static_cast(0)) + +#if defined(DOCTEST_CONFIG_EVALUATE_ASSERTS_EVEN_WHEN_DISABLED) \ + && defined(DOCTEST_CONFIG_ASSERTS_RETURN_VALUES) + +#define DOCTEST_WARN(...) [&] { return __VA_ARGS__; }() +#define DOCTEST_CHECK(...) [&] { return __VA_ARGS__; }() +#define DOCTEST_REQUIRE(...) [&] { return __VA_ARGS__; }() +#define DOCTEST_WARN_FALSE(...) [&] { return !(__VA_ARGS__); }() +#define DOCTEST_CHECK_FALSE(...) [&] { return !(__VA_ARGS__); }() +#define DOCTEST_REQUIRE_FALSE(...) [&] { return !(__VA_ARGS__); }() + +#define DOCTEST_WARN_MESSAGE(cond, ...) [&] { return cond; }() +#define DOCTEST_CHECK_MESSAGE(cond, ...) [&] { return cond; }() +#define DOCTEST_REQUIRE_MESSAGE(cond, ...) [&] { return cond; }() +#define DOCTEST_WARN_FALSE_MESSAGE(cond, ...) [&] { return !(cond); }() +#define DOCTEST_CHECK_FALSE_MESSAGE(cond, ...) [&] { return !(cond); }() +#define DOCTEST_REQUIRE_FALSE_MESSAGE(cond, ...) [&] { return !(cond); }() + +namespace doctest { +namespace detail { +#define DOCTEST_RELATIONAL_OP(name, op) \ + template \ + bool name(const DOCTEST_REF_WRAP(L) lhs, const DOCTEST_REF_WRAP(R) rhs) { return lhs op rhs; } + + DOCTEST_RELATIONAL_OP(eq, ==) + DOCTEST_RELATIONAL_OP(ne, !=) + DOCTEST_RELATIONAL_OP(lt, <) + DOCTEST_RELATIONAL_OP(gt, >) + DOCTEST_RELATIONAL_OP(le, <=) + DOCTEST_RELATIONAL_OP(ge, >=) +} // namespace detail +} // namespace doctest + +#define DOCTEST_WARN_EQ(...) [&] { return doctest::detail::eq(__VA_ARGS__); }() +#define DOCTEST_CHECK_EQ(...) [&] { return doctest::detail::eq(__VA_ARGS__); }() +#define DOCTEST_REQUIRE_EQ(...) [&] { return doctest::detail::eq(__VA_ARGS__); }() +#define DOCTEST_WARN_NE(...) [&] { return doctest::detail::ne(__VA_ARGS__); }() +#define DOCTEST_CHECK_NE(...) [&] { return doctest::detail::ne(__VA_ARGS__); }() +#define DOCTEST_REQUIRE_NE(...) [&] { return doctest::detail::ne(__VA_ARGS__); }() +#define DOCTEST_WARN_LT(...) [&] { return doctest::detail::lt(__VA_ARGS__); }() +#define DOCTEST_CHECK_LT(...) [&] { return doctest::detail::lt(__VA_ARGS__); }() +#define DOCTEST_REQUIRE_LT(...) [&] { return doctest::detail::lt(__VA_ARGS__); }() +#define DOCTEST_WARN_GT(...) [&] { return doctest::detail::gt(__VA_ARGS__); }() +#define DOCTEST_CHECK_GT(...) [&] { return doctest::detail::gt(__VA_ARGS__); }() +#define DOCTEST_REQUIRE_GT(...) [&] { return doctest::detail::gt(__VA_ARGS__); }() +#define DOCTEST_WARN_LE(...) [&] { return doctest::detail::le(__VA_ARGS__); }() +#define DOCTEST_CHECK_LE(...) [&] { return doctest::detail::le(__VA_ARGS__); }() +#define DOCTEST_REQUIRE_LE(...) [&] { return doctest::detail::le(__VA_ARGS__); }() +#define DOCTEST_WARN_GE(...) [&] { return doctest::detail::ge(__VA_ARGS__); }() +#define DOCTEST_CHECK_GE(...) [&] { return doctest::detail::ge(__VA_ARGS__); }() +#define DOCTEST_REQUIRE_GE(...) [&] { return doctest::detail::ge(__VA_ARGS__); }() +#define DOCTEST_WARN_UNARY(...) [&] { return __VA_ARGS__; }() +#define DOCTEST_CHECK_UNARY(...) [&] { return __VA_ARGS__; }() +#define DOCTEST_REQUIRE_UNARY(...) [&] { return __VA_ARGS__; }() +#define DOCTEST_WARN_UNARY_FALSE(...) [&] { return !(__VA_ARGS__); }() +#define DOCTEST_CHECK_UNARY_FALSE(...) [&] { return !(__VA_ARGS__); }() +#define DOCTEST_REQUIRE_UNARY_FALSE(...) [&] { return !(__VA_ARGS__); }() + +#ifndef DOCTEST_CONFIG_NO_EXCEPTIONS + +#define DOCTEST_WARN_THROWS_WITH(expr, with, ...) [] { static_assert(false, "Exception translation is not available when doctest is disabled."); return false; }() +#define DOCTEST_CHECK_THROWS_WITH(expr, with, ...) DOCTEST_WARN_THROWS_WITH(,,) +#define DOCTEST_REQUIRE_THROWS_WITH(expr, with, ...) DOCTEST_WARN_THROWS_WITH(,,) +#define DOCTEST_WARN_THROWS_WITH_AS(expr, with, ex, ...) DOCTEST_WARN_THROWS_WITH(,,) +#define DOCTEST_CHECK_THROWS_WITH_AS(expr, with, ex, ...) DOCTEST_WARN_THROWS_WITH(,,) +#define DOCTEST_REQUIRE_THROWS_WITH_AS(expr, with, ex, ...) DOCTEST_WARN_THROWS_WITH(,,) + +#define DOCTEST_WARN_THROWS_WITH_MESSAGE(expr, with, ...) DOCTEST_WARN_THROWS_WITH(,,) +#define DOCTEST_CHECK_THROWS_WITH_MESSAGE(expr, with, ...) DOCTEST_WARN_THROWS_WITH(,,) +#define DOCTEST_REQUIRE_THROWS_WITH_MESSAGE(expr, with, ...) DOCTEST_WARN_THROWS_WITH(,,) +#define DOCTEST_WARN_THROWS_WITH_AS_MESSAGE(expr, with, ex, ...) DOCTEST_WARN_THROWS_WITH(,,) +#define DOCTEST_CHECK_THROWS_WITH_AS_MESSAGE(expr, with, ex, ...) DOCTEST_WARN_THROWS_WITH(,,) +#define DOCTEST_REQUIRE_THROWS_WITH_AS_MESSAGE(expr, with, ex, ...) DOCTEST_WARN_THROWS_WITH(,,) + +#define DOCTEST_WARN_THROWS(...) [&] { try { __VA_ARGS__; return false; } catch (...) { return true; } }() +#define DOCTEST_CHECK_THROWS(...) [&] { try { __VA_ARGS__; return false; } catch (...) { return true; } }() +#define DOCTEST_REQUIRE_THROWS(...) [&] { try { __VA_ARGS__; return false; } catch (...) { return true; } }() +#define DOCTEST_WARN_THROWS_AS(expr, ...) [&] { try { expr; } catch (__VA_ARGS__) { return true; } catch (...) { } return false; }() +#define DOCTEST_CHECK_THROWS_AS(expr, ...) [&] { try { expr; } catch (__VA_ARGS__) { return true; } catch (...) { } return false; }() +#define DOCTEST_REQUIRE_THROWS_AS(expr, ...) [&] { try { expr; } catch (__VA_ARGS__) { return true; } catch (...) { } return false; }() +#define DOCTEST_WARN_NOTHROW(...) [&] { try { __VA_ARGS__; return true; } catch (...) { return false; } }() +#define DOCTEST_CHECK_NOTHROW(...) [&] { try { __VA_ARGS__; return true; } catch (...) { return false; } }() +#define DOCTEST_REQUIRE_NOTHROW(...) [&] { try { __VA_ARGS__; return true; } catch (...) { return false; } }() + +#define DOCTEST_WARN_THROWS_MESSAGE(expr, ...) [&] { try { __VA_ARGS__; return false; } catch (...) { return true; } }() +#define DOCTEST_CHECK_THROWS_MESSAGE(expr, ...) [&] { try { __VA_ARGS__; return false; } catch (...) { return true; } }() +#define DOCTEST_REQUIRE_THROWS_MESSAGE(expr, ...) [&] { try { __VA_ARGS__; return false; } catch (...) { return true; } }() +#define DOCTEST_WARN_THROWS_AS_MESSAGE(expr, ex, ...) [&] { try { expr; } catch (__VA_ARGS__) { return true; } catch (...) { } return false; }() +#define DOCTEST_CHECK_THROWS_AS_MESSAGE(expr, ex, ...) [&] { try { expr; } catch (__VA_ARGS__) { return true; } catch (...) { } return false; }() +#define DOCTEST_REQUIRE_THROWS_AS_MESSAGE(expr, ex, ...) [&] { try { expr; } catch (__VA_ARGS__) { return true; } catch (...) { } return false; }() +#define DOCTEST_WARN_NOTHROW_MESSAGE(expr, ...) [&] { try { __VA_ARGS__; return true; } catch (...) { return false; } }() +#define DOCTEST_CHECK_NOTHROW_MESSAGE(expr, ...) [&] { try { __VA_ARGS__; return true; } catch (...) { return false; } }() +#define DOCTEST_REQUIRE_NOTHROW_MESSAGE(expr, ...) [&] { try { __VA_ARGS__; return true; } catch (...) { return false; } }() + +#endif // DOCTEST_CONFIG_NO_EXCEPTIONS + +#else // DOCTEST_CONFIG_EVALUATE_ASSERTS_EVEN_WHEN_DISABLED + +#define DOCTEST_WARN(...) DOCTEST_FUNC_EMPTY +#define DOCTEST_CHECK(...) DOCTEST_FUNC_EMPTY +#define DOCTEST_REQUIRE(...) DOCTEST_FUNC_EMPTY +#define DOCTEST_WARN_FALSE(...) DOCTEST_FUNC_EMPTY +#define DOCTEST_CHECK_FALSE(...) DOCTEST_FUNC_EMPTY +#define DOCTEST_REQUIRE_FALSE(...) DOCTEST_FUNC_EMPTY + +#define DOCTEST_WARN_MESSAGE(cond, ...) DOCTEST_FUNC_EMPTY +#define DOCTEST_CHECK_MESSAGE(cond, ...) DOCTEST_FUNC_EMPTY +#define DOCTEST_REQUIRE_MESSAGE(cond, ...) DOCTEST_FUNC_EMPTY +#define DOCTEST_WARN_FALSE_MESSAGE(cond, ...) DOCTEST_FUNC_EMPTY +#define DOCTEST_CHECK_FALSE_MESSAGE(cond, ...) DOCTEST_FUNC_EMPTY +#define DOCTEST_REQUIRE_FALSE_MESSAGE(cond, ...) DOCTEST_FUNC_EMPTY + +#define DOCTEST_WARN_EQ(...) DOCTEST_FUNC_EMPTY +#define DOCTEST_CHECK_EQ(...) DOCTEST_FUNC_EMPTY +#define DOCTEST_REQUIRE_EQ(...) DOCTEST_FUNC_EMPTY +#define DOCTEST_WARN_NE(...) DOCTEST_FUNC_EMPTY +#define DOCTEST_CHECK_NE(...) DOCTEST_FUNC_EMPTY +#define DOCTEST_REQUIRE_NE(...) DOCTEST_FUNC_EMPTY +#define DOCTEST_WARN_GT(...) DOCTEST_FUNC_EMPTY +#define DOCTEST_CHECK_GT(...) DOCTEST_FUNC_EMPTY +#define DOCTEST_REQUIRE_GT(...) DOCTEST_FUNC_EMPTY +#define DOCTEST_WARN_LT(...) DOCTEST_FUNC_EMPTY +#define DOCTEST_CHECK_LT(...) DOCTEST_FUNC_EMPTY +#define DOCTEST_REQUIRE_LT(...) DOCTEST_FUNC_EMPTY +#define DOCTEST_WARN_GE(...) DOCTEST_FUNC_EMPTY +#define DOCTEST_CHECK_GE(...) DOCTEST_FUNC_EMPTY +#define DOCTEST_REQUIRE_GE(...) DOCTEST_FUNC_EMPTY +#define DOCTEST_WARN_LE(...) DOCTEST_FUNC_EMPTY +#define DOCTEST_CHECK_LE(...) DOCTEST_FUNC_EMPTY +#define DOCTEST_REQUIRE_LE(...) DOCTEST_FUNC_EMPTY + +#define DOCTEST_WARN_UNARY(...) DOCTEST_FUNC_EMPTY +#define DOCTEST_CHECK_UNARY(...) DOCTEST_FUNC_EMPTY +#define DOCTEST_REQUIRE_UNARY(...) DOCTEST_FUNC_EMPTY +#define DOCTEST_WARN_UNARY_FALSE(...) DOCTEST_FUNC_EMPTY +#define DOCTEST_CHECK_UNARY_FALSE(...) DOCTEST_FUNC_EMPTY +#define DOCTEST_REQUIRE_UNARY_FALSE(...) DOCTEST_FUNC_EMPTY + +#ifndef DOCTEST_CONFIG_NO_EXCEPTIONS + +#define DOCTEST_WARN_THROWS(...) DOCTEST_FUNC_EMPTY +#define DOCTEST_CHECK_THROWS(...) DOCTEST_FUNC_EMPTY +#define DOCTEST_REQUIRE_THROWS(...) DOCTEST_FUNC_EMPTY +#define DOCTEST_WARN_THROWS_AS(expr, ...) DOCTEST_FUNC_EMPTY +#define DOCTEST_CHECK_THROWS_AS(expr, ...) DOCTEST_FUNC_EMPTY +#define DOCTEST_REQUIRE_THROWS_AS(expr, ...) DOCTEST_FUNC_EMPTY +#define DOCTEST_WARN_THROWS_WITH(expr, ...) DOCTEST_FUNC_EMPTY +#define DOCTEST_CHECK_THROWS_WITH(expr, ...) DOCTEST_FUNC_EMPTY +#define DOCTEST_REQUIRE_THROWS_WITH(expr, ...) DOCTEST_FUNC_EMPTY +#define DOCTEST_WARN_THROWS_WITH_AS(expr, with, ...) DOCTEST_FUNC_EMPTY +#define DOCTEST_CHECK_THROWS_WITH_AS(expr, with, ...) DOCTEST_FUNC_EMPTY +#define DOCTEST_REQUIRE_THROWS_WITH_AS(expr, with, ...) DOCTEST_FUNC_EMPTY +#define DOCTEST_WARN_NOTHROW(...) DOCTEST_FUNC_EMPTY +#define DOCTEST_CHECK_NOTHROW(...) DOCTEST_FUNC_EMPTY +#define DOCTEST_REQUIRE_NOTHROW(...) DOCTEST_FUNC_EMPTY + +#define DOCTEST_WARN_THROWS_MESSAGE(expr, ...) DOCTEST_FUNC_EMPTY +#define DOCTEST_CHECK_THROWS_MESSAGE(expr, ...) DOCTEST_FUNC_EMPTY +#define DOCTEST_REQUIRE_THROWS_MESSAGE(expr, ...) DOCTEST_FUNC_EMPTY +#define DOCTEST_WARN_THROWS_AS_MESSAGE(expr, ex, ...) DOCTEST_FUNC_EMPTY +#define DOCTEST_CHECK_THROWS_AS_MESSAGE(expr, ex, ...) DOCTEST_FUNC_EMPTY +#define DOCTEST_REQUIRE_THROWS_AS_MESSAGE(expr, ex, ...) DOCTEST_FUNC_EMPTY +#define DOCTEST_WARN_THROWS_WITH_MESSAGE(expr, with, ...) DOCTEST_FUNC_EMPTY +#define DOCTEST_CHECK_THROWS_WITH_MESSAGE(expr, with, ...) DOCTEST_FUNC_EMPTY +#define DOCTEST_REQUIRE_THROWS_WITH_MESSAGE(expr, with, ...) DOCTEST_FUNC_EMPTY +#define DOCTEST_WARN_THROWS_WITH_AS_MESSAGE(expr, with, ex, ...) DOCTEST_FUNC_EMPTY +#define DOCTEST_CHECK_THROWS_WITH_AS_MESSAGE(expr, with, ex, ...) DOCTEST_FUNC_EMPTY +#define DOCTEST_REQUIRE_THROWS_WITH_AS_MESSAGE(expr, with, ex, ...) DOCTEST_FUNC_EMPTY +#define DOCTEST_WARN_NOTHROW_MESSAGE(expr, ...) DOCTEST_FUNC_EMPTY +#define DOCTEST_CHECK_NOTHROW_MESSAGE(expr, ...) DOCTEST_FUNC_EMPTY +#define DOCTEST_REQUIRE_NOTHROW_MESSAGE(expr, ...) DOCTEST_FUNC_EMPTY + +#endif // DOCTEST_CONFIG_NO_EXCEPTIONS + +#endif // DOCTEST_CONFIG_EVALUATE_ASSERTS_EVEN_WHEN_DISABLED + +#endif // DOCTEST_CONFIG_DISABLE + +#ifdef DOCTEST_CONFIG_NO_EXCEPTIONS + +#ifdef DOCTEST_CONFIG_NO_EXCEPTIONS_BUT_WITH_ALL_ASSERTS +#define DOCTEST_EXCEPTION_EMPTY_FUNC DOCTEST_FUNC_EMPTY +#else // DOCTEST_CONFIG_NO_EXCEPTIONS_BUT_WITH_ALL_ASSERTS +#define DOCTEST_EXCEPTION_EMPTY_FUNC [] { static_assert(false, "Exceptions are disabled! " \ + "Use DOCTEST_CONFIG_NO_EXCEPTIONS_BUT_WITH_ALL_ASSERTS if you want to compile with exceptions disabled."); return false; }() + +#undef DOCTEST_REQUIRE +#undef DOCTEST_REQUIRE_FALSE +#undef DOCTEST_REQUIRE_MESSAGE +#undef DOCTEST_REQUIRE_FALSE_MESSAGE +#undef DOCTEST_REQUIRE_EQ +#undef DOCTEST_REQUIRE_NE +#undef DOCTEST_REQUIRE_GT +#undef DOCTEST_REQUIRE_LT +#undef DOCTEST_REQUIRE_GE +#undef DOCTEST_REQUIRE_LE +#undef DOCTEST_REQUIRE_UNARY +#undef DOCTEST_REQUIRE_UNARY_FALSE + +#define DOCTEST_REQUIRE DOCTEST_EXCEPTION_EMPTY_FUNC +#define DOCTEST_REQUIRE_FALSE DOCTEST_EXCEPTION_EMPTY_FUNC +#define DOCTEST_REQUIRE_MESSAGE DOCTEST_EXCEPTION_EMPTY_FUNC +#define DOCTEST_REQUIRE_FALSE_MESSAGE DOCTEST_EXCEPTION_EMPTY_FUNC +#define DOCTEST_REQUIRE_EQ DOCTEST_EXCEPTION_EMPTY_FUNC +#define DOCTEST_REQUIRE_NE DOCTEST_EXCEPTION_EMPTY_FUNC +#define DOCTEST_REQUIRE_GT DOCTEST_EXCEPTION_EMPTY_FUNC +#define DOCTEST_REQUIRE_LT DOCTEST_EXCEPTION_EMPTY_FUNC +#define DOCTEST_REQUIRE_GE DOCTEST_EXCEPTION_EMPTY_FUNC +#define DOCTEST_REQUIRE_LE DOCTEST_EXCEPTION_EMPTY_FUNC +#define DOCTEST_REQUIRE_UNARY DOCTEST_EXCEPTION_EMPTY_FUNC +#define DOCTEST_REQUIRE_UNARY_FALSE DOCTEST_EXCEPTION_EMPTY_FUNC + +#endif // DOCTEST_CONFIG_NO_EXCEPTIONS_BUT_WITH_ALL_ASSERTS + +#define DOCTEST_WARN_THROWS(...) DOCTEST_EXCEPTION_EMPTY_FUNC +#define DOCTEST_CHECK_THROWS(...) DOCTEST_EXCEPTION_EMPTY_FUNC +#define DOCTEST_REQUIRE_THROWS(...) DOCTEST_EXCEPTION_EMPTY_FUNC +#define DOCTEST_WARN_THROWS_AS(expr, ...) DOCTEST_EXCEPTION_EMPTY_FUNC +#define DOCTEST_CHECK_THROWS_AS(expr, ...) DOCTEST_EXCEPTION_EMPTY_FUNC +#define DOCTEST_REQUIRE_THROWS_AS(expr, ...) DOCTEST_EXCEPTION_EMPTY_FUNC +#define DOCTEST_WARN_THROWS_WITH(expr, ...) DOCTEST_EXCEPTION_EMPTY_FUNC +#define DOCTEST_CHECK_THROWS_WITH(expr, ...) DOCTEST_EXCEPTION_EMPTY_FUNC +#define DOCTEST_REQUIRE_THROWS_WITH(expr, ...) DOCTEST_EXCEPTION_EMPTY_FUNC +#define DOCTEST_WARN_THROWS_WITH_AS(expr, with, ...) DOCTEST_EXCEPTION_EMPTY_FUNC +#define DOCTEST_CHECK_THROWS_WITH_AS(expr, with, ...) DOCTEST_EXCEPTION_EMPTY_FUNC +#define DOCTEST_REQUIRE_THROWS_WITH_AS(expr, with, ...) DOCTEST_EXCEPTION_EMPTY_FUNC +#define DOCTEST_WARN_NOTHROW(...) DOCTEST_EXCEPTION_EMPTY_FUNC +#define DOCTEST_CHECK_NOTHROW(...) DOCTEST_EXCEPTION_EMPTY_FUNC +#define DOCTEST_REQUIRE_NOTHROW(...) DOCTEST_EXCEPTION_EMPTY_FUNC + +#define DOCTEST_WARN_THROWS_MESSAGE(expr, ...) DOCTEST_EXCEPTION_EMPTY_FUNC +#define DOCTEST_CHECK_THROWS_MESSAGE(expr, ...) DOCTEST_EXCEPTION_EMPTY_FUNC +#define DOCTEST_REQUIRE_THROWS_MESSAGE(expr, ...) DOCTEST_EXCEPTION_EMPTY_FUNC +#define DOCTEST_WARN_THROWS_AS_MESSAGE(expr, ex, ...) DOCTEST_EXCEPTION_EMPTY_FUNC +#define DOCTEST_CHECK_THROWS_AS_MESSAGE(expr, ex, ...) DOCTEST_EXCEPTION_EMPTY_FUNC +#define DOCTEST_REQUIRE_THROWS_AS_MESSAGE(expr, ex, ...) DOCTEST_EXCEPTION_EMPTY_FUNC +#define DOCTEST_WARN_THROWS_WITH_MESSAGE(expr, with, ...) DOCTEST_EXCEPTION_EMPTY_FUNC +#define DOCTEST_CHECK_THROWS_WITH_MESSAGE(expr, with, ...) DOCTEST_EXCEPTION_EMPTY_FUNC +#define DOCTEST_REQUIRE_THROWS_WITH_MESSAGE(expr, with, ...) DOCTEST_EXCEPTION_EMPTY_FUNC +#define DOCTEST_WARN_THROWS_WITH_AS_MESSAGE(expr, with, ex, ...) DOCTEST_EXCEPTION_EMPTY_FUNC +#define DOCTEST_CHECK_THROWS_WITH_AS_MESSAGE(expr, with, ex, ...) DOCTEST_EXCEPTION_EMPTY_FUNC +#define DOCTEST_REQUIRE_THROWS_WITH_AS_MESSAGE(expr, with, ex, ...) DOCTEST_EXCEPTION_EMPTY_FUNC +#define DOCTEST_WARN_NOTHROW_MESSAGE(expr, ...) DOCTEST_EXCEPTION_EMPTY_FUNC +#define DOCTEST_CHECK_NOTHROW_MESSAGE(expr, ...) DOCTEST_EXCEPTION_EMPTY_FUNC +#define DOCTEST_REQUIRE_NOTHROW_MESSAGE(expr, ...) DOCTEST_EXCEPTION_EMPTY_FUNC + +#endif // DOCTEST_CONFIG_NO_EXCEPTIONS + +// clang-format off +// KEPT FOR BACKWARDS COMPATIBILITY - FORWARDING TO THE RIGHT MACROS +#define DOCTEST_FAST_WARN_EQ DOCTEST_WARN_EQ +#define DOCTEST_FAST_CHECK_EQ DOCTEST_CHECK_EQ +#define DOCTEST_FAST_REQUIRE_EQ DOCTEST_REQUIRE_EQ +#define DOCTEST_FAST_WARN_NE DOCTEST_WARN_NE +#define DOCTEST_FAST_CHECK_NE DOCTEST_CHECK_NE +#define DOCTEST_FAST_REQUIRE_NE DOCTEST_REQUIRE_NE +#define DOCTEST_FAST_WARN_GT DOCTEST_WARN_GT +#define DOCTEST_FAST_CHECK_GT DOCTEST_CHECK_GT +#define DOCTEST_FAST_REQUIRE_GT DOCTEST_REQUIRE_GT +#define DOCTEST_FAST_WARN_LT DOCTEST_WARN_LT +#define DOCTEST_FAST_CHECK_LT DOCTEST_CHECK_LT +#define DOCTEST_FAST_REQUIRE_LT DOCTEST_REQUIRE_LT +#define DOCTEST_FAST_WARN_GE DOCTEST_WARN_GE +#define DOCTEST_FAST_CHECK_GE DOCTEST_CHECK_GE +#define DOCTEST_FAST_REQUIRE_GE DOCTEST_REQUIRE_GE +#define DOCTEST_FAST_WARN_LE DOCTEST_WARN_LE +#define DOCTEST_FAST_CHECK_LE DOCTEST_CHECK_LE +#define DOCTEST_FAST_REQUIRE_LE DOCTEST_REQUIRE_LE + +#define DOCTEST_FAST_WARN_UNARY DOCTEST_WARN_UNARY +#define DOCTEST_FAST_CHECK_UNARY DOCTEST_CHECK_UNARY +#define DOCTEST_FAST_REQUIRE_UNARY DOCTEST_REQUIRE_UNARY +#define DOCTEST_FAST_WARN_UNARY_FALSE DOCTEST_WARN_UNARY_FALSE +#define DOCTEST_FAST_CHECK_UNARY_FALSE DOCTEST_CHECK_UNARY_FALSE +#define DOCTEST_FAST_REQUIRE_UNARY_FALSE DOCTEST_REQUIRE_UNARY_FALSE + +#define DOCTEST_TEST_CASE_TEMPLATE_INSTANTIATE(id, ...) DOCTEST_TEST_CASE_TEMPLATE_INVOKE(id,__VA_ARGS__) +// clang-format on + +// BDD style macros +// clang-format off +#define DOCTEST_SCENARIO(name) DOCTEST_TEST_CASE(" Scenario: " name) +#define DOCTEST_SCENARIO_CLASS(name) DOCTEST_TEST_CASE_CLASS(" Scenario: " name) +#define DOCTEST_SCENARIO_TEMPLATE(name, T, ...) DOCTEST_TEST_CASE_TEMPLATE(" Scenario: " name, T, __VA_ARGS__) +#define DOCTEST_SCENARIO_TEMPLATE_DEFINE(name, T, id) DOCTEST_TEST_CASE_TEMPLATE_DEFINE(" Scenario: " name, T, id) + +#define DOCTEST_GIVEN(name) DOCTEST_SUBCASE(" Given: " name) +#define DOCTEST_WHEN(name) DOCTEST_SUBCASE(" When: " name) +#define DOCTEST_AND_WHEN(name) DOCTEST_SUBCASE("And when: " name) +#define DOCTEST_THEN(name) DOCTEST_SUBCASE(" Then: " name) +#define DOCTEST_AND_THEN(name) DOCTEST_SUBCASE(" And: " name) +// clang-format on + +// == SHORT VERSIONS OF THE MACROS +#ifndef DOCTEST_CONFIG_NO_SHORT_MACRO_NAMES + +#define TEST_CASE(name) DOCTEST_TEST_CASE(name) +#define TEST_CASE_CLASS(name) DOCTEST_TEST_CASE_CLASS(name) +#define TEST_CASE_FIXTURE(x, name) DOCTEST_TEST_CASE_FIXTURE(x, name) +#define TYPE_TO_STRING_AS(str, ...) DOCTEST_TYPE_TO_STRING_AS(str, __VA_ARGS__) +#define TYPE_TO_STRING(...) DOCTEST_TYPE_TO_STRING(__VA_ARGS__) +#define TEST_CASE_TEMPLATE(name, T, ...) DOCTEST_TEST_CASE_TEMPLATE(name, T, __VA_ARGS__) +#define TEST_CASE_TEMPLATE_DEFINE(name, T, id) DOCTEST_TEST_CASE_TEMPLATE_DEFINE(name, T, id) +#define TEST_CASE_TEMPLATE_INVOKE(id, ...) DOCTEST_TEST_CASE_TEMPLATE_INVOKE(id, __VA_ARGS__) +#define TEST_CASE_TEMPLATE_APPLY(id, ...) DOCTEST_TEST_CASE_TEMPLATE_APPLY(id, __VA_ARGS__) +#define SUBCASE(name) DOCTEST_SUBCASE(name) +#define TEST_SUITE(decorators) DOCTEST_TEST_SUITE(decorators) +#define TEST_SUITE_BEGIN(name) DOCTEST_TEST_SUITE_BEGIN(name) +#define TEST_SUITE_END DOCTEST_TEST_SUITE_END +#define REGISTER_EXCEPTION_TRANSLATOR(signature) DOCTEST_REGISTER_EXCEPTION_TRANSLATOR(signature) +#define REGISTER_REPORTER(name, priority, reporter) DOCTEST_REGISTER_REPORTER(name, priority, reporter) +#define REGISTER_LISTENER(name, priority, reporter) DOCTEST_REGISTER_LISTENER(name, priority, reporter) +#define INFO(...) DOCTEST_INFO(__VA_ARGS__) +#define CAPTURE(x) DOCTEST_CAPTURE(x) +#define ADD_MESSAGE_AT(file, line, ...) DOCTEST_ADD_MESSAGE_AT(file, line, __VA_ARGS__) +#define ADD_FAIL_CHECK_AT(file, line, ...) DOCTEST_ADD_FAIL_CHECK_AT(file, line, __VA_ARGS__) +#define ADD_FAIL_AT(file, line, ...) DOCTEST_ADD_FAIL_AT(file, line, __VA_ARGS__) +#define MESSAGE(...) DOCTEST_MESSAGE(__VA_ARGS__) +#define FAIL_CHECK(...) DOCTEST_FAIL_CHECK(__VA_ARGS__) +#define FAIL(...) DOCTEST_FAIL(__VA_ARGS__) +#define TO_LVALUE(...) DOCTEST_TO_LVALUE(__VA_ARGS__) + +#define WARN(...) DOCTEST_WARN(__VA_ARGS__) +#define WARN_FALSE(...) DOCTEST_WARN_FALSE(__VA_ARGS__) +#define WARN_THROWS(...) DOCTEST_WARN_THROWS(__VA_ARGS__) +#define WARN_THROWS_AS(expr, ...) DOCTEST_WARN_THROWS_AS(expr, __VA_ARGS__) +#define WARN_THROWS_WITH(expr, ...) DOCTEST_WARN_THROWS_WITH(expr, __VA_ARGS__) +#define WARN_THROWS_WITH_AS(expr, with, ...) DOCTEST_WARN_THROWS_WITH_AS(expr, with, __VA_ARGS__) +#define WARN_NOTHROW(...) DOCTEST_WARN_NOTHROW(__VA_ARGS__) +#define CHECK(...) DOCTEST_CHECK(__VA_ARGS__) +#define CHECK_FALSE(...) DOCTEST_CHECK_FALSE(__VA_ARGS__) +#define CHECK_THROWS(...) DOCTEST_CHECK_THROWS(__VA_ARGS__) +#define CHECK_THROWS_AS(expr, ...) DOCTEST_CHECK_THROWS_AS(expr, __VA_ARGS__) +#define CHECK_THROWS_WITH(expr, ...) DOCTEST_CHECK_THROWS_WITH(expr, __VA_ARGS__) +#define CHECK_THROWS_WITH_AS(expr, with, ...) DOCTEST_CHECK_THROWS_WITH_AS(expr, with, __VA_ARGS__) +#define CHECK_NOTHROW(...) DOCTEST_CHECK_NOTHROW(__VA_ARGS__) +#define REQUIRE(...) DOCTEST_REQUIRE(__VA_ARGS__) +#define REQUIRE_FALSE(...) DOCTEST_REQUIRE_FALSE(__VA_ARGS__) +#define REQUIRE_THROWS(...) DOCTEST_REQUIRE_THROWS(__VA_ARGS__) +#define REQUIRE_THROWS_AS(expr, ...) DOCTEST_REQUIRE_THROWS_AS(expr, __VA_ARGS__) +#define REQUIRE_THROWS_WITH(expr, ...) DOCTEST_REQUIRE_THROWS_WITH(expr, __VA_ARGS__) +#define REQUIRE_THROWS_WITH_AS(expr, with, ...) DOCTEST_REQUIRE_THROWS_WITH_AS(expr, with, __VA_ARGS__) +#define REQUIRE_NOTHROW(...) DOCTEST_REQUIRE_NOTHROW(__VA_ARGS__) + +#define WARN_MESSAGE(cond, ...) DOCTEST_WARN_MESSAGE(cond, __VA_ARGS__) +#define WARN_FALSE_MESSAGE(cond, ...) DOCTEST_WARN_FALSE_MESSAGE(cond, __VA_ARGS__) +#define WARN_THROWS_MESSAGE(expr, ...) DOCTEST_WARN_THROWS_MESSAGE(expr, __VA_ARGS__) +#define WARN_THROWS_AS_MESSAGE(expr, ex, ...) DOCTEST_WARN_THROWS_AS_MESSAGE(expr, ex, __VA_ARGS__) +#define WARN_THROWS_WITH_MESSAGE(expr, with, ...) DOCTEST_WARN_THROWS_WITH_MESSAGE(expr, with, __VA_ARGS__) +#define WARN_THROWS_WITH_AS_MESSAGE(expr, with, ex, ...) DOCTEST_WARN_THROWS_WITH_AS_MESSAGE(expr, with, ex, __VA_ARGS__) +#define WARN_NOTHROW_MESSAGE(expr, ...) DOCTEST_WARN_NOTHROW_MESSAGE(expr, __VA_ARGS__) +#define CHECK_MESSAGE(cond, ...) DOCTEST_CHECK_MESSAGE(cond, __VA_ARGS__) +#define CHECK_FALSE_MESSAGE(cond, ...) DOCTEST_CHECK_FALSE_MESSAGE(cond, __VA_ARGS__) +#define CHECK_THROWS_MESSAGE(expr, ...) DOCTEST_CHECK_THROWS_MESSAGE(expr, __VA_ARGS__) +#define CHECK_THROWS_AS_MESSAGE(expr, ex, ...) DOCTEST_CHECK_THROWS_AS_MESSAGE(expr, ex, __VA_ARGS__) +#define CHECK_THROWS_WITH_MESSAGE(expr, with, ...) DOCTEST_CHECK_THROWS_WITH_MESSAGE(expr, with, __VA_ARGS__) +#define CHECK_THROWS_WITH_AS_MESSAGE(expr, with, ex, ...) DOCTEST_CHECK_THROWS_WITH_AS_MESSAGE(expr, with, ex, __VA_ARGS__) +#define CHECK_NOTHROW_MESSAGE(expr, ...) DOCTEST_CHECK_NOTHROW_MESSAGE(expr, __VA_ARGS__) +#define REQUIRE_MESSAGE(cond, ...) DOCTEST_REQUIRE_MESSAGE(cond, __VA_ARGS__) +#define REQUIRE_FALSE_MESSAGE(cond, ...) DOCTEST_REQUIRE_FALSE_MESSAGE(cond, __VA_ARGS__) +#define REQUIRE_THROWS_MESSAGE(expr, ...) DOCTEST_REQUIRE_THROWS_MESSAGE(expr, __VA_ARGS__) +#define REQUIRE_THROWS_AS_MESSAGE(expr, ex, ...) DOCTEST_REQUIRE_THROWS_AS_MESSAGE(expr, ex, __VA_ARGS__) +#define REQUIRE_THROWS_WITH_MESSAGE(expr, with, ...) DOCTEST_REQUIRE_THROWS_WITH_MESSAGE(expr, with, __VA_ARGS__) +#define REQUIRE_THROWS_WITH_AS_MESSAGE(expr, with, ex, ...) DOCTEST_REQUIRE_THROWS_WITH_AS_MESSAGE(expr, with, ex, __VA_ARGS__) +#define REQUIRE_NOTHROW_MESSAGE(expr, ...) DOCTEST_REQUIRE_NOTHROW_MESSAGE(expr, __VA_ARGS__) + +#define SCENARIO(name) DOCTEST_SCENARIO(name) +#define SCENARIO_CLASS(name) DOCTEST_SCENARIO_CLASS(name) +#define SCENARIO_TEMPLATE(name, T, ...) DOCTEST_SCENARIO_TEMPLATE(name, T, __VA_ARGS__) +#define SCENARIO_TEMPLATE_DEFINE(name, T, id) DOCTEST_SCENARIO_TEMPLATE_DEFINE(name, T, id) +#define GIVEN(name) DOCTEST_GIVEN(name) +#define WHEN(name) DOCTEST_WHEN(name) +#define AND_WHEN(name) DOCTEST_AND_WHEN(name) +#define THEN(name) DOCTEST_THEN(name) +#define AND_THEN(name) DOCTEST_AND_THEN(name) + +#define WARN_EQ(...) DOCTEST_WARN_EQ(__VA_ARGS__) +#define CHECK_EQ(...) DOCTEST_CHECK_EQ(__VA_ARGS__) +#define REQUIRE_EQ(...) DOCTEST_REQUIRE_EQ(__VA_ARGS__) +#define WARN_NE(...) DOCTEST_WARN_NE(__VA_ARGS__) +#define CHECK_NE(...) DOCTEST_CHECK_NE(__VA_ARGS__) +#define REQUIRE_NE(...) DOCTEST_REQUIRE_NE(__VA_ARGS__) +#define WARN_GT(...) DOCTEST_WARN_GT(__VA_ARGS__) +#define CHECK_GT(...) DOCTEST_CHECK_GT(__VA_ARGS__) +#define REQUIRE_GT(...) DOCTEST_REQUIRE_GT(__VA_ARGS__) +#define WARN_LT(...) DOCTEST_WARN_LT(__VA_ARGS__) +#define CHECK_LT(...) DOCTEST_CHECK_LT(__VA_ARGS__) +#define REQUIRE_LT(...) DOCTEST_REQUIRE_LT(__VA_ARGS__) +#define WARN_GE(...) DOCTEST_WARN_GE(__VA_ARGS__) +#define CHECK_GE(...) DOCTEST_CHECK_GE(__VA_ARGS__) +#define REQUIRE_GE(...) DOCTEST_REQUIRE_GE(__VA_ARGS__) +#define WARN_LE(...) DOCTEST_WARN_LE(__VA_ARGS__) +#define CHECK_LE(...) DOCTEST_CHECK_LE(__VA_ARGS__) +#define REQUIRE_LE(...) DOCTEST_REQUIRE_LE(__VA_ARGS__) +#define WARN_UNARY(...) DOCTEST_WARN_UNARY(__VA_ARGS__) +#define CHECK_UNARY(...) DOCTEST_CHECK_UNARY(__VA_ARGS__) +#define REQUIRE_UNARY(...) DOCTEST_REQUIRE_UNARY(__VA_ARGS__) +#define WARN_UNARY_FALSE(...) DOCTEST_WARN_UNARY_FALSE(__VA_ARGS__) +#define CHECK_UNARY_FALSE(...) DOCTEST_CHECK_UNARY_FALSE(__VA_ARGS__) +#define REQUIRE_UNARY_FALSE(...) DOCTEST_REQUIRE_UNARY_FALSE(__VA_ARGS__) + +// KEPT FOR BACKWARDS COMPATIBILITY +#define FAST_WARN_EQ(...) DOCTEST_FAST_WARN_EQ(__VA_ARGS__) +#define FAST_CHECK_EQ(...) DOCTEST_FAST_CHECK_EQ(__VA_ARGS__) +#define FAST_REQUIRE_EQ(...) DOCTEST_FAST_REQUIRE_EQ(__VA_ARGS__) +#define FAST_WARN_NE(...) DOCTEST_FAST_WARN_NE(__VA_ARGS__) +#define FAST_CHECK_NE(...) DOCTEST_FAST_CHECK_NE(__VA_ARGS__) +#define FAST_REQUIRE_NE(...) DOCTEST_FAST_REQUIRE_NE(__VA_ARGS__) +#define FAST_WARN_GT(...) DOCTEST_FAST_WARN_GT(__VA_ARGS__) +#define FAST_CHECK_GT(...) DOCTEST_FAST_CHECK_GT(__VA_ARGS__) +#define FAST_REQUIRE_GT(...) DOCTEST_FAST_REQUIRE_GT(__VA_ARGS__) +#define FAST_WARN_LT(...) DOCTEST_FAST_WARN_LT(__VA_ARGS__) +#define FAST_CHECK_LT(...) DOCTEST_FAST_CHECK_LT(__VA_ARGS__) +#define FAST_REQUIRE_LT(...) DOCTEST_FAST_REQUIRE_LT(__VA_ARGS__) +#define FAST_WARN_GE(...) DOCTEST_FAST_WARN_GE(__VA_ARGS__) +#define FAST_CHECK_GE(...) DOCTEST_FAST_CHECK_GE(__VA_ARGS__) +#define FAST_REQUIRE_GE(...) DOCTEST_FAST_REQUIRE_GE(__VA_ARGS__) +#define FAST_WARN_LE(...) DOCTEST_FAST_WARN_LE(__VA_ARGS__) +#define FAST_CHECK_LE(...) DOCTEST_FAST_CHECK_LE(__VA_ARGS__) +#define FAST_REQUIRE_LE(...) DOCTEST_FAST_REQUIRE_LE(__VA_ARGS__) + +#define FAST_WARN_UNARY(...) DOCTEST_FAST_WARN_UNARY(__VA_ARGS__) +#define FAST_CHECK_UNARY(...) DOCTEST_FAST_CHECK_UNARY(__VA_ARGS__) +#define FAST_REQUIRE_UNARY(...) DOCTEST_FAST_REQUIRE_UNARY(__VA_ARGS__) +#define FAST_WARN_UNARY_FALSE(...) DOCTEST_FAST_WARN_UNARY_FALSE(__VA_ARGS__) +#define FAST_CHECK_UNARY_FALSE(...) DOCTEST_FAST_CHECK_UNARY_FALSE(__VA_ARGS__) +#define FAST_REQUIRE_UNARY_FALSE(...) DOCTEST_FAST_REQUIRE_UNARY_FALSE(__VA_ARGS__) + +#define TEST_CASE_TEMPLATE_INSTANTIATE(id, ...) DOCTEST_TEST_CASE_TEMPLATE_INSTANTIATE(id, __VA_ARGS__) + +#endif // DOCTEST_CONFIG_NO_SHORT_MACRO_NAMES + +#ifndef DOCTEST_CONFIG_DISABLE + +// this is here to clear the 'current test suite' for the current translation unit - at the top +DOCTEST_TEST_SUITE_END(); + +#endif // DOCTEST_CONFIG_DISABLE + +DOCTEST_CLANG_SUPPRESS_WARNING_POP +DOCTEST_MSVC_SUPPRESS_WARNING_POP +DOCTEST_GCC_SUPPRESS_WARNING_POP + +DOCTEST_SUPPRESS_COMMON_WARNINGS_POP + +#endif // DOCTEST_LIBRARY_INCLUDED + +#ifndef DOCTEST_SINGLE_HEADER +#define DOCTEST_SINGLE_HEADER +#endif // DOCTEST_SINGLE_HEADER + +#if defined(DOCTEST_CONFIG_IMPLEMENT) || !defined(DOCTEST_SINGLE_HEADER) + +#ifndef DOCTEST_SINGLE_HEADER +#include "doctest_fwd.h" +#endif // DOCTEST_SINGLE_HEADER + +DOCTEST_CLANG_SUPPRESS_WARNING_WITH_PUSH("-Wunused-macros") + +#ifndef DOCTEST_LIBRARY_IMPLEMENTATION +#define DOCTEST_LIBRARY_IMPLEMENTATION + +DOCTEST_CLANG_SUPPRESS_WARNING_POP + +DOCTEST_SUPPRESS_COMMON_WARNINGS_PUSH + +DOCTEST_CLANG_SUPPRESS_WARNING_PUSH +DOCTEST_CLANG_SUPPRESS_WARNING("-Wglobal-constructors") +DOCTEST_CLANG_SUPPRESS_WARNING("-Wexit-time-destructors") +DOCTEST_CLANG_SUPPRESS_WARNING("-Wsign-conversion") +DOCTEST_CLANG_SUPPRESS_WARNING("-Wshorten-64-to-32") +DOCTEST_CLANG_SUPPRESS_WARNING("-Wmissing-variable-declarations") +DOCTEST_CLANG_SUPPRESS_WARNING("-Wswitch") +DOCTEST_CLANG_SUPPRESS_WARNING("-Wswitch-enum") +DOCTEST_CLANG_SUPPRESS_WARNING("-Wcovered-switch-default") +DOCTEST_CLANG_SUPPRESS_WARNING("-Wmissing-noreturn") +DOCTEST_CLANG_SUPPRESS_WARNING("-Wdisabled-macro-expansion") +DOCTEST_CLANG_SUPPRESS_WARNING("-Wmissing-braces") +DOCTEST_CLANG_SUPPRESS_WARNING("-Wmissing-field-initializers") +DOCTEST_CLANG_SUPPRESS_WARNING("-Wunused-member-function") +DOCTEST_CLANG_SUPPRESS_WARNING("-Wnonportable-system-include-path") + +DOCTEST_GCC_SUPPRESS_WARNING_PUSH +DOCTEST_GCC_SUPPRESS_WARNING("-Wconversion") +DOCTEST_GCC_SUPPRESS_WARNING("-Wsign-conversion") +DOCTEST_GCC_SUPPRESS_WARNING("-Wmissing-field-initializers") +DOCTEST_GCC_SUPPRESS_WARNING("-Wmissing-braces") +DOCTEST_GCC_SUPPRESS_WARNING("-Wswitch") +DOCTEST_GCC_SUPPRESS_WARNING("-Wswitch-enum") +DOCTEST_GCC_SUPPRESS_WARNING("-Wswitch-default") +DOCTEST_GCC_SUPPRESS_WARNING("-Wunsafe-loop-optimizations") +DOCTEST_GCC_SUPPRESS_WARNING("-Wold-style-cast") +DOCTEST_GCC_SUPPRESS_WARNING("-Wunused-function") +DOCTEST_GCC_SUPPRESS_WARNING("-Wmultiple-inheritance") +DOCTEST_GCC_SUPPRESS_WARNING("-Wsuggest-attribute") + +DOCTEST_MSVC_SUPPRESS_WARNING_PUSH +DOCTEST_MSVC_SUPPRESS_WARNING(4267) // 'var' : conversion from 'x' to 'y', possible loss of data +DOCTEST_MSVC_SUPPRESS_WARNING(4530) // C++ exception handler used, but unwind semantics not enabled +DOCTEST_MSVC_SUPPRESS_WARNING(4577) // 'noexcept' used with no exception handling mode specified +DOCTEST_MSVC_SUPPRESS_WARNING(4774) // format string expected in argument is not a string literal +DOCTEST_MSVC_SUPPRESS_WARNING(4365) // conversion from 'int' to 'unsigned', signed/unsigned mismatch +DOCTEST_MSVC_SUPPRESS_WARNING(5039) // pointer to potentially throwing function passed to extern C +DOCTEST_MSVC_SUPPRESS_WARNING(4800) // forcing value to bool 'true' or 'false' (performance warning) +DOCTEST_MSVC_SUPPRESS_WARNING(5245) // unreferenced function with internal linkage has been removed + +DOCTEST_MAKE_STD_HEADERS_CLEAN_FROM_WARNINGS_ON_WALL_BEGIN + +// required includes - will go only in one translation unit! +#include +#include +#include +// borland (Embarcadero) compiler requires math.h and not cmath - https://github.com/doctest/doctest/pull/37 +#ifdef __BORLANDC__ +#include +#endif // __BORLANDC__ +#include +#include +#include +#include +#include +#include +#include +#include +#ifndef DOCTEST_CONFIG_NO_INCLUDE_IOSTREAM +#include +#endif // DOCTEST_CONFIG_NO_INCLUDE_IOSTREAM +#include +#include +#include +#ifndef DOCTEST_CONFIG_NO_MULTITHREADING +#include +#include +#define DOCTEST_DECLARE_MUTEX(name) std::mutex name; +#define DOCTEST_DECLARE_STATIC_MUTEX(name) static DOCTEST_DECLARE_MUTEX(name) +#define DOCTEST_LOCK_MUTEX(name) std::lock_guard DOCTEST_ANONYMOUS(DOCTEST_ANON_LOCK_)(name); +#else // DOCTEST_CONFIG_NO_MULTITHREADING +#define DOCTEST_DECLARE_MUTEX(name) +#define DOCTEST_DECLARE_STATIC_MUTEX(name) +#define DOCTEST_LOCK_MUTEX(name) +#endif // DOCTEST_CONFIG_NO_MULTITHREADING +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include + +#ifdef DOCTEST_PLATFORM_MAC +#include +#include +#include +#endif // DOCTEST_PLATFORM_MAC + +#ifdef DOCTEST_PLATFORM_WINDOWS + +// defines for a leaner windows.h +#ifndef WIN32_LEAN_AND_MEAN +#define WIN32_LEAN_AND_MEAN +#define DOCTEST_UNDEF_WIN32_LEAN_AND_MEAN +#endif // WIN32_LEAN_AND_MEAN +#ifndef NOMINMAX +#define NOMINMAX +#define DOCTEST_UNDEF_NOMINMAX +#endif // NOMINMAX + +// not sure what AfxWin.h is for - here I do what Catch does +#ifdef __AFXDLL +#include +#else +#include +#endif +#include + +#else // DOCTEST_PLATFORM_WINDOWS + +#include +#include + +#endif // DOCTEST_PLATFORM_WINDOWS + +// this is a fix for https://github.com/doctest/doctest/issues/348 +// https://mail.gnome.org/archives/xml/2012-January/msg00000.html +#if !defined(HAVE_UNISTD_H) && !defined(STDOUT_FILENO) +#define STDOUT_FILENO fileno(stdout) +#endif // HAVE_UNISTD_H + +DOCTEST_MAKE_STD_HEADERS_CLEAN_FROM_WARNINGS_ON_WALL_END + +// counts the number of elements in a C array +#define DOCTEST_COUNTOF(x) (sizeof(x) / sizeof(x[0])) + +#ifdef DOCTEST_CONFIG_DISABLE +#define DOCTEST_BRANCH_ON_DISABLED(if_disabled, if_not_disabled) if_disabled +#else // DOCTEST_CONFIG_DISABLE +#define DOCTEST_BRANCH_ON_DISABLED(if_disabled, if_not_disabled) if_not_disabled +#endif // DOCTEST_CONFIG_DISABLE + +#ifndef DOCTEST_CONFIG_OPTIONS_PREFIX +#define DOCTEST_CONFIG_OPTIONS_PREFIX "dt-" +#endif + +#ifndef DOCTEST_THREAD_LOCAL +#if defined(DOCTEST_CONFIG_NO_MULTITHREADING) || DOCTEST_MSVC && (DOCTEST_MSVC < DOCTEST_COMPILER(19, 0, 0)) +#define DOCTEST_THREAD_LOCAL +#else // DOCTEST_MSVC +#define DOCTEST_THREAD_LOCAL thread_local +#endif // DOCTEST_MSVC +#endif // DOCTEST_THREAD_LOCAL + +#ifndef DOCTEST_MULTI_LANE_ATOMICS_THREAD_LANES +#define DOCTEST_MULTI_LANE_ATOMICS_THREAD_LANES 32 +#endif + +#ifndef DOCTEST_MULTI_LANE_ATOMICS_CACHE_LINE_SIZE +#define DOCTEST_MULTI_LANE_ATOMICS_CACHE_LINE_SIZE 64 +#endif + +#ifdef DOCTEST_CONFIG_NO_UNPREFIXED_OPTIONS +#define DOCTEST_OPTIONS_PREFIX_DISPLAY DOCTEST_CONFIG_OPTIONS_PREFIX +#else +#define DOCTEST_OPTIONS_PREFIX_DISPLAY "" +#endif + +#if defined(WINAPI_FAMILY) && (WINAPI_FAMILY == WINAPI_FAMILY_APP) +#define DOCTEST_CONFIG_NO_MULTI_LANE_ATOMICS +#endif + +#ifndef DOCTEST_CDECL +#define DOCTEST_CDECL __cdecl +#endif + +namespace doctest { + +bool is_running_in_test = false; + +namespace { + using namespace detail; + + template + DOCTEST_NORETURN void throw_exception(Ex const& e) { +#ifndef DOCTEST_CONFIG_NO_EXCEPTIONS + throw e; +#else // DOCTEST_CONFIG_NO_EXCEPTIONS +#ifdef DOCTEST_CONFIG_HANDLE_EXCEPTION + DOCTEST_CONFIG_HANDLE_EXCEPTION(e); +#else // DOCTEST_CONFIG_HANDLE_EXCEPTION +#ifndef DOCTEST_CONFIG_NO_INCLUDE_IOSTREAM + std::cerr << "doctest will terminate because it needed to throw an exception.\n" + << "The message was: " << e.what() << '\n'; +#endif // DOCTEST_CONFIG_NO_INCLUDE_IOSTREAM +#endif // DOCTEST_CONFIG_HANDLE_EXCEPTION + std::terminate(); +#endif // DOCTEST_CONFIG_NO_EXCEPTIONS + } + +#ifndef DOCTEST_INTERNAL_ERROR +#define DOCTEST_INTERNAL_ERROR(msg) \ + throw_exception(std::logic_error( \ + __FILE__ ":" DOCTEST_TOSTR(__LINE__) ": Internal doctest error: " msg)) +#endif // DOCTEST_INTERNAL_ERROR + + // case insensitive strcmp + int stricmp(const char* a, const char* b) { + for(;; a++, b++) { + const int d = tolower(*a) - tolower(*b); + if(d != 0 || !*a) + return d; + } + } + + struct Endianness + { + enum Arch + { + Big, + Little + }; + + static Arch which() { + int x = 1; + // casting any data pointer to char* is allowed + auto ptr = reinterpret_cast(&x); + if(*ptr) + return Little; + return Big; + } + }; +} // namespace + +namespace detail { + DOCTEST_THREAD_LOCAL class + { + std::vector stack; + std::stringstream ss; + + public: + std::ostream* push() { + stack.push_back(ss.tellp()); + return &ss; + } + + String pop() { + if (stack.empty()) + DOCTEST_INTERNAL_ERROR("TLSS was empty when trying to pop!"); + + std::streampos pos = stack.back(); + stack.pop_back(); + unsigned sz = static_cast(ss.tellp() - pos); + ss.rdbuf()->pubseekpos(pos, std::ios::in | std::ios::out); + return String(ss, sz); + } + } g_oss; + + std::ostream* tlssPush() { + return g_oss.push(); + } + + String tlssPop() { + return g_oss.pop(); + } + +#ifndef DOCTEST_CONFIG_DISABLE + +namespace timer_large_integer +{ + +#if defined(DOCTEST_PLATFORM_WINDOWS) + using type = ULONGLONG; +#else // DOCTEST_PLATFORM_WINDOWS + using type = std::uint64_t; +#endif // DOCTEST_PLATFORM_WINDOWS +} + +using ticks_t = timer_large_integer::type; + +#ifdef DOCTEST_CONFIG_GETCURRENTTICKS + ticks_t getCurrentTicks() { return DOCTEST_CONFIG_GETCURRENTTICKS(); } +#elif defined(DOCTEST_PLATFORM_WINDOWS) + ticks_t getCurrentTicks() { + static LARGE_INTEGER hz = { {0} }, hzo = { {0} }; + if(!hz.QuadPart) { + QueryPerformanceFrequency(&hz); + QueryPerformanceCounter(&hzo); + } + LARGE_INTEGER t; + QueryPerformanceCounter(&t); + return ((t.QuadPart - hzo.QuadPart) * LONGLONG(1000000)) / hz.QuadPart; + } +#else // DOCTEST_PLATFORM_WINDOWS + ticks_t getCurrentTicks() { + timeval t; + gettimeofday(&t, nullptr); + return static_cast(t.tv_sec) * 1000000 + static_cast(t.tv_usec); + } +#endif // DOCTEST_PLATFORM_WINDOWS + + struct Timer + { + void start() { m_ticks = getCurrentTicks(); } + unsigned int getElapsedMicroseconds() const { + return static_cast(getCurrentTicks() - m_ticks); + } + //unsigned int getElapsedMilliseconds() const { + // return static_cast(getElapsedMicroseconds() / 1000); + //} + double getElapsedSeconds() const { return static_cast(getCurrentTicks() - m_ticks) / 1000000.0; } + + private: + ticks_t m_ticks = 0; + }; + +#ifdef DOCTEST_CONFIG_NO_MULTITHREADING + template + using Atomic = T; +#else // DOCTEST_CONFIG_NO_MULTITHREADING + template + using Atomic = std::atomic; +#endif // DOCTEST_CONFIG_NO_MULTITHREADING + +#if defined(DOCTEST_CONFIG_NO_MULTI_LANE_ATOMICS) || defined(DOCTEST_CONFIG_NO_MULTITHREADING) + template + using MultiLaneAtomic = Atomic; +#else // DOCTEST_CONFIG_NO_MULTI_LANE_ATOMICS + // Provides a multilane implementation of an atomic variable that supports add, sub, load, + // store. Instead of using a single atomic variable, this splits up into multiple ones, + // each sitting on a separate cache line. The goal is to provide a speedup when most + // operations are modifying. It achieves this with two properties: + // + // * Multiple atomics are used, so chance of congestion from the same atomic is reduced. + // * Each atomic sits on a separate cache line, so false sharing is reduced. + // + // The disadvantage is that there is a small overhead due to the use of TLS, and load/store + // is slower because all atomics have to be accessed. + template + class MultiLaneAtomic + { + struct CacheLineAlignedAtomic + { + Atomic atomic{}; + char padding[DOCTEST_MULTI_LANE_ATOMICS_CACHE_LINE_SIZE - sizeof(Atomic)]; + }; + CacheLineAlignedAtomic m_atomics[DOCTEST_MULTI_LANE_ATOMICS_THREAD_LANES]; + + static_assert(sizeof(CacheLineAlignedAtomic) == DOCTEST_MULTI_LANE_ATOMICS_CACHE_LINE_SIZE, + "guarantee one atomic takes exactly one cache line"); + + public: + T operator++() DOCTEST_NOEXCEPT { return fetch_add(1) + 1; } + + T operator++(int) DOCTEST_NOEXCEPT { return fetch_add(1); } + + T fetch_add(T arg, std::memory_order order = std::memory_order_seq_cst) DOCTEST_NOEXCEPT { + return myAtomic().fetch_add(arg, order); + } + + T fetch_sub(T arg, std::memory_order order = std::memory_order_seq_cst) DOCTEST_NOEXCEPT { + return myAtomic().fetch_sub(arg, order); + } + + operator T() const DOCTEST_NOEXCEPT { return load(); } + + T load(std::memory_order order = std::memory_order_seq_cst) const DOCTEST_NOEXCEPT { + auto result = T(); + for(auto const& c : m_atomics) { + result += c.atomic.load(order); + } + return result; + } + + T operator=(T desired) DOCTEST_NOEXCEPT { // lgtm [cpp/assignment-does-not-return-this] + store(desired); + return desired; + } + + void store(T desired, std::memory_order order = std::memory_order_seq_cst) DOCTEST_NOEXCEPT { + // first value becomes desired", all others become 0. + for(auto& c : m_atomics) { + c.atomic.store(desired, order); + desired = {}; + } + } + + private: + // Each thread has a different atomic that it operates on. If more than NumLanes threads + // use this, some will use the same atomic. So performance will degrade a bit, but still + // everything will work. + // + // The logic here is a bit tricky. The call should be as fast as possible, so that there + // is minimal to no overhead in determining the correct atomic for the current thread. + // + // 1. A global static counter laneCounter counts continuously up. + // 2. Each successive thread will use modulo operation of that counter so it gets an atomic + // assigned in a round-robin fashion. + // 3. This tlsLaneIdx is stored in the thread local data, so it is directly available with + // little overhead. + Atomic& myAtomic() DOCTEST_NOEXCEPT { + static Atomic laneCounter; + DOCTEST_THREAD_LOCAL size_t tlsLaneIdx = + laneCounter++ % DOCTEST_MULTI_LANE_ATOMICS_THREAD_LANES; + + return m_atomics[tlsLaneIdx].atomic; + } + }; +#endif // DOCTEST_CONFIG_NO_MULTI_LANE_ATOMICS + + // this holds both parameters from the command line and runtime data for tests + struct ContextState : ContextOptions, TestRunStats, CurrentTestCaseStats + { + MultiLaneAtomic numAssertsCurrentTest_atomic; + MultiLaneAtomic numAssertsFailedCurrentTest_atomic; + + std::vector> filters = decltype(filters)(9); // 9 different filters + + std::vector reporters_currently_used; + + assert_handler ah = nullptr; + + Timer timer; + + std::vector stringifiedContexts; // logging from INFO() due to an exception + + // stuff for subcases + bool reachedLeaf; + std::vector subcaseStack; + std::vector nextSubcaseStack; + std::unordered_set fullyTraversedSubcases; + size_t currentSubcaseDepth; + Atomic shouldLogCurrentException; + + void resetRunData() { + numTestCases = 0; + numTestCasesPassingFilters = 0; + numTestSuitesPassingFilters = 0; + numTestCasesFailed = 0; + numAsserts = 0; + numAssertsFailed = 0; + numAssertsCurrentTest = 0; + numAssertsFailedCurrentTest = 0; + } + + void finalizeTestCaseData() { + seconds = timer.getElapsedSeconds(); + + // update the non-atomic counters + numAsserts += numAssertsCurrentTest_atomic; + numAssertsFailed += numAssertsFailedCurrentTest_atomic; + numAssertsCurrentTest = numAssertsCurrentTest_atomic; + numAssertsFailedCurrentTest = numAssertsFailedCurrentTest_atomic; + + if(numAssertsFailedCurrentTest) + failure_flags |= TestCaseFailureReason::AssertFailure; + + if(Approx(currentTest->m_timeout).epsilon(DBL_EPSILON) != 0 && + Approx(seconds).epsilon(DBL_EPSILON) > currentTest->m_timeout) + failure_flags |= TestCaseFailureReason::Timeout; + + if(currentTest->m_should_fail) { + if(failure_flags) { + failure_flags |= TestCaseFailureReason::ShouldHaveFailedAndDid; + } else { + failure_flags |= TestCaseFailureReason::ShouldHaveFailedButDidnt; + } + } else if(failure_flags && currentTest->m_may_fail) { + failure_flags |= TestCaseFailureReason::CouldHaveFailedAndDid; + } else if(currentTest->m_expected_failures > 0) { + if(numAssertsFailedCurrentTest == currentTest->m_expected_failures) { + failure_flags |= TestCaseFailureReason::FailedExactlyNumTimes; + } else { + failure_flags |= TestCaseFailureReason::DidntFailExactlyNumTimes; + } + } + + bool ok_to_fail = (TestCaseFailureReason::ShouldHaveFailedAndDid & failure_flags) || + (TestCaseFailureReason::CouldHaveFailedAndDid & failure_flags) || + (TestCaseFailureReason::FailedExactlyNumTimes & failure_flags); + + // if any subcase has failed - the whole test case has failed + testCaseSuccess = !(failure_flags && !ok_to_fail); + if(!testCaseSuccess) + numTestCasesFailed++; + } + }; + + ContextState* g_cs = nullptr; + + // used to avoid locks for the debug output + // TODO: figure out if this is indeed necessary/correct - seems like either there still + // could be a race or that there wouldn't be a race even if using the context directly + DOCTEST_THREAD_LOCAL bool g_no_colors; + +#endif // DOCTEST_CONFIG_DISABLE +} // namespace detail + +char* String::allocate(size_type sz) { + if (sz <= last) { + buf[sz] = '\0'; + setLast(last - sz); + return buf; + } else { + setOnHeap(); + data.size = sz; + data.capacity = data.size + 1; + data.ptr = new char[data.capacity]; + data.ptr[sz] = '\0'; + return data.ptr; + } +} + +void String::setOnHeap() noexcept { *reinterpret_cast(&buf[last]) = 128; } +void String::setLast(size_type in) noexcept { buf[last] = char(in); } +void String::setSize(size_type sz) noexcept { + if (isOnStack()) { buf[sz] = '\0'; setLast(last - sz); } + else { data.ptr[sz] = '\0'; data.size = sz; } +} + +void String::copy(const String& other) { + if(other.isOnStack()) { + memcpy(buf, other.buf, len); + } else { + memcpy(allocate(other.data.size), other.data.ptr, other.data.size); + } +} + +String::String() noexcept { + buf[0] = '\0'; + setLast(); +} + +String::~String() { + if(!isOnStack()) + delete[] data.ptr; +} // NOLINT(clang-analyzer-cplusplus.NewDeleteLeaks) + +String::String(const char* in) + : String(in, strlen(in)) {} + +String::String(const char* in, size_type in_size) { + memcpy(allocate(in_size), in, in_size); +} + +String::String(std::istream& in, size_type in_size) { + in.read(allocate(in_size), in_size); +} + +String::String(const String& other) { copy(other); } + +String& String::operator=(const String& other) { + if(this != &other) { + if(!isOnStack()) + delete[] data.ptr; + + copy(other); + } + + return *this; +} + +String& String::operator+=(const String& other) { + const size_type my_old_size = size(); + const size_type other_size = other.size(); + const size_type total_size = my_old_size + other_size; + if(isOnStack()) { + if(total_size < len) { + // append to the current stack space + memcpy(buf + my_old_size, other.c_str(), other_size + 1); + // NOLINTNEXTLINE(clang-analyzer-cplusplus.NewDeleteLeaks) + setLast(last - total_size); + } else { + // alloc new chunk + char* temp = new char[total_size + 1]; + // copy current data to new location before writing in the union + memcpy(temp, buf, my_old_size); // skip the +1 ('\0') for speed + // update data in union + setOnHeap(); + data.size = total_size; + data.capacity = data.size + 1; + data.ptr = temp; + // transfer the rest of the data + memcpy(data.ptr + my_old_size, other.c_str(), other_size + 1); + } + } else { + if(data.capacity > total_size) { + // append to the current heap block + data.size = total_size; + memcpy(data.ptr + my_old_size, other.c_str(), other_size + 1); + } else { + // resize + data.capacity *= 2; + if(data.capacity <= total_size) + data.capacity = total_size + 1; + // alloc new chunk + char* temp = new char[data.capacity]; + // copy current data to new location before releasing it + memcpy(temp, data.ptr, my_old_size); // skip the +1 ('\0') for speed + // release old chunk + delete[] data.ptr; + // update the rest of the union members + data.size = total_size; + data.ptr = temp; + // transfer the rest of the data + memcpy(data.ptr + my_old_size, other.c_str(), other_size + 1); + } + } + + return *this; +} + +String::String(String&& other) noexcept { + memcpy(buf, other.buf, len); + other.buf[0] = '\0'; + other.setLast(); +} + +String& String::operator=(String&& other) noexcept { + if(this != &other) { + if(!isOnStack()) + delete[] data.ptr; + memcpy(buf, other.buf, len); + other.buf[0] = '\0'; + other.setLast(); + } + return *this; +} + +char String::operator[](size_type i) const { + return const_cast(this)->operator[](i); +} + +char& String::operator[](size_type i) { + if(isOnStack()) + return reinterpret_cast(buf)[i]; + return data.ptr[i]; +} + +DOCTEST_GCC_SUPPRESS_WARNING_WITH_PUSH("-Wmaybe-uninitialized") +String::size_type String::size() const { + if(isOnStack()) + return last - (size_type(buf[last]) & 31); // using "last" would work only if "len" is 32 + return data.size; +} +DOCTEST_GCC_SUPPRESS_WARNING_POP + +String::size_type String::capacity() const { + if(isOnStack()) + return len; + return data.capacity; +} + +String String::substr(size_type pos, size_type cnt) && { + cnt = std::min(cnt, size() - 1 - pos); + char* cptr = c_str(); + memmove(cptr, cptr + pos, cnt); + setSize(cnt); + return std::move(*this); +} + +String String::substr(size_type pos, size_type cnt) const & { + cnt = std::min(cnt, size() - 1 - pos); + return String{ c_str() + pos, cnt }; +} + +String::size_type String::find(char ch, size_type pos) const { + const char* begin = c_str(); + const char* end = begin + size(); + const char* it = begin + pos; + for (; it < end && *it != ch; it++); + if (it < end) { return static_cast(it - begin); } + else { return npos; } +} + +String::size_type String::rfind(char ch, size_type pos) const { + const char* begin = c_str(); + const char* it = begin + std::min(pos, size() - 1); + for (; it >= begin && *it != ch; it--); + if (it >= begin) { return static_cast(it - begin); } + else { return npos; } +} + +int String::compare(const char* other, bool no_case) const { + if(no_case) + return doctest::stricmp(c_str(), other); + return std::strcmp(c_str(), other); +} + +int String::compare(const String& other, bool no_case) const { + return compare(other.c_str(), no_case); +} + +String operator+(const String& lhs, const String& rhs) { return String(lhs) += rhs; } + +bool operator==(const String& lhs, const String& rhs) { return lhs.compare(rhs) == 0; } +bool operator!=(const String& lhs, const String& rhs) { return lhs.compare(rhs) != 0; } +bool operator< (const String& lhs, const String& rhs) { return lhs.compare(rhs) < 0; } +bool operator> (const String& lhs, const String& rhs) { return lhs.compare(rhs) > 0; } +bool operator<=(const String& lhs, const String& rhs) { return (lhs != rhs) ? lhs.compare(rhs) < 0 : true; } +bool operator>=(const String& lhs, const String& rhs) { return (lhs != rhs) ? lhs.compare(rhs) > 0 : true; } + +std::ostream& operator<<(std::ostream& s, const String& in) { return s << in.c_str(); } + +Contains::Contains(const String& str) : string(str) { } + +bool Contains::checkWith(const String& other) const { + return strstr(other.c_str(), string.c_str()) != nullptr; +} + +String toString(const Contains& in) { + return "Contains( " + in.string + " )"; +} + +bool operator==(const String& lhs, const Contains& rhs) { return rhs.checkWith(lhs); } +bool operator==(const Contains& lhs, const String& rhs) { return lhs.checkWith(rhs); } +bool operator!=(const String& lhs, const Contains& rhs) { return !rhs.checkWith(lhs); } +bool operator!=(const Contains& lhs, const String& rhs) { return !lhs.checkWith(rhs); } + +namespace { + void color_to_stream(std::ostream&, Color::Enum) DOCTEST_BRANCH_ON_DISABLED({}, ;) +} // namespace + +namespace Color { + std::ostream& operator<<(std::ostream& s, Color::Enum code) { + color_to_stream(s, code); + return s; + } +} // namespace Color + +// clang-format off +const char* assertString(assertType::Enum at) { + DOCTEST_MSVC_SUPPRESS_WARNING_WITH_PUSH(4061) // enum 'x' in switch of enum 'y' is not explicitly handled + #define DOCTEST_GENERATE_ASSERT_TYPE_CASE(assert_type) case assertType::DT_ ## assert_type: return #assert_type + #define DOCTEST_GENERATE_ASSERT_TYPE_CASES(assert_type) \ + DOCTEST_GENERATE_ASSERT_TYPE_CASE(WARN_ ## assert_type); \ + DOCTEST_GENERATE_ASSERT_TYPE_CASE(CHECK_ ## assert_type); \ + DOCTEST_GENERATE_ASSERT_TYPE_CASE(REQUIRE_ ## assert_type) + switch(at) { + DOCTEST_GENERATE_ASSERT_TYPE_CASE(WARN); + DOCTEST_GENERATE_ASSERT_TYPE_CASE(CHECK); + DOCTEST_GENERATE_ASSERT_TYPE_CASE(REQUIRE); + + DOCTEST_GENERATE_ASSERT_TYPE_CASES(FALSE); + + DOCTEST_GENERATE_ASSERT_TYPE_CASES(THROWS); + + DOCTEST_GENERATE_ASSERT_TYPE_CASES(THROWS_AS); + + DOCTEST_GENERATE_ASSERT_TYPE_CASES(THROWS_WITH); + + DOCTEST_GENERATE_ASSERT_TYPE_CASES(THROWS_WITH_AS); + + DOCTEST_GENERATE_ASSERT_TYPE_CASES(NOTHROW); + + DOCTEST_GENERATE_ASSERT_TYPE_CASES(EQ); + DOCTEST_GENERATE_ASSERT_TYPE_CASES(NE); + DOCTEST_GENERATE_ASSERT_TYPE_CASES(GT); + DOCTEST_GENERATE_ASSERT_TYPE_CASES(LT); + DOCTEST_GENERATE_ASSERT_TYPE_CASES(GE); + DOCTEST_GENERATE_ASSERT_TYPE_CASES(LE); + + DOCTEST_GENERATE_ASSERT_TYPE_CASES(UNARY); + DOCTEST_GENERATE_ASSERT_TYPE_CASES(UNARY_FALSE); + + default: DOCTEST_INTERNAL_ERROR("Tried stringifying invalid assert type!"); + } + DOCTEST_MSVC_SUPPRESS_WARNING_POP +} +// clang-format on + +const char* failureString(assertType::Enum at) { + if(at & assertType::is_warn) //!OCLINT bitwise operator in conditional + return "WARNING"; + if(at & assertType::is_check) //!OCLINT bitwise operator in conditional + return "ERROR"; + if(at & assertType::is_require) //!OCLINT bitwise operator in conditional + return "FATAL ERROR"; + return ""; +} + +DOCTEST_CLANG_SUPPRESS_WARNING_WITH_PUSH("-Wnull-dereference") +DOCTEST_GCC_SUPPRESS_WARNING_WITH_PUSH("-Wnull-dereference") +// depending on the current options this will remove the path of filenames +const char* skipPathFromFilename(const char* file) { +#ifndef DOCTEST_CONFIG_DISABLE + if(getContextOptions()->no_path_in_filenames) { + auto back = std::strrchr(file, '\\'); + auto forward = std::strrchr(file, '/'); + if(back || forward) { + if(back > forward) + forward = back; + return forward + 1; + } + } +#endif // DOCTEST_CONFIG_DISABLE + return file; +} +DOCTEST_CLANG_SUPPRESS_WARNING_POP +DOCTEST_GCC_SUPPRESS_WARNING_POP + +bool SubcaseSignature::operator==(const SubcaseSignature& other) const { + return m_line == other.m_line + && std::strcmp(m_file, other.m_file) == 0 + && m_name == other.m_name; +} + +bool SubcaseSignature::operator<(const SubcaseSignature& other) const { + if(m_line != other.m_line) + return m_line < other.m_line; + if(std::strcmp(m_file, other.m_file) != 0) + return std::strcmp(m_file, other.m_file) < 0; + return m_name.compare(other.m_name) < 0; +} + +DOCTEST_DEFINE_INTERFACE(IContextScope) + +namespace detail { + void filldata::fill(std::ostream* stream, const void* in) { + if (in) { *stream << in; } + else { *stream << "nullptr"; } + } + + template + String toStreamLit(T t) { + std::ostream* os = tlssPush(); + os->operator<<(t); + return tlssPop(); + } +} + +#ifdef DOCTEST_CONFIG_TREAT_CHAR_STAR_AS_STRING +String toString(const char* in) { return String("\"") + (in ? in : "{null string}") + "\""; } +#endif // DOCTEST_CONFIG_TREAT_CHAR_STAR_AS_STRING + +#if DOCTEST_MSVC >= DOCTEST_COMPILER(19, 20, 0) +// see this issue on why this is needed: https://github.com/doctest/doctest/issues/183 +String toString(const std::string& in) { return in.c_str(); } +#endif // VS 2019 + +String toString(String in) { return in; } + +String toString(std::nullptr_t) { return "nullptr"; } + +String toString(bool in) { return in ? "true" : "false"; } + +String toString(float in) { return toStreamLit(in); } +String toString(double in) { return toStreamLit(in); } +String toString(double long in) { return toStreamLit(in); } + +String toString(char in) { return toStreamLit(static_cast(in)); } +String toString(char signed in) { return toStreamLit(static_cast(in)); } +String toString(char unsigned in) { return toStreamLit(static_cast(in)); } +String toString(short in) { return toStreamLit(in); } +String toString(short unsigned in) { return toStreamLit(in); } +String toString(signed in) { return toStreamLit(in); } +String toString(unsigned in) { return toStreamLit(in); } +String toString(long in) { return toStreamLit(in); } +String toString(long unsigned in) { return toStreamLit(in); } +String toString(long long in) { return toStreamLit(in); } +String toString(long long unsigned in) { return toStreamLit(in); } + +Approx::Approx(double value) + : m_epsilon(static_cast(std::numeric_limits::epsilon()) * 100) + , m_scale(1.0) + , m_value(value) {} + +Approx Approx::operator()(double value) const { + Approx approx(value); + approx.epsilon(m_epsilon); + approx.scale(m_scale); + return approx; +} + +Approx& Approx::epsilon(double newEpsilon) { + m_epsilon = newEpsilon; + return *this; +} +Approx& Approx::scale(double newScale) { + m_scale = newScale; + return *this; +} + +bool operator==(double lhs, const Approx& rhs) { + // Thanks to Richard Harris for his help refining this formula + return std::fabs(lhs - rhs.m_value) < + rhs.m_epsilon * (rhs.m_scale + std::max(std::fabs(lhs), std::fabs(rhs.m_value))); +} +bool operator==(const Approx& lhs, double rhs) { return operator==(rhs, lhs); } +bool operator!=(double lhs, const Approx& rhs) { return !operator==(lhs, rhs); } +bool operator!=(const Approx& lhs, double rhs) { return !operator==(rhs, lhs); } +bool operator<=(double lhs, const Approx& rhs) { return lhs < rhs.m_value || lhs == rhs; } +bool operator<=(const Approx& lhs, double rhs) { return lhs.m_value < rhs || lhs == rhs; } +bool operator>=(double lhs, const Approx& rhs) { return lhs > rhs.m_value || lhs == rhs; } +bool operator>=(const Approx& lhs, double rhs) { return lhs.m_value > rhs || lhs == rhs; } +bool operator<(double lhs, const Approx& rhs) { return lhs < rhs.m_value && lhs != rhs; } +bool operator<(const Approx& lhs, double rhs) { return lhs.m_value < rhs && lhs != rhs; } +bool operator>(double lhs, const Approx& rhs) { return lhs > rhs.m_value && lhs != rhs; } +bool operator>(const Approx& lhs, double rhs) { return lhs.m_value > rhs && lhs != rhs; } + +String toString(const Approx& in) { + return "Approx( " + doctest::toString(in.m_value) + " )"; +} +const ContextOptions* getContextOptions() { return DOCTEST_BRANCH_ON_DISABLED(nullptr, g_cs); } + +DOCTEST_MSVC_SUPPRESS_WARNING_WITH_PUSH(4738) +template +IsNaN::operator bool() const { + return std::isnan(value) ^ flipped; +} +DOCTEST_MSVC_SUPPRESS_WARNING_POP +template struct DOCTEST_INTERFACE_DEF IsNaN; +template struct DOCTEST_INTERFACE_DEF IsNaN; +template struct DOCTEST_INTERFACE_DEF IsNaN; +template +String toString(IsNaN in) { return String(in.flipped ? "! " : "") + "IsNaN( " + doctest::toString(in.value) + " )"; } +String toString(IsNaN in) { return toString(in); } +String toString(IsNaN in) { return toString(in); } +String toString(IsNaN in) { return toString(in); } + +} // namespace doctest + +#ifdef DOCTEST_CONFIG_DISABLE +namespace doctest { +Context::Context(int, const char* const*) {} +Context::~Context() = default; +void Context::applyCommandLine(int, const char* const*) {} +void Context::addFilter(const char*, const char*) {} +void Context::clearFilters() {} +void Context::setOption(const char*, bool) {} +void Context::setOption(const char*, int) {} +void Context::setOption(const char*, const char*) {} +bool Context::shouldExit() { return false; } +void Context::setAsDefaultForAssertsOutOfTestCases() {} +void Context::setAssertHandler(detail::assert_handler) {} +void Context::setCout(std::ostream*) {} +int Context::run() { return 0; } + +int IReporter::get_num_active_contexts() { return 0; } +const IContextScope* const* IReporter::get_active_contexts() { return nullptr; } +int IReporter::get_num_stringified_contexts() { return 0; } +const String* IReporter::get_stringified_contexts() { return nullptr; } + +int registerReporter(const char*, int, IReporter*) { return 0; } + +} // namespace doctest +#else // DOCTEST_CONFIG_DISABLE + +#if !defined(DOCTEST_CONFIG_COLORS_NONE) +#if !defined(DOCTEST_CONFIG_COLORS_WINDOWS) && !defined(DOCTEST_CONFIG_COLORS_ANSI) +#ifdef DOCTEST_PLATFORM_WINDOWS +#define DOCTEST_CONFIG_COLORS_WINDOWS +#else // linux +#define DOCTEST_CONFIG_COLORS_ANSI +#endif // platform +#endif // DOCTEST_CONFIG_COLORS_WINDOWS && DOCTEST_CONFIG_COLORS_ANSI +#endif // DOCTEST_CONFIG_COLORS_NONE + +namespace doctest_detail_test_suite_ns { +// holds the current test suite +doctest::detail::TestSuite& getCurrentTestSuite() { + static doctest::detail::TestSuite data{}; + return data; +} +} // namespace doctest_detail_test_suite_ns + +namespace doctest { +namespace { + // the int (priority) is part of the key for automatic sorting - sadly one can register a + // reporter with a duplicate name and a different priority but hopefully that won't happen often :| + using reporterMap = std::map, reporterCreatorFunc>; + + reporterMap& getReporters() { + static reporterMap data; + return data; + } + reporterMap& getListeners() { + static reporterMap data; + return data; + } +} // namespace +namespace detail { +#define DOCTEST_ITERATE_THROUGH_REPORTERS(function, ...) \ + for(auto& curr_rep : g_cs->reporters_currently_used) \ + curr_rep->function(__VA_ARGS__) + + bool checkIfShouldThrow(assertType::Enum at) { + if(at & assertType::is_require) //!OCLINT bitwise operator in conditional + return true; + + if((at & assertType::is_check) //!OCLINT bitwise operator in conditional + && getContextOptions()->abort_after > 0 && + (g_cs->numAssertsFailed + g_cs->numAssertsFailedCurrentTest_atomic) >= + getContextOptions()->abort_after) + return true; + + return false; + } + +#ifndef DOCTEST_CONFIG_NO_EXCEPTIONS + DOCTEST_NORETURN void throwException() { + g_cs->shouldLogCurrentException = false; + throw TestFailureException(); // NOLINT(hicpp-exception-baseclass) + } +#else // DOCTEST_CONFIG_NO_EXCEPTIONS + void throwException() {} +#endif // DOCTEST_CONFIG_NO_EXCEPTIONS +} // namespace detail + +namespace { + using namespace detail; + // matching of a string against a wildcard mask (case sensitivity configurable) taken from + // https://www.codeproject.com/Articles/1088/Wildcard-string-compare-globbing + int wildcmp(const char* str, const char* wild, bool caseSensitive) { + const char* cp = str; + const char* mp = wild; + + while((*str) && (*wild != '*')) { + if((caseSensitive ? (*wild != *str) : (tolower(*wild) != tolower(*str))) && + (*wild != '?')) { + return 0; + } + wild++; + str++; + } + + while(*str) { + if(*wild == '*') { + if(!*++wild) { + return 1; + } + mp = wild; + cp = str + 1; + } else if((caseSensitive ? (*wild == *str) : (tolower(*wild) == tolower(*str))) || + (*wild == '?')) { + wild++; + str++; + } else { + wild = mp; //!OCLINT parameter reassignment + str = cp++; //!OCLINT parameter reassignment + } + } + + while(*wild == '*') { + wild++; + } + return !*wild; + } + + // checks if the name matches any of the filters (and can be configured what to do when empty) + bool matchesAny(const char* name, const std::vector& filters, bool matchEmpty, + bool caseSensitive) { + if (filters.empty() && matchEmpty) + return true; + for (auto& curr : filters) + if (wildcmp(name, curr.c_str(), caseSensitive)) + return true; + return false; + } + + DOCTEST_NO_SANITIZE_INTEGER + unsigned long long hash(unsigned long long a, unsigned long long b) { + return (a << 5) + b; + } + + // C string hash function (djb2) - taken from http://www.cse.yorku.ca/~oz/hash.html + DOCTEST_NO_SANITIZE_INTEGER + unsigned long long hash(const char* str) { + unsigned long long hash = 5381; + char c; + while ((c = *str++)) + hash = ((hash << 5) + hash) + c; // hash * 33 + c + return hash; + } + + unsigned long long hash(const SubcaseSignature& sig) { + return hash(hash(hash(sig.m_file), hash(sig.m_name.c_str())), sig.m_line); + } + + unsigned long long hash(const std::vector& sigs, size_t count) { + unsigned long long running = 0; + auto end = sigs.begin() + count; + for (auto it = sigs.begin(); it != end; it++) { + running = hash(running, hash(*it)); + } + return running; + } + + unsigned long long hash(const std::vector& sigs) { + unsigned long long running = 0; + for (const SubcaseSignature& sig : sigs) { + running = hash(running, hash(sig)); + } + return running; + } +} // namespace +namespace detail { + bool Subcase::checkFilters() { + if (g_cs->subcaseStack.size() < size_t(g_cs->subcase_filter_levels)) { + if (!matchesAny(m_signature.m_name.c_str(), g_cs->filters[6], true, g_cs->case_sensitive)) + return true; + if (matchesAny(m_signature.m_name.c_str(), g_cs->filters[7], false, g_cs->case_sensitive)) + return true; + } + return false; + } + + Subcase::Subcase(const String& name, const char* file, int line) + : m_signature({name, file, line}) { + if (!g_cs->reachedLeaf) { + if (g_cs->nextSubcaseStack.size() <= g_cs->subcaseStack.size() + || g_cs->nextSubcaseStack[g_cs->subcaseStack.size()] == m_signature) { + // Going down. + if (checkFilters()) { return; } + + g_cs->subcaseStack.push_back(m_signature); + g_cs->currentSubcaseDepth++; + m_entered = true; + DOCTEST_ITERATE_THROUGH_REPORTERS(subcase_start, m_signature); + } + } else { + if (g_cs->subcaseStack[g_cs->currentSubcaseDepth] == m_signature) { + // This subcase is reentered via control flow. + g_cs->currentSubcaseDepth++; + m_entered = true; + DOCTEST_ITERATE_THROUGH_REPORTERS(subcase_start, m_signature); + } else if (g_cs->nextSubcaseStack.size() <= g_cs->currentSubcaseDepth + && g_cs->fullyTraversedSubcases.find(hash(hash(g_cs->subcaseStack, g_cs->currentSubcaseDepth), hash(m_signature))) + == g_cs->fullyTraversedSubcases.end()) { + if (checkFilters()) { return; } + // This subcase is part of the one to be executed next. + g_cs->nextSubcaseStack.clear(); + g_cs->nextSubcaseStack.insert(g_cs->nextSubcaseStack.end(), + g_cs->subcaseStack.begin(), g_cs->subcaseStack.begin() + g_cs->currentSubcaseDepth); + g_cs->nextSubcaseStack.push_back(m_signature); + } + } + } + + DOCTEST_MSVC_SUPPRESS_WARNING_WITH_PUSH(4996) // std::uncaught_exception is deprecated in C++17 + DOCTEST_GCC_SUPPRESS_WARNING_WITH_PUSH("-Wdeprecated-declarations") + DOCTEST_CLANG_SUPPRESS_WARNING_WITH_PUSH("-Wdeprecated-declarations") + + Subcase::~Subcase() { + if (m_entered) { + g_cs->currentSubcaseDepth--; + + if (!g_cs->reachedLeaf) { + // Leaf. + g_cs->fullyTraversedSubcases.insert(hash(g_cs->subcaseStack)); + g_cs->nextSubcaseStack.clear(); + g_cs->reachedLeaf = true; + } else if (g_cs->nextSubcaseStack.empty()) { + // All children are finished. + g_cs->fullyTraversedSubcases.insert(hash(g_cs->subcaseStack)); + } + +#if defined(__cpp_lib_uncaught_exceptions) && __cpp_lib_uncaught_exceptions >= 201411L && (!defined(__MAC_OS_X_VERSION_MIN_REQUIRED) || __MAC_OS_X_VERSION_MIN_REQUIRED >= 101200) + if(std::uncaught_exceptions() > 0 +#else + if(std::uncaught_exception() +#endif + && g_cs->shouldLogCurrentException) { + DOCTEST_ITERATE_THROUGH_REPORTERS( + test_case_exception, {"exception thrown in subcase - will translate later " + "when the whole test case has been exited (cannot " + "translate while there is an active exception)", + false}); + g_cs->shouldLogCurrentException = false; + } + + DOCTEST_ITERATE_THROUGH_REPORTERS(subcase_end, DOCTEST_EMPTY); + } + } + + DOCTEST_CLANG_SUPPRESS_WARNING_POP + DOCTEST_GCC_SUPPRESS_WARNING_POP + DOCTEST_MSVC_SUPPRESS_WARNING_POP + + Subcase::operator bool() const { return m_entered; } + + Result::Result(bool passed, const String& decomposition) + : m_passed(passed) + , m_decomp(decomposition) {} + + ExpressionDecomposer::ExpressionDecomposer(assertType::Enum at) + : m_at(at) {} + + TestSuite& TestSuite::operator*(const char* in) { + m_test_suite = in; + return *this; + } + + TestCase::TestCase(funcType test, const char* file, unsigned line, const TestSuite& test_suite, + const String& type, int template_id) { + m_file = file; + m_line = line; + m_name = nullptr; // will be later overridden in operator* + m_test_suite = test_suite.m_test_suite; + m_description = test_suite.m_description; + m_skip = test_suite.m_skip; + m_no_breaks = test_suite.m_no_breaks; + m_no_output = test_suite.m_no_output; + m_may_fail = test_suite.m_may_fail; + m_should_fail = test_suite.m_should_fail; + m_expected_failures = test_suite.m_expected_failures; + m_timeout = test_suite.m_timeout; + + m_test = test; + m_type = type; + m_template_id = template_id; + } + + TestCase::TestCase(const TestCase& other) + : TestCaseData() { + *this = other; + } + + DOCTEST_MSVC_SUPPRESS_WARNING_WITH_PUSH(26434) // hides a non-virtual function + TestCase& TestCase::operator=(const TestCase& other) { + TestCaseData::operator=(other); + m_test = other.m_test; + m_type = other.m_type; + m_template_id = other.m_template_id; + m_full_name = other.m_full_name; + + if(m_template_id != -1) + m_name = m_full_name.c_str(); + return *this; + } + DOCTEST_MSVC_SUPPRESS_WARNING_POP + + TestCase& TestCase::operator*(const char* in) { + m_name = in; + // make a new name with an appended type for templated test case + if(m_template_id != -1) { + m_full_name = String(m_name) + "<" + m_type + ">"; + // redirect the name to point to the newly constructed full name + m_name = m_full_name.c_str(); + } + return *this; + } + + bool TestCase::operator<(const TestCase& other) const { + // this will be used only to differentiate between test cases - not relevant for sorting + if(m_line != other.m_line) + return m_line < other.m_line; + const int name_cmp = strcmp(m_name, other.m_name); + if(name_cmp != 0) + return name_cmp < 0; + const int file_cmp = m_file.compare(other.m_file); + if(file_cmp != 0) + return file_cmp < 0; + return m_template_id < other.m_template_id; + } + + // all the registered tests + std::set& getRegisteredTests() { + static std::set data; + return data; + } +} // namespace detail +namespace { + using namespace detail; + // for sorting tests by file/line + bool fileOrderComparator(const TestCase* lhs, const TestCase* rhs) { + // this is needed because MSVC gives different case for drive letters + // for __FILE__ when evaluated in a header and a source file + const int res = lhs->m_file.compare(rhs->m_file, bool(DOCTEST_MSVC)); + if(res != 0) + return res < 0; + if(lhs->m_line != rhs->m_line) + return lhs->m_line < rhs->m_line; + return lhs->m_template_id < rhs->m_template_id; + } + + // for sorting tests by suite/file/line + bool suiteOrderComparator(const TestCase* lhs, const TestCase* rhs) { + const int res = std::strcmp(lhs->m_test_suite, rhs->m_test_suite); + if(res != 0) + return res < 0; + return fileOrderComparator(lhs, rhs); + } + + // for sorting tests by name/suite/file/line + bool nameOrderComparator(const TestCase* lhs, const TestCase* rhs) { + const int res = std::strcmp(lhs->m_name, rhs->m_name); + if(res != 0) + return res < 0; + return suiteOrderComparator(lhs, rhs); + } + + DOCTEST_CLANG_SUPPRESS_WARNING_WITH_PUSH("-Wdeprecated-declarations") + void color_to_stream(std::ostream& s, Color::Enum code) { + static_cast(s); // for DOCTEST_CONFIG_COLORS_NONE or DOCTEST_CONFIG_COLORS_WINDOWS + static_cast(code); // for DOCTEST_CONFIG_COLORS_NONE +#ifdef DOCTEST_CONFIG_COLORS_ANSI + if(g_no_colors || + (isatty(STDOUT_FILENO) == false && getContextOptions()->force_colors == false)) + return; + + auto col = ""; + // clang-format off + switch(code) { //!OCLINT missing break in switch statement / unnecessary default statement in covered switch statement + case Color::Red: col = "[0;31m"; break; + case Color::Green: col = "[0;32m"; break; + case Color::Blue: col = "[0;34m"; break; + case Color::Cyan: col = "[0;36m"; break; + case Color::Yellow: col = "[0;33m"; break; + case Color::Grey: col = "[1;30m"; break; + case Color::LightGrey: col = "[0;37m"; break; + case Color::BrightRed: col = "[1;31m"; break; + case Color::BrightGreen: col = "[1;32m"; break; + case Color::BrightWhite: col = "[1;37m"; break; + case Color::Bright: // invalid + case Color::None: + case Color::White: + default: col = "[0m"; + } + // clang-format on + s << "\033" << col; +#endif // DOCTEST_CONFIG_COLORS_ANSI + +#ifdef DOCTEST_CONFIG_COLORS_WINDOWS + if(g_no_colors || + (_isatty(_fileno(stdout)) == false && getContextOptions()->force_colors == false)) + return; + + static struct ConsoleHelper { + HANDLE stdoutHandle; + WORD origFgAttrs; + WORD origBgAttrs; + + ConsoleHelper() { + stdoutHandle = GetStdHandle(STD_OUTPUT_HANDLE); + CONSOLE_SCREEN_BUFFER_INFO csbiInfo; + GetConsoleScreenBufferInfo(stdoutHandle, &csbiInfo); + origFgAttrs = csbiInfo.wAttributes & ~(BACKGROUND_GREEN | BACKGROUND_RED | + BACKGROUND_BLUE | BACKGROUND_INTENSITY); + origBgAttrs = csbiInfo.wAttributes & ~(FOREGROUND_GREEN | FOREGROUND_RED | + FOREGROUND_BLUE | FOREGROUND_INTENSITY); + } + } ch; + +#define DOCTEST_SET_ATTR(x) SetConsoleTextAttribute(ch.stdoutHandle, x | ch.origBgAttrs) + + // clang-format off + switch (code) { + case Color::White: DOCTEST_SET_ATTR(FOREGROUND_GREEN | FOREGROUND_RED | FOREGROUND_BLUE); break; + case Color::Red: DOCTEST_SET_ATTR(FOREGROUND_RED); break; + case Color::Green: DOCTEST_SET_ATTR(FOREGROUND_GREEN); break; + case Color::Blue: DOCTEST_SET_ATTR(FOREGROUND_BLUE); break; + case Color::Cyan: DOCTEST_SET_ATTR(FOREGROUND_BLUE | FOREGROUND_GREEN); break; + case Color::Yellow: DOCTEST_SET_ATTR(FOREGROUND_RED | FOREGROUND_GREEN); break; + case Color::Grey: DOCTEST_SET_ATTR(0); break; + case Color::LightGrey: DOCTEST_SET_ATTR(FOREGROUND_INTENSITY); break; + case Color::BrightRed: DOCTEST_SET_ATTR(FOREGROUND_INTENSITY | FOREGROUND_RED); break; + case Color::BrightGreen: DOCTEST_SET_ATTR(FOREGROUND_INTENSITY | FOREGROUND_GREEN); break; + case Color::BrightWhite: DOCTEST_SET_ATTR(FOREGROUND_INTENSITY | FOREGROUND_GREEN | FOREGROUND_RED | FOREGROUND_BLUE); break; + case Color::None: + case Color::Bright: // invalid + default: DOCTEST_SET_ATTR(ch.origFgAttrs); + } + // clang-format on +#endif // DOCTEST_CONFIG_COLORS_WINDOWS + } + DOCTEST_CLANG_SUPPRESS_WARNING_POP + + std::vector& getExceptionTranslators() { + static std::vector data; + return data; + } + + String translateActiveException() { +#ifndef DOCTEST_CONFIG_NO_EXCEPTIONS + String res; + auto& translators = getExceptionTranslators(); + for(auto& curr : translators) + if(curr->translate(res)) + return res; + // clang-format off + DOCTEST_GCC_SUPPRESS_WARNING_WITH_PUSH("-Wcatch-value") + try { + throw; + } catch(std::exception& ex) { + return ex.what(); + } catch(std::string& msg) { + return msg.c_str(); + } catch(const char* msg) { + return msg; + } catch(...) { + return "unknown exception"; + } + DOCTEST_GCC_SUPPRESS_WARNING_POP +// clang-format on +#else // DOCTEST_CONFIG_NO_EXCEPTIONS + return ""; +#endif // DOCTEST_CONFIG_NO_EXCEPTIONS + } +} // namespace + +namespace detail { + // used by the macros for registering tests + int regTest(const TestCase& tc) { + getRegisteredTests().insert(tc); + return 0; + } + + // sets the current test suite + int setTestSuite(const TestSuite& ts) { + doctest_detail_test_suite_ns::getCurrentTestSuite() = ts; + return 0; + } + +#ifdef DOCTEST_IS_DEBUGGER_ACTIVE + bool isDebuggerActive() { return DOCTEST_IS_DEBUGGER_ACTIVE(); } +#else // DOCTEST_IS_DEBUGGER_ACTIVE +#ifdef DOCTEST_PLATFORM_LINUX + class ErrnoGuard { + public: + ErrnoGuard() : m_oldErrno(errno) {} + ~ErrnoGuard() { errno = m_oldErrno; } + private: + int m_oldErrno; + }; + // See the comments in Catch2 for the reasoning behind this implementation: + // https://github.com/catchorg/Catch2/blob/v2.13.1/include/internal/catch_debugger.cpp#L79-L102 + bool isDebuggerActive() { + ErrnoGuard guard; + std::ifstream in("/proc/self/status"); + for(std::string line; std::getline(in, line);) { + static const int PREFIX_LEN = 11; + if(line.compare(0, PREFIX_LEN, "TracerPid:\t") == 0) { + return line.length() > PREFIX_LEN && line[PREFIX_LEN] != '0'; + } + } + return false; + } +#elif defined(DOCTEST_PLATFORM_MAC) + // The following function is taken directly from the following technical note: + // https://developer.apple.com/library/archive/qa/qa1361/_index.html + // Returns true if the current process is being debugged (either + // running under the debugger or has a debugger attached post facto). + bool isDebuggerActive() { + int mib[4]; + kinfo_proc info; + size_t size; + // Initialize the flags so that, if sysctl fails for some bizarre + // reason, we get a predictable result. + info.kp_proc.p_flag = 0; + // Initialize mib, which tells sysctl the info we want, in this case + // we're looking for information about a specific process ID. + mib[0] = CTL_KERN; + mib[1] = KERN_PROC; + mib[2] = KERN_PROC_PID; + mib[3] = getpid(); + // Call sysctl. + size = sizeof(info); + if(sysctl(mib, DOCTEST_COUNTOF(mib), &info, &size, 0, 0) != 0) { + std::cerr << "\nCall to sysctl failed - unable to determine if debugger is active **\n"; + return false; + } + // We're being debugged if the P_TRACED flag is set. + return ((info.kp_proc.p_flag & P_TRACED) != 0); + } +#elif DOCTEST_MSVC || defined(__MINGW32__) || defined(__MINGW64__) + bool isDebuggerActive() { return ::IsDebuggerPresent() != 0; } +#else + bool isDebuggerActive() { return false; } +#endif // Platform +#endif // DOCTEST_IS_DEBUGGER_ACTIVE + + void registerExceptionTranslatorImpl(const IExceptionTranslator* et) { + if(std::find(getExceptionTranslators().begin(), getExceptionTranslators().end(), et) == + getExceptionTranslators().end()) + getExceptionTranslators().push_back(et); + } + + DOCTEST_THREAD_LOCAL std::vector g_infoContexts; // for logging with INFO() + + ContextScopeBase::ContextScopeBase() { + g_infoContexts.push_back(this); + } + + ContextScopeBase::ContextScopeBase(ContextScopeBase&& other) noexcept { + if (other.need_to_destroy) { + other.destroy(); + } + other.need_to_destroy = false; + g_infoContexts.push_back(this); + } + + DOCTEST_MSVC_SUPPRESS_WARNING_WITH_PUSH(4996) // std::uncaught_exception is deprecated in C++17 + DOCTEST_GCC_SUPPRESS_WARNING_WITH_PUSH("-Wdeprecated-declarations") + DOCTEST_CLANG_SUPPRESS_WARNING_WITH_PUSH("-Wdeprecated-declarations") + + // destroy cannot be inlined into the destructor because that would mean calling stringify after + // ContextScope has been destroyed (base class destructors run after derived class destructors). + // Instead, ContextScope calls this method directly from its destructor. + void ContextScopeBase::destroy() { +#if defined(__cpp_lib_uncaught_exceptions) && __cpp_lib_uncaught_exceptions >= 201411L && (!defined(__MAC_OS_X_VERSION_MIN_REQUIRED) || __MAC_OS_X_VERSION_MIN_REQUIRED >= 101200) + if(std::uncaught_exceptions() > 0) { +#else + if(std::uncaught_exception()) { +#endif + std::ostringstream s; + this->stringify(&s); + g_cs->stringifiedContexts.push_back(s.str().c_str()); + } + g_infoContexts.pop_back(); + } + + DOCTEST_CLANG_SUPPRESS_WARNING_POP + DOCTEST_GCC_SUPPRESS_WARNING_POP + DOCTEST_MSVC_SUPPRESS_WARNING_POP +} // namespace detail +namespace { + using namespace detail; + +#if !defined(DOCTEST_CONFIG_POSIX_SIGNALS) && !defined(DOCTEST_CONFIG_WINDOWS_SEH) + struct FatalConditionHandler + { + static void reset() {} + static void allocateAltStackMem() {} + static void freeAltStackMem() {} + }; +#else // DOCTEST_CONFIG_POSIX_SIGNALS || DOCTEST_CONFIG_WINDOWS_SEH + + void reportFatal(const std::string&); + +#ifdef DOCTEST_PLATFORM_WINDOWS + + struct SignalDefs + { + DWORD id; + const char* name; + }; + // There is no 1-1 mapping between signals and windows exceptions. + // Windows can easily distinguish between SO and SigSegV, + // but SigInt, SigTerm, etc are handled differently. + SignalDefs signalDefs[] = { + {static_cast(EXCEPTION_ILLEGAL_INSTRUCTION), + "SIGILL - Illegal instruction signal"}, + {static_cast(EXCEPTION_STACK_OVERFLOW), "SIGSEGV - Stack overflow"}, + {static_cast(EXCEPTION_ACCESS_VIOLATION), + "SIGSEGV - Segmentation violation signal"}, + {static_cast(EXCEPTION_INT_DIVIDE_BY_ZERO), "Divide by zero error"}, + }; + + struct FatalConditionHandler + { + static LONG CALLBACK handleException(PEXCEPTION_POINTERS ExceptionInfo) { + // Multiple threads may enter this filter/handler at once. We want the error message to be printed on the + // console just once no matter how many threads have crashed. + DOCTEST_DECLARE_STATIC_MUTEX(mutex) + static bool execute = true; + { + DOCTEST_LOCK_MUTEX(mutex) + if(execute) { + bool reported = false; + for(size_t i = 0; i < DOCTEST_COUNTOF(signalDefs); ++i) { + if(ExceptionInfo->ExceptionRecord->ExceptionCode == signalDefs[i].id) { + reportFatal(signalDefs[i].name); + reported = true; + break; + } + } + if(reported == false) + reportFatal("Unhandled SEH exception caught"); + if(isDebuggerActive() && !g_cs->no_breaks) + DOCTEST_BREAK_INTO_DEBUGGER(); + } + execute = false; + } + std::exit(EXIT_FAILURE); + } + + static void allocateAltStackMem() {} + static void freeAltStackMem() {} + + FatalConditionHandler() { + isSet = true; + // 32k seems enough for doctest to handle stack overflow, + // but the value was found experimentally, so there is no strong guarantee + guaranteeSize = 32 * 1024; + // Register an unhandled exception filter + previousTop = SetUnhandledExceptionFilter(handleException); + // Pass in guarantee size to be filled + SetThreadStackGuarantee(&guaranteeSize); + + // On Windows uncaught exceptions from another thread, exceptions from + // destructors, or calls to std::terminate are not a SEH exception + + // The terminal handler gets called when: + // - std::terminate is called FROM THE TEST RUNNER THREAD + // - an exception is thrown from a destructor FROM THE TEST RUNNER THREAD + original_terminate_handler = std::get_terminate(); + std::set_terminate([]() DOCTEST_NOEXCEPT { + reportFatal("Terminate handler called"); + if(isDebuggerActive() && !g_cs->no_breaks) + DOCTEST_BREAK_INTO_DEBUGGER(); + std::exit(EXIT_FAILURE); // explicitly exit - otherwise the SIGABRT handler may be called as well + }); + + // SIGABRT is raised when: + // - std::terminate is called FROM A DIFFERENT THREAD + // - an exception is thrown from a destructor FROM A DIFFERENT THREAD + // - an uncaught exception is thrown FROM A DIFFERENT THREAD + prev_sigabrt_handler = std::signal(SIGABRT, [](int signal) DOCTEST_NOEXCEPT { + if(signal == SIGABRT) { + reportFatal("SIGABRT - Abort (abnormal termination) signal"); + if(isDebuggerActive() && !g_cs->no_breaks) + DOCTEST_BREAK_INTO_DEBUGGER(); + std::exit(EXIT_FAILURE); + } + }); + + // The following settings are taken from google test, and more + // specifically from UnitTest::Run() inside of gtest.cc + + // the user does not want to see pop-up dialogs about crashes + prev_error_mode_1 = SetErrorMode(SEM_FAILCRITICALERRORS | SEM_NOALIGNMENTFAULTEXCEPT | + SEM_NOGPFAULTERRORBOX | SEM_NOOPENFILEERRORBOX); + // This forces the abort message to go to stderr in all circumstances. + prev_error_mode_2 = _set_error_mode(_OUT_TO_STDERR); + // In the debug version, Visual Studio pops up a separate dialog + // offering a choice to debug the aborted program - we want to disable that. + prev_abort_behavior = _set_abort_behavior(0x0, _WRITE_ABORT_MSG | _CALL_REPORTFAULT); + // In debug mode, the Windows CRT can crash with an assertion over invalid + // input (e.g. passing an invalid file descriptor). The default handling + // for these assertions is to pop up a dialog and wait for user input. + // Instead ask the CRT to dump such assertions to stderr non-interactively. + prev_report_mode = _CrtSetReportMode(_CRT_ASSERT, _CRTDBG_MODE_FILE | _CRTDBG_MODE_DEBUG); + prev_report_file = _CrtSetReportFile(_CRT_ASSERT, _CRTDBG_FILE_STDERR); + } + + static void reset() { + if(isSet) { + // Unregister handler and restore the old guarantee + SetUnhandledExceptionFilter(previousTop); + SetThreadStackGuarantee(&guaranteeSize); + std::set_terminate(original_terminate_handler); + std::signal(SIGABRT, prev_sigabrt_handler); + SetErrorMode(prev_error_mode_1); + _set_error_mode(prev_error_mode_2); + _set_abort_behavior(prev_abort_behavior, _WRITE_ABORT_MSG | _CALL_REPORTFAULT); + static_cast(_CrtSetReportMode(_CRT_ASSERT, prev_report_mode)); + static_cast(_CrtSetReportFile(_CRT_ASSERT, prev_report_file)); + isSet = false; + } + } + + ~FatalConditionHandler() { reset(); } + + private: + static UINT prev_error_mode_1; + static int prev_error_mode_2; + static unsigned int prev_abort_behavior; + static int prev_report_mode; + static _HFILE prev_report_file; + static void (DOCTEST_CDECL *prev_sigabrt_handler)(int); + static std::terminate_handler original_terminate_handler; + static bool isSet; + static ULONG guaranteeSize; + static LPTOP_LEVEL_EXCEPTION_FILTER previousTop; + }; + + UINT FatalConditionHandler::prev_error_mode_1; + int FatalConditionHandler::prev_error_mode_2; + unsigned int FatalConditionHandler::prev_abort_behavior; + int FatalConditionHandler::prev_report_mode; + _HFILE FatalConditionHandler::prev_report_file; + void (DOCTEST_CDECL *FatalConditionHandler::prev_sigabrt_handler)(int); + std::terminate_handler FatalConditionHandler::original_terminate_handler; + bool FatalConditionHandler::isSet = false; + ULONG FatalConditionHandler::guaranteeSize = 0; + LPTOP_LEVEL_EXCEPTION_FILTER FatalConditionHandler::previousTop = nullptr; + +#else // DOCTEST_PLATFORM_WINDOWS + + struct SignalDefs + { + int id; + const char* name; + }; + SignalDefs signalDefs[] = {{SIGINT, "SIGINT - Terminal interrupt signal"}, + {SIGILL, "SIGILL - Illegal instruction signal"}, + {SIGFPE, "SIGFPE - Floating point error signal"}, + {SIGSEGV, "SIGSEGV - Segmentation violation signal"}, + {SIGTERM, "SIGTERM - Termination request signal"}, + {SIGABRT, "SIGABRT - Abort (abnormal termination) signal"}}; + + struct FatalConditionHandler + { + static bool isSet; + static struct sigaction oldSigActions[DOCTEST_COUNTOF(signalDefs)]; + static stack_t oldSigStack; + static size_t altStackSize; + static char* altStackMem; + + static void handleSignal(int sig) { + const char* name = ""; + for(std::size_t i = 0; i < DOCTEST_COUNTOF(signalDefs); ++i) { + SignalDefs& def = signalDefs[i]; + if(sig == def.id) { + name = def.name; + break; + } + } + reset(); + reportFatal(name); + raise(sig); + } + + static void allocateAltStackMem() { + altStackMem = new char[altStackSize]; + } + + static void freeAltStackMem() { + delete[] altStackMem; + } + + FatalConditionHandler() { + isSet = true; + stack_t sigStack; + sigStack.ss_sp = altStackMem; + sigStack.ss_size = altStackSize; + sigStack.ss_flags = 0; + sigaltstack(&sigStack, &oldSigStack); + struct sigaction sa = {}; + sa.sa_handler = handleSignal; + sa.sa_flags = SA_ONSTACK; + for(std::size_t i = 0; i < DOCTEST_COUNTOF(signalDefs); ++i) { + sigaction(signalDefs[i].id, &sa, &oldSigActions[i]); + } + } + + ~FatalConditionHandler() { reset(); } + static void reset() { + if(isSet) { + // Set signals back to previous values -- hopefully nobody overwrote them in the meantime + for(std::size_t i = 0; i < DOCTEST_COUNTOF(signalDefs); ++i) { + sigaction(signalDefs[i].id, &oldSigActions[i], nullptr); + } + // Return the old stack + sigaltstack(&oldSigStack, nullptr); + isSet = false; + } + } + }; + + bool FatalConditionHandler::isSet = false; + struct sigaction FatalConditionHandler::oldSigActions[DOCTEST_COUNTOF(signalDefs)] = {}; + stack_t FatalConditionHandler::oldSigStack = {}; + size_t FatalConditionHandler::altStackSize = 4 * SIGSTKSZ; + char* FatalConditionHandler::altStackMem = nullptr; + +#endif // DOCTEST_PLATFORM_WINDOWS +#endif // DOCTEST_CONFIG_POSIX_SIGNALS || DOCTEST_CONFIG_WINDOWS_SEH + +} // namespace + +namespace { + using namespace detail; + +#ifdef DOCTEST_PLATFORM_WINDOWS +#define DOCTEST_OUTPUT_DEBUG_STRING(text) ::OutputDebugStringA(text) +#else + // TODO: integration with XCode and other IDEs +#define DOCTEST_OUTPUT_DEBUG_STRING(text) +#endif // Platform + + void addAssert(assertType::Enum at) { + if((at & assertType::is_warn) == 0) //!OCLINT bitwise operator in conditional + g_cs->numAssertsCurrentTest_atomic++; + } + + void addFailedAssert(assertType::Enum at) { + if((at & assertType::is_warn) == 0) //!OCLINT bitwise operator in conditional + g_cs->numAssertsFailedCurrentTest_atomic++; + } + +#if defined(DOCTEST_CONFIG_POSIX_SIGNALS) || defined(DOCTEST_CONFIG_WINDOWS_SEH) + void reportFatal(const std::string& message) { + g_cs->failure_flags |= TestCaseFailureReason::Crash; + + DOCTEST_ITERATE_THROUGH_REPORTERS(test_case_exception, {message.c_str(), true}); + + while (g_cs->subcaseStack.size()) { + g_cs->subcaseStack.pop_back(); + DOCTEST_ITERATE_THROUGH_REPORTERS(subcase_end, DOCTEST_EMPTY); + } + + g_cs->finalizeTestCaseData(); + + DOCTEST_ITERATE_THROUGH_REPORTERS(test_case_end, *g_cs); + + DOCTEST_ITERATE_THROUGH_REPORTERS(test_run_end, *g_cs); + } +#endif // DOCTEST_CONFIG_POSIX_SIGNALS || DOCTEST_CONFIG_WINDOWS_SEH +} // namespace + +AssertData::AssertData(assertType::Enum at, const char* file, int line, const char* expr, + const char* exception_type, const StringContains& exception_string) + : m_test_case(g_cs->currentTest), m_at(at), m_file(file), m_line(line), m_expr(expr), + m_failed(true), m_threw(false), m_threw_as(false), m_exception_type(exception_type), + m_exception_string(exception_string) { +#if DOCTEST_MSVC + if (m_expr[0] == ' ') // this happens when variadic macros are disabled under MSVC + ++m_expr; +#endif // MSVC +} + +namespace detail { + ResultBuilder::ResultBuilder(assertType::Enum at, const char* file, int line, const char* expr, + const char* exception_type, const String& exception_string) + : AssertData(at, file, line, expr, exception_type, exception_string) { } + + ResultBuilder::ResultBuilder(assertType::Enum at, const char* file, int line, const char* expr, + const char* exception_type, const Contains& exception_string) + : AssertData(at, file, line, expr, exception_type, exception_string) { } + + void ResultBuilder::setResult(const Result& res) { + m_decomp = res.m_decomp; + m_failed = !res.m_passed; + } + + void ResultBuilder::translateException() { + m_threw = true; + m_exception = translateActiveException(); + } + + bool ResultBuilder::log() { + if(m_at & assertType::is_throws) { //!OCLINT bitwise operator in conditional + m_failed = !m_threw; + } else if((m_at & assertType::is_throws_as) && (m_at & assertType::is_throws_with)) { //!OCLINT + m_failed = !m_threw_as || !m_exception_string.check(m_exception); + } else if(m_at & assertType::is_throws_as) { //!OCLINT bitwise operator in conditional + m_failed = !m_threw_as; + } else if(m_at & assertType::is_throws_with) { //!OCLINT bitwise operator in conditional + m_failed = !m_exception_string.check(m_exception); + } else if(m_at & assertType::is_nothrow) { //!OCLINT bitwise operator in conditional + m_failed = m_threw; + } + + if(m_exception.size()) + m_exception = "\"" + m_exception + "\""; + + if(is_running_in_test) { + addAssert(m_at); + DOCTEST_ITERATE_THROUGH_REPORTERS(log_assert, *this); + + if(m_failed) + addFailedAssert(m_at); + } else if(m_failed) { + failed_out_of_a_testing_context(*this); + } + + return m_failed && isDebuggerActive() && !getContextOptions()->no_breaks && + (g_cs->currentTest == nullptr || !g_cs->currentTest->m_no_breaks); // break into debugger + } + + void ResultBuilder::react() const { + if(m_failed && checkIfShouldThrow(m_at)) + throwException(); + } + + void failed_out_of_a_testing_context(const AssertData& ad) { + if(g_cs->ah) + g_cs->ah(ad); + else + std::abort(); + } + + bool decomp_assert(assertType::Enum at, const char* file, int line, const char* expr, + const Result& result) { + bool failed = !result.m_passed; + + // ################################################################################### + // IF THE DEBUGGER BREAKS HERE - GO 1 LEVEL UP IN THE CALLSTACK FOR THE FAILING ASSERT + // THIS IS THE EFFECT OF HAVING 'DOCTEST_CONFIG_SUPER_FAST_ASSERTS' DEFINED + // ################################################################################### + DOCTEST_ASSERT_OUT_OF_TESTS(result.m_decomp); + DOCTEST_ASSERT_IN_TESTS(result.m_decomp); + return !failed; + } + + MessageBuilder::MessageBuilder(const char* file, int line, assertType::Enum severity) { + m_stream = tlssPush(); + m_file = file; + m_line = line; + m_severity = severity; + } + + MessageBuilder::~MessageBuilder() { + if (!logged) + tlssPop(); + } + + DOCTEST_DEFINE_INTERFACE(IExceptionTranslator) + + bool MessageBuilder::log() { + if (!logged) { + m_string = tlssPop(); + logged = true; + } + + DOCTEST_ITERATE_THROUGH_REPORTERS(log_message, *this); + + const bool isWarn = m_severity & assertType::is_warn; + + // warn is just a message in this context so we don't treat it as an assert + if(!isWarn) { + addAssert(m_severity); + addFailedAssert(m_severity); + } + + return isDebuggerActive() && !getContextOptions()->no_breaks && !isWarn && + (g_cs->currentTest == nullptr || !g_cs->currentTest->m_no_breaks); // break into debugger + } + + void MessageBuilder::react() { + if(m_severity & assertType::is_require) //!OCLINT bitwise operator in conditional + throwException(); + } +} // namespace detail +namespace { + using namespace detail; + + // clang-format off + +// ================================================================================================= +// The following code has been taken verbatim from Catch2/include/internal/catch_xmlwriter.h/cpp +// This is done so cherry-picking bug fixes is trivial - even the style/formatting is untouched. +// ================================================================================================= + + class XmlEncode { + public: + enum ForWhat { ForTextNodes, ForAttributes }; + + XmlEncode( std::string const& str, ForWhat forWhat = ForTextNodes ); + + void encodeTo( std::ostream& os ) const; + + friend std::ostream& operator << ( std::ostream& os, XmlEncode const& xmlEncode ); + + private: + std::string m_str; + ForWhat m_forWhat; + }; + + class XmlWriter { + public: + + class ScopedElement { + public: + ScopedElement( XmlWriter* writer ); + + ScopedElement( ScopedElement&& other ) DOCTEST_NOEXCEPT; + ScopedElement& operator=( ScopedElement&& other ) DOCTEST_NOEXCEPT; + + ~ScopedElement(); + + ScopedElement& writeText( std::string const& text, bool indent = true ); + + template + ScopedElement& writeAttribute( std::string const& name, T const& attribute ) { + m_writer->writeAttribute( name, attribute ); + return *this; + } + + private: + mutable XmlWriter* m_writer = nullptr; + }; + +#ifndef DOCTEST_CONFIG_NO_INCLUDE_IOSTREAM + XmlWriter( std::ostream& os = std::cout ); +#else // DOCTEST_CONFIG_NO_INCLUDE_IOSTREAM + XmlWriter( std::ostream& os ); +#endif // DOCTEST_CONFIG_NO_INCLUDE_IOSTREAM + ~XmlWriter(); + + XmlWriter( XmlWriter const& ) = delete; + XmlWriter& operator=( XmlWriter const& ) = delete; + + XmlWriter& startElement( std::string const& name ); + + ScopedElement scopedElement( std::string const& name ); + + XmlWriter& endElement(); + + XmlWriter& writeAttribute( std::string const& name, std::string const& attribute ); + + XmlWriter& writeAttribute( std::string const& name, const char* attribute ); + + XmlWriter& writeAttribute( std::string const& name, bool attribute ); + + template + XmlWriter& writeAttribute( std::string const& name, T const& attribute ) { + std::stringstream rss; + rss << attribute; + return writeAttribute( name, rss.str() ); + } + + XmlWriter& writeText( std::string const& text, bool indent = true ); + + //XmlWriter& writeComment( std::string const& text ); + + //void writeStylesheetRef( std::string const& url ); + + //XmlWriter& writeBlankLine(); + + void ensureTagClosed(); + + void writeDeclaration(); + + private: + + void newlineIfNecessary(); + + bool m_tagIsOpen = false; + bool m_needsNewline = false; + std::vector m_tags; + std::string m_indent; + std::ostream& m_os; + }; + +// ================================================================================================= +// The following code has been taken verbatim from Catch2/include/internal/catch_xmlwriter.h/cpp +// This is done so cherry-picking bug fixes is trivial - even the style/formatting is untouched. +// ================================================================================================= + +using uchar = unsigned char; + +namespace { + + size_t trailingBytes(unsigned char c) { + if ((c & 0xE0) == 0xC0) { + return 2; + } + if ((c & 0xF0) == 0xE0) { + return 3; + } + if ((c & 0xF8) == 0xF0) { + return 4; + } + DOCTEST_INTERNAL_ERROR("Invalid multibyte utf-8 start byte encountered"); + } + + uint32_t headerValue(unsigned char c) { + if ((c & 0xE0) == 0xC0) { + return c & 0x1F; + } + if ((c & 0xF0) == 0xE0) { + return c & 0x0F; + } + if ((c & 0xF8) == 0xF0) { + return c & 0x07; + } + DOCTEST_INTERNAL_ERROR("Invalid multibyte utf-8 start byte encountered"); + } + + void hexEscapeChar(std::ostream& os, unsigned char c) { + std::ios_base::fmtflags f(os.flags()); + os << "\\x" + << std::uppercase << std::hex << std::setfill('0') << std::setw(2) + << static_cast(c); + os.flags(f); + } + +} // anonymous namespace + + XmlEncode::XmlEncode( std::string const& str, ForWhat forWhat ) + : m_str( str ), + m_forWhat( forWhat ) + {} + + void XmlEncode::encodeTo( std::ostream& os ) const { + // Apostrophe escaping not necessary if we always use " to write attributes + // (see: https://www.w3.org/TR/xml/#syntax) + + for( std::size_t idx = 0; idx < m_str.size(); ++ idx ) { + uchar c = m_str[idx]; + switch (c) { + case '<': os << "<"; break; + case '&': os << "&"; break; + + case '>': + // See: https://www.w3.org/TR/xml/#syntax + if (idx > 2 && m_str[idx - 1] == ']' && m_str[idx - 2] == ']') + os << ">"; + else + os << c; + break; + + case '\"': + if (m_forWhat == ForAttributes) + os << """; + else + os << c; + break; + + default: + // Check for control characters and invalid utf-8 + + // Escape control characters in standard ascii + // see https://stackoverflow.com/questions/404107/why-are-control-characters-illegal-in-xml-1-0 + if (c < 0x09 || (c > 0x0D && c < 0x20) || c == 0x7F) { + hexEscapeChar(os, c); + break; + } + + // Plain ASCII: Write it to stream + if (c < 0x7F) { + os << c; + break; + } + + // UTF-8 territory + // Check if the encoding is valid and if it is not, hex escape bytes. + // Important: We do not check the exact decoded values for validity, only the encoding format + // First check that this bytes is a valid lead byte: + // This means that it is not encoded as 1111 1XXX + // Or as 10XX XXXX + if (c < 0xC0 || + c >= 0xF8) { + hexEscapeChar(os, c); + break; + } + + auto encBytes = trailingBytes(c); + // Are there enough bytes left to avoid accessing out-of-bounds memory? + if (idx + encBytes - 1 >= m_str.size()) { + hexEscapeChar(os, c); + break; + } + // The header is valid, check data + // The next encBytes bytes must together be a valid utf-8 + // This means: bitpattern 10XX XXXX and the extracted value is sane (ish) + bool valid = true; + uint32_t value = headerValue(c); + for (std::size_t n = 1; n < encBytes; ++n) { + uchar nc = m_str[idx + n]; + valid &= ((nc & 0xC0) == 0x80); + value = (value << 6) | (nc & 0x3F); + } + + if ( + // Wrong bit pattern of following bytes + (!valid) || + // Overlong encodings + (value < 0x80) || + ( value < 0x800 && encBytes > 2) || // removed "0x80 <= value &&" because redundant + (0x800 < value && value < 0x10000 && encBytes > 3) || + // Encoded value out of range + (value >= 0x110000) + ) { + hexEscapeChar(os, c); + break; + } + + // If we got here, this is in fact a valid(ish) utf-8 sequence + for (std::size_t n = 0; n < encBytes; ++n) { + os << m_str[idx + n]; + } + idx += encBytes - 1; + break; + } + } + } + + std::ostream& operator << ( std::ostream& os, XmlEncode const& xmlEncode ) { + xmlEncode.encodeTo( os ); + return os; + } + + XmlWriter::ScopedElement::ScopedElement( XmlWriter* writer ) + : m_writer( writer ) + {} + + XmlWriter::ScopedElement::ScopedElement( ScopedElement&& other ) DOCTEST_NOEXCEPT + : m_writer( other.m_writer ){ + other.m_writer = nullptr; + } + XmlWriter::ScopedElement& XmlWriter::ScopedElement::operator=( ScopedElement&& other ) DOCTEST_NOEXCEPT { + if ( m_writer ) { + m_writer->endElement(); + } + m_writer = other.m_writer; + other.m_writer = nullptr; + return *this; + } + + + XmlWriter::ScopedElement::~ScopedElement() { + if( m_writer ) + m_writer->endElement(); + } + + XmlWriter::ScopedElement& XmlWriter::ScopedElement::writeText( std::string const& text, bool indent ) { + m_writer->writeText( text, indent ); + return *this; + } + + XmlWriter::XmlWriter( std::ostream& os ) : m_os( os ) + { + // writeDeclaration(); // called explicitly by the reporters that use the writer class - see issue #627 + } + + XmlWriter::~XmlWriter() { + while( !m_tags.empty() ) + endElement(); + } + + XmlWriter& XmlWriter::startElement( std::string const& name ) { + ensureTagClosed(); + newlineIfNecessary(); + m_os << m_indent << '<' << name; + m_tags.push_back( name ); + m_indent += " "; + m_tagIsOpen = true; + return *this; + } + + XmlWriter::ScopedElement XmlWriter::scopedElement( std::string const& name ) { + ScopedElement scoped( this ); + startElement( name ); + return scoped; + } + + XmlWriter& XmlWriter::endElement() { + newlineIfNecessary(); + m_indent = m_indent.substr( 0, m_indent.size()-2 ); + if( m_tagIsOpen ) { + m_os << "/>"; + m_tagIsOpen = false; + } + else { + m_os << m_indent << ""; + } + m_os << std::endl; + m_tags.pop_back(); + return *this; + } + + XmlWriter& XmlWriter::writeAttribute( std::string const& name, std::string const& attribute ) { + if( !name.empty() && !attribute.empty() ) + m_os << ' ' << name << "=\"" << XmlEncode( attribute, XmlEncode::ForAttributes ) << '"'; + return *this; + } + + XmlWriter& XmlWriter::writeAttribute( std::string const& name, const char* attribute ) { + if( !name.empty() && attribute && attribute[0] != '\0' ) + m_os << ' ' << name << "=\"" << XmlEncode( attribute, XmlEncode::ForAttributes ) << '"'; + return *this; + } + + XmlWriter& XmlWriter::writeAttribute( std::string const& name, bool attribute ) { + m_os << ' ' << name << "=\"" << ( attribute ? "true" : "false" ) << '"'; + return *this; + } + + XmlWriter& XmlWriter::writeText( std::string const& text, bool indent ) { + if( !text.empty() ){ + bool tagWasOpen = m_tagIsOpen; + ensureTagClosed(); + if( tagWasOpen && indent ) + m_os << m_indent; + m_os << XmlEncode( text ); + m_needsNewline = true; + } + return *this; + } + + //XmlWriter& XmlWriter::writeComment( std::string const& text ) { + // ensureTagClosed(); + // m_os << m_indent << ""; + // m_needsNewline = true; + // return *this; + //} + + //void XmlWriter::writeStylesheetRef( std::string const& url ) { + // m_os << "\n"; + //} + + //XmlWriter& XmlWriter::writeBlankLine() { + // ensureTagClosed(); + // m_os << '\n'; + // return *this; + //} + + void XmlWriter::ensureTagClosed() { + if( m_tagIsOpen ) { + m_os << ">" << std::endl; + m_tagIsOpen = false; + } + } + + void XmlWriter::writeDeclaration() { + m_os << "\n"; + } + + void XmlWriter::newlineIfNecessary() { + if( m_needsNewline ) { + m_os << std::endl; + m_needsNewline = false; + } + } + +// ================================================================================================= +// End of copy-pasted code from Catch +// ================================================================================================= + + // clang-format on + + struct XmlReporter : public IReporter + { + XmlWriter xml; + DOCTEST_DECLARE_MUTEX(mutex) + + // caching pointers/references to objects of these types - safe to do + const ContextOptions& opt; + const TestCaseData* tc = nullptr; + + XmlReporter(const ContextOptions& co) + : xml(*co.cout) + , opt(co) {} + + void log_contexts() { + int num_contexts = get_num_active_contexts(); + if(num_contexts) { + auto contexts = get_active_contexts(); + std::stringstream ss; + for(int i = 0; i < num_contexts; ++i) { + contexts[i]->stringify(&ss); + xml.scopedElement("Info").writeText(ss.str()); + ss.str(""); + } + } + } + + unsigned line(unsigned l) const { return opt.no_line_numbers ? 0 : l; } + + void test_case_start_impl(const TestCaseData& in) { + bool open_ts_tag = false; + if(tc != nullptr) { // we have already opened a test suite + if(std::strcmp(tc->m_test_suite, in.m_test_suite) != 0) { + xml.endElement(); + open_ts_tag = true; + } + } + else { + open_ts_tag = true; // first test case ==> first test suite + } + + if(open_ts_tag) { + xml.startElement("TestSuite"); + xml.writeAttribute("name", in.m_test_suite); + } + + tc = ∈ + xml.startElement("TestCase") + .writeAttribute("name", in.m_name) + .writeAttribute("filename", skipPathFromFilename(in.m_file.c_str())) + .writeAttribute("line", line(in.m_line)) + .writeAttribute("description", in.m_description); + + if(Approx(in.m_timeout) != 0) + xml.writeAttribute("timeout", in.m_timeout); + if(in.m_may_fail) + xml.writeAttribute("may_fail", true); + if(in.m_should_fail) + xml.writeAttribute("should_fail", true); + } + + // ========================================================================================= + // WHAT FOLLOWS ARE OVERRIDES OF THE VIRTUAL METHODS OF THE REPORTER INTERFACE + // ========================================================================================= + + void report_query(const QueryData& in) override { + test_run_start(); + if(opt.list_reporters) { + for(auto& curr : getListeners()) + xml.scopedElement("Listener") + .writeAttribute("priority", curr.first.first) + .writeAttribute("name", curr.first.second); + for(auto& curr : getReporters()) + xml.scopedElement("Reporter") + .writeAttribute("priority", curr.first.first) + .writeAttribute("name", curr.first.second); + } else if(opt.count || opt.list_test_cases) { + for(unsigned i = 0; i < in.num_data; ++i) { + xml.scopedElement("TestCase").writeAttribute("name", in.data[i]->m_name) + .writeAttribute("testsuite", in.data[i]->m_test_suite) + .writeAttribute("filename", skipPathFromFilename(in.data[i]->m_file.c_str())) + .writeAttribute("line", line(in.data[i]->m_line)) + .writeAttribute("skipped", in.data[i]->m_skip); + } + xml.scopedElement("OverallResultsTestCases") + .writeAttribute("unskipped", in.run_stats->numTestCasesPassingFilters); + } else if(opt.list_test_suites) { + for(unsigned i = 0; i < in.num_data; ++i) + xml.scopedElement("TestSuite").writeAttribute("name", in.data[i]->m_test_suite); + xml.scopedElement("OverallResultsTestCases") + .writeAttribute("unskipped", in.run_stats->numTestCasesPassingFilters); + xml.scopedElement("OverallResultsTestSuites") + .writeAttribute("unskipped", in.run_stats->numTestSuitesPassingFilters); + } + xml.endElement(); + } + + void test_run_start() override { + xml.writeDeclaration(); + + // remove .exe extension - mainly to have the same output on UNIX and Windows + std::string binary_name = skipPathFromFilename(opt.binary_name.c_str()); +#ifdef DOCTEST_PLATFORM_WINDOWS + if(binary_name.rfind(".exe") != std::string::npos) + binary_name = binary_name.substr(0, binary_name.length() - 4); +#endif // DOCTEST_PLATFORM_WINDOWS + + xml.startElement("doctest").writeAttribute("binary", binary_name); + if(opt.no_version == false) + xml.writeAttribute("version", DOCTEST_VERSION_STR); + + // only the consequential ones (TODO: filters) + xml.scopedElement("Options") + .writeAttribute("order_by", opt.order_by.c_str()) + .writeAttribute("rand_seed", opt.rand_seed) + .writeAttribute("first", opt.first) + .writeAttribute("last", opt.last) + .writeAttribute("abort_after", opt.abort_after) + .writeAttribute("subcase_filter_levels", opt.subcase_filter_levels) + .writeAttribute("case_sensitive", opt.case_sensitive) + .writeAttribute("no_throw", opt.no_throw) + .writeAttribute("no_skip", opt.no_skip); + } + + void test_run_end(const TestRunStats& p) override { + if(tc) // the TestSuite tag - only if there has been at least 1 test case + xml.endElement(); + + xml.scopedElement("OverallResultsAsserts") + .writeAttribute("successes", p.numAsserts - p.numAssertsFailed) + .writeAttribute("failures", p.numAssertsFailed); + + xml.startElement("OverallResultsTestCases") + .writeAttribute("successes", + p.numTestCasesPassingFilters - p.numTestCasesFailed) + .writeAttribute("failures", p.numTestCasesFailed); + if(opt.no_skipped_summary == false) + xml.writeAttribute("skipped", p.numTestCases - p.numTestCasesPassingFilters); + xml.endElement(); + + xml.endElement(); + } + + void test_case_start(const TestCaseData& in) override { + test_case_start_impl(in); + xml.ensureTagClosed(); + } + + void test_case_reenter(const TestCaseData&) override {} + + void test_case_end(const CurrentTestCaseStats& st) override { + xml.startElement("OverallResultsAsserts") + .writeAttribute("successes", + st.numAssertsCurrentTest - st.numAssertsFailedCurrentTest) + .writeAttribute("failures", st.numAssertsFailedCurrentTest) + .writeAttribute("test_case_success", st.testCaseSuccess); + if(opt.duration) + xml.writeAttribute("duration", st.seconds); + if(tc->m_expected_failures) + xml.writeAttribute("expected_failures", tc->m_expected_failures); + xml.endElement(); + + xml.endElement(); + } + + void test_case_exception(const TestCaseException& e) override { + DOCTEST_LOCK_MUTEX(mutex) + + xml.scopedElement("Exception") + .writeAttribute("crash", e.is_crash) + .writeText(e.error_string.c_str()); + } + + void subcase_start(const SubcaseSignature& in) override { + xml.startElement("SubCase") + .writeAttribute("name", in.m_name) + .writeAttribute("filename", skipPathFromFilename(in.m_file)) + .writeAttribute("line", line(in.m_line)); + xml.ensureTagClosed(); + } + + void subcase_end() override { xml.endElement(); } + + void log_assert(const AssertData& rb) override { + if(!rb.m_failed && !opt.success) + return; + + DOCTEST_LOCK_MUTEX(mutex) + + xml.startElement("Expression") + .writeAttribute("success", !rb.m_failed) + .writeAttribute("type", assertString(rb.m_at)) + .writeAttribute("filename", skipPathFromFilename(rb.m_file)) + .writeAttribute("line", line(rb.m_line)); + + xml.scopedElement("Original").writeText(rb.m_expr); + + if(rb.m_threw) + xml.scopedElement("Exception").writeText(rb.m_exception.c_str()); + + if(rb.m_at & assertType::is_throws_as) + xml.scopedElement("ExpectedException").writeText(rb.m_exception_type); + if(rb.m_at & assertType::is_throws_with) + xml.scopedElement("ExpectedExceptionString").writeText(rb.m_exception_string.c_str()); + if((rb.m_at & assertType::is_normal) && !rb.m_threw) + xml.scopedElement("Expanded").writeText(rb.m_decomp.c_str()); + + log_contexts(); + + xml.endElement(); + } + + void log_message(const MessageData& mb) override { + DOCTEST_LOCK_MUTEX(mutex) + + xml.startElement("Message") + .writeAttribute("type", failureString(mb.m_severity)) + .writeAttribute("filename", skipPathFromFilename(mb.m_file)) + .writeAttribute("line", line(mb.m_line)); + + xml.scopedElement("Text").writeText(mb.m_string.c_str()); + + log_contexts(); + + xml.endElement(); + } + + void test_case_skipped(const TestCaseData& in) override { + if(opt.no_skipped_summary == false) { + test_case_start_impl(in); + xml.writeAttribute("skipped", "true"); + xml.endElement(); + } + } + }; + + DOCTEST_REGISTER_REPORTER("xml", 0, XmlReporter); + + void fulltext_log_assert_to_stream(std::ostream& s, const AssertData& rb) { + if((rb.m_at & (assertType::is_throws_as | assertType::is_throws_with)) == + 0) //!OCLINT bitwise operator in conditional + s << Color::Cyan << assertString(rb.m_at) << "( " << rb.m_expr << " ) " + << Color::None; + + if(rb.m_at & assertType::is_throws) { //!OCLINT bitwise operator in conditional + s << (rb.m_threw ? "threw as expected!" : "did NOT throw at all!") << "\n"; + } else if((rb.m_at & assertType::is_throws_as) && + (rb.m_at & assertType::is_throws_with)) { //!OCLINT + s << Color::Cyan << assertString(rb.m_at) << "( " << rb.m_expr << ", \"" + << rb.m_exception_string.c_str() + << "\", " << rb.m_exception_type << " ) " << Color::None; + if(rb.m_threw) { + if(!rb.m_failed) { + s << "threw as expected!\n"; + } else { + s << "threw a DIFFERENT exception! (contents: " << rb.m_exception << ")\n"; + } + } else { + s << "did NOT throw at all!\n"; + } + } else if(rb.m_at & + assertType::is_throws_as) { //!OCLINT bitwise operator in conditional + s << Color::Cyan << assertString(rb.m_at) << "( " << rb.m_expr << ", " + << rb.m_exception_type << " ) " << Color::None + << (rb.m_threw ? (rb.m_threw_as ? "threw as expected!" : + "threw a DIFFERENT exception: ") : + "did NOT throw at all!") + << Color::Cyan << rb.m_exception << "\n"; + } else if(rb.m_at & + assertType::is_throws_with) { //!OCLINT bitwise operator in conditional + s << Color::Cyan << assertString(rb.m_at) << "( " << rb.m_expr << ", \"" + << rb.m_exception_string.c_str() + << "\" ) " << Color::None + << (rb.m_threw ? (!rb.m_failed ? "threw as expected!" : + "threw a DIFFERENT exception: ") : + "did NOT throw at all!") + << Color::Cyan << rb.m_exception << "\n"; + } else if(rb.m_at & assertType::is_nothrow) { //!OCLINT bitwise operator in conditional + s << (rb.m_threw ? "THREW exception: " : "didn't throw!") << Color::Cyan + << rb.m_exception << "\n"; + } else { + s << (rb.m_threw ? "THREW exception: " : + (!rb.m_failed ? "is correct!\n" : "is NOT correct!\n")); + if(rb.m_threw) + s << rb.m_exception << "\n"; + else + s << " values: " << assertString(rb.m_at) << "( " << rb.m_decomp << " )\n"; + } + } + + // TODO: + // - log_message() + // - respond to queries + // - honor remaining options + // - more attributes in tags + struct JUnitReporter : public IReporter + { + XmlWriter xml; + DOCTEST_DECLARE_MUTEX(mutex) + Timer timer; + std::vector deepestSubcaseStackNames; + + struct JUnitTestCaseData + { + static std::string getCurrentTimestamp() { + // Beware, this is not reentrant because of backward compatibility issues + // Also, UTC only, again because of backward compatibility (%z is C++11) + time_t rawtime; + std::time(&rawtime); + auto const timeStampSize = sizeof("2017-01-16T17:06:45Z"); + + std::tm timeInfo; +#ifdef DOCTEST_PLATFORM_WINDOWS + gmtime_s(&timeInfo, &rawtime); +#else // DOCTEST_PLATFORM_WINDOWS + gmtime_r(&rawtime, &timeInfo); +#endif // DOCTEST_PLATFORM_WINDOWS + + char timeStamp[timeStampSize]; + const char* const fmt = "%Y-%m-%dT%H:%M:%SZ"; + + std::strftime(timeStamp, timeStampSize, fmt, &timeInfo); + return std::string(timeStamp); + } + + struct JUnitTestMessage + { + JUnitTestMessage(const std::string& _message, const std::string& _type, const std::string& _details) + : message(_message), type(_type), details(_details) {} + + JUnitTestMessage(const std::string& _message, const std::string& _details) + : message(_message), type(), details(_details) {} + + std::string message, type, details; + }; + + struct JUnitTestCase + { + JUnitTestCase(const std::string& _classname, const std::string& _name) + : classname(_classname), name(_name), time(0), failures() {} + + std::string classname, name; + double time; + std::vector failures, errors; + }; + + void add(const std::string& classname, const std::string& name) { + testcases.emplace_back(classname, name); + } + + void appendSubcaseNamesToLastTestcase(std::vector nameStack) { + for(auto& curr: nameStack) + if(curr.size()) + testcases.back().name += std::string("/") + curr.c_str(); + } + + void addTime(double time) { + if(time < 1e-4) + time = 0; + testcases.back().time = time; + totalSeconds += time; + } + + void addFailure(const std::string& message, const std::string& type, const std::string& details) { + testcases.back().failures.emplace_back(message, type, details); + ++totalFailures; + } + + void addError(const std::string& message, const std::string& details) { + testcases.back().errors.emplace_back(message, details); + ++totalErrors; + } + + std::vector testcases; + double totalSeconds = 0; + int totalErrors = 0, totalFailures = 0; + }; + + JUnitTestCaseData testCaseData; + + // caching pointers/references to objects of these types - safe to do + const ContextOptions& opt; + const TestCaseData* tc = nullptr; + + JUnitReporter(const ContextOptions& co) + : xml(*co.cout) + , opt(co) {} + + unsigned line(unsigned l) const { return opt.no_line_numbers ? 0 : l; } + + // ========================================================================================= + // WHAT FOLLOWS ARE OVERRIDES OF THE VIRTUAL METHODS OF THE REPORTER INTERFACE + // ========================================================================================= + + void report_query(const QueryData&) override { + xml.writeDeclaration(); + } + + void test_run_start() override { + xml.writeDeclaration(); + } + + void test_run_end(const TestRunStats& p) override { + // remove .exe extension - mainly to have the same output on UNIX and Windows + std::string binary_name = skipPathFromFilename(opt.binary_name.c_str()); +#ifdef DOCTEST_PLATFORM_WINDOWS + if(binary_name.rfind(".exe") != std::string::npos) + binary_name = binary_name.substr(0, binary_name.length() - 4); +#endif // DOCTEST_PLATFORM_WINDOWS + xml.startElement("testsuites"); + xml.startElement("testsuite").writeAttribute("name", binary_name) + .writeAttribute("errors", testCaseData.totalErrors) + .writeAttribute("failures", testCaseData.totalFailures) + .writeAttribute("tests", p.numAsserts); + if(opt.no_time_in_output == false) { + xml.writeAttribute("time", testCaseData.totalSeconds); + xml.writeAttribute("timestamp", JUnitTestCaseData::getCurrentTimestamp()); + } + if(opt.no_version == false) + xml.writeAttribute("doctest_version", DOCTEST_VERSION_STR); + + for(const auto& testCase : testCaseData.testcases) { + xml.startElement("testcase") + .writeAttribute("classname", testCase.classname) + .writeAttribute("name", testCase.name); + if(opt.no_time_in_output == false) + xml.writeAttribute("time", testCase.time); + // This is not ideal, but it should be enough to mimic gtest's junit output. + xml.writeAttribute("status", "run"); + + for(const auto& failure : testCase.failures) { + xml.scopedElement("failure") + .writeAttribute("message", failure.message) + .writeAttribute("type", failure.type) + .writeText(failure.details, false); + } + + for(const auto& error : testCase.errors) { + xml.scopedElement("error") + .writeAttribute("message", error.message) + .writeText(error.details); + } + + xml.endElement(); + } + xml.endElement(); + xml.endElement(); + } + + void test_case_start(const TestCaseData& in) override { + testCaseData.add(skipPathFromFilename(in.m_file.c_str()), in.m_name); + timer.start(); + } + + void test_case_reenter(const TestCaseData& in) override { + testCaseData.addTime(timer.getElapsedSeconds()); + testCaseData.appendSubcaseNamesToLastTestcase(deepestSubcaseStackNames); + deepestSubcaseStackNames.clear(); + + timer.start(); + testCaseData.add(skipPathFromFilename(in.m_file.c_str()), in.m_name); + } + + void test_case_end(const CurrentTestCaseStats&) override { + testCaseData.addTime(timer.getElapsedSeconds()); + testCaseData.appendSubcaseNamesToLastTestcase(deepestSubcaseStackNames); + deepestSubcaseStackNames.clear(); + } + + void test_case_exception(const TestCaseException& e) override { + DOCTEST_LOCK_MUTEX(mutex) + testCaseData.addError("exception", e.error_string.c_str()); + } + + void subcase_start(const SubcaseSignature& in) override { + deepestSubcaseStackNames.push_back(in.m_name); + } + + void subcase_end() override {} + + void log_assert(const AssertData& rb) override { + if(!rb.m_failed) // report only failures & ignore the `success` option + return; + + DOCTEST_LOCK_MUTEX(mutex) + + std::ostringstream os; + os << skipPathFromFilename(rb.m_file) << (opt.gnu_file_line ? ":" : "(") + << line(rb.m_line) << (opt.gnu_file_line ? ":" : "):") << std::endl; + + fulltext_log_assert_to_stream(os, rb); + log_contexts(os); + testCaseData.addFailure(rb.m_decomp.c_str(), assertString(rb.m_at), os.str()); + } + + void log_message(const MessageData& mb) override { + if(mb.m_severity & assertType::is_warn) // report only failures + return; + + DOCTEST_LOCK_MUTEX(mutex) + + std::ostringstream os; + os << skipPathFromFilename(mb.m_file) << (opt.gnu_file_line ? ":" : "(") + << line(mb.m_line) << (opt.gnu_file_line ? ":" : "):") << std::endl; + + os << mb.m_string.c_str() << "\n"; + log_contexts(os); + + testCaseData.addFailure(mb.m_string.c_str(), + mb.m_severity & assertType::is_check ? "FAIL_CHECK" : "FAIL", os.str()); + } + + void test_case_skipped(const TestCaseData&) override {} + + void log_contexts(std::ostringstream& s) { + int num_contexts = get_num_active_contexts(); + if(num_contexts) { + auto contexts = get_active_contexts(); + + s << " logged: "; + for(int i = 0; i < num_contexts; ++i) { + s << (i == 0 ? "" : " "); + contexts[i]->stringify(&s); + s << std::endl; + } + } + } + }; + + DOCTEST_REGISTER_REPORTER("junit", 0, JUnitReporter); + + struct Whitespace + { + int nrSpaces; + explicit Whitespace(int nr) + : nrSpaces(nr) {} + }; + + std::ostream& operator<<(std::ostream& out, const Whitespace& ws) { + if(ws.nrSpaces != 0) + out << std::setw(ws.nrSpaces) << ' '; + return out; + } + + struct ConsoleReporter : public IReporter + { + std::ostream& s; + bool hasLoggedCurrentTestStart; + std::vector subcasesStack; + size_t currentSubcaseLevel; + DOCTEST_DECLARE_MUTEX(mutex) + + // caching pointers/references to objects of these types - safe to do + const ContextOptions& opt; + const TestCaseData* tc; + + ConsoleReporter(const ContextOptions& co) + : s(*co.cout) + , opt(co) {} + + ConsoleReporter(const ContextOptions& co, std::ostream& ostr) + : s(ostr) + , opt(co) {} + + // ========================================================================================= + // WHAT FOLLOWS ARE HELPERS USED BY THE OVERRIDES OF THE VIRTUAL METHODS OF THE INTERFACE + // ========================================================================================= + + void separator_to_stream() { + s << Color::Yellow + << "===============================================================================" + "\n"; + } + + const char* getSuccessOrFailString(bool success, assertType::Enum at, + const char* success_str) { + if(success) + return success_str; + return failureString(at); + } + + Color::Enum getSuccessOrFailColor(bool success, assertType::Enum at) { + return success ? Color::BrightGreen : + (at & assertType::is_warn) ? Color::Yellow : Color::Red; + } + + void successOrFailColoredStringToStream(bool success, assertType::Enum at, + const char* success_str = "SUCCESS") { + s << getSuccessOrFailColor(success, at) + << getSuccessOrFailString(success, at, success_str) << ": "; + } + + void log_contexts() { + int num_contexts = get_num_active_contexts(); + if(num_contexts) { + auto contexts = get_active_contexts(); + + s << Color::None << " logged: "; + for(int i = 0; i < num_contexts; ++i) { + s << (i == 0 ? "" : " "); + contexts[i]->stringify(&s); + s << "\n"; + } + } + + s << "\n"; + } + + // this was requested to be made virtual so users could override it + virtual void file_line_to_stream(const char* file, int line, + const char* tail = "") { + s << Color::LightGrey << skipPathFromFilename(file) << (opt.gnu_file_line ? ":" : "(") + << (opt.no_line_numbers ? 0 : line) // 0 or the real num depending on the option + << (opt.gnu_file_line ? ":" : "):") << tail; + } + + void logTestStart() { + if(hasLoggedCurrentTestStart) + return; + + separator_to_stream(); + file_line_to_stream(tc->m_file.c_str(), tc->m_line, "\n"); + if(tc->m_description) + s << Color::Yellow << "DESCRIPTION: " << Color::None << tc->m_description << "\n"; + if(tc->m_test_suite && tc->m_test_suite[0] != '\0') + s << Color::Yellow << "TEST SUITE: " << Color::None << tc->m_test_suite << "\n"; + if(strncmp(tc->m_name, " Scenario:", 11) != 0) + s << Color::Yellow << "TEST CASE: "; + s << Color::None << tc->m_name << "\n"; + + for(size_t i = 0; i < currentSubcaseLevel; ++i) { + if(subcasesStack[i].m_name[0] != '\0') + s << " " << subcasesStack[i].m_name << "\n"; + } + + if(currentSubcaseLevel != subcasesStack.size()) { + s << Color::Yellow << "\nDEEPEST SUBCASE STACK REACHED (DIFFERENT FROM THE CURRENT ONE):\n" << Color::None; + for(size_t i = 0; i < subcasesStack.size(); ++i) { + if(subcasesStack[i].m_name[0] != '\0') + s << " " << subcasesStack[i].m_name << "\n"; + } + } + + s << "\n"; + + hasLoggedCurrentTestStart = true; + } + + void printVersion() { + if(opt.no_version == false) + s << Color::Cyan << "[doctest] " << Color::None << "doctest version is \"" + << DOCTEST_VERSION_STR << "\"\n"; + } + + void printIntro() { + if(opt.no_intro == false) { + printVersion(); + s << Color::Cyan << "[doctest] " << Color::None + << "run with \"--" DOCTEST_OPTIONS_PREFIX_DISPLAY "help\" for options\n"; + } + } + + void printHelp() { + int sizePrefixDisplay = static_cast(strlen(DOCTEST_OPTIONS_PREFIX_DISPLAY)); + printVersion(); + // clang-format off + s << Color::Cyan << "[doctest]\n" << Color::None; + s << Color::Cyan << "[doctest] " << Color::None; + s << "boolean values: \"1/on/yes/true\" or \"0/off/no/false\"\n"; + s << Color::Cyan << "[doctest] " << Color::None; + s << "filter values: \"str1,str2,str3\" (comma separated strings)\n"; + s << Color::Cyan << "[doctest]\n" << Color::None; + s << Color::Cyan << "[doctest] " << Color::None; + s << "filters use wildcards for matching strings\n"; + s << Color::Cyan << "[doctest] " << Color::None; + s << "something passes a filter if any of the strings in a filter matches\n"; +#ifndef DOCTEST_CONFIG_NO_UNPREFIXED_OPTIONS + s << Color::Cyan << "[doctest]\n" << Color::None; + s << Color::Cyan << "[doctest] " << Color::None; + s << "ALL FLAGS, OPTIONS AND FILTERS ALSO AVAILABLE WITH A \"" DOCTEST_CONFIG_OPTIONS_PREFIX "\" PREFIX!!!\n"; +#endif + s << Color::Cyan << "[doctest]\n" << Color::None; + s << Color::Cyan << "[doctest] " << Color::None; + s << "Query flags - the program quits after them. Available:\n\n"; + s << " -" DOCTEST_OPTIONS_PREFIX_DISPLAY "?, --" DOCTEST_OPTIONS_PREFIX_DISPLAY "help, -" DOCTEST_OPTIONS_PREFIX_DISPLAY "h " + << Whitespace(sizePrefixDisplay*0) << "prints this message\n"; + s << " -" DOCTEST_OPTIONS_PREFIX_DISPLAY "v, --" DOCTEST_OPTIONS_PREFIX_DISPLAY "version " + << Whitespace(sizePrefixDisplay*1) << "prints the version\n"; + s << " -" DOCTEST_OPTIONS_PREFIX_DISPLAY "c, --" DOCTEST_OPTIONS_PREFIX_DISPLAY "count " + << Whitespace(sizePrefixDisplay*1) << "prints the number of matching tests\n"; + s << " -" DOCTEST_OPTIONS_PREFIX_DISPLAY "ltc, --" DOCTEST_OPTIONS_PREFIX_DISPLAY "list-test-cases " + << Whitespace(sizePrefixDisplay*1) << "lists all matching tests by name\n"; + s << " -" DOCTEST_OPTIONS_PREFIX_DISPLAY "lts, --" DOCTEST_OPTIONS_PREFIX_DISPLAY "list-test-suites " + << Whitespace(sizePrefixDisplay*1) << "lists all matching test suites\n"; + s << " -" DOCTEST_OPTIONS_PREFIX_DISPLAY "lr, --" DOCTEST_OPTIONS_PREFIX_DISPLAY "list-reporters " + << Whitespace(sizePrefixDisplay*1) << "lists all registered reporters\n\n"; + // ================================================================================== << 79 + s << Color::Cyan << "[doctest] " << Color::None; + s << "The available / options/filters are:\n\n"; + s << " -" DOCTEST_OPTIONS_PREFIX_DISPLAY "tc, --" DOCTEST_OPTIONS_PREFIX_DISPLAY "test-case= " + << Whitespace(sizePrefixDisplay*1) << "filters tests by their name\n"; + s << " -" DOCTEST_OPTIONS_PREFIX_DISPLAY "tce, --" DOCTEST_OPTIONS_PREFIX_DISPLAY "test-case-exclude= " + << Whitespace(sizePrefixDisplay*1) << "filters OUT tests by their name\n"; + s << " -" DOCTEST_OPTIONS_PREFIX_DISPLAY "sf, --" DOCTEST_OPTIONS_PREFIX_DISPLAY "source-file= " + << Whitespace(sizePrefixDisplay*1) << "filters tests by their file\n"; + s << " -" DOCTEST_OPTIONS_PREFIX_DISPLAY "sfe, --" DOCTEST_OPTIONS_PREFIX_DISPLAY "source-file-exclude= " + << Whitespace(sizePrefixDisplay*1) << "filters OUT tests by their file\n"; + s << " -" DOCTEST_OPTIONS_PREFIX_DISPLAY "ts, --" DOCTEST_OPTIONS_PREFIX_DISPLAY "test-suite= " + << Whitespace(sizePrefixDisplay*1) << "filters tests by their test suite\n"; + s << " -" DOCTEST_OPTIONS_PREFIX_DISPLAY "tse, --" DOCTEST_OPTIONS_PREFIX_DISPLAY "test-suite-exclude= " + << Whitespace(sizePrefixDisplay*1) << "filters OUT tests by their test suite\n"; + s << " -" DOCTEST_OPTIONS_PREFIX_DISPLAY "sc, --" DOCTEST_OPTIONS_PREFIX_DISPLAY "subcase= " + << Whitespace(sizePrefixDisplay*1) << "filters subcases by their name\n"; + s << " -" DOCTEST_OPTIONS_PREFIX_DISPLAY "sce, --" DOCTEST_OPTIONS_PREFIX_DISPLAY "subcase-exclude= " + << Whitespace(sizePrefixDisplay*1) << "filters OUT subcases by their name\n"; + s << " -" DOCTEST_OPTIONS_PREFIX_DISPLAY "r, --" DOCTEST_OPTIONS_PREFIX_DISPLAY "reporters= " + << Whitespace(sizePrefixDisplay*1) << "reporters to use (console is default)\n"; + s << " -" DOCTEST_OPTIONS_PREFIX_DISPLAY "o, --" DOCTEST_OPTIONS_PREFIX_DISPLAY "out= " + << Whitespace(sizePrefixDisplay*1) << "output filename\n"; + s << " -" DOCTEST_OPTIONS_PREFIX_DISPLAY "ob, --" DOCTEST_OPTIONS_PREFIX_DISPLAY "order-by= " + << Whitespace(sizePrefixDisplay*1) << "how the tests should be ordered\n"; + s << Whitespace(sizePrefixDisplay*3) << " - [file/suite/name/rand/none]\n"; + s << " -" DOCTEST_OPTIONS_PREFIX_DISPLAY "rs, --" DOCTEST_OPTIONS_PREFIX_DISPLAY "rand-seed= " + << Whitespace(sizePrefixDisplay*1) << "seed for random ordering\n"; + s << " -" DOCTEST_OPTIONS_PREFIX_DISPLAY "f, --" DOCTEST_OPTIONS_PREFIX_DISPLAY "first= " + << Whitespace(sizePrefixDisplay*1) << "the first test passing the filters to\n"; + s << Whitespace(sizePrefixDisplay*3) << " execute - for range-based execution\n"; + s << " -" DOCTEST_OPTIONS_PREFIX_DISPLAY "l, --" DOCTEST_OPTIONS_PREFIX_DISPLAY "last= " + << Whitespace(sizePrefixDisplay*1) << "the last test passing the filters to\n"; + s << Whitespace(sizePrefixDisplay*3) << " execute - for range-based execution\n"; + s << " -" DOCTEST_OPTIONS_PREFIX_DISPLAY "aa, --" DOCTEST_OPTIONS_PREFIX_DISPLAY "abort-after= " + << Whitespace(sizePrefixDisplay*1) << "stop after failed assertions\n"; + s << " -" DOCTEST_OPTIONS_PREFIX_DISPLAY "scfl,--" DOCTEST_OPTIONS_PREFIX_DISPLAY "subcase-filter-levels= " + << Whitespace(sizePrefixDisplay*1) << "apply filters for the first levels\n"; + s << Color::Cyan << "\n[doctest] " << Color::None; + s << "Bool options - can be used like flags and true is assumed. Available:\n\n"; + s << " -" DOCTEST_OPTIONS_PREFIX_DISPLAY "s, --" DOCTEST_OPTIONS_PREFIX_DISPLAY "success= " + << Whitespace(sizePrefixDisplay*1) << "include successful assertions in output\n"; + s << " -" DOCTEST_OPTIONS_PREFIX_DISPLAY "cs, --" DOCTEST_OPTIONS_PREFIX_DISPLAY "case-sensitive= " + << Whitespace(sizePrefixDisplay*1) << "filters being treated as case sensitive\n"; + s << " -" DOCTEST_OPTIONS_PREFIX_DISPLAY "e, --" DOCTEST_OPTIONS_PREFIX_DISPLAY "exit= " + << Whitespace(sizePrefixDisplay*1) << "exits after the tests finish\n"; + s << " -" DOCTEST_OPTIONS_PREFIX_DISPLAY "d, --" DOCTEST_OPTIONS_PREFIX_DISPLAY "duration= " + << Whitespace(sizePrefixDisplay*1) << "prints the time duration of each test\n"; + s << " -" DOCTEST_OPTIONS_PREFIX_DISPLAY "m, --" DOCTEST_OPTIONS_PREFIX_DISPLAY "minimal= " + << Whitespace(sizePrefixDisplay*1) << "minimal console output (only failures)\n"; + s << " -" DOCTEST_OPTIONS_PREFIX_DISPLAY "q, --" DOCTEST_OPTIONS_PREFIX_DISPLAY "quiet= " + << Whitespace(sizePrefixDisplay*1) << "no console output\n"; + s << " -" DOCTEST_OPTIONS_PREFIX_DISPLAY "nt, --" DOCTEST_OPTIONS_PREFIX_DISPLAY "no-throw= " + << Whitespace(sizePrefixDisplay*1) << "skips exceptions-related assert checks\n"; + s << " -" DOCTEST_OPTIONS_PREFIX_DISPLAY "ne, --" DOCTEST_OPTIONS_PREFIX_DISPLAY "no-exitcode= " + << Whitespace(sizePrefixDisplay*1) << "returns (or exits) always with success\n"; + s << " -" DOCTEST_OPTIONS_PREFIX_DISPLAY "nr, --" DOCTEST_OPTIONS_PREFIX_DISPLAY "no-run= " + << Whitespace(sizePrefixDisplay*1) << "skips all runtime doctest operations\n"; + s << " -" DOCTEST_OPTIONS_PREFIX_DISPLAY "ni, --" DOCTEST_OPTIONS_PREFIX_DISPLAY "no-intro= " + << Whitespace(sizePrefixDisplay*1) << "omit the framework intro in the output\n"; + s << " -" DOCTEST_OPTIONS_PREFIX_DISPLAY "nv, --" DOCTEST_OPTIONS_PREFIX_DISPLAY "no-version= " + << Whitespace(sizePrefixDisplay*1) << "omit the framework version in the output\n"; + s << " -" DOCTEST_OPTIONS_PREFIX_DISPLAY "nc, --" DOCTEST_OPTIONS_PREFIX_DISPLAY "no-colors= " + << Whitespace(sizePrefixDisplay*1) << "disables colors in output\n"; + s << " -" DOCTEST_OPTIONS_PREFIX_DISPLAY "fc, --" DOCTEST_OPTIONS_PREFIX_DISPLAY "force-colors= " + << Whitespace(sizePrefixDisplay*1) << "use colors even when not in a tty\n"; + s << " -" DOCTEST_OPTIONS_PREFIX_DISPLAY "nb, --" DOCTEST_OPTIONS_PREFIX_DISPLAY "no-breaks= " + << Whitespace(sizePrefixDisplay*1) << "disables breakpoints in debuggers\n"; + s << " -" DOCTEST_OPTIONS_PREFIX_DISPLAY "ns, --" DOCTEST_OPTIONS_PREFIX_DISPLAY "no-skip= " + << Whitespace(sizePrefixDisplay*1) << "don't skip test cases marked as skip\n"; + s << " -" DOCTEST_OPTIONS_PREFIX_DISPLAY "gfl, --" DOCTEST_OPTIONS_PREFIX_DISPLAY "gnu-file-line= " + << Whitespace(sizePrefixDisplay*1) << ":n: vs (n): for line numbers in output\n"; + s << " -" DOCTEST_OPTIONS_PREFIX_DISPLAY "npf, --" DOCTEST_OPTIONS_PREFIX_DISPLAY "no-path-filenames= " + << Whitespace(sizePrefixDisplay*1) << "only filenames and no paths in output\n"; + s << " -" DOCTEST_OPTIONS_PREFIX_DISPLAY "nln, --" DOCTEST_OPTIONS_PREFIX_DISPLAY "no-line-numbers= " + << Whitespace(sizePrefixDisplay*1) << "0 instead of real line numbers in output\n"; + // ================================================================================== << 79 + // clang-format on + + s << Color::Cyan << "\n[doctest] " << Color::None; + s << "for more information visit the project documentation\n\n"; + } + + void printRegisteredReporters() { + printVersion(); + auto printReporters = [this] (const reporterMap& reporters, const char* type) { + if(reporters.size()) { + s << Color::Cyan << "[doctest] " << Color::None << "listing all registered " << type << "\n"; + for(auto& curr : reporters) + s << "priority: " << std::setw(5) << curr.first.first + << " name: " << curr.first.second << "\n"; + } + }; + printReporters(getListeners(), "listeners"); + printReporters(getReporters(), "reporters"); + } + + // ========================================================================================= + // WHAT FOLLOWS ARE OVERRIDES OF THE VIRTUAL METHODS OF THE REPORTER INTERFACE + // ========================================================================================= + + void report_query(const QueryData& in) override { + if(opt.version) { + printVersion(); + } else if(opt.help) { + printHelp(); + } else if(opt.list_reporters) { + printRegisteredReporters(); + } else if(opt.count || opt.list_test_cases) { + if(opt.list_test_cases) { + s << Color::Cyan << "[doctest] " << Color::None + << "listing all test case names\n"; + separator_to_stream(); + } + + for(unsigned i = 0; i < in.num_data; ++i) + s << Color::None << in.data[i]->m_name << "\n"; + + separator_to_stream(); + + s << Color::Cyan << "[doctest] " << Color::None + << "unskipped test cases passing the current filters: " + << g_cs->numTestCasesPassingFilters << "\n"; + + } else if(opt.list_test_suites) { + s << Color::Cyan << "[doctest] " << Color::None << "listing all test suites\n"; + separator_to_stream(); + + for(unsigned i = 0; i < in.num_data; ++i) + s << Color::None << in.data[i]->m_test_suite << "\n"; + + separator_to_stream(); + + s << Color::Cyan << "[doctest] " << Color::None + << "unskipped test cases passing the current filters: " + << g_cs->numTestCasesPassingFilters << "\n"; + s << Color::Cyan << "[doctest] " << Color::None + << "test suites with unskipped test cases passing the current filters: " + << g_cs->numTestSuitesPassingFilters << "\n"; + } + } + + void test_run_start() override { + if(!opt.minimal) + printIntro(); + } + + void test_run_end(const TestRunStats& p) override { + if(opt.minimal && p.numTestCasesFailed == 0) + return; + + separator_to_stream(); + s << std::dec; + + auto totwidth = int(std::ceil(log10(static_cast(std::max(p.numTestCasesPassingFilters, static_cast(p.numAsserts))) + 1))); + auto passwidth = int(std::ceil(log10(static_cast(std::max(p.numTestCasesPassingFilters - p.numTestCasesFailed, static_cast(p.numAsserts - p.numAssertsFailed))) + 1))); + auto failwidth = int(std::ceil(log10(static_cast(std::max(p.numTestCasesFailed, static_cast(p.numAssertsFailed))) + 1))); + const bool anythingFailed = p.numTestCasesFailed > 0 || p.numAssertsFailed > 0; + s << Color::Cyan << "[doctest] " << Color::None << "test cases: " << std::setw(totwidth) + << p.numTestCasesPassingFilters << " | " + << ((p.numTestCasesPassingFilters == 0 || anythingFailed) ? Color::None : + Color::Green) + << std::setw(passwidth) << p.numTestCasesPassingFilters - p.numTestCasesFailed << " passed" + << Color::None << " | " << (p.numTestCasesFailed > 0 ? Color::Red : Color::None) + << std::setw(failwidth) << p.numTestCasesFailed << " failed" << Color::None << " |"; + if(opt.no_skipped_summary == false) { + const int numSkipped = p.numTestCases - p.numTestCasesPassingFilters; + s << " " << (numSkipped == 0 ? Color::None : Color::Yellow) << numSkipped + << " skipped" << Color::None; + } + s << "\n"; + s << Color::Cyan << "[doctest] " << Color::None << "assertions: " << std::setw(totwidth) + << p.numAsserts << " | " + << ((p.numAsserts == 0 || anythingFailed) ? Color::None : Color::Green) + << std::setw(passwidth) << (p.numAsserts - p.numAssertsFailed) << " passed" << Color::None + << " | " << (p.numAssertsFailed > 0 ? Color::Red : Color::None) << std::setw(failwidth) + << p.numAssertsFailed << " failed" << Color::None << " |\n"; + s << Color::Cyan << "[doctest] " << Color::None + << "Status: " << (p.numTestCasesFailed > 0 ? Color::Red : Color::Green) + << ((p.numTestCasesFailed > 0) ? "FAILURE!" : "SUCCESS!") << Color::None << std::endl; + } + + void test_case_start(const TestCaseData& in) override { + hasLoggedCurrentTestStart = false; + tc = ∈ + subcasesStack.clear(); + currentSubcaseLevel = 0; + } + + void test_case_reenter(const TestCaseData&) override { + subcasesStack.clear(); + } + + void test_case_end(const CurrentTestCaseStats& st) override { + if(tc->m_no_output) + return; + + // log the preamble of the test case only if there is something + // else to print - something other than that an assert has failed + if(opt.duration || + (st.failure_flags && st.failure_flags != static_cast(TestCaseFailureReason::AssertFailure))) + logTestStart(); + + if(opt.duration) + s << Color::None << std::setprecision(6) << std::fixed << st.seconds + << " s: " << tc->m_name << "\n"; + + if(st.failure_flags & TestCaseFailureReason::Timeout) + s << Color::Red << "Test case exceeded time limit of " << std::setprecision(6) + << std::fixed << tc->m_timeout << "!\n"; + + if(st.failure_flags & TestCaseFailureReason::ShouldHaveFailedButDidnt) { + s << Color::Red << "Should have failed but didn't! Marking it as failed!\n"; + } else if(st.failure_flags & TestCaseFailureReason::ShouldHaveFailedAndDid) { + s << Color::Yellow << "Failed as expected so marking it as not failed\n"; + } else if(st.failure_flags & TestCaseFailureReason::CouldHaveFailedAndDid) { + s << Color::Yellow << "Allowed to fail so marking it as not failed\n"; + } else if(st.failure_flags & TestCaseFailureReason::DidntFailExactlyNumTimes) { + s << Color::Red << "Didn't fail exactly " << tc->m_expected_failures + << " times so marking it as failed!\n"; + } else if(st.failure_flags & TestCaseFailureReason::FailedExactlyNumTimes) { + s << Color::Yellow << "Failed exactly " << tc->m_expected_failures + << " times as expected so marking it as not failed!\n"; + } + if(st.failure_flags & TestCaseFailureReason::TooManyFailedAsserts) { + s << Color::Red << "Aborting - too many failed asserts!\n"; + } + s << Color::None; // lgtm [cpp/useless-expression] + } + + void test_case_exception(const TestCaseException& e) override { + DOCTEST_LOCK_MUTEX(mutex) + if(tc->m_no_output) + return; + + logTestStart(); + + file_line_to_stream(tc->m_file.c_str(), tc->m_line, " "); + successOrFailColoredStringToStream(false, e.is_crash ? assertType::is_require : + assertType::is_check); + s << Color::Red << (e.is_crash ? "test case CRASHED: " : "test case THREW exception: ") + << Color::Cyan << e.error_string << "\n"; + + int num_stringified_contexts = get_num_stringified_contexts(); + if(num_stringified_contexts) { + auto stringified_contexts = get_stringified_contexts(); + s << Color::None << " logged: "; + for(int i = num_stringified_contexts; i > 0; --i) { + s << (i == num_stringified_contexts ? "" : " ") + << stringified_contexts[i - 1] << "\n"; + } + } + s << "\n" << Color::None; + } + + void subcase_start(const SubcaseSignature& subc) override { + subcasesStack.push_back(subc); + ++currentSubcaseLevel; + hasLoggedCurrentTestStart = false; + } + + void subcase_end() override { + --currentSubcaseLevel; + hasLoggedCurrentTestStart = false; + } + + void log_assert(const AssertData& rb) override { + if((!rb.m_failed && !opt.success) || tc->m_no_output) + return; + + DOCTEST_LOCK_MUTEX(mutex) + + logTestStart(); + + file_line_to_stream(rb.m_file, rb.m_line, " "); + successOrFailColoredStringToStream(!rb.m_failed, rb.m_at); + + fulltext_log_assert_to_stream(s, rb); + + log_contexts(); + } + + void log_message(const MessageData& mb) override { + if(tc->m_no_output) + return; + + DOCTEST_LOCK_MUTEX(mutex) + + logTestStart(); + + file_line_to_stream(mb.m_file, mb.m_line, " "); + s << getSuccessOrFailColor(false, mb.m_severity) + << getSuccessOrFailString(mb.m_severity & assertType::is_warn, mb.m_severity, + "MESSAGE") << ": "; + s << Color::None << mb.m_string << "\n"; + log_contexts(); + } + + void test_case_skipped(const TestCaseData&) override {} + }; + + DOCTEST_REGISTER_REPORTER("console", 0, ConsoleReporter); + +#ifdef DOCTEST_PLATFORM_WINDOWS + struct DebugOutputWindowReporter : public ConsoleReporter + { + DOCTEST_THREAD_LOCAL static std::ostringstream oss; + + DebugOutputWindowReporter(const ContextOptions& co) + : ConsoleReporter(co, oss) {} + +#define DOCTEST_DEBUG_OUTPUT_REPORTER_OVERRIDE(func, type, arg) \ + void func(type arg) override { \ + bool with_col = g_no_colors; \ + g_no_colors = false; \ + ConsoleReporter::func(arg); \ + if(oss.tellp() != std::streampos{}) { \ + DOCTEST_OUTPUT_DEBUG_STRING(oss.str().c_str()); \ + oss.str(""); \ + } \ + g_no_colors = with_col; \ + } + + DOCTEST_DEBUG_OUTPUT_REPORTER_OVERRIDE(test_run_start, DOCTEST_EMPTY, DOCTEST_EMPTY) + DOCTEST_DEBUG_OUTPUT_REPORTER_OVERRIDE(test_run_end, const TestRunStats&, in) + DOCTEST_DEBUG_OUTPUT_REPORTER_OVERRIDE(test_case_start, const TestCaseData&, in) + DOCTEST_DEBUG_OUTPUT_REPORTER_OVERRIDE(test_case_reenter, const TestCaseData&, in) + DOCTEST_DEBUG_OUTPUT_REPORTER_OVERRIDE(test_case_end, const CurrentTestCaseStats&, in) + DOCTEST_DEBUG_OUTPUT_REPORTER_OVERRIDE(test_case_exception, const TestCaseException&, in) + DOCTEST_DEBUG_OUTPUT_REPORTER_OVERRIDE(subcase_start, const SubcaseSignature&, in) + DOCTEST_DEBUG_OUTPUT_REPORTER_OVERRIDE(subcase_end, DOCTEST_EMPTY, DOCTEST_EMPTY) + DOCTEST_DEBUG_OUTPUT_REPORTER_OVERRIDE(log_assert, const AssertData&, in) + DOCTEST_DEBUG_OUTPUT_REPORTER_OVERRIDE(log_message, const MessageData&, in) + DOCTEST_DEBUG_OUTPUT_REPORTER_OVERRIDE(test_case_skipped, const TestCaseData&, in) + }; + + DOCTEST_THREAD_LOCAL std::ostringstream DebugOutputWindowReporter::oss; +#endif // DOCTEST_PLATFORM_WINDOWS + + // the implementation of parseOption() + bool parseOptionImpl(int argc, const char* const* argv, const char* pattern, String* value) { + // going from the end to the beginning and stopping on the first occurrence from the end + for(int i = argc; i > 0; --i) { + auto index = i - 1; + auto temp = std::strstr(argv[index], pattern); + if(temp && (value || strlen(temp) == strlen(pattern))) { //!OCLINT prefer early exits and continue + // eliminate matches in which the chars before the option are not '-' + bool noBadCharsFound = true; + auto curr = argv[index]; + while(curr != temp) { + if(*curr++ != '-') { + noBadCharsFound = false; + break; + } + } + if(noBadCharsFound && argv[index][0] == '-') { + if(value) { + // parsing the value of an option + temp += strlen(pattern); + const unsigned len = strlen(temp); + if(len) { + *value = temp; + return true; + } + } else { + // just a flag - no value + return true; + } + } + } + } + return false; + } + + // parses an option and returns the string after the '=' character + bool parseOption(int argc, const char* const* argv, const char* pattern, String* value = nullptr, + const String& defaultVal = String()) { + if(value) + *value = defaultVal; +#ifndef DOCTEST_CONFIG_NO_UNPREFIXED_OPTIONS + // offset (normally 3 for "dt-") to skip prefix + if(parseOptionImpl(argc, argv, pattern + strlen(DOCTEST_CONFIG_OPTIONS_PREFIX), value)) + return true; +#endif // DOCTEST_CONFIG_NO_UNPREFIXED_OPTIONS + return parseOptionImpl(argc, argv, pattern, value); + } + + // locates a flag on the command line + bool parseFlag(int argc, const char* const* argv, const char* pattern) { + return parseOption(argc, argv, pattern); + } + + // parses a comma separated list of words after a pattern in one of the arguments in argv + bool parseCommaSepArgs(int argc, const char* const* argv, const char* pattern, + std::vector& res) { + String filtersString; + if(parseOption(argc, argv, pattern, &filtersString)) { + // tokenize with "," as a separator, unless escaped with backslash + std::ostringstream s; + auto flush = [&s, &res]() { + auto string = s.str(); + if(string.size() > 0) { + res.push_back(string.c_str()); + } + s.str(""); + }; + + bool seenBackslash = false; + const char* current = filtersString.c_str(); + const char* end = current + strlen(current); + while(current != end) { + char character = *current++; + if(seenBackslash) { + seenBackslash = false; + if(character == ',' || character == '\\') { + s.put(character); + continue; + } + s.put('\\'); + } + if(character == '\\') { + seenBackslash = true; + } else if(character == ',') { + flush(); + } else { + s.put(character); + } + } + + if(seenBackslash) { + s.put('\\'); + } + flush(); + return true; + } + return false; + } + + enum optionType + { + option_bool, + option_int + }; + + // parses an int/bool option from the command line + bool parseIntOption(int argc, const char* const* argv, const char* pattern, optionType type, + int& res) { + String parsedValue; + if(!parseOption(argc, argv, pattern, &parsedValue)) + return false; + + if(type) { + // integer + // TODO: change this to use std::stoi or something else! currently it uses undefined behavior - assumes '0' on failed parse... + int theInt = std::atoi(parsedValue.c_str()); + if (theInt != 0) { + res = theInt; //!OCLINT parameter reassignment + return true; + } + } else { + // boolean + const char positive[][5] = { "1", "true", "on", "yes" }; // 5 - strlen("true") + 1 + const char negative[][6] = { "0", "false", "off", "no" }; // 6 - strlen("false") + 1 + + // if the value matches any of the positive/negative possibilities + for (unsigned i = 0; i < 4; i++) { + if (parsedValue.compare(positive[i], true) == 0) { + res = 1; //!OCLINT parameter reassignment + return true; + } + if (parsedValue.compare(negative[i], true) == 0) { + res = 0; //!OCLINT parameter reassignment + return true; + } + } + } + return false; + } +} // namespace + +Context::Context(int argc, const char* const* argv) + : p(new detail::ContextState) { + parseArgs(argc, argv, true); + if(argc) + p->binary_name = argv[0]; +} + +Context::~Context() { + if(g_cs == p) + g_cs = nullptr; + delete p; +} + +void Context::applyCommandLine(int argc, const char* const* argv) { + parseArgs(argc, argv); + if(argc) + p->binary_name = argv[0]; +} + +// parses args +void Context::parseArgs(int argc, const char* const* argv, bool withDefaults) { + using namespace detail; + + // clang-format off + parseCommaSepArgs(argc, argv, DOCTEST_CONFIG_OPTIONS_PREFIX "source-file=", p->filters[0]); + parseCommaSepArgs(argc, argv, DOCTEST_CONFIG_OPTIONS_PREFIX "sf=", p->filters[0]); + parseCommaSepArgs(argc, argv, DOCTEST_CONFIG_OPTIONS_PREFIX "source-file-exclude=",p->filters[1]); + parseCommaSepArgs(argc, argv, DOCTEST_CONFIG_OPTIONS_PREFIX "sfe=", p->filters[1]); + parseCommaSepArgs(argc, argv, DOCTEST_CONFIG_OPTIONS_PREFIX "test-suite=", p->filters[2]); + parseCommaSepArgs(argc, argv, DOCTEST_CONFIG_OPTIONS_PREFIX "ts=", p->filters[2]); + parseCommaSepArgs(argc, argv, DOCTEST_CONFIG_OPTIONS_PREFIX "test-suite-exclude=", p->filters[3]); + parseCommaSepArgs(argc, argv, DOCTEST_CONFIG_OPTIONS_PREFIX "tse=", p->filters[3]); + parseCommaSepArgs(argc, argv, DOCTEST_CONFIG_OPTIONS_PREFIX "test-case=", p->filters[4]); + parseCommaSepArgs(argc, argv, DOCTEST_CONFIG_OPTIONS_PREFIX "tc=", p->filters[4]); + parseCommaSepArgs(argc, argv, DOCTEST_CONFIG_OPTIONS_PREFIX "test-case-exclude=", p->filters[5]); + parseCommaSepArgs(argc, argv, DOCTEST_CONFIG_OPTIONS_PREFIX "tce=", p->filters[5]); + parseCommaSepArgs(argc, argv, DOCTEST_CONFIG_OPTIONS_PREFIX "subcase=", p->filters[6]); + parseCommaSepArgs(argc, argv, DOCTEST_CONFIG_OPTIONS_PREFIX "sc=", p->filters[6]); + parseCommaSepArgs(argc, argv, DOCTEST_CONFIG_OPTIONS_PREFIX "subcase-exclude=", p->filters[7]); + parseCommaSepArgs(argc, argv, DOCTEST_CONFIG_OPTIONS_PREFIX "sce=", p->filters[7]); + parseCommaSepArgs(argc, argv, DOCTEST_CONFIG_OPTIONS_PREFIX "reporters=", p->filters[8]); + parseCommaSepArgs(argc, argv, DOCTEST_CONFIG_OPTIONS_PREFIX "r=", p->filters[8]); + // clang-format on + + int intRes = 0; + String strRes; + +#define DOCTEST_PARSE_AS_BOOL_OR_FLAG(name, sname, var, default) \ + if(parseIntOption(argc, argv, DOCTEST_CONFIG_OPTIONS_PREFIX name "=", option_bool, intRes) || \ + parseIntOption(argc, argv, DOCTEST_CONFIG_OPTIONS_PREFIX sname "=", option_bool, intRes)) \ + p->var = static_cast(intRes); \ + else if(parseFlag(argc, argv, DOCTEST_CONFIG_OPTIONS_PREFIX name) || \ + parseFlag(argc, argv, DOCTEST_CONFIG_OPTIONS_PREFIX sname)) \ + p->var = true; \ + else if(withDefaults) \ + p->var = default + +#define DOCTEST_PARSE_INT_OPTION(name, sname, var, default) \ + if(parseIntOption(argc, argv, DOCTEST_CONFIG_OPTIONS_PREFIX name "=", option_int, intRes) || \ + parseIntOption(argc, argv, DOCTEST_CONFIG_OPTIONS_PREFIX sname "=", option_int, intRes)) \ + p->var = intRes; \ + else if(withDefaults) \ + p->var = default + +#define DOCTEST_PARSE_STR_OPTION(name, sname, var, default) \ + if(parseOption(argc, argv, DOCTEST_CONFIG_OPTIONS_PREFIX name "=", &strRes, default) || \ + parseOption(argc, argv, DOCTEST_CONFIG_OPTIONS_PREFIX sname "=", &strRes, default) || \ + withDefaults) \ + p->var = strRes + + // clang-format off + DOCTEST_PARSE_STR_OPTION("out", "o", out, ""); + DOCTEST_PARSE_STR_OPTION("order-by", "ob", order_by, "file"); + DOCTEST_PARSE_INT_OPTION("rand-seed", "rs", rand_seed, 0); + + DOCTEST_PARSE_INT_OPTION("first", "f", first, 0); + DOCTEST_PARSE_INT_OPTION("last", "l", last, UINT_MAX); + + DOCTEST_PARSE_INT_OPTION("abort-after", "aa", abort_after, 0); + DOCTEST_PARSE_INT_OPTION("subcase-filter-levels", "scfl", subcase_filter_levels, INT_MAX); + + DOCTEST_PARSE_AS_BOOL_OR_FLAG("success", "s", success, false); + DOCTEST_PARSE_AS_BOOL_OR_FLAG("case-sensitive", "cs", case_sensitive, false); + DOCTEST_PARSE_AS_BOOL_OR_FLAG("exit", "e", exit, false); + DOCTEST_PARSE_AS_BOOL_OR_FLAG("duration", "d", duration, false); + DOCTEST_PARSE_AS_BOOL_OR_FLAG("minimal", "m", minimal, false); + DOCTEST_PARSE_AS_BOOL_OR_FLAG("quiet", "q", quiet, false); + DOCTEST_PARSE_AS_BOOL_OR_FLAG("no-throw", "nt", no_throw, false); + DOCTEST_PARSE_AS_BOOL_OR_FLAG("no-exitcode", "ne", no_exitcode, false); + DOCTEST_PARSE_AS_BOOL_OR_FLAG("no-run", "nr", no_run, false); + DOCTEST_PARSE_AS_BOOL_OR_FLAG("no-intro", "ni", no_intro, false); + DOCTEST_PARSE_AS_BOOL_OR_FLAG("no-version", "nv", no_version, false); + DOCTEST_PARSE_AS_BOOL_OR_FLAG("no-colors", "nc", no_colors, false); + DOCTEST_PARSE_AS_BOOL_OR_FLAG("force-colors", "fc", force_colors, false); + DOCTEST_PARSE_AS_BOOL_OR_FLAG("no-breaks", "nb", no_breaks, false); + DOCTEST_PARSE_AS_BOOL_OR_FLAG("no-skip", "ns", no_skip, false); + DOCTEST_PARSE_AS_BOOL_OR_FLAG("gnu-file-line", "gfl", gnu_file_line, !bool(DOCTEST_MSVC)); + DOCTEST_PARSE_AS_BOOL_OR_FLAG("no-path-filenames", "npf", no_path_in_filenames, false); + DOCTEST_PARSE_AS_BOOL_OR_FLAG("no-line-numbers", "nln", no_line_numbers, false); + DOCTEST_PARSE_AS_BOOL_OR_FLAG("no-debug-output", "ndo", no_debug_output, false); + DOCTEST_PARSE_AS_BOOL_OR_FLAG("no-skipped-summary", "nss", no_skipped_summary, false); + DOCTEST_PARSE_AS_BOOL_OR_FLAG("no-time-in-output", "ntio", no_time_in_output, false); + // clang-format on + + if(withDefaults) { + p->help = false; + p->version = false; + p->count = false; + p->list_test_cases = false; + p->list_test_suites = false; + p->list_reporters = false; + } + if(parseFlag(argc, argv, DOCTEST_CONFIG_OPTIONS_PREFIX "help") || + parseFlag(argc, argv, DOCTEST_CONFIG_OPTIONS_PREFIX "h") || + parseFlag(argc, argv, DOCTEST_CONFIG_OPTIONS_PREFIX "?")) { + p->help = true; + p->exit = true; + } + if(parseFlag(argc, argv, DOCTEST_CONFIG_OPTIONS_PREFIX "version") || + parseFlag(argc, argv, DOCTEST_CONFIG_OPTIONS_PREFIX "v")) { + p->version = true; + p->exit = true; + } + if(parseFlag(argc, argv, DOCTEST_CONFIG_OPTIONS_PREFIX "count") || + parseFlag(argc, argv, DOCTEST_CONFIG_OPTIONS_PREFIX "c")) { + p->count = true; + p->exit = true; + } + if(parseFlag(argc, argv, DOCTEST_CONFIG_OPTIONS_PREFIX "list-test-cases") || + parseFlag(argc, argv, DOCTEST_CONFIG_OPTIONS_PREFIX "ltc")) { + p->list_test_cases = true; + p->exit = true; + } + if(parseFlag(argc, argv, DOCTEST_CONFIG_OPTIONS_PREFIX "list-test-suites") || + parseFlag(argc, argv, DOCTEST_CONFIG_OPTIONS_PREFIX "lts")) { + p->list_test_suites = true; + p->exit = true; + } + if(parseFlag(argc, argv, DOCTEST_CONFIG_OPTIONS_PREFIX "list-reporters") || + parseFlag(argc, argv, DOCTEST_CONFIG_OPTIONS_PREFIX "lr")) { + p->list_reporters = true; + p->exit = true; + } +} + +// allows the user to add procedurally to the filters from the command line +void Context::addFilter(const char* filter, const char* value) { setOption(filter, value); } + +// allows the user to clear all filters from the command line +void Context::clearFilters() { + for(auto& curr : p->filters) + curr.clear(); +} + +// allows the user to override procedurally the bool options from the command line +void Context::setOption(const char* option, bool value) { + setOption(option, value ? "true" : "false"); +} + +// allows the user to override procedurally the int options from the command line +void Context::setOption(const char* option, int value) { + setOption(option, toString(value).c_str()); +} + +// allows the user to override procedurally the string options from the command line +void Context::setOption(const char* option, const char* value) { + auto argv = String("-") + option + "=" + value; + auto lvalue = argv.c_str(); + parseArgs(1, &lvalue); +} + +// users should query this in their main() and exit the program if true +bool Context::shouldExit() { return p->exit; } + +void Context::setAsDefaultForAssertsOutOfTestCases() { g_cs = p; } + +void Context::setAssertHandler(detail::assert_handler ah) { p->ah = ah; } + +void Context::setCout(std::ostream* out) { p->cout = out; } + +static class DiscardOStream : public std::ostream +{ +private: + class : public std::streambuf + { + private: + // allowing some buffering decreases the amount of calls to overflow + char buf[1024]; + + protected: + std::streamsize xsputn(const char_type*, std::streamsize count) override { return count; } + + int_type overflow(int_type ch) override { + setp(std::begin(buf), std::end(buf)); + return traits_type::not_eof(ch); + } + } discardBuf; + +public: + DiscardOStream() + : std::ostream(&discardBuf) {} +} discardOut; + +// the main function that does all the filtering and test running +int Context::run() { + using namespace detail; + + // save the old context state in case such was setup - for using asserts out of a testing context + auto old_cs = g_cs; + // this is the current contest + g_cs = p; + is_running_in_test = true; + + g_no_colors = p->no_colors; + p->resetRunData(); + + std::fstream fstr; + if(p->cout == nullptr) { + if(p->quiet) { + p->cout = &discardOut; + } else if(p->out.size()) { + // to a file if specified + fstr.open(p->out.c_str(), std::fstream::out); + p->cout = &fstr; + } else { +#ifndef DOCTEST_CONFIG_NO_INCLUDE_IOSTREAM + // stdout by default + p->cout = &std::cout; +#else // DOCTEST_CONFIG_NO_INCLUDE_IOSTREAM + return EXIT_FAILURE; +#endif // DOCTEST_CONFIG_NO_INCLUDE_IOSTREAM + } + } + + FatalConditionHandler::allocateAltStackMem(); + + auto cleanup_and_return = [&]() { + FatalConditionHandler::freeAltStackMem(); + + if(fstr.is_open()) + fstr.close(); + + // restore context + g_cs = old_cs; + is_running_in_test = false; + + // we have to free the reporters which were allocated when the run started + for(auto& curr : p->reporters_currently_used) + delete curr; + p->reporters_currently_used.clear(); + + if(p->numTestCasesFailed && !p->no_exitcode) + return EXIT_FAILURE; + return EXIT_SUCCESS; + }; + + // setup default reporter if none is given through the command line + if(p->filters[8].empty()) + p->filters[8].push_back("console"); + + // check to see if any of the registered reporters has been selected + for(auto& curr : getReporters()) { + if(matchesAny(curr.first.second.c_str(), p->filters[8], false, p->case_sensitive)) + p->reporters_currently_used.push_back(curr.second(*g_cs)); + } + + // TODO: check if there is nothing in reporters_currently_used + + // prepend all listeners + for(auto& curr : getListeners()) + p->reporters_currently_used.insert(p->reporters_currently_used.begin(), curr.second(*g_cs)); + +#ifdef DOCTEST_PLATFORM_WINDOWS + if(isDebuggerActive() && p->no_debug_output == false) + p->reporters_currently_used.push_back(new DebugOutputWindowReporter(*g_cs)); +#endif // DOCTEST_PLATFORM_WINDOWS + + // handle version, help and no_run + if(p->no_run || p->version || p->help || p->list_reporters) { + DOCTEST_ITERATE_THROUGH_REPORTERS(report_query, QueryData()); + + return cleanup_and_return(); + } + + std::vector testArray; + for(auto& curr : getRegisteredTests()) + testArray.push_back(&curr); + p->numTestCases = testArray.size(); + + // sort the collected records + if(!testArray.empty()) { + if(p->order_by.compare("file", true) == 0) { + std::sort(testArray.begin(), testArray.end(), fileOrderComparator); + } else if(p->order_by.compare("suite", true) == 0) { + std::sort(testArray.begin(), testArray.end(), suiteOrderComparator); + } else if(p->order_by.compare("name", true) == 0) { + std::sort(testArray.begin(), testArray.end(), nameOrderComparator); + } else if(p->order_by.compare("rand", true) == 0) { + std::srand(p->rand_seed); + + // random_shuffle implementation + const auto first = &testArray[0]; + for(size_t i = testArray.size() - 1; i > 0; --i) { + int idxToSwap = std::rand() % (i + 1); + + const auto temp = first[i]; + + first[i] = first[idxToSwap]; + first[idxToSwap] = temp; + } + } else if(p->order_by.compare("none", true) == 0) { + // means no sorting - beneficial for death tests which call into the executable + // with a specific test case in mind - we don't want to slow down the startup times + } + } + + std::set testSuitesPassingFilt; + + bool query_mode = p->count || p->list_test_cases || p->list_test_suites; + std::vector queryResults; + + if(!query_mode) + DOCTEST_ITERATE_THROUGH_REPORTERS(test_run_start, DOCTEST_EMPTY); + + // invoke the registered functions if they match the filter criteria (or just count them) + for(auto& curr : testArray) { + const auto& tc = *curr; + + bool skip_me = false; + if(tc.m_skip && !p->no_skip) + skip_me = true; + + if(!matchesAny(tc.m_file.c_str(), p->filters[0], true, p->case_sensitive)) + skip_me = true; + if(matchesAny(tc.m_file.c_str(), p->filters[1], false, p->case_sensitive)) + skip_me = true; + if(!matchesAny(tc.m_test_suite, p->filters[2], true, p->case_sensitive)) + skip_me = true; + if(matchesAny(tc.m_test_suite, p->filters[3], false, p->case_sensitive)) + skip_me = true; + if(!matchesAny(tc.m_name, p->filters[4], true, p->case_sensitive)) + skip_me = true; + if(matchesAny(tc.m_name, p->filters[5], false, p->case_sensitive)) + skip_me = true; + + if(!skip_me) + p->numTestCasesPassingFilters++; + + // skip the test if it is not in the execution range + if((p->last < p->numTestCasesPassingFilters && p->first <= p->last) || + (p->first > p->numTestCasesPassingFilters)) + skip_me = true; + + if(skip_me) { + if(!query_mode) + DOCTEST_ITERATE_THROUGH_REPORTERS(test_case_skipped, tc); + continue; + } + + // do not execute the test if we are to only count the number of filter passing tests + if(p->count) + continue; + + // print the name of the test and don't execute it + if(p->list_test_cases) { + queryResults.push_back(&tc); + continue; + } + + // print the name of the test suite if not done already and don't execute it + if(p->list_test_suites) { + if((testSuitesPassingFilt.count(tc.m_test_suite) == 0) && tc.m_test_suite[0] != '\0') { + queryResults.push_back(&tc); + testSuitesPassingFilt.insert(tc.m_test_suite); + p->numTestSuitesPassingFilters++; + } + continue; + } + + // execute the test if it passes all the filtering + { + p->currentTest = &tc; + + p->failure_flags = TestCaseFailureReason::None; + p->seconds = 0; + + // reset atomic counters + p->numAssertsFailedCurrentTest_atomic = 0; + p->numAssertsCurrentTest_atomic = 0; + + p->fullyTraversedSubcases.clear(); + + DOCTEST_ITERATE_THROUGH_REPORTERS(test_case_start, tc); + + p->timer.start(); + + bool run_test = true; + + do { + // reset some of the fields for subcases (except for the set of fully passed ones) + p->reachedLeaf = false; + // May not be empty if previous subcase exited via exception. + p->subcaseStack.clear(); + p->currentSubcaseDepth = 0; + + p->shouldLogCurrentException = true; + + // reset stuff for logging with INFO() + p->stringifiedContexts.clear(); + +#ifndef DOCTEST_CONFIG_NO_EXCEPTIONS + try { +#endif // DOCTEST_CONFIG_NO_EXCEPTIONS +// MSVC 2015 diagnoses fatalConditionHandler as unused (because reset() is a static method) +DOCTEST_MSVC_SUPPRESS_WARNING_WITH_PUSH(4101) // unreferenced local variable + FatalConditionHandler fatalConditionHandler; // Handle signals + // execute the test + tc.m_test(); + fatalConditionHandler.reset(); +DOCTEST_MSVC_SUPPRESS_WARNING_POP +#ifndef DOCTEST_CONFIG_NO_EXCEPTIONS + } catch(const TestFailureException&) { + p->failure_flags |= TestCaseFailureReason::AssertFailure; + } catch(...) { + DOCTEST_ITERATE_THROUGH_REPORTERS(test_case_exception, + {translateActiveException(), false}); + p->failure_flags |= TestCaseFailureReason::Exception; + } +#endif // DOCTEST_CONFIG_NO_EXCEPTIONS + + // exit this loop if enough assertions have failed - even if there are more subcases + if(p->abort_after > 0 && + p->numAssertsFailed + p->numAssertsFailedCurrentTest_atomic >= p->abort_after) { + run_test = false; + p->failure_flags |= TestCaseFailureReason::TooManyFailedAsserts; + } + + if(!p->nextSubcaseStack.empty() && run_test) + DOCTEST_ITERATE_THROUGH_REPORTERS(test_case_reenter, tc); + if(p->nextSubcaseStack.empty()) + run_test = false; + } while(run_test); + + p->finalizeTestCaseData(); + + DOCTEST_ITERATE_THROUGH_REPORTERS(test_case_end, *g_cs); + + p->currentTest = nullptr; + + // stop executing tests if enough assertions have failed + if(p->abort_after > 0 && p->numAssertsFailed >= p->abort_after) + break; + } + } + + if(!query_mode) { + DOCTEST_ITERATE_THROUGH_REPORTERS(test_run_end, *g_cs); + } else { + QueryData qdata; + qdata.run_stats = g_cs; + qdata.data = queryResults.data(); + qdata.num_data = unsigned(queryResults.size()); + DOCTEST_ITERATE_THROUGH_REPORTERS(report_query, qdata); + } + + return cleanup_and_return(); +} + +DOCTEST_DEFINE_INTERFACE(IReporter) + +int IReporter::get_num_active_contexts() { return detail::g_infoContexts.size(); } +const IContextScope* const* IReporter::get_active_contexts() { + return get_num_active_contexts() ? &detail::g_infoContexts[0] : nullptr; +} + +int IReporter::get_num_stringified_contexts() { return detail::g_cs->stringifiedContexts.size(); } +const String* IReporter::get_stringified_contexts() { + return get_num_stringified_contexts() ? &detail::g_cs->stringifiedContexts[0] : nullptr; +} + +namespace detail { + void registerReporterImpl(const char* name, int priority, reporterCreatorFunc c, bool isReporter) { + if(isReporter) + getReporters().insert(reporterMap::value_type(reporterMap::key_type(priority, name), c)); + else + getListeners().insert(reporterMap::value_type(reporterMap::key_type(priority, name), c)); + } +} // namespace detail + +} // namespace doctest + +#endif // DOCTEST_CONFIG_DISABLE + +#ifdef DOCTEST_CONFIG_IMPLEMENT_WITH_MAIN +DOCTEST_MSVC_SUPPRESS_WARNING_WITH_PUSH(4007) // 'function' : must be 'attribute' - see issue #182 +int main(int argc, char** argv) { return doctest::Context(argc, argv).run(); } +DOCTEST_MSVC_SUPPRESS_WARNING_POP +#endif // DOCTEST_CONFIG_IMPLEMENT_WITH_MAIN + +DOCTEST_CLANG_SUPPRESS_WARNING_POP +DOCTEST_MSVC_SUPPRESS_WARNING_POP +DOCTEST_GCC_SUPPRESS_WARNING_POP + +DOCTEST_SUPPRESS_COMMON_WARNINGS_POP + +#endif // DOCTEST_LIBRARY_IMPLEMENTATION +#endif // DOCTEST_CONFIG_IMPLEMENT + +#ifdef DOCTEST_UNDEF_WIN32_LEAN_AND_MEAN +#undef WIN32_LEAN_AND_MEAN +#undef DOCTEST_UNDEF_WIN32_LEAN_AND_MEAN +#endif // DOCTEST_UNDEF_WIN32_LEAN_AND_MEAN + +#ifdef DOCTEST_UNDEF_NOMINMAX +#undef NOMINMAX +#undef DOCTEST_UNDEF_NOMINMAX +#endif // DOCTEST_UNDEF_NOMINMAX diff --git a/src/AnyCamera.hpp b/src/AnyCamera.hpp new file mode 100644 index 0000000..7c7b3cd --- /dev/null +++ b/src/AnyCamera.hpp @@ -0,0 +1,173 @@ +#pragma once + +#include "IsAnyCamera.hpp" +#include +#include +#include +#include +#include + +namespace FairyCam +{ + +class AnyCamera +{ + private: + class Base + { + public: + virtual ~Base() = default; + virtual bool open(int idx, int apiPreference, + const std::vector ¶ms) = 0; + virtual bool isOpened() const = 0; + virtual void release() = 0; + virtual bool grab() = 0; + virtual bool retrieve(cv::OutputArray image, int flag = 0) = 0; + virtual bool read(cv::OutputArray image) = 0; + virtual bool set(int propId, double value) = 0; + virtual double get(int propId) const = 0; + virtual void setExceptionMode(bool enable) = 0; + virtual bool getExceptionMode() const = 0; + virtual Base &operator>>(cv::Mat &image) = 0; + virtual Base &operator>>(cv::UMat &image) = 0; + }; + + template class Model : public Base + { + public: + CameraType m_camera; + + virtual ~Model(){}; + template + Model(Args... args) : m_camera{std::forward(args...)} + { + } + Model() : m_camera{} {} + Model(CameraType &&camera) : m_camera{std::forward(camera)} + { + } + + bool open(int idx, int apiPreference, + const std::vector ¶ms) override final + { + return m_camera.open(idx, apiPreference, params); + } + bool isOpened() const override final { return m_camera.isOpened(); } + void release() override final { m_camera.release(); } + bool grab() override final { return m_camera.grab(); } + bool retrieve(cv::OutputArray image, int flag) override final + { + return m_camera.retrieve(image, flag); + } + bool read(cv::OutputArray image) override final + { + return m_camera.read(image); + } + bool set(int propId, double value) override final + { + return m_camera.set(propId, value); + } + double get(int propId) const override final + { + return m_camera.get(propId); + } + void setExceptionMode(bool enable) override final + { + m_camera.setExceptionMode(enable); + } + bool getExceptionMode() const override final + { + // required, as some opencv version do not support it + // https://github.com/opencv/opencv/pull/25062 + return const_cast(std::addressof(m_camera)) + ->getExceptionMode(); + } + Base &operator>>(cv::Mat &image) override final + { + m_camera >> image; + return *this; + } + Base &operator>>(cv::UMat &image) override final + { + m_camera >> image; + return *this; + } + }; + + std::unique_ptr m_camera; + + template + AnyCamera(std::unique_ptr> cam) : m_camera(std::move(cam)) + { + } + + public: + template + AnyCamera(CameraType &&camera) + : m_camera{std::make_unique>( + std::forward(camera))} + { + } + + template static AnyCamera create() + { + return AnyCamera(std::make_unique>()); + } + + template + static AnyCamera create(Args... args) + { + return AnyCamera(std::make_unique>( + std::forward(args...))); + } + + template + std::optional> dynamicCast() noexcept + { + auto modelPtr = dynamic_cast *>(m_camera.get()); + if (!modelPtr) + return std::nullopt; + return std::optional>( + modelPtr->m_camera); + } + + template + std::optional> + dynamicCast() const noexcept + { + auto modelPtr = dynamic_cast *>(m_camera.get()); + if (!modelPtr) + return std::nullopt; + return std::optional>( + modelPtr->m_camera); + } + + bool open(int idx, int apiPreference, const std::vector ¶ms) + { + return m_camera->open(idx, apiPreference, params); + } + bool isOpened() const { return m_camera->isOpened(); } + void release() { m_camera->release(); } + bool grab() { return m_camera->grab(); } + bool retrieve(cv::OutputArray image, int flag = 0) + { + return m_camera->retrieve(image, flag); + } + bool read(cv::OutputArray image) { return m_camera->read(image); } + bool set(int propId, double value) { return m_camera->set(propId, value); } + double get(int propId) const { return m_camera->get(propId); } + void setExceptionMode(bool enable) { m_camera->setExceptionMode(enable); } + bool getExceptionMode() const { return m_camera->getExceptionMode(); } + AnyCamera &operator>>(cv::Mat &image) + { + *m_camera >> image; + return *this; + } + AnyCamera &operator>>(cv::UMat &image) + { + *m_camera >> image; + return *this; + } +}; + +} // namespace FairyCam diff --git a/src/CMakeLists.txt b/src/CMakeLists.txt new file mode 100644 index 0000000..961afff --- /dev/null +++ b/src/CMakeLists.txt @@ -0,0 +1,63 @@ +find_package(OpenCV REQUIRED) +find_package(Poco REQUIRED Foundation Net) + +add_library(FairyCam STATIC + HttpCamera.cpp + DirectoryTriggerCamera.cpp + FileCamera.cpp +) + +set (HEADERS + "AnyCamera.hpp" + "FileCamera.hpp" + "HttpCamera.hpp" + "DirectoryCamera.hpp" + "DirectoryTriggerCamera.hpp" + "IsAnyCamera.hpp" +) + +target_sources(FairyCam PUBLIC FILE_SET HEADERS + FILES + ${HEADERS} +) + +target_include_directories(FairyCam PUBLIC ${OpenCV_INCLUDE_DIRS} ) +target_link_libraries(FairyCam PUBLIC ${OpenCV_LIBS}) +target_link_libraries(FairyCam PRIVATE Poco::Foundation Poco::Net) +target_compile_features(FairyCam PUBLIC cxx_std_20) + +include(GNUInstallDirs) +install(TARGETS FairyCam + LIBRARY DESTINATION ${CMAKE_INSTALL_LIBDIR} + RUNTIME DESTINATION ${CMAKE_INSTALL_BINDIR} +) + +set_target_properties(FairyCam PROPERTIES PUBLIC_HEADER "${HEADERS}") + +include(GNUInstallDirs) +install(TARGETS FairyCam + LIBRARY DESTINATION ${CMAKE_INSTALL_LIBDIR} + RUNTIME DESTINATION ${CMAKE_INSTALL_BINDIR} + PUBLIC_HEADER DESTINATION ${CMAKE_INSTALL_INCLUDEDIR}/${PROJECT_NAME} + INCLUDES DESTINATION ${CMAKE_INSTALL_INCLUDEDIR} +) + +include(CMakePackageConfigHelpers) + + +write_basic_package_version_file( + "${CMAKE_CURRENT_BINARY_DIR}/${PROJECT_NAME}ConfigVersion.cmake" + VERSION ${PROJECT_VERSION} + COMPATIBILITY SameMajorVersion +) + +configure_package_config_file(${CMAKE_CURRENT_SOURCE_DIR}/Config.cmake.in + "${CMAKE_CURRENT_BINARY_DIR}/${PROJECT_NAME}Config.cmake" + INSTALL_DESTINATION cmake +) + +install(FILES + "${CMAKE_CURRENT_BINARY_DIR}/${PROJECT_NAME}Config.cmake" + "${CMAKE_CURRENT_BINARY_DIR}/${PROJECT_NAME}ConfigVersion.cmake" + DESTINATION cmake +) \ No newline at end of file diff --git a/src/Config.cmake.in b/src/Config.cmake.in new file mode 100644 index 0000000..7cb8364 --- /dev/null +++ b/src/Config.cmake.in @@ -0,0 +1,5 @@ +@PACKAGE_INIT@ + +include("${CMAKE_CURRENT_LIST_DIR}/@PROJECT_NAME@Targets.cmake") + +check_required_components(@PROJECT_NAME@) \ No newline at end of file diff --git a/src/DirectoryCamera.hpp b/src/DirectoryCamera.hpp new file mode 100644 index 0000000..1f82765 --- /dev/null +++ b/src/DirectoryCamera.hpp @@ -0,0 +1,85 @@ +#pragma once + +#include +#include +#include +#include +#include +#include + +namespace FairyCam +{ + +class DirectoryCamera +{ + public: + struct Options + { + std::filesystem::path dir = ""; + bool circular = false; + }; + + private: + Options m_opts; + bool m_is_open = false; + bool m_throw = false; + std::filesystem::directory_iterator m_iter; + std::filesystem::directory_iterator m_iter_end; + std::filesystem::path m_file; + + public: + DirectoryCamera() : DirectoryCamera(Options{}) {} + DirectoryCamera(Options opts) : m_opts{std::move(opts)} {} + bool open(int idx, int apiPreference, const std::vector ¶ms) + { + if (!std::filesystem::is_directory(m_opts.dir)) + return false; + m_is_open = true; + m_iter = std::filesystem::directory_iterator(m_opts.dir); + return true; + } + bool isOpened() const { return m_is_open; } + void release() { m_is_open = false; } + bool grab() + { + if (!m_is_open || (m_iter == m_iter_end)) + return false; + m_file = m_iter->path(); + ++m_iter; + if (m_opts.circular && m_iter == m_iter_end) + m_iter = std::filesystem::directory_iterator(m_opts.dir); + + return std::filesystem::exists(m_file); + } + bool retrieve(cv::OutputArray image, int flag = 0) + { + auto mat = cv::imread(m_file.string()); + image.assign(mat); + return !mat.empty(); + } + bool read(cv::OutputArray image) + { + if (!grab()) + { + image.assign(cv::Mat()); + return false; + } + return retrieve(image); + } + bool set(int propId, double value) { return false; } + double get(int propId) const { return -1.0; } + void setExceptionMode(bool enable) {} + bool getExceptionMode() const { return false; } + DirectoryCamera &operator>>(CV_OUT cv::Mat &image) + { + read(image); + return *this; + } + DirectoryCamera &operator>>(CV_OUT cv::UMat &image) + { + read(image); + return *this; + } +}; + +} // namespace FairyCam diff --git a/src/DirectoryTriggerCamera.cpp b/src/DirectoryTriggerCamera.cpp new file mode 100644 index 0000000..ceaa4e3 --- /dev/null +++ b/src/DirectoryTriggerCamera.cpp @@ -0,0 +1,104 @@ +#include "DirectoryTriggerCamera.hpp" + +#include +#include +#include +#include +#include + +namespace FairyCam +{ + +struct DirectoryTriggerCamera::Impl +{ + class NewImageNotification : public Poco::Notification + { + std::filesystem::path m_path; + + public: + NewImageNotification(std::filesystem::path p) : m_path{std::move(p)} {} + virtual ~NewImageNotification() override = default; + const std::filesystem::path &path() const noexcept { return m_path; } + }; + + void onItemAdded(const Poco::DirectoryWatcher::DirectoryEvent &event) + { + m_queue.enqueueNotification( + new NewImageNotification(event.item.path())); + } + + Options m_opts; + Poco::NotificationQueue m_queue; + std::unique_ptr m_watcher = nullptr; + Poco::AutoPtr m_msg = nullptr; + + Impl(Options opts) : m_opts{std::move(opts)} {} +}; + +DirectoryTriggerCamera::DirectoryTriggerCamera(Options opts) + : d{std::make_unique(std::move(opts))} +{ +} + +DirectoryTriggerCamera::~DirectoryTriggerCamera() { release(); } + +bool DirectoryTriggerCamera::open(int idx, int apiPreference, + const std::vector ¶ms) +{ + if (!std::filesystem::is_directory(d->m_opts.dir)) + return false; + + d->m_watcher = std::make_unique( + d->m_opts.dir.string(), Poco::DirectoryWatcher::DW_ITEM_ADDED); + + d->m_watcher->itemAdded += + Poco::delegate(d.get(), &DirectoryTriggerCamera::Impl::onItemAdded); + + return true; +} + +bool DirectoryTriggerCamera::isOpened() const +{ + return d->m_watcher != nullptr; +} + +void DirectoryTriggerCamera::release() +{ + d->m_watcher.reset(); + d->m_msg.reset(); + d->m_queue.clear(); +} + +bool DirectoryTriggerCamera::grab() +{ + if (!isOpened()) + return false; + + d->m_msg = dynamic_cast( + d->m_queue.waitDequeueNotification(d->m_opts.grabTimeOutMs)); + return d->m_msg != nullptr; +} + +bool DirectoryTriggerCamera::retrieve(cv::OutputArray image, int flag) +{ + if (!isOpened()) + return false; + if (d->m_msg == nullptr) + return false; + + auto mat = cv::imread(d->m_msg->path().string()); + image.assign(mat); + return !mat.empty(); +} + +bool DirectoryTriggerCamera::read(cv::OutputArray image) +{ + if (!grab()) + { + image.assign(cv::Mat()); + return false; + } + return retrieve(image); +} + +} // namespace FairyCam diff --git a/src/DirectoryTriggerCamera.hpp b/src/DirectoryTriggerCamera.hpp new file mode 100644 index 0000000..46f7237 --- /dev/null +++ b/src/DirectoryTriggerCamera.hpp @@ -0,0 +1,47 @@ +#pragma once +#include +#include +#include + +namespace FairyCam +{ + +class DirectoryTriggerCamera +{ + public: + struct Options + { + std::filesystem::path dir = ""; + uint32_t grabTimeOutMs = 10000; + }; + + DirectoryTriggerCamera() : DirectoryTriggerCamera(Options{}) {} + DirectoryTriggerCamera(Options opts); + ~DirectoryTriggerCamera(); + bool open(int idx, int apiPreference, const std::vector ¶ms); + bool isOpened() const; + void release(); + bool grab(); + bool retrieve(cv::OutputArray image, int flag = 0); + bool read(cv::OutputArray image); + bool set(int propId, double value) { return false; } + double get(int propId) const { return -1.0; } + void setExceptionMode(bool enable) {} + bool getExceptionMode() const { return false; } + DirectoryTriggerCamera &operator>>(cv::Mat &image) + { + read(image); + return *this; + } + DirectoryTriggerCamera &operator>>(cv::UMat &image) + { + read(image); + return *this; + } + + private: + struct Impl; + std::unique_ptr d; +}; + +} // namespace FairyCam diff --git a/src/FileCamera.cpp b/src/FileCamera.cpp new file mode 100644 index 0000000..372dc7d --- /dev/null +++ b/src/FileCamera.cpp @@ -0,0 +1,43 @@ +#include "FileCamera.hpp" + +#include + +namespace FairyCam +{ + +bool FileCamera::grab() +{ + if (!m_is_open) + return false; + + m_current = m_next; + if (m_current == m_opts.files.cend()) + return false; + + ++m_next; + if (m_opts.circular && m_next == m_opts.files.cend()) + m_next = m_opts.files.begin(); + + return std::filesystem::exists(*m_current); +} + +bool FileCamera::retrieve(cv::OutputArray image, int flag) +{ + if (!m_is_open || m_current == m_opts.files.cend()) + return false; + auto mat = cv::imread(*m_current); + image.assign(mat); + return !mat.empty(); +} + +bool FileCamera::read(cv::OutputArray image) +{ + if (!grab()) + { + image.assign(cv::Mat()); + return false; + } + return retrieve(image); +} + +} // namespace FairyCam diff --git a/src/FileCamera.hpp b/src/FileCamera.hpp new file mode 100644 index 0000000..17d614c --- /dev/null +++ b/src/FileCamera.hpp @@ -0,0 +1,66 @@ +#pragma once + +#include +#include +#include + +namespace FairyCam +{ + +class FileCamera +{ + public: + using FilesContainer = std::vector; + struct Options + { + + FilesContainer files = {}; + bool circular = false; + }; + + private: + Options m_opts; + bool m_is_open = false; + FilesContainer::const_iterator m_current; + FilesContainer::const_iterator m_next; + + public: + FileCamera() : FileCamera(Options{}) {} + FileCamera(Options opts) + : m_opts{std::move(opts)}, m_current{m_opts.files.cend()}, + m_next{m_opts.files.cend()} + { + } + bool open(int idx, int apiPreference, const std::vector ¶ms) + { + m_is_open = true; + m_next = m_opts.files.cbegin(); + return true; + } + bool isOpened() const { return m_is_open; } + void release() + { + m_is_open = false; + m_current = m_opts.files.cend(); + m_next = m_opts.files.cend(); + } + bool grab(); + bool retrieve(cv::OutputArray image, int flag = 0); + bool read(cv::OutputArray image); + bool set(int propId, double value) { return false; } + double get(int propId) const { return -1.0; } + void setExceptionMode(bool enable) {} + bool getExceptionMode() const { return false; } + FileCamera &operator>>(CV_OUT cv::Mat &image) + { + read(image); + return *this; + } + FileCamera &operator>>(CV_OUT cv::UMat &image) + { + read(image); + return *this; + } +}; + +} // namespace FairyCam diff --git a/src/HttpCamera.cpp b/src/HttpCamera.cpp new file mode 100644 index 0000000..e360b73 --- /dev/null +++ b/src/HttpCamera.cpp @@ -0,0 +1,174 @@ +#include "HttpCamera.hpp" + +#include +#include +#include + +#include +#include +#include +#include +#include +#include +#include +#include +#include + +using namespace Poco::Net; + +namespace FairyCam +{ + +struct HttpCamera::Impl +{ + class NewImageNotification : public Poco::Notification + { + cv::Mat m_mat; + + public: + NewImageNotification(cv::Mat m) : m_mat{std::move(m)} {} + virtual ~NewImageNotification() override = default; + const cv::Mat &image() const noexcept { return m_mat; } + }; + + class ImageRequestHandler : public HTTPRequestHandler + { + Poco::NotificationQueue &m_queue; + + public: + ImageRequestHandler(Poco::NotificationQueue &queue) : m_queue{queue} {} + + void setImage(HTTPServerRequest &request, HTTPServerResponse &response) + { + std::vector buffer( + std::istreambuf_iterator(request.stream()), {}); + auto notify = Poco::makeAuto( + cv::imdecode(buffer, cv::IMREAD_UNCHANGED)); + + if (notify->image().empty()) + { + response.setStatusAndReason(HTTPResponse::HTTP_BAD_REQUEST); + } + else + { + m_queue.enqueueNotification(std::move(notify)); + response.setStatusAndReason(HTTPResponse::HTTP_ACCEPTED); + } + } + + void handleRequest(HTTPServerRequest &request, + HTTPServerResponse &response) override + { + auto method = request.getMethod(); + if (method == HTTPServerRequest::HTTP_POST) + { + setImage(request, response); + } + else if (method == HTTPServerRequest::HTTP_DELETE) + { + m_queue.clear(); + response.setStatusAndReason(HTTPResponse::HTTP_OK); + } + else + { + response.setStatusAndReason(HTTPResponse::HTTP_NOT_FOUND); + } + response.send(); + } + }; + + class ImageRequestHandlerFactory : public HTTPRequestHandlerFactory + { + Poco::NotificationQueue &m_queue; + + public: + ImageRequestHandlerFactory(Poco::NotificationQueue &queue) + : m_queue{queue} + { + } + + HTTPRequestHandler * + createRequestHandler(const HTTPServerRequest &request) + { + if (request.getURI() == "/image") + return new ImageRequestHandler(m_queue); + else + return 0; + } + }; + + bool m_started = false; + Poco::NotificationQueue m_queue; + Poco::Net::HTTPServer m_srv; + Poco::AutoPtr m_msg = nullptr; + HttpCamera::Options m_opts; + Impl(HttpCamera::Options opts) + : m_queue{}, + m_srv(Poco::makeShared(m_queue), + ServerSocket(opts.port), createParams()), + m_opts{std::move(opts)} + { + } + + private: + static Poco::AutoPtr createParams() + { + auto ptr = Poco::makeAuto(); + return ptr; + } +}; + +HttpCamera::HttpCamera(Options opts) : d(std::make_unique(opts)) {} + +HttpCamera::~HttpCamera() { release(); } + +bool HttpCamera::open(int idx, int apiPreference, + const std::vector ¶ms) +{ + d->m_srv.start(); + d->m_started = true; + return true; +} + +bool HttpCamera::isOpened() const noexcept { return d->m_started; } + +void HttpCamera::release() noexcept +{ + + d->m_started = false; + d->m_srv.stop(); + d->m_msg.reset(); + d->m_queue.clear(); +} + +bool HttpCamera::grab() +{ + if (!d->m_started) + return false; + + d->m_msg = dynamic_cast( + d->m_queue.waitDequeueNotification(d->m_opts.grabTimeOutMs)); + return d->m_msg != nullptr; +} + +bool HttpCamera::retrieve(cv::OutputArray image, int flag) +{ + if (d->m_msg == nullptr) + return false; + + cv::Mat m = d->m_msg->image(); + image.assign(m); + return !m.empty(); +} + +bool HttpCamera::read(cv::OutputArray image) +{ + if (!grab()) + { + image.assign(cv::Mat()); + return false; + } + return retrieve(image); +} + +} // namespace FairyCam diff --git a/src/HttpCamera.hpp b/src/HttpCamera.hpp new file mode 100644 index 0000000..28c2f12 --- /dev/null +++ b/src/HttpCamera.hpp @@ -0,0 +1,48 @@ +#pragma once + +#include +#include +#include + +namespace FairyCam +{ + +class HttpCamera +{ + public: + struct Options + { + uint32_t port = 5001; + int grabTimeOutMs = 10000; + }; + + HttpCamera() : HttpCamera(Options{}) {} + HttpCamera(Options opts); + ~HttpCamera(); + bool open(int idx, int apiPreference, const std::vector ¶ms); + bool isOpened() const noexcept; + void release() noexcept; + bool grab(); + bool retrieve(cv::OutputArray image, int flag = 0); + bool read(cv::OutputArray image); + bool set(int propId, double value) { return false; } + double get(int propId) const noexcept { return -1.0; } + void setExceptionMode(bool enable) {} + bool getExceptionMode() const noexcept { return false; } + HttpCamera &operator>>(cv::Mat &image) + { + read(image); + return *this; + } + HttpCamera &operator>>(cv::UMat &image) + { + read(image); + return *this; + } + + private: + struct Impl; + std::unique_ptr d; +}; + +} // namespace FairyCam diff --git a/src/IsAnyCamera.hpp b/src/IsAnyCamera.hpp new file mode 100644 index 0000000..7b338cf --- /dev/null +++ b/src/IsAnyCamera.hpp @@ -0,0 +1,40 @@ +#pragma once + +#include +#include + +namespace FairyCam +{ + +template +concept IsAnyCamera = requires(T v, cv::Mat m, cv::UMat um) { + { + v.open(int{}, int{}, std::vector{}) + } -> std::convertible_to; + { + v.isOpened() + } -> std::convertible_to; + { + v.grab() + } -> std::convertible_to; + { + v.retrieve(cv::Mat(), int{}) + } -> std::convertible_to; + { + v.set(int{}, double{}) + } -> std::convertible_to; + { + v.get(int{}) + } -> std::convertible_to; + { + v.setExceptionMode(bool{}) + }; + { + v >> m + } -> std::convertible_to; + { + v >> um + } -> std::convertible_to; +}; + +} \ No newline at end of file diff --git a/tests/AnyCamera.cpp b/tests/AnyCamera.cpp new file mode 100644 index 0000000..1e0ee5a --- /dev/null +++ b/tests/AnyCamera.cpp @@ -0,0 +1,33 @@ +#include + +#include +#include +#include +#include +#include +#include + +using namespace FairyCam; + +TEST_SUITE("AnyCamera") +{ + TEST_CASE_TEMPLATE("create", T, cv::VideoCapture, DirectoryTriggerCamera, + FileCamera, DirectoryTriggerCamera, HttpCamera) + { + AnyCamera cam = AnyCamera::create(); + } + + TEST_CASE("dynamicCast") + { + AnyCamera cam = AnyCamera::create(); + REQUIRE_FALSE(cam.dynamicCast()); + auto fileCam = cam.dynamicCast(); + REQUIRE(fileCam); + FileCamera &cam_ref = *fileCam; + REQUIRE_FALSE(cam.isOpened()); + REQUIRE_FALSE(cam_ref.isOpened()); + cam_ref.open(0, 0, {}); + REQUIRE(cam.isOpened()); + REQUIRE(cam_ref.isOpened()); + } +} diff --git a/tests/CMakeLists.txt b/tests/CMakeLists.txt new file mode 100644 index 0000000..f5cacc0 --- /dev/null +++ b/tests/CMakeLists.txt @@ -0,0 +1,24 @@ + +find_package(OpenCV REQUIRED) +find_package(Poco REQUIRED Foundation Net) + +add_executable(Tests main.cpp + main.cpp + FileCamera.cpp + DirectoryCamera.cpp + HttpCamera.cpp + AnyCamera.cpp + DirectoryTriggerCamera.cpp +) + +target_include_directories(Tests PUBLIC ${OpenCV_INCLUDE_DIRS} ) +target_link_libraries(Tests PRIVATE Poco::Foundation Poco::Net doctest FairyCam) + +target_compile_definitions(Tests PRIVATE "TEST_DATA=\"${CMAKE_CURRENT_SOURCE_DIR}/data/\"") +target_compile_features(Tests PUBLIC cxx_std_20) + +if ("${CMAKE_CXX_COMPILER_ID}" MATCHES "Clang") + target_link_libraries(Tests PUBLIC -fsanitize=address,undefined) +endif() + +add_test(NAME Tests COMMAND Tests) diff --git a/tests/DirectoryCamera.cpp b/tests/DirectoryCamera.cpp new file mode 100644 index 0000000..622fd13 --- /dev/null +++ b/tests/DirectoryCamera.cpp @@ -0,0 +1,57 @@ +#include +#include +#include + +using namespace FairyCam; + +TEST_SUITE("DirectoryCamera") +{ + TEST_CASE("load image") + { + for (const bool circular : std::vector{false, true}) + { + CAPTURE(circular); + AnyCamera cam = + AnyCamera::create(DirectoryCamera::Options{ + .dir = TEST_DATA, .circular = circular}); + + REQUIRE(cam.open(0, 0, {})); + + SUBCASE("close and reopen") + { + REQUIRE(cam.isOpened()); + cam.release(); + REQUIRE_FALSE(cam.isOpened()); + cv::Mat m; + REQUIRE_FALSE(cam.read(m)); + REQUIRE(m.empty()); + cam.open(0, 0, {}); + } + + CHECK(cam.isOpened()); + cv::Mat m; + REQUIRE(cam.read(m)); + CHECK(!m.empty()); + CHECK(cam.read(m) == circular); + CHECK(m.empty() != circular); + } + } + + TEST_CASE("load not existing directory") + { + REQUIRE(std::filesystem::is_regular_file(TEST_DATA "/test.png")); + for (const auto &dir : std::vector{ + "", TEST_DATA "/doesnotexists/", TEST_DATA "/test.png"}) + { + CAPTURE(dir); + AnyCamera cam = AnyCamera::create( + DirectoryCamera::Options{.dir = dir}); + + REQUIRE_FALSE(cam.open(0, 0, {})); + CHECK_FALSE(cam.isOpened()); + cv::Mat m; + REQUIRE_FALSE(cam.read(m)); + CHECK(m.empty()); + } + } +} diff --git a/tests/DirectoryTriggerCamera.cpp b/tests/DirectoryTriggerCamera.cpp new file mode 100644 index 0000000..5710074 --- /dev/null +++ b/tests/DirectoryTriggerCamera.cpp @@ -0,0 +1,86 @@ +#include +#include +#include + +#include +#include + +#include +#include + +using namespace FairyCam; + +TEST_SUITE("DirectoryTriggerCamera") +{ + TEST_CASE("invalid directory") + { + AnyCamera cam = AnyCamera::create( + DirectoryTriggerCamera::Options{.dir = ""}); + + CHECK_FALSE(cam.open(0, 0, {})); + CHECK_FALSE(cam.isOpened()); + + cv::Mat mat; + CHECK_FALSE(cam.read(mat)); + CHECK(mat.empty()); + } + + TEST_CASE("existing directory without new images") + { + AnyCamera cam = AnyCamera::create( + DirectoryTriggerCamera::Options{.dir = TEST_DATA, + .grabTimeOutMs = 0}); + + CHECK(cam.open(0, 0, {})); + CHECK(cam.isOpened()); + + cv::Mat mat; + CHECK_FALSE(cam.read(mat)); + CHECK(mat.empty()); + } + + TEST_CASE("existing directory with new images") + { + struct Remover + { + std::filesystem::path dir; + ~Remover() + { + std::error_code ec; + std::filesystem::remove_all(dir, ec); + } + }; + for (const auto n_images : std::vector{1, 10}) + { + CAPTURE(n_images); + auto new_dir = std::filesystem::temp_directory_path() / + "DirectoryTriggerCameraTest"; + + std::filesystem::remove_all(new_dir); + Remover cleanup{.dir = new_dir}; + REQUIRE(std::filesystem::create_directories(new_dir)); + + AnyCamera cam = AnyCamera::create( + DirectoryTriggerCamera::Options{.dir = new_dir, + .grabTimeOutMs = 50}); + + CHECK(cam.open(0, 0, {})); + CHECK(cam.isOpened()); + + cv::Mat mat; + CHECK_FALSE(cam.read(mat)); + CHECK(mat.empty()); + + for (auto i = 0; i < n_images; ++i) + { + cv::imwrite((new_dir / std::format("output{}.png", i)).string(), + cv::Mat(16, 16, CV_8UC3)); + CHECK(cam.read(mat)); + CHECK(!mat.empty()); + } + + CHECK_FALSE(cam.read(mat)); + CHECK(mat.empty()); + } + } +} diff --git a/tests/FileCamera.cpp b/tests/FileCamera.cpp new file mode 100644 index 0000000..7662ad0 --- /dev/null +++ b/tests/FileCamera.cpp @@ -0,0 +1,85 @@ +#include +#include +#include + +using namespace FairyCam; + +TEST_SUITE("FileCamera") +{ + TEST_CASE("load image") + { + for (const bool circular : std::vector{false, true}) + { + CAPTURE(circular); + AnyCamera cam = AnyCamera::create(FileCamera::Options{ + .files = {TEST_DATA "test.png"}, .circular = circular}); + + REQUIRE(cam.open(0, 0, {})); + + SUBCASE("close and reopen") + { + REQUIRE(cam.isOpened()); + cam.release(); + REQUIRE_FALSE(cam.isOpened()); + cv::Mat m; + REQUIRE_FALSE(cam.read(m)); + REQUIRE(m.empty()); + cam.open(0, 0, {}); + } + + CHECK(cam.isOpened()); + cv::Mat m; + REQUIRE(cam.read(m)); + CHECK(!m.empty()); + CHECK(cam.read(m) == circular); + CHECK(m.empty() != circular); + } + } + + TEST_CASE("load not existing image") + { + for (auto images : std::vector{ + std::vector(), std::vector{""}, + std::vector{TEST_DATA "doesnotexists.png"}}) + { + CAPTURE(images); + AnyCamera cam = AnyCamera::create( + FileCamera::Options{.files = std::move(images)}); + + REQUIRE(cam.open(0, 0, {})); + CHECK(cam.isOpened()); + cv::Mat m; + REQUIRE_FALSE(cam.read(m)); + CHECK(m.empty()); + } + } + + TEST_CASE("load multiple images") + { + for (const bool circular : std::vector{false, true}) + { + CAPTURE(circular); + AnyCamera cam = AnyCamera::create(FileCamera::Options{ + .files = {TEST_DATA "test.png", TEST_DATA "test.png", + TEST_DATA "", TEST_DATA "test.png"}, + .circular = circular}); + + REQUIRE(cam.open(0, 0, {})); + CHECK(cam.isOpened()); + cv::Mat m; + REQUIRE(cam.read(m)); + CHECK(!m.empty()); + REQUIRE(cam.read(m)); + CHECK(!m.empty()); + + REQUIRE(!cam.read(m)); + CHECK(m.empty()); + + REQUIRE(cam.read(m)); + CHECK(!m.empty()); + + CHECK(cam.read(m) == circular); + CHECK(m.empty() != circular); + } + } +} diff --git a/tests/HttpCamera.cpp b/tests/HttpCamera.cpp new file mode 100644 index 0000000..fa06145 --- /dev/null +++ b/tests/HttpCamera.cpp @@ -0,0 +1,217 @@ +#include +#include +#include +#include +#include +#include +#include +#include + +using namespace FairyCam; + +uint getPort() +{ + static uint port = 5000; + return port++; +} + +bool sendFileImage(Poco::Net::HTTPClientSession &client, std::string_view path) +{ + std::ifstream file; + file.open(std::string{path}, std::ios::binary); + if (!file.is_open()) + return false; + + Poco::Net::HTTPRequest request(Poco::Net::HTTPRequest::HTTP_POST, "/image", + Poco::Net::HTTPMessage::HTTP_1_1); + auto &ostream = client.sendRequest(request); + Poco::StreamCopier::copyStream(file, ostream); + + Poco::Net::HTTPResponse response; + client.receiveResponse(response); + bool result = response.getStatus() == + Poco::Net::HTTPResponse::HTTPStatus::HTTP_ACCEPTED; + return result; +} + +bool sendInvalidImage(Poco::Net::HTTPClientSession &client) +{ + + Poco::Net::HTTPRequest request(Poco::Net::HTTPRequest::HTTP_POST, "/image", + Poco::Net::HTTPMessage::HTTP_1_1); + auto &ostream = client.sendRequest(request); + ostream << "invalid image string"; + + Poco::Net::HTTPResponse response; + client.receiveResponse(response); + bool result = response.getStatus() == + Poco::Net::HTTPResponse::HTTPStatus::HTTP_BAD_REQUEST; + return result; +} + +TEST_SUITE("HttpCamera") +{ + TEST_CASE("close and reopen") + { + AnyCamera cam = AnyCamera::create( + HttpCamera::Options{.port = getPort(), .grabTimeOutMs = 0}); + + REQUIRE(cam.open(0, 0, {})); + REQUIRE(cam.isOpened()); + cam.release(); + REQUIRE_FALSE(cam.isOpened()); + cv::Mat m; + REQUIRE_FALSE(cam.read(m)); + REQUIRE(m.empty()); + REQUIRE(cam.open(0, 0, {})); + REQUIRE(cam.isOpened()); + cam.release(); + REQUIRE_FALSE(cam.isOpened()); + } + + TEST_CASE("release queue") + { + const auto port = getPort(); + AnyCamera cam = AnyCamera::create( + HttpCamera::Options{.port = port, .grabTimeOutMs = 0}); + Poco::Net::HTTPClientSession client = + Poco::Net::HTTPClientSession("127.0.0.1", port); + + REQUIRE(cam.open(0, 0, {})); + REQUIRE(cam.isOpened()); + + REQUIRE(sendFileImage(client, TEST_DATA "test.png")); + + cam.release(); + REQUIRE(cam.open(0, 0, {})); + REQUIRE(cam.isOpened()); + cv::Mat m; + REQUIRE(!cam.read(m)); + CHECK(m.empty()); + } + + TEST_CASE("transfer images in queue") + { + for (auto n_images : std::vector{1, 10, 100}) + { + CAPTURE(n_images); + const auto port = getPort(); + AnyCamera cam = AnyCamera::create( + HttpCamera::Options{.port = port, .grabTimeOutMs = 0}); + Poco::Net::HTTPClientSession client = + Poco::Net::HTTPClientSession("127.0.0.1", port); + + REQUIRE(cam.open(0, 0, {})); + REQUIRE(cam.isOpened()); + + for (int i = 0; i < n_images; ++i) + { + REQUIRE(sendFileImage(client, TEST_DATA "test.png")); + } + + cv::Mat m; + for (int i = 0; i < n_images; ++i) + { + REQUIRE(cam.read(m)); + CHECK(!m.empty()); + } + REQUIRE(!cam.read(m)); + CHECK(m.empty()); + } + } + TEST_CASE("transfer multiple images out of queue") + { + for (auto n_images : std::vector{10}) + { + CAPTURE(n_images); + const auto port = getPort(); + AnyCamera cam = AnyCamera::create( + HttpCamera::Options{.port = port, .grabTimeOutMs = 0}); + Poco::Net::HTTPClientSession client = + Poco::Net::HTTPClientSession("127.0.0.1", port); + + REQUIRE(cam.open(0, 0, {})); + REQUIRE(cam.isOpened()); + + cv::Mat m; + for (auto i = 0; i < n_images; ++i) + { + REQUIRE(sendFileImage(client, TEST_DATA "test.png")); + REQUIRE(cam.read(m)); + CHECK(!m.empty()); + } + REQUIRE(!cam.read(m)); + CHECK(m.empty()); + } + } + + TEST_CASE("clear queue by HTTP DELETE") + { + for (auto n_images : std::vector{10}) + { + CAPTURE(n_images); + const auto port = getPort(); + AnyCamera cam = AnyCamera::create( + HttpCamera::Options{.port = port, .grabTimeOutMs = 0}); + Poco::Net::HTTPClientSession client = + Poco::Net::HTTPClientSession("127.0.0.1", port); + + REQUIRE(cam.open(0, 0, {})); + REQUIRE(cam.isOpened()); + + cv::Mat m; + for (auto i = 0; i < n_images; ++i) + { + REQUIRE(sendFileImage(client, TEST_DATA "test.png")); + } + + Poco::Net::HTTPRequest request(Poco::Net::HTTPRequest::HTTP_DELETE, + "/image", + Poco::Net::HTTPMessage::HTTP_1_1); + client.sendRequest(request); + Poco::Net::HTTPResponse response; + client.receiveResponse(response); + CHECK(response.getStatus() == Poco::Net::HTTPResponse::HTTP_OK); + + // no images are left in the queue + REQUIRE(!cam.read(m)); + CHECK(m.empty()); + } + } + + TEST_CASE("transfer multiple valid and invalid images out of queue") + { + for (auto n_images : std::vector{10}) + { + CAPTURE(n_images); + const auto port = getPort(); + AnyCamera cam = AnyCamera::create( + HttpCamera::Options{.port = port, .grabTimeOutMs = 0}); + Poco::Net::HTTPClientSession client = + Poco::Net::HTTPClientSession("127.0.0.1", port); + + REQUIRE(cam.open(0, 0, {})); + REQUIRE(cam.isOpened()); + + cv::Mat m; + for (auto i = 0; i < n_images; ++i) + { + CAPTURE(i); + if (i % 2) + { + REQUIRE(sendFileImage(client, TEST_DATA "test.png")); + REQUIRE(cam.read(m)); + CHECK(!m.empty()); + } + else + { + REQUIRE(sendInvalidImage(client)); + REQUIRE(!cam.read(m)); + CHECK(m.empty()); + } + } + REQUIRE(!cam.read(m)); + CHECK(m.empty()); + } + } +} diff --git a/tests/data/test.png b/tests/data/test.png new file mode 100644 index 0000000000000000000000000000000000000000..c829a227ee135e9f0b9e43b20ee4b064bcd1333a GIT binary patch literal 8949 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 literal 0 HcmV?d00001 diff --git a/tests/main.cpp b/tests/main.cpp new file mode 100644 index 0000000..b8e3a4b --- /dev/null +++ b/tests/main.cpp @@ -0,0 +1,2 @@ +#define DOCTEST_CONFIG_IMPLEMENT_WITH_MAIN +#include