diff --git a/.clang-format b/.clang-format index 8c8b3b260..2822d19f5 100644 --- a/.clang-format +++ b/.clang-format @@ -1,10 +1,14 @@ -# clang-format: 11 +# clang-format: 19 AccessModifierOffset: -2 AlignAfterOpenBracket: Align AlignConsecutiveBitFields: false -AllowShortBlocksOnASingleLine: false +AllowShortBlocksOnASingleLine: Always +AllowShortCaseLabelsOnASingleLine: true +AllowShortEnumsOnASingleLine: true AllowShortFunctionsOnASingleLine: Inline +AllowShortIfStatementsOnASingleLine: AllIfsAndElse AllowShortLambdasOnASingleLine: All +AllowShortLoopsOnASingleLine: true AlwaysBreakTemplateDeclarations: true BasedOnStyle: WebKit BinPackArguments: true @@ -13,12 +17,14 @@ BreakBeforeBraces: Attach ColumnLimit: 0 Cpp11BracedListStyle: true FixNamespaceComments: true +IfMacros: ['SECTION'] IncludeBlocks: Preserve IndentCaseLabels: true IndentPPDirectives: None IndentWidth: 2 KeepEmptyLinesAtTheStartOfBlocks: false NamespaceIndentation: All +PackConstructorInitializers: CurrentLine PenaltyBreakBeforeFirstCallParameter: 200 PenaltyBreakComment: 5 PenaltyBreakFirstLessLess: 50 @@ -27,7 +33,11 @@ PointerAlignment: Right SortIncludes: true SpaceAfterTemplateKeyword: false SpaceBeforeCpp11BracedList: false +SpaceBeforeParens: Custom +SpaceBeforeParensOptions: + AfterControlStatements: true + AfterIfMacros: false SpaceInEmptyBlock: false Standard: Latest TabWidth: 2 -UseTab: Never \ No newline at end of file +UseTab: Never diff --git a/.github/workflows/auto-clang-format.yml b/.github/workflows/auto-clang-format.yml new file mode 100644 index 000000000..1cb913575 --- /dev/null +++ b/.github/workflows/auto-clang-format.yml @@ -0,0 +1,27 @@ +name: auto-clang-format +on: + push: + branches: + - develop + - master + +jobs: + build: + runs-on: ubuntu-latest + + steps: + - uses: actions/checkout@v6 + - uses: DoozyX/clang-format-lint-action@v0.20 + with: + source: '.' + exclude: './third_party ./external ./unittests/catch.hpp' + extensions: 'h,cpp,hpp' + clangFormatVersion: 19 + inplace: True + - uses: EndBug/add-and-commit@v9 + with: + author_name: Clang Robot + author_email: robot@example.com + message: ':art: Committing clang-format changes' + env: + GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }} diff --git a/.github/workflows/ci.yml b/.github/workflows/ci.yml new file mode 100644 index 000000000..6cb446b33 --- /dev/null +++ b/.github/workflows/ci.yml @@ -0,0 +1,163 @@ +name: CI + +on: + push: + branches: [develop, main] + pull_request: + branches: [develop, main] + workflow_dispatch: + +jobs: + linux: + name: Linux GCC ${{ matrix.build_type }} threads=${{ matrix.multithread }} + runs-on: ubuntu-latest + strategy: + fail-fast: false + matrix: + build_type: [Debug, Release] + multithread: [ON, OFF] + steps: + - uses: actions/checkout@v4 + + - name: Install Ninja + run: sudo apt-get install -y ninja-build + + - name: Configure + run: cmake -B build -G Ninja -DCMAKE_BUILD_TYPE=${{ matrix.build_type }} -DMULTITHREAD_SUPPORT_ENABLED=${{ matrix.multithread }} + + - name: Build + run: cmake --build build + + - name: Test + run: ctest --test-dir build --output-on-failure + + macos: + name: macOS AppleClang ${{ matrix.build_type }} threads=${{ matrix.multithread }} + runs-on: macos-latest + strategy: + fail-fast: false + matrix: + build_type: [Debug, Release] + multithread: [ON, OFF] + steps: + - uses: actions/checkout@v4 + + - name: Install Ninja + run: brew install ninja + + - name: Configure + run: cmake -B build -G Ninja -DCMAKE_BUILD_TYPE=${{ matrix.build_type }} -DMULTITHREAD_SUPPORT_ENABLED=${{ matrix.multithread }} + + - name: Build + run: cmake --build build + + - name: Test + run: ctest --test-dir build --output-on-failure + + linux-sanitizers: + name: Linux GCC ASAN+UBSAN ${{ matrix.build_type }} + runs-on: ubuntu-latest + strategy: + fail-fast: false + matrix: + build_type: [Debug, Release] + steps: + - uses: actions/checkout@v4 + + - name: Install Ninja + run: sudo apt-get install -y ninja-build + + - name: Configure + run: cmake -B build -G Ninja -DCMAKE_BUILD_TYPE=${{ matrix.build_type }} -DENABLE_ADDRESS_SANITIZER=ON -DENABLE_UNDEFINED_SANITIZER=ON + + - name: Build + run: cmake --build build + + - name: Test + run: ctest --test-dir build --output-on-failure + + macos-sanitizers: + name: macOS AppleClang ASAN+UBSAN ${{ matrix.build_type }} + runs-on: macos-latest + strategy: + fail-fast: false + matrix: + build_type: [Debug, Release] + steps: + - uses: actions/checkout@v4 + + - name: Install Ninja + run: brew install ninja + + - name: Configure + run: cmake -B build -G Ninja -DCMAKE_BUILD_TYPE=${{ matrix.build_type }} -DENABLE_ADDRESS_SANITIZER=ON -DENABLE_UNDEFINED_SANITIZER=ON + + - name: Build + run: cmake --build build + + - name: Test + run: ctest --test-dir build --output-on-failure + + windows: + name: Windows MSVC ${{ matrix.build_type }} threads=${{ matrix.multithread }} + runs-on: windows-latest + strategy: + fail-fast: false + matrix: + build_type: [Debug, Release] + multithread: [ON, OFF] + steps: + - uses: actions/checkout@v4 + + - name: Configure + run: cmake -B build -DMULTITHREAD_SUPPORT_ENABLED=${{ matrix.multithread }} + + - name: Build + run: cmake --build build --config ${{ matrix.build_type }} -j + + - name: Test + run: ctest --test-dir build --output-on-failure -C ${{ matrix.build_type }} + + linux-tsan: + name: Linux GCC TSAN ${{ matrix.build_type }} + runs-on: ubuntu-latest + strategy: + fail-fast: false + matrix: + build_type: [Debug, Release] + steps: + - uses: actions/checkout@v4 + + - name: Install Ninja + run: sudo apt-get install -y ninja-build + + - name: Configure + run: cmake -B build -G Ninja -DCMAKE_BUILD_TYPE=${{ matrix.build_type }} -DENABLE_THREAD_SANITIZER=ON -DMULTITHREAD_SUPPORT_ENABLED=ON + + - name: Build + run: cmake --build build + + - name: Test + run: ctest --test-dir build --output-on-failure + + macos-tsan: + name: macOS AppleClang TSAN ${{ matrix.build_type }} + runs-on: macos-latest + strategy: + fail-fast: false + matrix: + build_type: [Debug, Release] + steps: + - uses: actions/checkout@v4 + + - name: Install Ninja + run: brew install ninja + + - name: Configure + run: cmake -B build -G Ninja -DCMAKE_BUILD_TYPE=${{ matrix.build_type }} -DENABLE_THREAD_SANITIZER=ON -DMULTITHREAD_SUPPORT_ENABLED=ON + + - name: Build + run: cmake --build build + + - name: Test + run: ctest --test-dir build --output-on-failure diff --git a/.github/workflows/emscripten.yml b/.github/workflows/emscripten.yml new file mode 100644 index 000000000..4be8931f9 --- /dev/null +++ b/.github/workflows/emscripten.yml @@ -0,0 +1,71 @@ +name: Emscripten + +on: + push: + branches: [develop, main] + pull_request: + branches: [develop, main] + workflow_dispatch: + +jobs: + emscripten: + name: Emscripten WebAssembly Build + runs-on: ubuntu-latest + steps: + - uses: actions/checkout@v4 + + - name: Setup Emscripten + uses: mymindstorm/setup-emsdk@v14 + + - name: Configure + run: emcmake cmake -B build-em -S emscripten -DCMAKE_BUILD_TYPE=Release + + - name: Build + run: cmake --build build-em -j + + - name: Verify artifacts + run: | + test -f build-em/chaiscript.js + test -f build-em/chaiscript.wasm + test -f build-em/chaiscript.html + echo "All expected artifacts present" + ls -lh build-em/chaiscript.* + + - name: Upload artifacts + uses: actions/upload-artifact@v4 + with: + name: chaiscript-web + path: | + build-em/chaiscript.js + build-em/chaiscript.wasm + build-em/chaiscript.html + retention-days: 90 + + publish: + name: Publish WASM Release + needs: emscripten + if: github.ref == 'refs/heads/develop' && github.event_name == 'push' + runs-on: ubuntu-latest + permissions: + contents: write + steps: + - uses: actions/download-artifact@v4 + with: + name: chaiscript-web + path: artifacts + + - name: Publish to wasm-latest release + env: + GH_TOKEN: ${{ secrets.GITHUB_TOKEN }} + run: | + # Flatten: artifacts may contain build-em/ subdirectory + find artifacts -type f \( -name 'chaiscript.js' -o -name 'chaiscript.wasm' -o -name 'chaiscript.html' \) -exec cp {} . \; + + # Delete existing release if present, then recreate + gh release delete wasm-latest --repo "$GITHUB_REPOSITORY" -y 2>/dev/null || true + gh release create wasm-latest \ + --repo "$GITHUB_REPOSITORY" \ + --title "ChaiScript WASM Build (latest)" \ + --notes "Auto-published from develop branch. Built with Emscripten for browser use." \ + --prerelease \ + chaiscript.js chaiscript.wasm chaiscript.html diff --git a/.gitignore b/.gitignore index 9e6e44291..3e48530b0 100644 --- a/.gitignore +++ b/.gitignore @@ -3,3 +3,4 @@ /buckaroo/ .buckconfig.local BUCKAROO_DEPS +/build diff --git a/CMakeLists.txt b/CMakeLists.txt index 7eef73e36..48d6f7913 100644 --- a/CMakeLists.txt +++ b/CMakeLists.txt @@ -1,7 +1,7 @@ cmake_minimum_required(VERSION 3.12) cmake_policy(SET CMP0054 NEW) -set(CMAKE_CXX_STANDARD 17) +set(CMAKE_CXX_STANDARD 20) set(CMAKE_CXX_STANDARD_REQUIRED ON) # required since cmake 3.4 at least for libc++ @@ -34,7 +34,7 @@ if(CMAKE_COMPILER_IS_GNUCC) endif() endif() -if(CMAKE_COMPILER_IS_GNUCC OR "${CMAKE_CXX_COMPILER_ID}" STREQUAL "Clang") +if(CMAKE_COMPILER_IS_GNUCC OR "${CMAKE_CXX_COMPILER_ID}" STREQUAL "Clang" OR "${CMAKE_CXX_COMPILER_ID}" STREQUAL "AppleClang") option(ENABLE_THREAD_SANITIZER "Enable thread sanitizer testing in gcc/clang" FALSE) if(ENABLE_THREAD_SANITIZER) add_definitions(-fsanitize=thread -g) @@ -87,6 +87,16 @@ if(CMAKE_COMPILER_IS_GNUCC OR "${CMAKE_CXX_COMPILER_ID}" STREQUAL "Clang") endif() +elseif(MSVC) + option(ENABLE_ADDRESS_SANITIZER "Enable address sanitizer testing in MSVC" FALSE) + if(ENABLE_ADDRESS_SANITIZER) + add_compile_options(/fsanitize=address) + # ASAN is incompatible with /RTC (runtime error checks) and incremental linking + string(REGEX REPLACE "/RTC[^ ]*" "" CMAKE_CXX_FLAGS_DEBUG "${CMAKE_CXX_FLAGS_DEBUG}") + string(REGEX REPLACE "/RTC[^ ]*" "" CMAKE_C_FLAGS_DEBUG "${CMAKE_C_FLAGS_DEBUG}") + add_link_options(/INCREMENTAL:NO) + endif() + endif() list(APPEND CPACK_SOURCE_IGNORE_FILES "${CMAKE_CURRENT_BINARY_DIR}") @@ -145,7 +155,7 @@ else() endif() if(MSVC) - add_definitions(/W4 /w14545 /w34242 /w34254 /w34287 /w44263 /w44265 /w44296 /w44311 /w44826 /we4289 /w14546 /w14547 /w14549 /w14555 /w14619 /w14905 /w14906 /w14928) + add_definitions(/WX /W4 /w14545 /w34242 /w34254 /w34287 /w44263 /w44265 /w44296 /w44311 /w44826 /we4289 /w14546 /w14547 /w14549 /w14555 /w14619 /w14905 /w14906 /w14928) if(MSVC_VERSION STREQUAL "1800") # VS2013 doesn't have magic statics @@ -165,10 +175,10 @@ if(MSVC) # how to workaround or fix the error. So I'm disabling it globally. add_definitions(/wd4503) else() - add_definitions(-Wall -Wextra -Wconversion -Wshadow -Wnon-virtual-dtor -Wold-style-cast -Wcast-align -Wcast-qual -Wunused -Woverloaded-virtual -Wno-noexcept-type -Wpedantic -Werror=return-type) + add_definitions(-Werror -Wall -Wextra -Wconversion -Wshadow -Wnon-virtual-dtor -Wold-style-cast -Wcast-align -Wcast-qual -Wunused -Woverloaded-virtual -Wno-noexcept-type -Wpedantic -Werror=return-type) - if("${CMAKE_CXX_COMPILER_ID}" STREQUAL "Clang") - add_definitions(-Weverything -Wno-c++98-compat-pedantic -Wno-c++98-compat -Wno-documentation -Wno-switch-enum -Wno-weak-vtables -Wno-missing-prototypes -Wno-padded -Wno-missing-noreturn -Wno-exit-time-destructors -Wno-documentation-unknown-command -Wno-unused-template -Wno-undef -Wno-double-promotion) + if("${CMAKE_CXX_COMPILER_ID}" STREQUAL "Clang" OR "${CMAKE_CXX_COMPILER_ID}" STREQUAL "AppleClang") + add_definitions(-Weverything -Wno-c++98-compat-pedantic -Wno-c++98-compat -Wno-documentation -Wno-switch-enum -Wno-weak-vtables -Wno-missing-prototypes -Wno-padded -Wno-missing-noreturn -Wno-exit-time-destructors -Wno-documentation-unknown-command -Wno-unused-template -Wno-undef -Wno-double-promotion -Wno-switch-default -Wno-nrvo -Wno-shadow-uncaptured-local -Wno-unsafe-buffer-usage-in-libc-call -Wno-c++20-extensions -Wno-unknown-warning-option -Wno-poison-system-directories -Wno-c++20-compat -Wno-c++17-compat) else() add_definitions(-Wnoexcept) endif() @@ -282,6 +292,16 @@ endif() file(GLOB UNIT_TESTS RELATIVE ${CMAKE_CURRENT_SOURCE_DIR}/unittests/ ${CMAKE_CURRENT_SOURCE_DIR}/unittests/*.chai ${CMAKE_CURRENT_SOURCE_DIR}/unittests/3.x/*.chai) list(SORT UNIT_TESTS) +if(NOT MULTITHREAD_SUPPORT_ENABLED) + list(REMOVE_ITEM UNIT_TESTS + async_engine_lifetime.chai + async_return_value.chai + future.chai + list_push_front.chai + move_async.chai + ) +endif() + file(GLOB PERFORMANCE_TESTS RELATIVE ${CMAKE_CURRENT_SOURCE_DIR}/performance_tests/ ${CMAKE_CURRENT_SOURCE_DIR}/performance_tests/*.chai) list(SORT PERFORMANCE_TESTS) @@ -370,8 +390,16 @@ if(BUILD_TESTING) ) if(NOT UNIT_TEST_LIGHT) + add_library(catch2 STATIC unittests/catch_amalgamated.cpp) + if(NOT MSVC) + target_compile_options(catch2 PRIVATE -Wno-conversion -Wno-noexcept -Wno-maybe-uninitialized) + endif() + if("${CMAKE_CXX_COMPILER_ID}" STREQUAL "Clang" OR "${CMAKE_CXX_COMPILER_ID}" STREQUAL "AppleClang") + target_compile_options(catch2 PUBLIC -Wno-unknown-warning-option -Wno-covered-switch-default -Wno-disabled-macro-expansion -Wno-unsafe-buffer-usage -Wno-unused-macros) + endif() + add_executable(compiled_tests unittests/compiled_tests.cpp) - target_link_libraries(compiled_tests ${LIBS} ${CHAISCRIPT_LIBS}) + target_link_libraries(compiled_tests catch2 ${LIBS} ${CHAISCRIPT_LIBS}) catch_discover_tests(compiled_tests TEST_PREFIX "compiled.") add_executable(static_chaiscript_test unittests/static_chaiscript.cpp) @@ -383,7 +411,7 @@ if(BUILD_TESTING) add_test(NAME Boxed_Cast_Test COMMAND boxed_cast_test) add_executable(type_info_test unittests/type_info_test.cpp) - target_link_libraries(type_info_test ${LIBS}) + target_link_libraries(type_info_test catch2 ${LIBS}) add_test(NAME Type_Info_Test COMMAND type_info_test) add_executable(c_linkage_test unittests/c_linkage_test.cpp) @@ -403,6 +431,9 @@ if(BUILD_TESTING) "CHAI_USE_PATH=${CMAKE_CURRENT_SOURCE_DIR}/unittests/" "CHAI_MODULE_PATH=${CMAKE_CURRENT_BINARY_DIR}/" ) + add_executable(async_engine_lifetime_test unittests/async_engine_lifetime_test.cpp) + target_link_libraries(async_engine_lifetime_test ${LIBS}) + add_test(NAME Async_Engine_Lifetime_Test COMMAND async_engine_lifetime_test) endif() add_executable(multifile_test @@ -413,6 +444,22 @@ if(BUILD_TESTING) target_link_libraries(multifile_test ${LIBS}) add_test(NAME MultiFile_Test COMMAND multifile_test) + add_executable(emscripten_eval_test unittests/emscripten_eval_test.cpp) + target_link_libraries(emscripten_eval_test ${LIBS}) + add_test(NAME Emscripten_Eval_Test COMMAND emscripten_eval_test) + + add_executable(emscripten_exception_test unittests/emscripten_exception_test.cpp) + target_link_libraries(emscripten_exception_test ${LIBS}) + add_test(NAME Emscripten_Exception_Test COMMAND emscripten_exception_test) + + add_executable(emscripten_state_test unittests/emscripten_state_test.cpp) + target_link_libraries(emscripten_state_test ${LIBS}) + add_test(NAME Emscripten_State_Test COMMAND emscripten_state_test) + + add_executable(threading_config_test unittests/threading_config_test.cpp) + target_link_libraries(threading_config_test ${LIBS}) + add_test(NAME Threading_Config_Test COMMAND threading_config_test) + install(TARGETS test_module RUNTIME DESTINATION bin LIBRARY DESTINATION "${CMAKE_INSTALL_LIBDIR}/chaiscript") endif() endif() diff --git a/appveyor.yml b/appveyor.yml deleted file mode 100644 index 5a4fe67e0..000000000 --- a/appveyor.yml +++ /dev/null @@ -1,23 +0,0 @@ -version: 6.1.x.{build} -image: - - Visual Studio 2019 -environment: - matrix: - - VS_VERSION: "Visual Studio 16" -build_script: -- cmd: >- - mkdir build - - cd build - - cmake c:\Projects\chaiscript -G "%VS_VERSION%" -DBUILD_TESTING:BOOL=ON -DBUILD_MODULES:BOOL=ON - - cmake --build . --config Debug -test_script: -- cmd: ctest -C Debug -notifications: -- provider: Webhook - url: https://webhooks.gitter.im/e/9ff725a985b5679d1d5d - on_build_success: true - on_build_failure: true - on_build_status_changed: false diff --git a/cheatsheet.md b/cheatsheet.md index 1daf298f6..bb7041054 100644 --- a/cheatsheet.md +++ b/cheatsheet.md @@ -16,6 +16,67 @@ chaiscript::ChaiScript chai; // initializes ChaiScript, adding the standard Chai Note that ChaiScript cannot be used as a global / static object unless it is being compiled with `CHAISCRIPT_NO_THREADS`. +## Engine Options (`Options`) + +Engine-level options control which scripting capabilities are exposed. These are passed as a `std::vector` to the `ChaiScript` or `ChaiScript_Basic` constructor. + +| Option | Effect | +|--------|--------| +| `Options::Load_Modules` | Enables `load_module()` in scripts (default) | +| `Options::No_Load_Modules` | Disables `load_module()` | +| `Options::External_Scripts` | Enables `use()` and `eval_file()` in scripts (default) | +| `Options::No_External_Scripts` | Disables `use()` and `eval_file()` | + +```cpp +// Sandboxed engine: no dynamic module loading, no external script evaluation +chaiscript::ChaiScript chai({}, {}, + {chaiscript::Options::No_Load_Modules, chaiscript::Options::No_External_Scripts}); +``` + +## Library Options (`Library_Options`) + +Library-level options control which parts of the standard library are registered. These are passed as a `std::vector`. + +| Option | Effect | +|--------|--------| +| `Library_Options::No_Stdlib` | Disables the entire standard library (types, I/O, prelude, JSON — everything) | +| `Library_Options::No_IO` | Disables `print_string` and `println_string` (and the prelude's `print`/`puts` wrappers) | +| `Library_Options::No_Prelude` | Disables the ChaiScript prelude (`print`, `puts`, `filter`, `map`, `foldl`, `join`, etc.) | +| `Library_Options::No_JSON` | Disables `from_json` and `to_json` | + +With the `ChaiScript` convenience class, pass library options as the fourth constructor parameter: + +```cpp +// No I/O functions +chaiscript::ChaiScript chai({}, {}, chaiscript::default_options(), + {chaiscript::Library_Options::No_IO}); + +// No JSON support +chaiscript::ChaiScript chai({}, {}, chaiscript::default_options(), + {chaiscript::Library_Options::No_JSON}); + +// Completely bare engine — no stdlib at all +chaiscript::ChaiScript chai({}, {}, chaiscript::default_options(), + {chaiscript::Library_Options::No_Stdlib}); + +// Combine both: no external scripts and no I/O +chaiscript::ChaiScript chai({}, {}, + {chaiscript::Options::No_Load_Modules, chaiscript::Options::No_External_Scripts}, + {chaiscript::Library_Options::No_IO}); +``` + +With `ChaiScript_Basic`, pass library options directly to `Std_Lib::library()`: + +```cpp +chaiscript::ChaiScript_Basic chai( + chaiscript::Std_Lib::library({chaiscript::Library_Options::No_IO}), + create_chaiscript_parser(), + {}, {}, + {chaiscript::Options::No_Load_Modules, chaiscript::Options::No_External_Scripts}); +``` + +Note: `No_Prelude` disables the prelude script which defines convenience functions like `print` (which wraps `print_string`). If you disable the prelude but not I/O, `print_string` and `println_string` are still available. + # Adding Things To The Engine ## Adding a Function / Method / Member @@ -48,7 +109,7 @@ chai.add(chaiscript::fun(&function_with_ove ```cpp chai.add(chaiscript::fun(static_cast(&function_with_overloads)), "function_name"); ``` -This overload technique is also used when exposing base member using derived type +This overload technique is also used when exposing base members using derived type ```cpp struct Base @@ -90,7 +151,7 @@ chai.add(chaiscript::user_type(), "MyClass"); ## Adding Type Conversions -User defined type conversions are possible, defined in either script or in C++. +User-defined type conversions are possible, defined in either script or in C++. @@ -111,7 +172,7 @@ Invoking a C++ type conversion possible with `static_cast` chai.add(chaiscript::type_conversion()); ``` -Calling a user defined type conversion that takes a lambda +Calling a user-defined type conversion that takes a lambda ```cpp chai.add(chaiscript::type_conversion([](const TestBaseType &t_bt) { /* return converted thing */ })); @@ -153,17 +214,39 @@ This allows you to pass a ChaiScript function to a function requiring `std::vect ## Adding Objects -``` -chai.add(chaiscript::var(somevar), "somevar"); // copied in -chai.add(chaiscript::var(std::ref(somevar)), "somevar"); // by reference, shared between C++ and chai +### `add` — Thread-Local Scoped Variables + +`add` adds an object to the current thread's local scope. The variable is only visible in the +thread that added it. If the variable already exists in the current scope, it is overwritten. + +```cpp +chai.add(chaiscript::var(somevar), "somevar"); // copied in +chai.add(chaiscript::var(std::ref(somevar)), "somevar"); // by reference, shared between C++ and chai auto shareddouble = std::make_shared(4.3); -chai.add(chaiscript::var(shareddouble), "shareddouble"); // by shared_ptr, shared between c++ and chai -chai.add(chaiscript::const_var(somevar), "somevar"); // copied in and made const -chai.add_global_const(chaiscript::const_var(somevar), "somevar"); // global const. Throws if value is non-const, throws if object exists -chai.add_global(chaiscript::var(somevar), "somevar"); // global non-const, throws if object exists -chai.set_global(chaiscript::var(somevar), "somevar"); // global non-const, overwrites existing object +chai.add(chaiscript::var(shareddouble), "shareddouble"); // by shared_ptr, shared between C++ and chai +chai.add(chaiscript::const_var(somevar), "somevar"); // copied in and made const +``` + +### `add_global` / `add_global_const` / `set_global` — Global Shared Variables + +Global variables are shared between all threads and are visible from any scope (including inside +functions). Use these when you need a variable accessible everywhere. + +```cpp +chai.add_global_const(chaiscript::const_var(somevar), "somevar"); // global const, throws if value is non-const or object already exists +chai.add_global(chaiscript::var(somevar), "somevar"); // global non-const, throws if object already exists +chai.set_global(chaiscript::var(somevar), "somevar"); // global non-const, overwrites existing or creates new ``` +### Summary of Differences + +| Method | Scope | Thread Safety | If Name Exists | +|--------|-------|---------------|----------------| +| `add` | Thread-local, current scope | Not shared between threads | Overwrites | +| `add_global` | Global, all scopes and threads | Mutex-protected, shared between threads | Throws exception | +| `add_global_const` | Global, all scopes and threads | Mutex-protected, shared between threads | Throws exception | +| `set_global` | Global, all scopes and threads | Mutex-protected, shared between threads | Overwrites | + ## Adding Namespaces Namespaces will not be populated until `import` is called. @@ -183,7 +266,7 @@ print(math.pi) // prints 3.14159 ``` # Using STL -ChaiScript recognize many types from STL, but you have to add specific instantiation yourself. +ChaiScript recognizes many types from STL, but you have to add specific instantiation yourself. ```cpp typedef std::vector> data_list; @@ -286,7 +369,7 @@ try { } catch (float) { } catch (const std::string &) { } catch (const std::exception &e) { - // This is the one what will be called in the specific throw() above + // This is the one that will be called in the specific throw() above } ``` @@ -388,9 +471,9 @@ switch (myvalue) { } ``` -## Built in Types +## Built-in Types -There are a number of build-in types that are part of ChaiScript. +There are a number of built-in types that are part of ChaiScript. ### Vectors and Maps @@ -427,7 +510,7 @@ on your platform. ## Functions Note that any type of ChaiScript function can be passed freely to C++ and automatically -converted into an `std::function` object. +converted into a `std::function` object. ### General @@ -477,34 +560,227 @@ n(2); // returns 20 -## ChaiScript Defined Types +## ChaiScript Defined Types (Classes) + +ChaiScript supports user-defined types using the `class` keyword. Classes can have attributes, +constructors, methods, guards, and operator overloads. There is no inheritance between +ChaiScript-defined types, but C++ class hierarchies can be exposed (see *Class Hierarchies* above). -Define a type called "MyType" with one member value "a" and a getter +### Class Definition (Block Syntax) -### Preferred +Define a type with attributes, a constructor, and methods inside a `class` block. +The keywords `var`, `attr`, and `auto` are interchangeable for declaring attributes. ``` -class MyType { - var value; - def MyType() { this.value = "a"; } - def get_value() { "Value Is: " + this.value; } -}; +class Rectangle { + var width + var height + def Rectangle(w, h) { this.width = w; this.height = h; } + def Rectangle() { this.width = 0; this.height = 0; } + def area() { this.width * this.height; } +} + +var r = Rectangle(3, 4) +print(r.area()) // prints 12 ``` -### Alternative +### Class Definition (Open Syntax) + +Equivalently, attributes and methods can be defined outside a block using the `TypeName::` prefix. ``` -attr MyType::value; -def MyType::MyType() { this.value = "a"; } -def MyType::get_value() { "Value Is: " + this.value; } +attr Circle::radius +def Circle::Circle(r) { this.radius = r; } +def Circle::circumference() { 2.0 * 3.14159 * this.radius; } +``` + +Methods can also be added to an existing class after its initial definition: + +``` +def Rectangle::perimeter() { 2 * (this.width + this.height); } ``` ### Using ``` -var m = MyType(); // calls constructor -print(m.get_value()); // prints "Value Is: a" -print(get_value(m)); // prints "Value Is: a" +var m = Rectangle(5, 10) +print(m.area()) // prints 50 — method call syntax +print(area(m)) // prints 50 — function call syntax (equivalent) +``` + +### Constructor and Method Guards + +Constructors and methods can have guard expressions (after `:`) that control which +overload is selected at call time. + +``` +class Clamped { + var value + def Clamped(x) : x >= 0 { this.value = x; } + def Clamped(x) { this.value = 0; } // fallback when guard fails +} + +Clamped(5).value // 5 +Clamped(-3).value // 0 + +class Abs { + var x + def Abs(v) { this.x = v; } + def get() : this.x >= 0 { this.x; } + def get() { -this.x; } +} +``` + +### Operator Overloading + +Operators can be overloaded on user-defined types using backtick-quoted operator names. + +``` +class Vec2 { + var x + var y + def Vec2(x, y) { this.x = x; this.y = y; } + def `+`(other) { Vec2(this.x + other.x, this.y + other.y); } +} + +var v = Vec2(1, 2) + Vec2(3, 4) // v.x == 4, v.y == 6 +``` + +Operators can also be overloaded as free functions with guards: + +``` +def `-`(a, b) : is_type(a, "Vec2") && is_type(b, "Vec2") { + Vec2(a.x - b.x, a.y - b.y) +} +``` + +### Cloning Objects + +Use `clone()` to create a deep copy of a ChaiScript-defined object. + +``` +var original = Rectangle(10, 20) +var copy = clone(original) +copy.width = 99 +print(original.width) // still 10 +``` + +## Enums + +ChaiScript supports strongly-typed enums using `enum class` (or equivalently `enum struct`), +matching C++ scoped-enum semantics. Values are accessed via `::` syntax and are type-safe — +a plain integer cannot be passed where an enum type is expected. + +### Basic Definition + +``` +enum class Color { Red, Green, Blue } +``` + +Values are auto-numbered starting from 0. Access them with `Color::Red`, `Color::Green`, etc. + +### Explicit Values + +``` +enum class Priority { Low = 10, Medium = 20, High = 30 } +``` + +Auto-numbering continues from the last explicit value: + +``` +enum class Status { Pending, Active = 5, Done } +// Pending = 0, Active = 5, Done = 6 +``` + +### Specifying an Underlying Type + +By default the underlying type is `int`. Use `: type` to choose a different numeric type: + +``` +enum class Flags : char { Read = 1, Write = 2, Execute = 4 } +``` + +The underlying type must be a numeric type registered in ChaiScript. `string` and other +non-numeric types cannot be used. The available underlying types are: + +| Type | Description | +|------|-------------| +| `int` | (default) signed integer | +| `unsigned_int` | unsigned integer | +| `long` | signed long | +| `unsigned_long` | unsigned long | +| `long_long` | signed long long | +| `unsigned_long_long` | unsigned long long | +| `char` | character (8-bit) | +| `wchar_t` | wide character | +| `char16_t` | 16-bit character | +| `char32_t` | 32-bit character | +| `float` | single-precision float | +| `double` | double-precision float | +| `long_double` | extended-precision float | +| `size_t` | unsigned size type | +| `int8_t` | signed 8-bit | +| `int16_t` | signed 16-bit | +| `int32_t` | signed 32-bit | +| `int64_t` | signed 64-bit | +| `uint8_t` | unsigned 8-bit | +| `uint16_t` | unsigned 16-bit | +| `uint32_t` | unsigned 32-bit | + +### `enum struct` Syntax + +`enum struct` is accepted as a synonym for `enum class`, just like in C++: + +``` +enum struct Direction { North, East, South, West } +``` + +### Constructing from a Value + +Each enum type has a constructor that accepts the underlying type. It validates that the +value matches one of the defined enumerators: + +``` +auto c = Color::Color(1) // creates Color::Green +Color::Color(52) // throws: invalid value +``` + +### `to_underlying` + +Convert an enum value back to its underlying numeric type: + +``` +Color::Red.to_underlying() // 0 +Priority::High.to_underlying() // 30 +``` + +### Comparison + +`==` and `!=` are defined for values of the same enum type: + +``` +assert_true(Color::Red == Color::Red) +assert_true(Color::Red != Color::Green) +``` + +### Type-Safe Dispatch + +Functions declared with an enum parameter type reject plain integers: + +``` +def handle(Color c) { /* ... */ } +handle(Color::Red) // ok +handle(42) // throws: dispatch error +``` + +### Using with `switch` + +``` +switch(Color::Green) { + case (Color::Red) { print("red"); break } + case (Color::Green) { print("green"); break } + case (Color::Blue) { print("blue"); break } +} ``` ## Dynamic Objects @@ -553,6 +829,94 @@ class My_Class { }; ``` +## Strong Typedefs + +Strong typedefs create distinct types that are not interchangeable with their underlying type +or with other typedefs of the same underlying type. They use `Dynamic_Object` internally and +automatically expose operators that the underlying type supports. + +### Basic Usage + +``` +using Meters = int +using Seconds = int + +var d = Meters(100) +var t = Seconds(10) + +// d and t are distinct types — you cannot accidentally mix them +// Meters + Seconds would require an explicit conversion +``` + +### Arithmetic and Comparison + +Operators from the underlying type are forwarded and remain strongly typed: + +``` +using Meters = int + +var a = Meters(10) +var b = Meters(20) + +var c = a + b // Meters(30) — result is still Meters +var bigger = b > a // true — comparisons return bool + +// Compound assignment operators work too +a += b // a is now Meters(30) +``` + +### String-Based Strong Typedefs + +Strong typedefs work with any type, not just numeric types: + +``` +using Name = string + +var n = Name("Alice") +var greeting = Name("Hello, ") + Name("world") // Name — string concatenation is forwarded +``` + +### Accessing the Underlying Value + +Use `to_underlying` to extract the wrapped value: + +``` +using Meters = int + +var d = Meters(42) +var raw = to_underlying(d) // 42, plain int +``` + +### Extending Strong Typedefs + +You can add custom operations to strong typedefs just like any other ChaiScript type: + +``` +using Meters = int +using Seconds = int +using MetersPerSecond = int + +def speed(Meters d, Seconds t) { + MetersPerSecond(to_underlying(d) / to_underlying(t)) +} + +var s = speed(Meters(100), Seconds(10)) // MetersPerSecond(10) +``` + +You can also overload operators between different strong typedefs: + +``` +using Meters = int +using Feet = int + +def to_feet(Meters m) { + Feet((to_underlying(m) * 328) / 100) +} + +var m = Meters(10) +var f = to_feet(m) // Feet(32) +``` + ## method_missing A function of the signature `method_missing(object, name, param1, param2, param3)` will be called if an appropriate @@ -580,7 +944,7 @@ If both a 2 parameter and a 3 parameter signature match, the 3 parameter functio * `__FUNC__` Name of current function -# Built In Functions +# Built-in Functions ## Evaluation @@ -593,10 +957,213 @@ use("filename") // evals file exactly once and returns value of last statement Both `use` and `eval_file` search the 'usepaths' passed to the ChaiScript constructor +## Reflection and Introspection + +ChaiScript provides built-in reflection capabilities for inspecting types, functions, and objects at runtime. + +### Type Inspection + +``` +type_name(x) // returns the type name of a value as a string +is_type(x, "typename") // returns true if x is of the named type +type("typename") // returns a Type_Info object for the named type + +// Examples +type_name(1) // "int" +type_name("hello") // "string" +is_type(1, "int") // true +is_type(1, "string") // false +``` + +### Object Inspection Methods + +Every object in ChaiScript supports these methods: + +``` +x.get_type_info() // returns a Type_Info object for the value +x.is_type("string") // returns true if x is of the named type +x.is_type(string_type) // returns true if x matches the Type_Info +x.is_var_const() // returns true if x is immutable +x.is_var_null() // returns true if x is a null pointer +x.is_var_pointer() // returns true if x is stored as a pointer +x.is_var_reference() // returns true if x is stored as a reference +x.is_var_undef() // returns true if x is undefined +``` + +### Type_Info + +`Type_Info` objects describe a type. You can get them via `type("typename")` or `x.get_type_info()`. + +``` +var ti = type("int") +ti.name() // ChaiScript registered name, e.g. "int" +ti.cpp_name() // mangled C++ type name +ti.cpp_bare_name() // C++ name without const/pointer/reference +ti.bare_equal(other) // true if types match ignoring const/ptr/ref +ti.is_type_const() // true if type is const +ti.is_type_reference() // true if type is a reference +ti.is_type_void() // true if type is void +ti.is_type_undef() // true if type is undefined +ti.is_type_pointer() // true if type is a pointer +ti.is_type_arithmetic() // true if type is arithmetic (int, double, etc.) +``` + +Built-in type constants are available: `int_type`, `double_type`, `string_type`, `bool_type`, `Object_type`, `Function_type`, `vector_type`, `map_type`. + +### Function Introspection + +Function objects support these introspection methods: + +``` +f.get_arity() // number of parameters (-1 for variadic) +f.get_param_types() // Vector of Type_Info (first element is return type) +f.get_contained_functions() // Vector of overloaded functions (empty if not a conglomerate) +f.has_guard() // true if the function has a guard condition +f.get_guard() // returns the guard function (throws if none) +f.get_annotation() // returns the annotation description +f.call([param1, param2]) // call the function with a vector of parameters + +// Examples +def my_func(a, b) { return a + b; } +my_func.get_arity() // 2 +my_func.has_guard() // false + +def guarded(x) : x > 0 { return x; } +guarded.has_guard() // true +guarded.get_guard().get_arity() // 1 + +// Calling functions dynamically +`+`.call([1, 2]) // 3 +``` + +### System Introspection + +``` +get_functions() // returns a Map of all registered functions (name -> function) +get_objects() // returns a Map of all scripting objects (name -> value) +function_exists("f") // returns true if a function named "f" is registered +call_exists(`f`, args) // returns true if f can be called with the given args +dump_system() // prints all registered functions to stdout +dump_object(x) // prints information about a value to stdout + +// Examples +var funcs = get_functions() +funcs["print"] // the print function object +function_exists("print") // true +call_exists(`+`, 1, 2) // true +``` + +### Dynamic_Object Reflection + +ChaiScript-defined classes are Dynamic_Objects internally. They support: + +``` +obj.get_type_name() // returns the ChaiScript class name (e.g. "MyClass") +obj.get_attrs() // returns a Map of all attributes +obj.has_attr("name") // returns true if the attribute exists +obj.get_attr("name") // returns the value of the attribute +obj.set_explicit(true) // disables dynamic attribute creation +obj.is_explicit() // returns true if explicit mode is enabled + +// Example +class MyClass { + var x + def MyClass() { this.x = 10; } +} +var m = MyClass() +m.get_type_name() // "MyClass" +m.get_attrs() // map containing "x" -> 10 +type_name(m) // "Dynamic_Object" (the underlying C++ type) +m.is_type("MyClass") // true (checks the ChaiScript class name) +``` + ## JSON * `from_json` converts a JSON string into its strongly typed (map, vector, int, double, string) representations * `to_json` converts a ChaiScript object (either a `Object` or one of map, vector, int, double, string) tree into its JSON string representation +## IO Redirection + +By default, ChaiScript's `print()` and `puts()` functions write to stdout. You can redirect +output on a per-instance basis by setting a single print handler. Both `println_string` +(used by `print()`) and `print_string` (used by `puts()`) dispatch through the same handler — +`println_string` simply appends a newline before calling it. + +```cpp +chaiscript::ChaiScript chai; + +// Redirect all output (print_string and println_string both use this handler) +chai.set_print_handler([](const std::string &s) { + my_log_window.append(s); +}); +``` + +This is useful for embedding ChaiScript in GUI applications, logging frameworks, or any +context where stdout is not the desired output destination. + +```cpp +// Example: capture all output to a string +std::string captured; +chai.set_print_handler([&captured](const std::string &s) { + captured += s; +}); + +chai.eval("print(42)"); // captured == "42\n" +chai.eval("puts(\"hi\")"); // captured == "42\nhi" +``` + +The print handler can also be set from within ChaiScript itself via `set_print_handler`: + +```chaiscript +// Redirect output from within a script +set_print_handler(fun(s) { my_custom_log(s) }) +``` + +## Custom File Loading + +By default, ChaiScript reads files from the filesystem when `eval_file()` or `use()` is called. +You can override this behavior on a per-instance basis by setting a custom file reader callback. +This follows the same pattern as `set_print_handler` and enables use cases such as encrypted +script files, in-memory virtual filesystems, or platform-specific file access (e.g., Android assets). + +```cpp +chaiscript::ChaiScript chai; + +// Provide scripts from an in-memory map instead of the filesystem +std::map virtual_fs = { + {"init.chai", "var x = 42"}, + {"utils.chai", "def add(a, b) { a + b }"} +}; + +chai.set_file_reader([&virtual_fs](const std::string &filename) -> std::string { + const auto it = virtual_fs.find(filename); + if (it != virtual_fs.end()) { + return it->second; + } + throw chaiscript::exception::file_not_found_error(filename); +}); + +chai.eval_file("init.chai"); // evaluates "var x = 42" +chai.use("utils.chai"); // evaluates "def add(a, b) { a + b }" +``` + +The file reader can also be set from within ChaiScript itself via `set_file_reader`: + +```chaiscript +// Override file loading from within a script +set_file_reader(fun(filename) { return my_custom_read(filename) }) +``` + +When no custom file reader is set, ChaiScript uses its built-in filesystem reader. + ## Extras ChaiScript itself does not provide a link to the math functions defined in ``. You can either add them yourself, or use the [ChaiScript_Extras](https://github.com/ChaiScript/ChaiScript_Extras) helper library. (Which also provides some additional string functions.) + +## Grammar Railroad Diagrams + +A formal EBNF grammar for ChaiScript is available in [`grammar/chaiscript.ebnf`](grammar/chaiscript.ebnf). You can visualize it as navigable railroad diagrams by pasting its contents into one of these tools: + + * [rr — Railroad Diagram Generator (IPv6)](https://www.bottlecaps.de/rr/ui) + * [rr — Railroad Diagram Generator (IPv4)](https://rr.red-dove.com/ui) + +Open either link, switch to the **Edit Grammar** tab, paste the file contents, then click **View Diagram**. diff --git a/cmake/Catch.cmake b/cmake/Catch.cmake index 486e32331..fcdebd60a 100644 --- a/cmake/Catch.cmake +++ b/cmake/Catch.cmake @@ -33,6 +33,13 @@ same as the Catch name; see also ``TEST_PREFIX`` and ``TEST_SUFFIX``. [TEST_SUFFIX suffix] [PROPERTIES name1 value1...] [TEST_LIST var] + [REPORTER reporter] + [OUTPUT_DIR dir] + [OUTPUT_PREFIX prefix] + [OUTPUT_SUFFIX suffix] + [DISCOVERY_MODE ] + [SKIP_IS_FAILURE] + [ADD_TAGS_AS_LABELS] ) ``catch_discover_tests`` sets up a post-build command on the test executable @@ -90,86 +97,222 @@ same as the Catch name; see also ``TEST_PREFIX`` and ``TEST_SUFFIX``. executable is being used in multiple calls to ``catch_discover_tests()``. Note that this variable is only available in CTest. + ``REPORTER reporter`` + Use the specified reporter when running the test case. The reporter will + be passed to the Catch executable as ``--reporter reporter``. + + ``OUTPUT_DIR dir`` + If specified, the parameter is passed along as + ``--out dir/`` to Catch executable. The actual file name is the + same as the test name. This should be used instead of + ``EXTRA_ARGS --out foo`` to avoid race conditions writing the result output + when using parallel test execution. + + ``OUTPUT_PREFIX prefix`` + May be used in conjunction with ``OUTPUT_DIR``. + If specified, ``prefix`` is added to each output file name, like so + ``--out dir/prefix``. + + ``OUTPUT_SUFFIX suffix`` + May be used in conjunction with ``OUTPUT_DIR``. + If specified, ``suffix`` is added to each output file name, like so + ``--out dir/suffix``. This can be used to add a file extension to + the output e.g. ".xml". + + ``DL_PATHS path...`` + Specifies paths that need to be set for the dynamic linker to find shared + libraries/DLLs when running the test executable (PATH/LD_LIBRARY_PATH respectively). + These paths will both be set when retrieving the list of test cases from the + test executable and when the tests are executed themselves. This requires + cmake/ctest >= 3.22. + + ``DL_FRAMEWORK_PATHS path...`` + Specifies paths that need to be set for the dynamic linker to find libraries + packaged as frameworks on Apple platforms when running the test executable + (DYLD_FRAMEWORK_PATH). These paths will both be set when retrieving the list + of test cases from the test executable and when the tests are executed themselves. + This requires cmake/ctest >= 3.22. + + ``DISCOVERY_MODE mode`` + Provides control over when ``catch_discover_tests`` performs test discovery. + By default, ``POST_BUILD`` sets up a post-build command to perform test discovery + at build time. In certain scenarios, like cross-compiling, this ``POST_BUILD`` + behavior is not desirable. By contrast, ``PRE_TEST`` delays test discovery until + just prior to test execution. This way test discovery occurs in the target environment + where the test has a better chance at finding appropriate runtime dependencies. + + ``DISCOVERY_MODE`` defaults to the value of the + ``CMAKE_CATCH_DISCOVER_TESTS_DISCOVERY_MODE`` variable if it is not passed when + calling ``catch_discover_tests``. This provides a mechanism for globally selecting + a preferred test discovery behavior without having to modify each call site. + + ``SKIP_IS_FAILURE`` + Disables skipped test detection. + + ``ADD_TAGS_AS_LABELS`` + Adds all test tags as CTest labels. + #]=======================================================================] #------------------------------------------------------------------------------ function(catch_discover_tests TARGET) + cmake_parse_arguments( "" - "" - "TEST_PREFIX;TEST_SUFFIX;WORKING_DIRECTORY;TEST_LIST" - "TEST_SPEC;EXTRA_ARGS;PROPERTIES" + "SKIP_IS_FAILURE;ADD_TAGS_AS_LABELS" + "TEST_PREFIX;TEST_SUFFIX;WORKING_DIRECTORY;TEST_LIST;REPORTER;OUTPUT_DIR;OUTPUT_PREFIX;OUTPUT_SUFFIX;DISCOVERY_MODE" + "TEST_SPEC;EXTRA_ARGS;PROPERTIES;DL_PATHS;DL_FRAMEWORK_PATHS" ${ARGN} ) + if(${CMAKE_VERSION} VERSION_LESS "3.19") + message(FATAL_ERROR "This script requires JSON support from CMake version 3.19 or greater.") + endif() + if(NOT _WORKING_DIRECTORY) set(_WORKING_DIRECTORY "${CMAKE_CURRENT_BINARY_DIR}") endif() if(NOT _TEST_LIST) set(_TEST_LIST ${TARGET}_TESTS) endif() + if(_DL_PATHS AND ${CMAKE_VERSION} VERSION_LESS "3.22.0") + message(FATAL_ERROR "The DL_PATHS option requires at least cmake 3.22") + endif() + if(_DL_FRAMEWORK_PATHS AND ${CMAKE_VERSION} VERSION_LESS "3.22.0") + message(FATAL_ERROR "The DL_FRAMEWORK_PATHS option requires at least cmake 3.22") + endif() + if(NOT _DISCOVERY_MODE) + if(NOT CMAKE_CATCH_DISCOVER_TESTS_DISCOVERY_MODE) + set(CMAKE_CATCH_DISCOVER_TESTS_DISCOVERY_MODE "POST_BUILD") + endif() + set(_DISCOVERY_MODE ${CMAKE_CATCH_DISCOVER_TESTS_DISCOVERY_MODE}) + endif() + if(NOT _DISCOVERY_MODE MATCHES "^(POST_BUILD|PRE_TEST)$") + message(FATAL_ERROR "Unknown DISCOVERY_MODE: ${_DISCOVERY_MODE}") + endif() ## Generate a unique name based on the extra arguments - string(SHA1 args_hash "${_TEST_SPEC} ${_EXTRA_ARGS}") + string(SHA1 args_hash "${_TEST_SPEC} ${_EXTRA_ARGS} ${_REPORTER} ${_OUTPUT_DIR} ${_OUTPUT_PREFIX} ${_OUTPUT_SUFFIX}") string(SUBSTRING ${args_hash} 0 7 args_hash) # Define rule to generate test list for aforementioned test executable - set(ctest_include_file "${CMAKE_CURRENT_BINARY_DIR}/${TARGET}_include-${args_hash}.cmake") - set(ctest_tests_file "${CMAKE_CURRENT_BINARY_DIR}/${TARGET}_tests-${args_hash}.cmake") + set(ctest_file_base "${CMAKE_CURRENT_BINARY_DIR}/${TARGET}-${args_hash}") + set(ctest_include_file "${ctest_file_base}_include.cmake") + set(ctest_tests_file "${ctest_file_base}_tests.cmake") + get_property(crosscompiling_emulator TARGET ${TARGET} PROPERTY CROSSCOMPILING_EMULATOR ) - add_custom_command( - TARGET ${TARGET} POST_BUILD - BYPRODUCTS "${ctest_tests_file}" - COMMAND "${CMAKE_COMMAND}" - -D "TEST_TARGET=${TARGET}" - -D "TEST_EXECUTABLE=$" - -D "TEST_EXECUTOR=${crosscompiling_emulator}" - -D "TEST_WORKING_DIR=${_WORKING_DIRECTORY}" - -D "TEST_SPEC=${_TEST_SPEC}" - -D "TEST_EXTRA_ARGS=${_EXTRA_ARGS}" - -D "TEST_PROPERTIES=${_PROPERTIES}" - -D "TEST_PREFIX=${_TEST_PREFIX}" - -D "TEST_SUFFIX=${_TEST_SUFFIX}" - -D "TEST_LIST=${_TEST_LIST}" - -D "CTEST_FILE=${ctest_tests_file}" - -P "${_CATCH_DISCOVER_TESTS_SCRIPT}" - VERBATIM - ) + if(NOT _SKIP_IS_FAILURE) + set(_PROPERTIES ${_PROPERTIES} SKIP_RETURN_CODE 4) + endif() - file(WRITE "${ctest_include_file}" - "if(EXISTS \"${ctest_tests_file}\")\n" - " include(\"${ctest_tests_file}\")\n" - "else()\n" - " add_test(${TARGET}_NOT_BUILT-${args_hash} ${TARGET}_NOT_BUILT-${args_hash})\n" - "endif()\n" - ) + if(_DISCOVERY_MODE STREQUAL "POST_BUILD") + add_custom_command( + TARGET ${TARGET} POST_BUILD + BYPRODUCTS "${ctest_tests_file}" + COMMAND "${CMAKE_COMMAND}" + -D "TEST_TARGET=${TARGET}" + -D "TEST_EXECUTABLE=$" + -D "TEST_EXECUTOR=${crosscompiling_emulator}" + -D "TEST_WORKING_DIR=${_WORKING_DIRECTORY}" + -D "TEST_SPEC=${_TEST_SPEC}" + -D "TEST_EXTRA_ARGS=${_EXTRA_ARGS}" + -D "TEST_PROPERTIES=${_PROPERTIES}" + -D "TEST_PREFIX=${_TEST_PREFIX}" + -D "TEST_SUFFIX=${_TEST_SUFFIX}" + -D "TEST_LIST=${_TEST_LIST}" + -D "TEST_REPORTER=${_REPORTER}" + -D "TEST_OUTPUT_DIR=${_OUTPUT_DIR}" + -D "TEST_OUTPUT_PREFIX=${_OUTPUT_PREFIX}" + -D "TEST_OUTPUT_SUFFIX=${_OUTPUT_SUFFIX}" + -D "TEST_DL_PATHS=${_DL_PATHS}" + -D "TEST_DL_FRAMEWORK_PATHS=${_DL_FRAMEWORK_PATHS}" + -D "CTEST_FILE=${ctest_tests_file}" + -D "ADD_TAGS_AS_LABELS=${_ADD_TAGS_AS_LABELS}" + -P "${_CATCH_DISCOVER_TESTS_SCRIPT}" + VERBATIM + ) + + file(WRITE "${ctest_include_file}" + "if(EXISTS \"${ctest_tests_file}\")\n" + " include(\"${ctest_tests_file}\")\n" + "else()\n" + " add_test(${TARGET}_NOT_BUILT-${args_hash} ${TARGET}_NOT_BUILT-${args_hash})\n" + "endif()\n" + ) + + elseif(_DISCOVERY_MODE STREQUAL "PRE_TEST") - if(NOT ${CMAKE_VERSION} VERSION_LESS "3.10.0") - # Add discovered tests to directory TEST_INCLUDE_FILES - set_property(DIRECTORY - APPEND PROPERTY TEST_INCLUDE_FILES "${ctest_include_file}" + get_property(GENERATOR_IS_MULTI_CONFIG GLOBAL + PROPERTY GENERATOR_IS_MULTI_CONFIG ) - else() - # Add discovered tests as directory TEST_INCLUDE_FILE if possible - get_property(test_include_file_set DIRECTORY PROPERTY TEST_INCLUDE_FILE SET) - if (NOT ${test_include_file_set}) - set_property(DIRECTORY - PROPERTY TEST_INCLUDE_FILE "${ctest_include_file}" + + if(GENERATOR_IS_MULTI_CONFIG) + set(ctest_tests_file "${ctest_file_base}_tests-$.cmake") + endif() + + string(CONCAT ctest_include_content + "if(EXISTS \"$\")" "\n" + " if(NOT EXISTS \"${ctest_tests_file}\" OR" "\n" + " NOT \"${ctest_tests_file}\" IS_NEWER_THAN \"$\" OR\n" + " NOT \"${ctest_tests_file}\" IS_NEWER_THAN \"\${CMAKE_CURRENT_LIST_FILE}\")\n" + " include(\"${_CATCH_DISCOVER_TESTS_SCRIPT}\")" "\n" + " catch_discover_tests_impl(" "\n" + " TEST_EXECUTABLE" " [==[" "$" "]==]" "\n" + " TEST_EXECUTOR" " [==[" "${crosscompiling_emulator}" "]==]" "\n" + " TEST_WORKING_DIR" " [==[" "${_WORKING_DIRECTORY}" "]==]" "\n" + " TEST_SPEC" " [==[" "${_TEST_SPEC}" "]==]" "\n" + " TEST_EXTRA_ARGS" " [==[" "${_EXTRA_ARGS}" "]==]" "\n" + " TEST_PROPERTIES" " [==[" "${_PROPERTIES}" "]==]" "\n" + " TEST_PREFIX" " [==[" "${_TEST_PREFIX}" "]==]" "\n" + " TEST_SUFFIX" " [==[" "${_TEST_SUFFIX}" "]==]" "\n" + " TEST_LIST" " [==[" "${_TEST_LIST}" "]==]" "\n" + " TEST_REPORTER" " [==[" "${_REPORTER}" "]==]" "\n" + " TEST_OUTPUT_DIR" " [==[" "${_OUTPUT_DIR}" "]==]" "\n" + " TEST_OUTPUT_PREFIX" " [==[" "${_OUTPUT_PREFIX}" "]==]" "\n" + " TEST_OUTPUT_SUFFIX" " [==[" "${_OUTPUT_SUFFIX}" "]==]" "\n" + " CTEST_FILE" " [==[" "${ctest_tests_file}" "]==]" "\n" + " TEST_DL_PATHS" " [==[" "${_DL_PATHS}" "]==]" "\n" + " TEST_DL_FRAMEWORK_PATHS" " [==[" "${_DL_FRAMEWORK_PATHS}" "]==]" "\n" + " ADD_TAGS_AS_LABELS" " [==[" "${_ADD_TAGS_AS_LABELS}" "]==]" "\n" + " )" "\n" + " endif()" "\n" + " include(\"${ctest_tests_file}\")" "\n" + "else()" "\n" + " add_test(${TARGET}_NOT_BUILT ${TARGET}_NOT_BUILT)" "\n" + "endif()" "\n" + ) + + if(GENERATOR_IS_MULTI_CONFIG) + foreach(_config ${CMAKE_CONFIGURATION_TYPES}) + file(GENERATE OUTPUT "${ctest_file_base}_include-${_config}.cmake" CONTENT "${ctest_include_content}" CONDITION $) + endforeach() + string(CONCAT ctest_include_multi_content + "if(NOT CTEST_CONFIGURATION_TYPE)" "\n" + " message(\"No configuration for testing specified, use '-C '.\")" "\n" + "else()" "\n" + " include(\"${ctest_file_base}_include-\${CTEST_CONFIGURATION_TYPE}.cmake\")" "\n" + "endif()" "\n" ) + file(GENERATE OUTPUT "${ctest_include_file}" CONTENT "${ctest_include_multi_content}") else() - message(FATAL_ERROR - "Cannot set more than one TEST_INCLUDE_FILE" - ) + file(GENERATE OUTPUT "${ctest_file_base}_include.cmake" CONTENT "${ctest_include_content}") + file(WRITE "${ctest_include_file}" "include(\"${ctest_file_base}_include.cmake\")") endif() endif() + # Add discovered tests to directory TEST_INCLUDE_FILES + set_property(DIRECTORY + APPEND PROPERTY TEST_INCLUDE_FILES "${ctest_include_file}" + ) + endfunction() ############################################################################### set(_CATCH_DISCOVER_TESTS_SCRIPT ${CMAKE_CURRENT_LIST_DIR}/CatchAddTests.cmake + CACHE INTERNAL "Catch2 full path to CatchAddTests.cmake helper file" ) diff --git a/cmake/CatchAddTests.cmake b/cmake/CatchAddTests.cmake index 3575a35c6..4c27f479e 100644 --- a/cmake/CatchAddTests.cmake +++ b/cmake/CatchAddTests.cmake @@ -1,18 +1,12 @@ # Distributed under the OSI-approved BSD 3-Clause License. See accompanying # file Copyright.txt or https://cmake.org/licensing for details. -set(prefix "${TEST_PREFIX}") -set(suffix "${TEST_SUFFIX}") -set(spec ${TEST_SPEC}) -set(extra_args ${TEST_EXTRA_ARGS}) -set(properties ${TEST_PROPERTIES}) -set(script) -set(suite) -set(tests) - function(add_command NAME) set(_args "") - foreach(_arg ${ARGN}) + # use ARGV* instead of ARGN, because ARGN splits arrays into multiple arguments + math(EXPR _last_arg ${ARGC}-1) + foreach(_n RANGE 1 ${_last_arg}) + set(_arg "${ARGV${_n}}") if(_arg MATCHES "[^-./:a-zA-Z0-9_]") set(_args "${_args} [==[${_arg}]==]") # form a bracket_argument else() @@ -22,55 +16,239 @@ function(add_command NAME) set(script "${script}${NAME}(${_args})\n" PARENT_SCOPE) endfunction() -# Run test executable to get list of available tests -if(NOT EXISTS "${TEST_EXECUTABLE}") - message(FATAL_ERROR - "Specified test executable '${TEST_EXECUTABLE}' does not exist" - ) -endif() -execute_process( - COMMAND ${TEST_EXECUTOR} "${TEST_EXECUTABLE}" ${spec} --list-test-names-only - OUTPUT_VARIABLE output - RESULT_VARIABLE result -) -# Catch --list-test-names-only reports the number of tests, so 0 is... surprising -if(${result} EQUAL 0) - message(WARNING - "Test executable '${TEST_EXECUTABLE}' contains no tests!\n" - ) -elseif(${result} LESS 0) - message(FATAL_ERROR - "Error running test executable '${TEST_EXECUTABLE}':\n" - " Result: ${result}\n" - " Output: ${output}\n" - ) -endif() +function(catch_discover_tests_impl) -string(REPLACE "\n" ";" output "${output}") - -# Parse output -foreach(line ${output}) - set(test ${line}) - # ...and add to script - add_command(add_test - "${prefix}${test}${suffix}" - ${TEST_EXECUTOR} - "${TEST_EXECUTABLE}" - ${test} - ${extra_args} + cmake_parse_arguments( + "" + "" + "TEST_EXECUTABLE;TEST_WORKING_DIR;TEST_OUTPUT_DIR;TEST_OUTPUT_PREFIX;TEST_OUTPUT_SUFFIX;TEST_PREFIX;TEST_REPORTER;TEST_SPEC;TEST_SUFFIX;TEST_LIST;CTEST_FILE" + "TEST_EXTRA_ARGS;TEST_PROPERTIES;TEST_EXECUTOR;TEST_DL_PATHS;TEST_DL_FRAMEWORK_PATHS;ADD_TAGS_AS_LABELS" + ${ARGN} ) - add_command(set_tests_properties - "${prefix}${test}${suffix}" - PROPERTIES - WORKING_DIRECTORY "${TEST_WORKING_DIR}" - ${properties} + + set(add_tags "${_ADD_TAGS_AS_LABELS}") + set(prefix "${_TEST_PREFIX}") + set(suffix "${_TEST_SUFFIX}") + set(spec ${_TEST_SPEC}) + set(extra_args ${_TEST_EXTRA_ARGS}) + set(properties ${_TEST_PROPERTIES}) + set(reporter ${_TEST_REPORTER}) + set(output_dir ${_TEST_OUTPUT_DIR}) + set(output_prefix ${_TEST_OUTPUT_PREFIX}) + set(output_suffix ${_TEST_OUTPUT_SUFFIX}) + set(dl_paths ${_TEST_DL_PATHS}) + set(dl_framework_paths ${_TEST_DL_FRAMEWORK_PATHS}) + set(environment_modifications "") + set(script) + set(suite) + set(tests) + + if(WIN32) + set(dl_paths_variable_name PATH) + elseif(APPLE) + set(dl_paths_variable_name DYLD_LIBRARY_PATH) + else() + set(dl_paths_variable_name LD_LIBRARY_PATH) + endif() + + # Run test executable to get list of available tests + if(NOT EXISTS "${_TEST_EXECUTABLE}") + message(FATAL_ERROR + "Specified test executable '${_TEST_EXECUTABLE}' does not exist" + ) + endif() + + if(dl_paths) + cmake_path(CONVERT "$ENV{${dl_paths_variable_name}}" TO_NATIVE_PATH_LIST env_dl_paths) + list(PREPEND env_dl_paths "${dl_paths}") + cmake_path(CONVERT "${env_dl_paths}" TO_NATIVE_PATH_LIST paths) + set(ENV{${dl_paths_variable_name}} "${paths}") + endif() + + if(APPLE AND dl_framework_paths) + cmake_path(CONVERT "$ENV{DYLD_FRAMEWORK_PATH}" TO_NATIVE_PATH_LIST env_dl_framework_paths) + list(PREPEND env_dl_framework_paths "${dl_framework_paths}") + cmake_path(CONVERT "${env_dl_framework_paths}" TO_NATIVE_PATH_LIST paths) + set(ENV{DYLD_FRAMEWORK_PATH} "${paths}") + endif() + + execute_process( + COMMAND ${_TEST_EXECUTOR} "${_TEST_EXECUTABLE}" ${spec} --list-tests --reporter json + OUTPUT_VARIABLE listing_output + RESULT_VARIABLE result + WORKING_DIRECTORY "${_TEST_WORKING_DIR}" ) - list(APPEND tests "${prefix}${test}${suffix}") -endforeach() + if(NOT ${result} EQUAL 0) + message(FATAL_ERROR + "Error listing tests from executable '${_TEST_EXECUTABLE}':\n" + " Result: ${result}\n" + " Output: ${listing_output}\n" + ) + endif() + + # Prepare reporter + if(reporter) + set(reporter_arg "--reporter ${reporter}") + + # Run test executable to check whether reporter is available + # note that the use of --list-reporters is not the important part, + # we only want to check whether the execution succeeds with ${reporter_arg} + execute_process( + COMMAND ${_TEST_EXECUTOR} "${_TEST_EXECUTABLE}" ${spec} ${reporter_arg} --list-reporters + OUTPUT_VARIABLE reporter_check_output + RESULT_VARIABLE reporter_check_result + WORKING_DIRECTORY "${_TEST_WORKING_DIR}" + ) + if(${reporter_check_result} EQUAL 255) + message(FATAL_ERROR + "\"${reporter}\" is not a valid reporter!\n" + ) + elseif(NOT ${reporter_check_result} EQUAL 0) + message(FATAL_ERROR + "Error checking for reporter in test executable '${_TEST_EXECUTABLE}':\n" + " Result: ${reporter_check_result}\n" + " Output: ${reporter_check_output}\n" + ) + endif() + endif() + + # Prepare output dir + if(output_dir AND NOT IS_ABSOLUTE ${output_dir}) + set(output_dir "${_TEST_WORKING_DIR}/${output_dir}") + if(NOT EXISTS ${output_dir}) + file(MAKE_DIRECTORY ${output_dir}) + endif() + endif() + + if(dl_paths) + foreach(path ${dl_paths}) + cmake_path(NATIVE_PATH path native_path) + list(PREPEND environment_modifications "${dl_paths_variable_name}=path_list_prepend:${native_path}") + endforeach() + endif() + + if(APPLE AND dl_framework_paths) + foreach(path ${dl_framework_paths}) + cmake_path(NATIVE_PATH path native_path) + list(PREPEND environment_modifications "DYLD_FRAMEWORK_PATH=path_list_prepend:${native_path}") + endforeach() + endif() -# Create a list of all discovered tests, which users may use to e.g. set -# properties on the tests -add_command(set ${TEST_LIST} ${tests}) + # Parse JSON output for list of tests/class names/tags + string(JSON version GET "${listing_output}" "version") + if(NOT version STREQUAL "1") + message(FATAL_ERROR "Unsupported catch output version: '${version}'") + endif() -# Write CTest script -file(WRITE "${CTEST_FILE}" "${script}") + # Speed-up reparsing by cutting away unneeded parts of JSON. + string(JSON test_listing GET "${listing_output}" "listings" "tests") + string(JSON num_tests LENGTH "${test_listing}") + + # Exit early if no tests are detected + if(num_tests STREQUAL "0") + file(WRITE "${_CTEST_FILE}" "") + return() + endif() + + # CMake's foreach-RANGE is inclusive, so we have to subtract 1 + math(EXPR num_tests "${num_tests} - 1") + + foreach(idx RANGE ${num_tests}) + string(JSON single_test GET ${test_listing} ${idx}) + string(JSON test_tags GET "${single_test}" "tags") + string(JSON plain_name GET "${single_test}" "name") + + # Escape characters in test case names that would be parsed by Catch2 + # Note that the \ escaping must happen FIRST! Do not change the order. + set(escaped_name "${plain_name}") + foreach(char \\ , [ ] ;) + string(REPLACE ${char} "\\${char}" escaped_name "${escaped_name}") + endforeach(char) + # ...add output dir + if(output_dir) + string(REGEX REPLACE "[^A-Za-z0-9_]" "_" escaped_name_clean "${escaped_name}") + set(output_dir_arg "--out ${output_dir}/${output_prefix}${escaped_name_clean}${output_suffix}") + endif() + + # ...and add to script + add_command(add_test + "${prefix}${plain_name}${suffix}" + ${_TEST_EXECUTOR} + "${_TEST_EXECUTABLE}" + "${escaped_name}" + ${extra_args} + "${reporter_arg}" + "${output_dir_arg}" + ) + add_command(set_tests_properties + "${prefix}${plain_name}${suffix}" + PROPERTIES + WORKING_DIRECTORY "${_TEST_WORKING_DIR}" + ${properties} + ) + + if(add_tags) + string(JSON num_tags LENGTH "${test_tags}") + math(EXPR num_tags "${num_tags} - 1") + set(tag_list "") + if(num_tags GREATER_EQUAL "0") + foreach(tag_idx RANGE ${num_tags}) + string(JSON a_tag GET "${test_tags}" "${tag_idx}") + # Catch2's tags can contain semicolons, which are list element separators + # in CMake, so we have to escape them. Ideally we could use the [=[...]=] + # syntax for this, but CTest currently keeps the square quotes in the label + # name. So we add 2 backslashes to escape it instead. + # **IMPORTANT**: The number of backslashes depends on how many layers + # of CMake the tag goes. If this script is changed, the + # number of backslashes to escape may change as well. + string(REPLACE ";" "\\;" a_tag "${a_tag}") + list(APPEND tag_list "${a_tag}") + endforeach() + + add_command(set_tests_properties + "${prefix}${plain_name}${suffix}" + PROPERTIES + LABELS "${tag_list}" + ) + endif() + endif(add_tags) + + if(environment_modifications) + add_command(set_tests_properties + "${prefix}${plain_name}${suffix}" + PROPERTIES + ENVIRONMENT_MODIFICATION "${environment_modifications}") + endif() + + list(APPEND tests "${prefix}${plain_name}${suffix}") + endforeach() + + # Create a list of all discovered tests, which users may use to e.g. set + # properties on the tests + add_command(set ${_TEST_LIST} ${tests}) + + # Write CTest script + file(WRITE "${_CTEST_FILE}" "${script}") +endfunction() + +if(CMAKE_SCRIPT_MODE_FILE) + catch_discover_tests_impl( + TEST_EXECUTABLE ${TEST_EXECUTABLE} + TEST_EXECUTOR ${TEST_EXECUTOR} + TEST_WORKING_DIR ${TEST_WORKING_DIR} + TEST_SPEC ${TEST_SPEC} + TEST_EXTRA_ARGS ${TEST_EXTRA_ARGS} + TEST_PROPERTIES ${TEST_PROPERTIES} + TEST_PREFIX ${TEST_PREFIX} + TEST_SUFFIX ${TEST_SUFFIX} + TEST_LIST ${TEST_LIST} + TEST_REPORTER ${TEST_REPORTER} + TEST_OUTPUT_DIR ${TEST_OUTPUT_DIR} + TEST_OUTPUT_PREFIX ${TEST_OUTPUT_PREFIX} + TEST_OUTPUT_SUFFIX ${TEST_OUTPUT_SUFFIX} + TEST_DL_PATHS ${TEST_DL_PATHS} + TEST_DL_FRAMEWORK_PATHS ${TEST_DL_FRAMEWORK_PATHS} + CTEST_FILE ${CTEST_FILE} + ADD_TAGS_AS_LABELS ${ADD_TAGS_AS_LABELS} + ) +endif() diff --git a/cmake/CatchShardTests.cmake b/cmake/CatchShardTests.cmake new file mode 100644 index 000000000..89666d666 --- /dev/null +++ b/cmake/CatchShardTests.cmake @@ -0,0 +1,72 @@ + +# Copyright Catch2 Authors +# Distributed under the Boost Software License, Version 1.0. +# (See accompanying file LICENSE.txt or copy at +# https://www.boost.org/LICENSE_1_0.txt) + +# SPDX-License-Identifier: BSL-1.0 + +# Supported optional args: +# * SHARD_COUNT - number of shards to split target's tests into +# * REPORTER - reporter spec to use for tests +# * TEST_SPEC - test spec used for filtering tests +function(catch_add_sharded_tests TARGET) + if(${CMAKE_VERSION} VERSION_LESS "3.10.0") + message(FATAL_ERROR "add_sharded_catch_tests only supports CMake versions 3.10.0 and up") + endif() + + cmake_parse_arguments( + "" + "" + "SHARD_COUNT;REPORTER;TEST_SPEC" + "" + ${ARGN} + ) + + if(NOT DEFINED _SHARD_COUNT) + set(_SHARD_COUNT 2) + endif() + + # Generate a unique name based on the extra arguments + string(SHA1 args_hash "${_TEST_SPEC} ${_EXTRA_ARGS} ${_REPORTER} ${_OUTPUT_DIR} ${_OUTPUT_PREFIX} ${_OUTPUT_SUFFIX} ${_SHARD_COUNT}") + string(SUBSTRING ${args_hash} 0 7 args_hash) + + set(ctest_include_file "${CMAKE_CURRENT_BINARY_DIR}/${TARGET}-sharded-tests-include-${args_hash}.cmake") + set(ctest_tests_file "${CMAKE_CURRENT_BINARY_DIR}/${TARGET}-sharded-tests-impl-${args_hash}.cmake") + + file(WRITE "${ctest_include_file}" + "if(EXISTS \"${ctest_tests_file}\")\n" + " include(\"${ctest_tests_file}\")\n" + "else()\n" + " add_test(${TARGET}_NOT_BUILT-${args_hash} ${TARGET}_NOT_BUILT-${args_hash})\n" + "endif()\n" + ) + + set_property(DIRECTORY + APPEND PROPERTY TEST_INCLUDE_FILES "${ctest_include_file}" + ) + + set(shard_impl_script_file "${_CATCH_DISCOVER_SHARD_TESTS_IMPL_SCRIPT}") + + add_custom_command( + TARGET ${TARGET} POST_BUILD + BYPRODUCTS "${ctest_tests_file}" + COMMAND "${CMAKE_COMMAND}" + -D "TARGET_NAME=${TARGET}" + -D "TEST_BINARY=$" + -D "CTEST_FILE=${ctest_tests_file}" + -D "SHARD_COUNT=${_SHARD_COUNT}" + -D "REPORTER_SPEC=${_REPORTER}" + -D "TEST_SPEC=${_TEST_SPEC}" + -P "${shard_impl_script_file}" + VERBATIM + ) +endfunction() + + +############################################################################### + +set(_CATCH_DISCOVER_SHARD_TESTS_IMPL_SCRIPT + ${CMAKE_CURRENT_LIST_DIR}/CatchShardTestsImpl.cmake + CACHE INTERNAL "Catch2 full path to CatchShardTestsImpl.cmake helper file" +) diff --git a/cmake/CatchShardTestsImpl.cmake b/cmake/CatchShardTestsImpl.cmake new file mode 100644 index 000000000..83fac688b --- /dev/null +++ b/cmake/CatchShardTestsImpl.cmake @@ -0,0 +1,52 @@ + +# Copyright Catch2 Authors +# Distributed under the Boost Software License, Version 1.0. +# (See accompanying file LICENSE.txt or copy at +# https://www.boost.org/LICENSE_1_0.txt) + +# SPDX-License-Identifier: BSL-1.0 + +# Indirection for CatchShardTests that allows us to delay the script +# file generation until build time. + +# Expected args: +# * TEST_BINARY - full path to the test binary to run sharded +# * CTEST_FILE - full path to ctest script file to write to +# * TARGET_NAME - name of the target to shard (used for test names) +# * SHARD_COUNT - number of shards to split the binary into +# Optional args: +# * REPORTER_SPEC - reporter specs to be passed down to the binary +# * TEST_SPEC - test spec to pass down to the test binary + +if(NOT EXISTS "${TEST_BINARY}") + message(FATAL_ERROR + "Specified test binary '${TEST_BINARY}' does not exist" + ) +endif() + +set(other_args "") +if(TEST_SPEC) + set(other_args "${other_args} ${TEST_SPEC}") +endif() +if(REPORTER_SPEC) + set(other_args "${other_args} --reporter ${REPORTER_SPEC}") +endif() + +# foreach RANGE in cmake is inclusive of the end, so we have to adjust it +math(EXPR adjusted_shard_count "${SHARD_COUNT} - 1") + +file(WRITE "${CTEST_FILE}" + "string(RANDOM LENGTH 8 ALPHABET \"0123456789abcdef\" rng_seed)\n" + "\n" + "foreach(shard_idx RANGE ${adjusted_shard_count})\n" + " add_test(${TARGET_NAME}-shard-" [[${shard_idx}]] "/${adjusted_shard_count}\n" + " ${TEST_BINARY}" + " --shard-index " [[${shard_idx}]] + " --shard-count ${SHARD_COUNT}" + " --rng-seed " [[0x${rng_seed}]] + " --order rand" + "${other_args}" + "\n" + " )\n" + "endforeach()\n" +) diff --git a/cmake/ParseAndAddCatchTests.cmake b/cmake/ParseAndAddCatchTests.cmake index cb2846d0a..d7c17ace9 100644 --- a/cmake/ParseAndAddCatchTests.cmake +++ b/cmake/ParseAndAddCatchTests.cmake @@ -1,9 +1,11 @@ #==================================================================================================# # supported macros # # - TEST_CASE, # +# - TEMPLATE_TEST_CASE # # - SCENARIO, # # - TEST_CASE_METHOD, # # - CATCH_TEST_CASE, # +# - CATCH_TEMPLATE_TEST_CASE # # - CATCH_SCENARIO, # # - CATCH_TEST_CASE_METHOD. # # # @@ -39,9 +41,24 @@ # PARSE_CATCH_TESTS_ADD_TO_CONFIGURE_DEPENDS (Default OFF) # # -- causes CMake to rerun when file with tests changes so that new tests will be discovered # # # +# One can also set (locally) the optional variable OptionalCatchTestLauncher to precise the way # +# a test should be run. For instance to use test MPI, one can write # +# set(OptionalCatchTestLauncher ${MPIEXEC} ${MPIEXEC_NUMPROC_FLAG} ${NUMPROC}) # +# just before calling this ParseAndAddCatchTests function # +# # +# The AdditionalCatchParameters optional variable can be used to pass extra argument to the test # +# command. For example, to include successful tests in the output, one can write # +# set(AdditionalCatchParameters --success) # +# # +# After the script, the ParseAndAddCatchTests_TESTS property for the target, and for each source # +# file in the target is set, and contains the list of the tests extracted from that target, or # +# from that file. This is useful, for example to add further labels or properties to the tests. # +# # #==================================================================================================# -cmake_minimum_required(VERSION 2.8.8) +if(CMAKE_MINIMUM_REQUIRED_VERSION VERSION_LESS 2.8.8) + message(FATAL_ERROR "ParseAndAddCatchTests requires CMake 2.8.8 or newer") +endif() option(PARSE_CATCH_TESTS_VERBOSE "Print Catch to CTest parser debug messages" OFF) option(PARSE_CATCH_TESTS_NO_HIDDEN_TESTS "Exclude tests with [!hide], [.] or [.foo] tags" OFF) @@ -49,10 +66,10 @@ option(PARSE_CATCH_TESTS_ADD_FIXTURE_IN_TEST_NAME "Add fixture class name to the option(PARSE_CATCH_TESTS_ADD_TARGET_IN_TEST_NAME "Add target name to the test name" ON) option(PARSE_CATCH_TESTS_ADD_TO_CONFIGURE_DEPENDS "Add test file to CMAKE_CONFIGURE_DEPENDS property" OFF) -function(PrintDebugMessage) - if(PARSE_CATCH_TESTS_VERBOSE) - message(STATUS "ParseAndAddCatchTests: ${ARGV}") - endif() +function(ParseAndAddCatchTests_PrintDebugMessage) + if(PARSE_CATCH_TESTS_VERBOSE) + message(STATUS "ParseAndAddCatchTests: ${ARGV}") + endif() endfunction() # This removes the contents between @@ -60,7 +77,7 @@ endfunction() # - full line comments (i.e. // ... ) # contents have been read into '${CppCode}'. # !keep partial line comments -function(RemoveComments CppCode) +function(ParseAndAddCatchTests_RemoveComments CppCode) string(ASCII 2 CMakeBeginBlockComment) string(ASCII 3 CMakeEndBlockComment) string(REGEX REPLACE "/\\*" "${CMakeBeginBlockComment}" ${CppCode} "${${CppCode}}") @@ -72,114 +89,162 @@ function(RemoveComments CppCode) endfunction() # Worker function -function(ParseFile SourceFile TestTarget) - # According to CMake docs EXISTS behavior is well-defined only for full paths. - get_filename_component(SourceFile ${SourceFile} ABSOLUTE) - if(NOT EXISTS ${SourceFile}) - message(WARNING "Cannot find source file: ${SourceFile}") - return() +function(ParseAndAddCatchTests_ParseFile SourceFile TestTarget) + # If SourceFile is an object library, do not scan it (as it is not a file). Exit without giving a warning about a missing file. + if(SourceFile MATCHES "\\\$") + ParseAndAddCatchTests_PrintDebugMessage("Detected OBJECT library: ${SourceFile} this will not be scanned for tests.") + return() + endif() + # According to CMake docs EXISTS behavior is well-defined only for full paths. + get_filename_component(SourceFile ${SourceFile} ABSOLUTE) + if(NOT EXISTS ${SourceFile}) + message(WARNING "Cannot find source file: ${SourceFile}") + return() + endif() + ParseAndAddCatchTests_PrintDebugMessage("parsing ${SourceFile}") + file(STRINGS ${SourceFile} Contents NEWLINE_CONSUME) + + # Remove block and fullline comments + ParseAndAddCatchTests_RemoveComments(Contents) + + # Find definition of test names + # https://regex101.com/r/JygOND/1 + string(REGEX MATCHALL "[ \t]*(CATCH_)?(TEMPLATE_)?(TEST_CASE_METHOD|SCENARIO|TEST_CASE)[ \t]*\\([ \t\n]*\"[^\"]*\"[ \t\n]*(,[ \t\n]*\"[^\"]*\")?(,[ \t\n]*[^\,\)]*)*\\)[ \t\n]*\{+[ \t]*(//[^\n]*[Tt][Ii][Mm][Ee][Oo][Uu][Tt][ \t]*[0-9]+)*" Tests "${Contents}") + + if(PARSE_CATCH_TESTS_ADD_TO_CONFIGURE_DEPENDS AND Tests) + ParseAndAddCatchTests_PrintDebugMessage("Adding ${SourceFile} to CMAKE_CONFIGURE_DEPENDS property") + set_property( + DIRECTORY + APPEND + PROPERTY CMAKE_CONFIGURE_DEPENDS ${SourceFile} + ) + endif() + + # check CMP0110 policy for new add_test() behavior + if(POLICY CMP0110) + cmake_policy(GET CMP0110 _cmp0110_value) # new add_test() behavior + else() + # just to be thorough explicitly set the variable + set(_cmp0110_value) + endif() + + foreach(TestName ${Tests}) + # Strip newlines + string(REGEX REPLACE "\\\\\n|\n" "" TestName "${TestName}") + + # Get test type and fixture if applicable + string(REGEX MATCH "(CATCH_)?(TEMPLATE_)?(TEST_CASE_METHOD|SCENARIO|TEST_CASE)[ \t]*\\([^,^\"]*" TestTypeAndFixture "${TestName}") + string(REGEX MATCH "(CATCH_)?(TEMPLATE_)?(TEST_CASE_METHOD|SCENARIO|TEST_CASE)" TestType "${TestTypeAndFixture}") + string(REGEX REPLACE "${TestType}\\([ \t]*" "" TestFixture "${TestTypeAndFixture}") + + # Get string parts of test definition + string(REGEX MATCHALL "\"+([^\\^\"]|\\\\\")+\"+" TestStrings "${TestName}") + + # Strip wrapping quotation marks + string(REGEX REPLACE "^\"(.*)\"$" "\\1" TestStrings "${TestStrings}") + string(REPLACE "\";\"" ";" TestStrings "${TestStrings}") + + # Validate that a test name and tags have been provided + list(LENGTH TestStrings TestStringsLength) + if(TestStringsLength GREATER 2 OR TestStringsLength LESS 1) + message(FATAL_ERROR "You must provide a valid test name and tags for all tests in ${SourceFile}") endif() - PrintDebugMessage("parsing ${SourceFile}") - file(STRINGS ${SourceFile} Contents NEWLINE_CONSUME) - - # Remove block and fullline comments - RemoveComments(Contents) - # Find definition of test names - string(REGEX MATCHALL "[ \t]*(CATCH_)?(TEST_CASE_METHOD|SCENARIO|TEST_CASE)[ \t]*\\([^\)]+\\)+[ \t\n]*{+[ \t]*(//[^\n]*[Tt][Ii][Mm][Ee][Oo][Uu][Tt][ \t]*[0-9]+)*" Tests "${Contents}") + # Assign name and tags + list(GET TestStrings 0 Name) + if("${TestType}" STREQUAL "SCENARIO") + set(Name "Scenario: ${Name}") + endif() + if(PARSE_CATCH_TESTS_ADD_FIXTURE_IN_TEST_NAME AND "${TestType}" MATCHES "(CATCH_)?TEST_CASE_METHOD" AND TestFixture) + set(CTestName "${TestFixture}:${Name}") + else() + set(CTestName "${Name}") + endif() + if(PARSE_CATCH_TESTS_ADD_TARGET_IN_TEST_NAME) + set(CTestName "${TestTarget}:${CTestName}") + endif() + # add target to labels to enable running all tests added from this target + set(Labels ${TestTarget}) + if(TestStringsLength EQUAL 2) + list(GET TestStrings 1 Tags) + string(TOLOWER "${Tags}" Tags) + # remove target from labels if the test is hidden + if("${Tags}" MATCHES ".*\\[!?(hide|\\.)\\].*") + list(REMOVE_ITEM Labels ${TestTarget}) + endif() + string(REPLACE "]" ";" Tags "${Tags}") + string(REPLACE "[" "" Tags "${Tags}") + else() + # unset tags variable from previous loop + unset(Tags) + endif() - if(PARSE_CATCH_TESTS_ADD_TO_CONFIGURE_DEPENDS AND Tests) - PrintDebugMessage("Adding ${SourceFile} to CMAKE_CONFIGURE_DEPENDS property") + list(APPEND Labels ${Tags}) + + set(HiddenTagFound OFF) + foreach(label ${Labels}) + string(REGEX MATCH "^!hide|^\\." result ${label}) + if(result) + set(HiddenTagFound ON) + break() + endif() + endforeach(label) + if(PARSE_CATCH_TESTS_NO_HIDDEN_TESTS AND ${HiddenTagFound} AND ${CMAKE_VERSION} VERSION_LESS "3.9") + ParseAndAddCatchTests_PrintDebugMessage("Skipping test \"${CTestName}\" as it has [!hide], [.] or [.foo] label") + else() + ParseAndAddCatchTests_PrintDebugMessage("Adding test \"${CTestName}\"") + if(Labels) + ParseAndAddCatchTests_PrintDebugMessage("Setting labels to ${Labels}") + endif() + + # Escape commas in the test spec + string(REPLACE "," "\\," Name ${Name}) + + # Work around CMake 3.18.0 change in `add_test()`, before the escaped quotes were necessary, + # only with CMake 3.18.0 the escaped double quotes confuse the call. This change is reverted in 3.18.1 + # And properly introduced in 3.19 with the CMP0110 policy + if(_cmp0110_value STREQUAL "NEW" OR ${CMAKE_VERSION} VERSION_EQUAL "3.18") + ParseAndAddCatchTests_PrintDebugMessage("CMP0110 set to NEW, no need for add_test(\"\") workaround") + else() + ParseAndAddCatchTests_PrintDebugMessage("CMP0110 set to OLD adding \"\" for add_test() workaround") + set(CTestName "\"${CTestName}\"") + endif() + + # Handle template test cases + if("${TestTypeAndFixture}" MATCHES ".*TEMPLATE_.*") + set(Name "${Name} - *") + endif() + + # Add the test and set its properties + add_test(NAME "${CTestName}" COMMAND ${OptionalCatchTestLauncher} $ ${Name} ${AdditionalCatchParameters}) + # Old CMake versions do not document VERSION_GREATER_EQUAL, so we use VERSION_GREATER with 3.8 instead + if(PARSE_CATCH_TESTS_NO_HIDDEN_TESTS AND ${HiddenTagFound} AND ${CMAKE_VERSION} VERSION_GREATER "3.8") + ParseAndAddCatchTests_PrintDebugMessage("Setting DISABLED test property") + set_tests_properties("${CTestName}" PROPERTIES DISABLED ON) + else() + set_tests_properties("${CTestName}" PROPERTIES FAIL_REGULAR_EXPRESSION "No tests ran" + LABELS "${Labels}") + endif() set_property( - DIRECTORY + TARGET ${TestTarget} APPEND - PROPERTY CMAKE_CONFIGURE_DEPENDS ${SourceFile} - ) + PROPERTY ParseAndAddCatchTests_TESTS "${CTestName}") + set_property( + SOURCE ${SourceFile} + APPEND + PROPERTY ParseAndAddCatchTests_TESTS "${CTestName}") endif() - - foreach(TestName ${Tests}) - # Strip newlines - string(REGEX REPLACE "\\\\\n|\n" "" TestName "${TestName}") - - # Get test type and fixture if applicable - string(REGEX MATCH "(CATCH_)?(TEST_CASE_METHOD|SCENARIO|TEST_CASE)[ \t]*\\([^,^\"]*" TestTypeAndFixture "${TestName}") - string(REGEX MATCH "(CATCH_)?(TEST_CASE_METHOD|SCENARIO|TEST_CASE)" TestType "${TestTypeAndFixture}") - string(REPLACE "${TestType}(" "" TestFixture "${TestTypeAndFixture}") - - # Get string parts of test definition - string(REGEX MATCHALL "\"+([^\\^\"]|\\\\\")+\"+" TestStrings "${TestName}") - - # Strip wrapping quotation marks - string(REGEX REPLACE "^\"(.*)\"$" "\\1" TestStrings "${TestStrings}") - string(REPLACE "\";\"" ";" TestStrings "${TestStrings}") - - # Validate that a test name and tags have been provided - list(LENGTH TestStrings TestStringsLength) - if(TestStringsLength GREATER 2 OR TestStringsLength LESS 1) - message(FATAL_ERROR "You must provide a valid test name and tags for all tests in ${SourceFile}") - endif() - - # Assign name and tags - list(GET TestStrings 0 Name) - if("${TestType}" STREQUAL "SCENARIO") - set(Name "Scenario: ${Name}") - endif() - if(PARSE_CATCH_TESTS_ADD_FIXTURE_IN_TEST_NAME AND TestFixture) - set(CTestName "${TestFixture}:${Name}") - else() - set(CTestName "${Name}") - endif() - if(PARSE_CATCH_TESTS_ADD_TARGET_IN_TEST_NAME) - set(CTestName "${TestTarget}:${CTestName}") - endif() - # add target to labels to enable running all tests added from this target - set(Labels ${TestTarget}) - if(TestStringsLength EQUAL 2) - list(GET TestStrings 1 Tags) - string(TOLOWER "${Tags}" Tags) - # remove target from labels if the test is hidden - if("${Tags}" MATCHES ".*\\[!?(hide|\\.)\\].*") - list(REMOVE_ITEM Labels ${TestTarget}) - endif() - string(REPLACE "]" ";" Tags "${Tags}") - string(REPLACE "[" "" Tags "${Tags}") - endif() - - list(APPEND Labels ${Tags}) - - list(FIND Labels "!hide" IndexOfHideLabel) - set(HiddenTagFound OFF) - foreach(label ${Labels}) - string(REGEX MATCH "^!hide|^\\." result ${label}) - if(result) - set(HiddenTagFound ON) - break() - endif(result) - endforeach(label) - if(PARSE_CATCH_TESTS_NO_HIDDEN_TESTS AND ${HiddenTagFound}) - PrintDebugMessage("Skipping test \"${CTestName}\" as it has [!hide], [.] or [.foo] label") - else() - PrintDebugMessage("Adding test \"${CTestName}\"") - if(Labels) - PrintDebugMessage("Setting labels to ${Labels}") - endif() - - # Add the test and set its properties - add_test(NAME "\"${CTestName}\"" COMMAND ${TestTarget} ${Name} ${AdditionalCatchParameters}) - set_tests_properties("\"${CTestName}\"" PROPERTIES FAIL_REGULAR_EXPRESSION "No tests ran" - LABELS "${Labels}") - endif() - - endforeach() + endforeach() endfunction() # entry point function(ParseAndAddCatchTests TestTarget) - PrintDebugMessage("Started parsing ${TestTarget}") - get_target_property(SourceFiles ${TestTarget} SOURCES) - PrintDebugMessage("Found the following sources: ${SourceFiles}") - foreach(SourceFile ${SourceFiles}) - ParseFile(${SourceFile} ${TestTarget}) - endforeach() - PrintDebugMessage("Finished parsing ${TestTarget}") + message(DEPRECATION "ParseAndAddCatchTest: function deprecated because of possibility of missed test cases. Consider using 'catch_discover_tests' from 'Catch.cmake'") + ParseAndAddCatchTests_PrintDebugMessage("Started parsing ${TestTarget}") + get_target_property(SourceFiles ${TestTarget} SOURCES) + ParseAndAddCatchTests_PrintDebugMessage("Found the following sources: ${SourceFiles}") + foreach(SourceFile ${SourceFiles}) + ParseAndAddCatchTests_ParseFile(${SourceFile} ${TestTarget}) + endforeach() + ParseAndAddCatchTests_PrintDebugMessage("Finished parsing ${TestTarget}") endfunction() diff --git a/emscripten/CMakeLists.txt b/emscripten/CMakeLists.txt new file mode 100644 index 000000000..2a08d5200 --- /dev/null +++ b/emscripten/CMakeLists.txt @@ -0,0 +1,40 @@ +# Emscripten/WebAssembly build for ChaiScript +# Based on work by Rob Loach: https://github.com/RobLoach/ChaiScript.js +# +# Usage: +# emcmake cmake -B build-em -S emscripten +# cmake --build build-em + +cmake_minimum_required(VERSION 3.12) +project(chaiscript_em) + +set(CMAKE_CXX_STANDARD 20) +set(CMAKE_CXX_STANDARD_REQUIRED ON) + +# Emscripten-specific compiler/linker flags +add_definitions(-DCHAISCRIPT_NO_THREADS -DCHAISCRIPT_NO_DYNLOAD) + +add_executable(chaiscript chaiscript_em.cpp) +target_include_directories(chaiscript PRIVATE ${CMAKE_CURRENT_SOURCE_DIR}/../include) + +# Enable WASM exception handling — ChaiScript relies on C++ exceptions for +# error propagation; without this flag exceptions cause an abort in WASM. +target_compile_options(chaiscript PRIVATE -fwasm-exceptions) + +# Emscripten link flags: enable embind, allow memory growth, export as ES module-compatible +target_link_options(chaiscript PRIVATE + --bind + -fwasm-exceptions + -sALLOW_MEMORY_GROWTH=1 + -sEXPORT_ES6=0 + -sMODULARIZE=0 + -sINVOKE_RUN=0 +) + +# Copy the HTML shell to the build output directory +add_custom_command(TARGET chaiscript POST_BUILD + COMMAND ${CMAKE_COMMAND} -E copy + ${CMAKE_CURRENT_SOURCE_DIR}/chaiscript.html + ${CMAKE_CURRENT_BINARY_DIR}/chaiscript.html + COMMENT "Copying HTML frontend to build directory" +) diff --git a/emscripten/chaiscript.html b/emscripten/chaiscript.html new file mode 100644 index 000000000..ae7cd1a89 --- /dev/null +++ b/emscripten/chaiscript.html @@ -0,0 +1,240 @@ + + + + + + + ChaiScript + + + +
+

ChaiScript

+ Interactive Playground +
Loading...
+
+
+
+
Input
+ +
+
+
+
Output
+
+
+
+
+ + + Ctrl+Enter to run +
+ + + + + diff --git a/emscripten/chaiscript_em.cpp b/emscripten/chaiscript_em.cpp new file mode 100644 index 000000000..7a48c593d --- /dev/null +++ b/emscripten/chaiscript_em.cpp @@ -0,0 +1,28 @@ +// This file is distributed under the BSD License. +// See "license.txt" for details. +// Copyright 2019, Rob Loach (https://github.com/RobLoach/ChaiScript.js) +// Copyright 2009-2018, Jason Turner (jason@emptycrate.com) +// http://www.chaiscript.com +// +// Emscripten/WebAssembly wrapper for ChaiScript. +// Based on work by Rob Loach: https://github.com/RobLoach/ChaiScript.js + +#include "chaiscript_eval.hpp" + +#ifdef __EMSCRIPTEN__ +#include + +EMSCRIPTEN_BINDINGS(chaiscript) { + emscripten::function("create", &chaiscript_create); + emscripten::function("destroy", &chaiscript_destroy); + emscripten::function("eval", &chaiscript_eval); + emscripten::function("evalString", &chaiscript_eval_string); + emscripten::function("evalBool", &chaiscript_eval_bool); + emscripten::function("evalInt", &chaiscript_eval_int); + emscripten::function("evalFloat", &chaiscript_eval_float); + emscripten::function("evalDouble", &chaiscript_eval_double); + emscripten::function("saveState", &chaiscript_save_state); + emscripten::function("restoreState", &chaiscript_restore_state); + emscripten::function("releaseState", &chaiscript_release_state); +} +#endif diff --git a/emscripten/chaiscript_eval.hpp b/emscripten/chaiscript_eval.hpp new file mode 100644 index 000000000..ab16706e7 --- /dev/null +++ b/emscripten/chaiscript_eval.hpp @@ -0,0 +1,124 @@ +// This file is distributed under the BSD License. +// See "license.txt" for details. +// Copyright 2019, Rob Loach (https://github.com/RobLoach/ChaiScript.js) +// Copyright 2009-2018, Jason Turner (jason@emptycrate.com) +// http://www.chaiscript.com + +// Shared eval helper functions for the ChaiScript Emscripten wrapper. +// These functions provide typed evaluation of ChaiScript expressions, +// used by both the Emscripten/WebAssembly build and native tests. +// +// The interface is opaque and handle-based: JS callers create one or more +// engines via chaiscript_create(), pass the resulting handle to the eval and +// state helpers, and release the engine with chaiscript_destroy() when done. +// State snapshots are themselves opaque handles produced by +// chaiscript_save_state() and consumed by chaiscript_restore_state() / +// chaiscript_release_state(). Hiding ChaiScript and ChaiScript::State behind +// integer handles keeps embind from having to manage their lifetimes and +// avoids forcing a static singleton on the C++ side. + +#ifndef CHAISCRIPT_EMSCRIPTEN_EVAL_HPP_ +#define CHAISCRIPT_EMSCRIPTEN_EVAL_HPP_ + +#include +#include +#include +#include +#include + +namespace detail { + // ChaiScript::State captures globals/functions/types but not the top-level + // scripting locals (variables created by `var x = ...` at the script's + // outermost scope). The playground's reset-between-runs use case needs both, + // so the snapshot pairs the engine state with the locals map. + struct Snapshot { + chaiscript::ChaiScript::State engine_state; + std::map locals; + }; + + inline std::unordered_map> &chai_registry() { + static std::unordered_map> registry; + return registry; + } + + inline std::unordered_map &state_registry() { + static std::unordered_map registry; + return registry; + } + + inline int next_handle() { + static int handle = 0; + return ++handle; + } + + inline chaiscript::ChaiScript &get_chai(const int handle) { + return *chai_registry().at(handle); + } +} // namespace detail + +// Construct a fresh ChaiScript engine and return an opaque handle. The handle +// is owned by the caller and must be released with chaiscript_destroy(). +inline int chaiscript_create() { + const int handle = detail::next_handle(); + detail::chai_registry().emplace(handle, std::make_unique()); + return handle; +} + +// Destroy an engine handle. Unknown handles are silently ignored so JS callers +// do not need to track validity defensively. +inline void chaiscript_destroy(const int handle) { + detail::chai_registry().erase(handle); +} + +inline void chaiscript_eval(const int handle, const std::string &input) { + detail::get_chai(handle).eval(input); +} + +inline std::string chaiscript_eval_string(const int handle, const std::string &input) { + return detail::get_chai(handle).eval(input); +} + +inline bool chaiscript_eval_bool(const int handle, const std::string &input) { + return detail::get_chai(handle).eval(input); +} + +inline int chaiscript_eval_int(const int handle, const std::string &input) { + return detail::get_chai(handle).eval(input); +} + +inline float chaiscript_eval_float(const int handle, const std::string &input) { + return detail::get_chai(handle).eval(input); +} + +inline double chaiscript_eval_double(const int handle, const std::string &input) { + return detail::get_chai(handle).eval(input); +} + +// Snapshot the engine identified by chai_handle and return a fresh opaque +// state handle. Release it with chaiscript_release_state() when no longer +// needed. +inline int chaiscript_save_state(const int chai_handle) { + const int state_handle = detail::next_handle(); + auto &chai = detail::get_chai(chai_handle); + detail::state_registry().emplace(state_handle, detail::Snapshot{chai.get_state(), chai.get_locals()}); + return state_handle; +} + +// Restore a previously snapshotted state onto the engine identified by +// chai_handle. Unknown state handles are silently ignored. +inline void chaiscript_restore_state(const int chai_handle, const int state_handle) { + const auto it = detail::state_registry().find(state_handle); + if (it != detail::state_registry().end()) { + auto &chai = detail::get_chai(chai_handle); + chai.set_state(it->second.engine_state); + chai.set_locals(it->second.locals); + } +} + +// Release a state handle returned by chaiscript_save_state. Releasing an +// unknown handle is a no-op. +inline void chaiscript_release_state(const int state_handle) { + detail::state_registry().erase(state_handle); +} + +#endif /* CHAISCRIPT_EMSCRIPTEN_EVAL_HPP_ */ diff --git a/grammar/chaiscript.ebnf b/grammar/chaiscript.ebnf new file mode 100644 index 000000000..6e13d5477 --- /dev/null +++ b/grammar/chaiscript.ebnf @@ -0,0 +1,189 @@ +/* + * ChaiScript Grammar — EBNF for Railroad Diagram Generation + * + * View as navigable railroad diagrams at: + * https://www.bottlecaps.de/rr/ui (IPv6) + * https://rr.red-dove.com/ui (IPv4) + * + * Copy and paste this file into the 'Edit Grammar' tab, then + * click 'View Diagram'. + * + * This grammar uses the notation accepted by + * https://github.com/GuntherRademacher/rr : + * - "::=" as rule separator + * - no semicolon at end of rule + * - "?" "+" "*" for repetition + * - C comments + */ + +/* ---- Top-level ---- */ + +statements ::= ( def | try | if | while | class | enum + | for | switch | return | break | continue + | equation | block | eol )+ + +/* ---- Functions ---- */ + +def ::= "def" id ( "::" id )? "(" decl_arg_list ")" eol* + ( ":" guard )? eol* block + +lambda ::= "fun" ( "[" id_arg_list "]" )? "(" decl_arg_list ")" eol* block + +guard ::= operator + +/* ---- Exception handling ---- */ + +try ::= "try" eol* block catch* finally? +catch ::= "catch" ( "(" arg ")" )? eol* block +finally ::= "finally" eol* block + +/* ---- Control flow ---- */ + +if ::= "if" "(" equation ( eol equation )? ")" eol* block + ( "else" ( if | eol* block ) )* + +while ::= "while" "(" operator ")" eol* block + +for ::= "for" "(" ( for_guards | equation ":" equation ) ")" eol* block +for_guards ::= equation eol equation eol equation + +switch ::= "switch" "(" operator ")" eol* "{" ( case | default )+ "}" +case ::= "case" "(" operator ")" eol* block +default ::= "default" eol* block + +/* ---- Classes ---- */ + +class ::= "class" id ( ":" id )? eol* class_block +class_block ::= "{" class_statements* "}" +class_statements ::= def | var_decl | eol + +/* ---- Enums ---- */ + +enum ::= "enum" ( "class" | "struct" ) id ( ":" underlying_type )? + "{" enum_entries? "}" + +enum_entries ::= enum_entry ( "," enum_entry )* + +enum_entry ::= id ( "=" integer )? + +underlying_type ::= id + + +/* ---- Blocks & flow keywords ---- */ + +block ::= "{" statements* "}" +return ::= "return" operator? +break ::= "break" +continue ::= "continue" + +/* ---- Line termination ---- */ + +eol ::= "\n" | "\r\n" | ";" + +/* ---- Equations & operators ---- */ + +equation ::= operator ( ( "=" | ":=" | "+=" | "-=" | "*=" | "/=" + | "%=" | "<<=" | ">>=" | "&=" | "^=" | "|=" ) + equation )? + +operator ::= prefix + | value + | operator binary_operator operator + | operator "?" operator ":" operator + +prefix ::= ( "++" | "--" | "-" | "+" | "!" | "~" ) operator + +binary_operator ::= "||" | "&&" + | "|" | "^" | "&" + | "==" | "!=" + | "<" | "<=" | ">" | ">=" + | "<<" | ">>" + | "+" | "-" + | "*" | "/" | "%" + +/* ---- Values & access ---- */ + +value ::= var_decl | dot_fun_array | prefix + +dot_fun_array ::= ( lambda | num | quoted_string + | single_quoted_string | raw_string + | paren_expression | inline_container + | id ) + ( fun_call | array_call | dot_access )* + +fun_call ::= "(" arg_list ")" +array_call ::= "[" operator "]" +dot_access ::= "." id + +/* ---- Variable declarations ---- */ + +var_decl ::= ( "auto" | "var" | "const" ) ( reference | id ) + | "global" ( reference | id ) + | "attr" id ( "::" id )? + +reference ::= "&" id + +/* ---- Parenthesised & inline containers ---- */ + +paren_expression ::= "(" operator ")" + +inline_container ::= "[" container_arg_list "]" +container_arg_list ::= value_range + | map_pair ( "," map_pair )* + | operator ( "," operator )* + +value_range ::= operator ".." operator +map_pair ::= operator ":" operator + +/* ---- String literals ---- */ + +quoted_string ::= '"' ( char | escape | interpolation )* '"' +single_quoted_string ::= "'" ( char | escape ) "'" +raw_string ::= 'R"' delimiter? "(" char* ")" delimiter? '"' +delimiter ::= [a-zA-Z0-9_]+ +interpolation ::= "${" equation "}" + +/* ---- Escape sequences ---- */ + +escape ::= "\" ( "'" | '"' | "?" | "\" | "a" | "b" + | "f" | "n" | "r" | "t" | "v" | "$" + | "0" + | "x" hex_digit+ + | "u" hex_digit hex_digit hex_digit hex_digit + | "U" hex_digit hex_digit hex_digit hex_digit + hex_digit hex_digit hex_digit hex_digit + | octal_digit+ ) + +/* ---- Argument lists ---- */ + +id_arg_list ::= id ( "," id )* +decl_arg_list ::= ( arg ( "," arg )* )? +arg_list ::= ( equation ( "," equation )* )? +arg ::= id id? + +/* ---- Identifiers ---- */ + +id ::= ( [a-zA-Z_] [a-zA-Z0-9_]* ) + | ( "`" [^`]+ "`" ) + | "true" | "false" + | "Infinity" | "NaN" + | "_" + | "__LINE__" | "__FILE__" | "__FUNC__" | "__CLASS__" + +/* ---- Numeric literals ---- */ + +num ::= hex | binary | float | integer + +hex ::= "0" ( "x" | "X" ) [0-9a-fA-F]+ int_suffix* +binary ::= "0" ( "b" | "B" ) [01]+ int_suffix* +float ::= [0-9]+ "." [0-9]+ ( ( "e" | "E" ) ( "+" | "-" )? [0-9]+ )? float_suffix? +integer ::= [0-9]+ int_suffix* + +int_suffix ::= "l" | "L" | "ll" | "LL" | "u" | "U" +float_suffix ::= "l" | "L" | "f" | "F" + +/* ---- Character classes ---- */ + +octal_digit ::= [0-7] +hex_digit ::= [0-9a-fA-F] +char ::= [^"\] diff --git a/include/chaiscript/chaiscript.hpp b/include/chaiscript/chaiscript.hpp index 871b353b6..de8c32885 100644 --- a/include/chaiscript/chaiscript.hpp +++ b/include/chaiscript/chaiscript.hpp @@ -824,12 +824,14 @@ namespace chaiscript { public: ChaiScript(std::vector t_modulepaths = {}, std::vector t_usepaths = {}, - std::vector t_opts = chaiscript::default_options()) - : ChaiScript_Basic(chaiscript::Std_Lib::library(), + std::vector t_opts = chaiscript::default_options(), + std::vector t_lib_opts = {}) + : ChaiScript_Basic(chaiscript::Std_Lib::library(t_lib_opts), std::make_unique>(), std::move(t_modulepaths), std::move(t_usepaths), - std::move(t_opts)) { + std::move(t_opts), + std::find(t_lib_opts.begin(), t_lib_opts.end(), Library_Options::No_IO) != t_lib_opts.end()) { } }; } // namespace chaiscript diff --git a/include/chaiscript/chaiscript_defines.hpp b/include/chaiscript/chaiscript_defines.hpp index e01aadd35..f8677ec89 100644 --- a/include/chaiscript/chaiscript_defines.hpp +++ b/include/chaiscript/chaiscript_defines.hpp @@ -7,6 +7,16 @@ #ifndef CHAISCRIPT_DEFINES_HPP_ #define CHAISCRIPT_DEFINES_HPP_ +// MacOSX Fix: std::get/std::get_if/std::visit for std::variant are annotated +// __attribute__((availability(macos,strict,introduced=10.14))) in Apple libc++. +// Defining this macro before any libc++ header strips those annotations so +// ChaiScript can be built against older MacOSX deployment targets. +#if defined(__APPLE__) && defined(__clang__) +#ifndef _LIBCPP_DISABLE_AVAILABILITY +#define _LIBCPP_DISABLE_AVAILABILITY +#endif +#endif + #ifdef _MSC_VER #define CHAISCRIPT_STRINGIZE(x) "" #x #define CHAISCRIPT_STRINGIZE_EXPANDED(x) CHAISCRIPT_STRINGIZE(x) @@ -59,16 +69,30 @@ static_assert(_MSC_FULL_VER >= 190024210, "Visual C++ 2015 Update 3 or later req #define CHAISCRIPT_MODULE_EXPORT extern "C" #endif -#if defined(CHAISCRIPT_MSVC) || (defined(__GNUC__) && __GNUC__ >= 5) || defined(CHAISCRIPT_CLANG) -#define CHAISCRIPT_UTF16_UTF32 -#endif - #ifdef _DEBUG #define CHAISCRIPT_DEBUG true #else #define CHAISCRIPT_DEBUG false #endif +// Upper bound on the depth of nested ChaiScript function calls. Hitting it +// causes the dispatcher to throw chaiscript::exception::stack_overflow_error +// instead of letting the native call stack overflow. Defining the macro on +// the command line overrides the default. +// +// MSVC Debug builds emit very large per-frame native stack usage (no inlining, +// /RTC, buffer security checks) and Windows defaults to a 1 MiB thread stack, +// so the same ChaiScript depth that fits comfortably on Linux/macOS or in an +// MSVC Release build overflows the native stack before the depth check fires. +// We pick a tighter default in that configuration to keep the throw reachable. +#ifndef CHAISCRIPT_MAX_CALL_DEPTH +#if defined(CHAISCRIPT_MSVC) && CHAISCRIPT_DEBUG +#define CHAISCRIPT_MAX_CALL_DEPTH 32 +#else +#define CHAISCRIPT_MAX_CALL_DEPTH 256 +#endif +#endif + #include #include #include @@ -82,6 +106,9 @@ namespace chaiscript { constexpr static const char *compiler_name = CHAISCRIPT_COMPILER_NAME; constexpr static const bool debug_build = CHAISCRIPT_DEBUG; + constexpr static const int max_call_depth = CHAISCRIPT_MAX_CALL_DEPTH; + static_assert(max_call_depth > 0, "CHAISCRIPT_MAX_CALL_DEPTH must be a positive integer"); + template inline std::shared_ptr make_shared(Arg &&...arg) { #ifdef CHAISCRIPT_USE_STD_MAKE_SHARED @@ -211,6 +238,13 @@ namespace chaiscript { External_Scripts }; + enum class Library_Options { + No_Stdlib, + No_IO, + No_Prelude, + No_JSON + }; + template struct is_nothrow_forward_constructible : std::bool_constant()})> { }; diff --git a/include/chaiscript/chaiscript_stdlib.hpp b/include/chaiscript/chaiscript_stdlib.hpp index 5cb4fa68b..f2034b6c6 100644 --- a/include/chaiscript/chaiscript_stdlib.hpp +++ b/include/chaiscript/chaiscript_stdlib.hpp @@ -10,6 +10,7 @@ #include #include #include +#include #include #include @@ -18,11 +19,11 @@ #include "dispatchkit/function_call.hpp" -//#include "dispatchkit/dispatchkit.hpp" +// #include "dispatchkit/dispatchkit.hpp" #include "dispatchkit/bootstrap.hpp" #include "dispatchkit/bootstrap_stl.hpp" #include "dispatchkit/operators.hpp" -//#include "dispatchkit/boxed_value.hpp" +// #include "dispatchkit/boxed_value.hpp" #include "dispatchkit/register_function.hpp" #include "language/chaiscript_prelude.hpp" #include "utility/json_wrap.hpp" @@ -38,25 +39,41 @@ namespace chaiscript { class Std_Lib { public: - [[nodiscard]] static ModulePtr library() { + [[nodiscard]] static ModulePtr library(const std::vector &t_opts = {}) { + if (std::find(t_opts.begin(), t_opts.end(), Library_Options::No_Stdlib) != t_opts.end()) { + return std::make_shared(); + } + auto lib = std::make_shared(); + + const bool no_prelude = std::find(t_opts.begin(), t_opts.end(), Library_Options::No_Prelude) != t_opts.end(); + const bool no_json = std::find(t_opts.begin(), t_opts.end(), Library_Options::No_JSON) != t_opts.end(); + bootstrap::Bootstrap::bootstrap(*lib); bootstrap::standard_library::vector_type>("Vector", *lib); bootstrap::standard_library::string_type("string", *lib); + bootstrap::standard_library::string_view_type("string_view", *lib); bootstrap::standard_library::map_type>("Map", *lib); bootstrap::standard_library::pair_type>("Pair", *lib); + // Allow explicit conversion from std::string_view back to std::string, + // e.g. `string(sv)` in ChaiScript. + lib->add(fun([](const std::string_view sv) { return std::string{sv}; }), "string"); + #ifndef CHAISCRIPT_NO_THREADS bootstrap::standard_library::future_type>("future", *lib); - lib->add(chaiscript::fun( - [](const std::function &t_func) { return std::async(std::launch::async, t_func); }), - "async"); + // Note: async() is registered in ChaiScript_Basic::build_eval_system() + // with thread tracking to prevent heap-use-after-free on engine destruction. #endif - json_wrap::library(*lib); + if (!no_json) { + json_wrap::library(*lib); + } - lib->eval(ChaiScript_Prelude::chaiscript_prelude() /*, "standard prelude"*/); + if (!no_prelude) { + lib->eval(ChaiScript_Prelude::chaiscript_prelude() /*, "standard prelude"*/); + } return lib; } diff --git a/include/chaiscript/dispatchkit/bootstrap.hpp b/include/chaiscript/dispatchkit/bootstrap.hpp index ba1de5ade..3c15f0ec1 100644 --- a/include/chaiscript/dispatchkit/bootstrap.hpp +++ b/include/chaiscript/dispatchkit/bootstrap.hpp @@ -26,7 +26,7 @@ namespace chaiscript::bootstrap { throw std::range_error("Array index out of range. Received: " + std::to_string(index) + " expected < " + std::to_string(extent)); } else { - return t[index]; + return *std::next(t, static_cast(index)); } }), "[]"); @@ -37,7 +37,7 @@ namespace chaiscript::bootstrap { throw std::range_error("Array index out of range. Received: " + std::to_string(index) + " expected < " + std::to_string(extent)); } else { - return t[index]; + return *std::next(t, static_cast(index)); } }), "[]"); @@ -115,6 +115,8 @@ namespace chaiscript::bootstrap { m.add(fun(&parse_string), "to_" + name); m.add(fun([](const T t) { return t; }), "to_" + name); + m.add(fun([](const Boxed_Number &bn) { return bn.get_as(); }), "to_" + name); + m.add(fun(&parse_string), name); } /// "clone" function for a shared_ptr type. This is used in the case @@ -266,7 +268,7 @@ namespace chaiscript::bootstrap { public: /// \brief perform all common bootstrap functions for std::string, void and POD types /// \param[in,out] m Module to add bootstrapped functions to - /// \returns passed in Module + /// \param[in] t_no_io If true, skip registering print_string and println_string static void bootstrap(Module &m) { m.add(user_type(), "void"); m.add(user_type(), "bool"); @@ -435,9 +437,6 @@ namespace chaiscript::bootstrap { m.add(fun(&Build_Info::compiler_id), "compiler_id"); m.add(fun(&Build_Info::debug_build), "debug_build"); - m.add(fun(&print), "print_string"); - m.add(fun(&println), "println_string"); - m.add(dispatch::make_dynamic_proxy_function(&bind_function), "bind"); m.add(fun(&shared_ptr_unconst_clone), "clone"); diff --git a/include/chaiscript/dispatchkit/bootstrap_stl.hpp b/include/chaiscript/dispatchkit/bootstrap_stl.hpp index f56304d7b..6ac2939cb 100644 --- a/include/chaiscript/dispatchkit/bootstrap_stl.hpp +++ b/include/chaiscript/dispatchkit/bootstrap_stl.hpp @@ -18,6 +18,7 @@ #include #include #include +#include #include #include @@ -84,6 +85,15 @@ namespace chaiscript::bootstrap::standard_library { return t_target.count(t_key); } + template + Boxed_Value find(const T &t_target, const typename T::key_type &t_key) { + const auto itr = t_target.find(t_key); + if (itr != t_target.end()) { + return Boxed_Value(itr->second); + } + return Boxed_Value(); + } + template void insert(T &t_target, const T &t_other) { t_target.insert(t_other.begin(), t_other.end()); @@ -170,18 +180,22 @@ namespace chaiscript::bootstrap::standard_library { /// http://www.sgi.com/tech/stl/Assignable.html template void assignable_type(const std::string &type, Module &m) { - copy_constructor(type, m); - operators::assign(m); + if constexpr (std::is_copy_constructible_v) { + copy_constructor(type, m); + operators::assign(m); + } } /// Add container resize concept to the given ContainerType /// http://www.cplusplus.com/reference/stl/ template void resizable_type(const std::string & /*type*/, Module &m) { - m.add(fun([](ContainerType *a, typename ContainerType::size_type n, const typename ContainerType::value_type &val) { - return a->resize(n, val); - }), - "resize"); + if constexpr (std::is_copy_constructible_v) { + m.add(fun([](ContainerType *a, typename ContainerType::size_type n, const typename ContainerType::value_type &val) { + return a->resize(n, val); + }), + "resize"); + } m.add(fun([](ContainerType *a, typename ContainerType::size_type n) { return a->resize(n); }), "resize"); } @@ -213,13 +227,15 @@ namespace chaiscript::bootstrap::standard_library { /// http://www.sgi.com/tech/stl/Sequence.html template void sequence_type(const std::string & /*type*/, Module &m) { - m.add(fun(&detail::insert_at), []() -> std::string { - if (typeid(typename ContainerType::value_type) == typeid(Boxed_Value)) { - return "insert_ref_at"; - } else { - return "insert_at"; - } - }()); + if constexpr (std::is_copy_constructible_v) { + m.add(fun(&detail::insert_at), []() -> std::string { + if (typeid(typename ContainerType::value_type) == typeid(Boxed_Value)) { + return "insert_ref_at"; + } else { + return "insert_at"; + } + }()); + } m.add(fun(&detail::erase_at), "erase_at"); } @@ -245,27 +261,29 @@ namespace chaiscript::bootstrap::standard_library { }), "back"); - using push_back = void (ContainerType::*)(const typename ContainerType::value_type &); - m.add(fun(static_cast(&ContainerType::push_back)), [&]() -> std::string { - if (typeid(typename ContainerType::value_type) == typeid(Boxed_Value)) { - m.eval("# Pushes the second value onto the container while making a clone of the value\n" - "def push_back(" - + type - + " container, x)\n" - "{ \n" - " if (x.is_var_return_value()) {\n" - " x.reset_var_return_value() \n" - " container.push_back_ref(x) \n" - " } else { \n" - " container.push_back_ref(clone(x)); \n" - " }\n" - "} \n"); - - return "push_back_ref"; - } else { - return "push_back"; - } - }()); + if constexpr (std::is_copy_constructible_v) { + using push_back = void (ContainerType::*)(const typename ContainerType::value_type &); + m.add(fun(static_cast(&ContainerType::push_back)), [&]() -> std::string { + if (typeid(typename ContainerType::value_type) == typeid(Boxed_Value)) { + m.eval("# Pushes the second value onto the container while making a clone of the value\n" + "def push_back(" + + type + + " container, x)\n" + "{ \n" + " if (x.is_var_return_value()) {\n" + " x.reset_var_return_value() \n" + " container.push_back_ref(x) \n" + " } else { \n" + " container.push_back_ref(clone(x)); \n" + " }\n" + "} \n"); + + return "push_back_ref"; + } else { + return "push_back"; + } + }()); + } m.add(fun(&ContainerType::pop_back), "pop_back"); } @@ -295,25 +313,27 @@ namespace chaiscript::bootstrap::standard_library { }), "front"); - m.add(fun(static_cast(&ContainerType::push_front)), [&]() -> std::string { - if (typeid(typename ContainerType::value_type) == typeid(Boxed_Value)) { - m.eval("# Pushes the second value onto the front of container while making a clone of the value\n" - "def push_front(" - + type - + " container, x)\n" - "{ \n" - " if (x.is_var_return_value()) {\n" - " x.reset_var_return_value() \n" - " container.push_front_ref(x) \n" - " } else { \n" - " container.push_front_ref(clone(x)); \n" - " }\n" - "} \n"); - return "push_front_ref"; - } else { - return "push_front"; - } - }()); + if constexpr (std::is_copy_constructible_v) { + m.add(fun(static_cast(&ContainerType::push_front)), [&]() -> std::string { + if (typeid(typename ContainerType::value_type) == typeid(Boxed_Value)) { + m.eval("# Pushes the second value onto the front of container while making a clone of the value\n" + "def push_front(" + + type + + " container, x)\n" + "{ \n" + " if (x.is_var_return_value()) {\n" + " x.reset_var_return_value() \n" + " container.push_front_ref(x) \n" + " } else { \n" + " container.push_front_ref(clone(x)); \n" + " }\n" + "} \n"); + return "push_front_ref"; + } else { + return "push_front"; + } + }()); + } m.add(fun(static_cast(&ContainerType::pop_front)), "pop_front"); } @@ -344,6 +364,7 @@ namespace chaiscript::bootstrap::standard_library { template void unique_associative_container_type(const std::string & /*type*/, Module &m) { m.add(fun(detail::count), "count"); + m.add(fun(detail::find), "find"); using erase_ptr = size_t (ContainerType::*)(const typename ContainerType::key_type &); @@ -518,6 +539,61 @@ namespace chaiscript::bootstrap::standard_library { m.add(fun([](const String *s, size_t pos, size_t len) { return s->substr(pos, len); }), "substr"); } + /// Add a String_View type (e.g. std::string_view), with conversions to and from + /// the matching owning String type (e.g. std::string). + /// + /// Mirrors the search/substring surface of string_type so that scripts can + /// traverse a buffer through StringView without allocating. Sharing method + /// names with the owning String type is safe: dispatch deprioritizes any + /// candidate whose first/receiver argument requires a type conversion (see + /// dispatch() in proxy_functions.hpp), so myString.substr(1, 2) still + /// resolves to String::substr while mySV.substr(1, 2) resolves to + /// StringView::substr (returning a StringView). + /// + /// \note A String_View is a non-owning reference. Constructing one from a + /// temporary owning String yields a dangling reference once that temporary + /// is destroyed; the same lifetime caveats as in C++ apply here. + template + void string_view_type(const std::string &type, Module &m) { + m.add(user_type(), type); + m.add(constructor(), type); + m.add(constructor(), type); + m.add(fun([](const String &s) { return StringView{s}; }), type); + + opers_comparison(m); + + m.add(fun([](const StringView *s) { return s->size(); }), "size"); + m.add(fun([](const StringView *s) { return s->length(); }), "length"); + m.add(fun([](const StringView *s) { return s->empty(); }), "empty"); + m.add(fun([](const StringView *s) { return s->data(); }), "data"); + + // Random-access via [] / at, returning const_reference; StringView is read-only. + m.add(fun([](const StringView &sv, int index) -> typename StringView::const_reference { + return sv.at(static_cast(index)); + }), + "[]"); + + m.add(fun([](const StringView *s, const StringView &f, size_t pos) { return s->find(f, pos); }), "find"); + m.add(fun([](const StringView *s, const StringView &f, size_t pos) { return s->rfind(f, pos); }), "rfind"); + m.add(fun([](const StringView *s, const StringView &f, size_t pos) { return s->find_first_of(f, pos); }), "find_first_of"); + m.add(fun([](const StringView *s, const StringView &f, size_t pos) { return s->find_last_of(f, pos); }), "find_last_of"); + m.add(fun([](const StringView *s, const StringView &f, size_t pos) { return s->find_last_not_of(f, pos); }), "find_last_not_of"); + m.add(fun([](const StringView *s, const StringView &f, size_t pos) { return s->find_first_not_of(f, pos); }), "find_first_not_of"); + + m.add(fun([](const StringView *s, const StringView &f) { return s->starts_with(f); }), "starts_with"); + m.add(fun([](const StringView *s, const StringView &f) { return s->ends_with(f); }), "ends_with"); + + m.add(fun([](const StringView *s, size_t pos, size_t len) { return s->substr(pos, len); }), "substr"); + + // Built-in implicit conversion from owning String to non-owning StringView. + m.add(type_conversion()); + + // Explicit conversion from StringView back to owning String, registered as + // to_string(sv); the call site can also register it under the owning type's + // name (e.g. string(sv)) when desired. + m.add(fun([](const StringView sv) { return String{sv}; }), "to_string"); + } + /// Add a MapType container /// http://www.sgi.com/tech/stl/Map.html template diff --git a/include/chaiscript/dispatchkit/boxed_number.hpp b/include/chaiscript/dispatchkit/boxed_number.hpp index 816fe5a37..05faa4af7 100644 --- a/include/chaiscript/dispatchkit/boxed_number.hpp +++ b/include/chaiscript/dispatchkit/boxed_number.hpp @@ -41,7 +41,7 @@ namespace chaiscript { // this is OK, so we're disabling size/and sign type warnings #ifdef CHAISCRIPT_MSVC #pragma warning(push) -#pragma warning(disable : 4244 4018 4389 4146 4365 4267 4242) +#pragma warning(disable : 4244 4018 4389 4146 4365 4267 4242 4702) // 4702 is for broken unreachable warning #endif #ifdef __GNUC__ @@ -220,7 +220,6 @@ namespace chaiscript { if constexpr (!std::is_floating_point::value && !std::is_floating_point::value) { switch (t_oper) { case Operators::Opers::assign_bitwise_and: - check_divide_by_zero(c_rhs); *t_lhs &= c_rhs; return t_bv; case Operators::Opers::assign_bitwise_or: @@ -233,6 +232,7 @@ namespace chaiscript { *t_lhs >>= c_rhs; return t_bv; case Operators::Opers::assign_remainder: + check_divide_by_zero(c_rhs); *t_lhs %= c_rhs; return t_bv; case Operators::Opers::assign_bitwise_xor: diff --git a/include/chaiscript/dispatchkit/boxed_value.hpp b/include/chaiscript/dispatchkit/boxed_value.hpp index 06941b2f1..89a0ed2fe 100644 --- a/include/chaiscript/dispatchkit/boxed_value.hpp +++ b/include/chaiscript/dispatchkit/boxed_value.hpp @@ -31,7 +31,7 @@ namespace chaiscript { /// structure which holds the internal state of a Boxed_Value /// \todo Get rid of Any and merge it with this, reducing an allocation in the process struct Data { - Data(const Type_Info &ti, chaiscript::detail::Any to, bool is_ref, const void *t_void_ptr, bool t_return_value) + Data(const Type_Info &ti, chaiscript::detail::Any to, bool is_ref, const void *t_void_ptr, bool t_return_value) noexcept : m_type_info(ti) , m_obj(std::move(to)) , m_data_ptr(ti.is_const() ? nullptr : const_cast(t_void_ptr)) @@ -157,6 +157,12 @@ namespace chaiscript { bool is_const() const noexcept { return m_data->m_type_info.is_const(); } + /// Mark this Boxed_Value as const (used for script-level const declarations) + void make_const() noexcept { + m_data->m_type_info.make_const(); + m_data->m_data_ptr = nullptr; + } + bool is_type(const Type_Info &ti) const noexcept { return m_data->m_type_info.bare_equal(ti); } template diff --git a/include/chaiscript/dispatchkit/dispatchkit.hpp b/include/chaiscript/dispatchkit/dispatchkit.hpp index 06e8992ac..f38147c0b 100644 --- a/include/chaiscript/dispatchkit/dispatchkit.hpp +++ b/include/chaiscript/dispatchkit/dispatchkit.hpp @@ -19,6 +19,7 @@ #include #include #include +#include #include #include #include @@ -33,7 +34,6 @@ #include "dynamic_object.hpp" #include "proxy_constructors.hpp" #include "proxy_functions.hpp" -#include "short_alloc.hpp" #include "type_conversions.hpp" #include "type_info.hpp" @@ -121,6 +121,19 @@ namespace chaiscript { global_non_const(const global_non_const &) = default; ~global_non_const() noexcept override = default; }; + + /// Exception thrown when the ChaiScript call stack exceeds + /// chaiscript::max_call_depth, signalling runaway recursion before the + /// native stack can overflow. + class stack_overflow_error : public std::runtime_error { + public: + stack_overflow_error() noexcept + : std::runtime_error("Maximum call stack depth exceeded") { + } + + stack_overflow_error(const stack_overflow_error &) = default; + ~stack_overflow_error() noexcept override = default; + }; } // namespace exception /// \brief Holds a collection of ChaiScript settings which can be applied to the ChaiScript runtime. @@ -317,9 +330,6 @@ namespace chaiscript { namespace detail { struct Stack_Holder { - // template - // using SmallVector = std::vector>; - template using SmallVector = std::vector; @@ -370,6 +380,28 @@ namespace chaiscript { , m_parser(parser) { } + ~Dispatch_Engine() { + join_async_threads(); + } + + Dispatch_Engine(const Dispatch_Engine &) = delete; + Dispatch_Engine &operator=(const Dispatch_Engine &) = delete; + Dispatch_Engine(Dispatch_Engine &&) = delete; + Dispatch_Engine &operator=(Dispatch_Engine &&) = delete; + +#ifndef CHAISCRIPT_NO_THREADS + /// Track an async thread so it can be joined during destruction + void track_async_thread(std::thread t_thread) { + chaiscript::detail::threading::unique_lock l(m_async_mutex); + // Clean up already-finished threads to avoid unbounded growth + m_async_threads.erase( + std::remove_if(m_async_threads.begin(), m_async_threads.end(), + [](std::thread &t) { return !t.joinable(); }), + m_async_threads.end()); + m_async_threads.push_back(std::move(t_thread)); + } +#endif + /// \brief casts an object while applying any Dynamic_Conversion available template decltype(auto) boxed_cast(const Boxed_Value &bv) const { @@ -767,13 +799,13 @@ namespace chaiscript { t_loc = uint_fast32_t(funs.first); } - const auto do_attribute_call = [this](int l_num_params, + const auto do_attribute_call = [this](std::size_t l_num_params, Function_Params l_params, const std::vector &l_funs, const Type_Conversions_State &l_conversions) -> Boxed_Value { - Function_Params attr_params(l_params.begin(), l_params.begin() + l_num_params); + Function_Params attr_params(l_params.first(l_num_params)); Boxed_Value bv = dispatch::dispatch(l_funs, attr_params, l_conversions); - if (l_num_params < int(l_params.size()) || bv.get_type_info().bare_equal(user_type())) { + if (l_num_params < l_params.size() || bv.get_type_info().bare_equal(user_type())) { struct This_Foist { This_Foist(Dispatch_Engine &e, const Boxed_Value &t_bv) : m_e(e) { @@ -791,16 +823,16 @@ namespace chaiscript { try { auto func = boxed_cast(bv); try { - return (*func)({l_params.begin() + l_num_params, l_params.end()}, l_conversions); + return (*func)(l_params.subspan(l_num_params), l_conversions); } catch (const chaiscript::exception::bad_boxed_cast &) { } catch (const chaiscript::exception::arity_error &) { } catch (const chaiscript::exception::guard_error &) { } - throw chaiscript::exception::dispatch_error({l_params.begin() + l_num_params, l_params.end()}, + throw chaiscript::exception::dispatch_error(l_params.subspan(l_num_params), std::vector{boxed_cast(bv)}); } catch (const chaiscript::exception::bad_boxed_cast &) { // unable to convert bv into a Proxy_Function_Base - throw chaiscript::exception::dispatch_error({l_params.begin() + l_num_params, l_params.end()}, + throw chaiscript::exception::dispatch_error(l_params.subspan(l_num_params), std::vector(l_funs.begin(), l_funs.end())); } } else { @@ -850,7 +882,7 @@ namespace chaiscript { if (!functions.empty()) { try { if (is_no_param) { - auto tmp_params = params.to_vector(); + auto tmp_params = std::vector(params.begin(), params.end()); tmp_params.insert(tmp_params.begin() + 1, var(t_name)); return do_attribute_call(2, Function_Params(tmp_params), functions, t_conversions); } else { @@ -925,7 +957,7 @@ namespace chaiscript { const auto &f = this->boxed_cast(params[0]); const Type_Conversions_State convs(m_conversions, m_conversions.conversion_saves()); - return const_var(f->call_match(Function_Params(params.begin() + 1, params.end()), convs)); + return const_var(f->call_match(Function_Params(params.subspan(1)), convs)); } /// Dump all system info to stdout @@ -991,6 +1023,10 @@ namespace chaiscript { void save_function_params(const Function_Params &t_params) { save_function_params(*m_stack_holder, t_params); } void new_function_call(Stack_Holder &t_s, Type_Conversions::Conversion_Saves &t_saves) { + if (t_s.call_depth >= max_call_depth) { + throw chaiscript::exception::stack_overflow_error{}; + } + if (t_s.call_depth == 0) { m_conversions.enable_conversion_saves(t_saves, true); } @@ -1060,6 +1096,27 @@ namespace chaiscript { return true; } + // Sort more-derived Dynamic_Object types before base types so that + // overridden methods in derived classes are tried first during dispatch + const auto &lhs_dotn = lhs->dynamic_object_type_name(); + const auto &rhs_dotn = rhs->dynamic_object_type_name(); + if (lhs_dotn != rhs_dotn) { + if (!lhs_dotn.empty() && !rhs_dotn.empty()) { + if (dispatch::Dynamic_Object::type_matches(lhs_dotn, rhs_dotn)) { + return true; // lhs is derived from rhs, so lhs is more specific + } + if (dispatch::Dynamic_Object::type_matches(rhs_dotn, lhs_dotn)) { + return false; // rhs is derived from lhs, so rhs is more specific + } + } + // Impose a total order on type names to maintain strict-weak ordering: + // non-empty names sort before empty names, then lexicographically + if (lhs_dotn.empty() != rhs_dotn.empty()) { + return !lhs_dotn.empty(); + } + return lhs_dotn < rhs_dotn; + } + const auto &lhsparamtypes = lhs->get_param_types(); const auto &rhsparamtypes = rhs->get_param_types(); @@ -1107,7 +1164,9 @@ namespace chaiscript { return lt < rt; } - return false; + // When all overlapping parameters match, order by arity to maintain + // strict-weak ordering (transitivity of equivalence) + return lhssize < rhssize; } /// Implementation detail for adding a function. @@ -1152,6 +1211,21 @@ namespace chaiscript { get_function_objects_int().insert_or_assign(t_name, std::move(new_func)); } + void join_async_threads() { +#ifndef CHAISCRIPT_NO_THREADS + std::vector threads; + { + chaiscript::detail::threading::unique_lock l(m_async_mutex); + threads = std::move(m_async_threads); + } + for (auto &t : threads) { + if (t.joinable()) { + t.join(); + } + } +#endif + } + mutable chaiscript::detail::threading::shared_mutex m_mutex; Type_Conversions m_conversions; @@ -1161,6 +1235,11 @@ namespace chaiscript { mutable std::atomic_uint_fast32_t m_method_missing_loc = {0}; State m_state; + +#ifndef CHAISCRIPT_NO_THREADS + mutable chaiscript::detail::threading::shared_mutex m_async_mutex; + std::vector m_async_threads; +#endif }; class Dispatch_State { diff --git a/include/chaiscript/dispatchkit/dynamic_object.hpp b/include/chaiscript/dispatchkit/dynamic_object.hpp index b242a2077..cfcc5f179 100644 --- a/include/chaiscript/dispatchkit/dynamic_object.hpp +++ b/include/chaiscript/dispatchkit/dynamic_object.hpp @@ -44,6 +44,27 @@ namespace chaiscript { Dynamic_Object() = default; + /// Register that derived_name inherits from base_name + static void register_inheritance(const std::string &derived_name, const std::string &base_name) { + inheritance_map()[derived_name] = base_name; + } + + /// Check if type_name is, or inherits from, base_name + static bool type_matches(const std::string &type_name, const std::string &base_name) noexcept { + if (type_name == base_name) { + return true; + } + const auto &m = inheritance_map(); + auto it = m.find(type_name); + while (it != m.end()) { + if (it->second == base_name) { + return true; + } + it = m.find(it->second); + } + return false; + } + bool is_explicit() const noexcept { return m_option_explicit; } void set_explicit(const bool t_explicit) noexcept { m_option_explicit = t_explicit; } @@ -87,6 +108,11 @@ namespace chaiscript { std::map get_attrs() const { return m_attrs; } private: + static std::map &inheritance_map() { + static std::map s_map; + return s_map; + } + const std::string m_type_name = ""; bool m_option_explicit = false; diff --git a/include/chaiscript/dispatchkit/dynamic_object_detail.hpp b/include/chaiscript/dispatchkit/dynamic_object_detail.hpp index 07477c1ed..824c5355e 100644 --- a/include/chaiscript/dispatchkit/dynamic_object_detail.hpp +++ b/include/chaiscript/dispatchkit/dynamic_object_detail.hpp @@ -72,6 +72,8 @@ namespace chaiscript { bool is_attribute_function() const noexcept override { return m_is_attribute; } + const std::string &dynamic_object_type_name() const noexcept override { return m_type_name; } + bool call_match(const chaiscript::Function_Params &vals, const Type_Conversions_State &t_conversions) const noexcept override { if (dynamic_object_typename_match(vals, m_type_name, m_ti, t_conversions)) { return m_func->call_match(vals, t_conversions); @@ -101,7 +103,9 @@ namespace chaiscript { assert(types.size() > 1); // assert(types[1].bare_equal(user_type())); - types[1] = t_objectti; + // When the object type_info is undefined (ChaiScript-defined class), use + // Dynamic_Object so that dispatch priority scoring works correctly. + types[1] = t_objectti.is_undef() ? user_type() : t_objectti; return types; } @@ -112,7 +116,7 @@ namespace chaiscript { if (bv.get_type_info().bare_equal(m_doti)) { try { const Dynamic_Object &d = boxed_cast(bv, &t_conversions); - return name == "Dynamic_Object" || d.get_type_name() == name; + return name == "Dynamic_Object" || Dynamic_Object::type_matches(d.get_type_name(), name); } catch (const std::bad_cast &) { return false; } @@ -144,11 +148,11 @@ namespace chaiscript { }; /** - * A Proxy_Function implementation designed for creating a new - * Dynamic_Object - * that is automatically guarded based on the first param based on the - * param's type name - */ + * A Proxy_Function implementation designed for creating a new + * Dynamic_Object + * that is automatically guarded based on the first param based on the + * param's type name + */ class Dynamic_Object_Constructor final : public Proxy_Function_Base { public: Dynamic_Object_Constructor(std::string t_type_name, const Proxy_Function &t_func) diff --git a/include/chaiscript/dispatchkit/function_call_detail.hpp b/include/chaiscript/dispatchkit/function_call_detail.hpp index f67a5a005..8bc2f366d 100644 --- a/include/chaiscript/dispatchkit/function_call_detail.hpp +++ b/include/chaiscript/dispatchkit/function_call_detail.hpp @@ -55,10 +55,23 @@ namespace chaiscript::dispatch::detail { } } + template + struct is_shared_ptr : std::false_type {}; + + template + struct is_shared_ptr> : std::true_type {}; + template static Boxed_Value box(Q &&q) { + using bare_p = std::remove_cv_t>; if constexpr (std::is_same_v>) { return std::forward(q); + } else if constexpr (is_shared_ptr::value) { + // Pass shared_ptr arguments through by value rather than wrapping a + // reference. Boxed_Value's shared_ptr-aware overloads then record the + // bare type as the pointee, allowing dispatch to a C++ function that + // takes the same shared_ptr on the other side. + return Boxed_Value(bare_p(std::forward(q))); } else if constexpr (std::is_reference_v

) { return Boxed_Value(std::ref(std::forward(q))); } else { diff --git a/include/chaiscript/dispatchkit/function_params.hpp b/include/chaiscript/dispatchkit/function_params.hpp index 9a77c5ee5..a1e5aacfe 100644 --- a/include/chaiscript/dispatchkit/function_params.hpp +++ b/include/chaiscript/dispatchkit/function_params.hpp @@ -12,55 +12,11 @@ #include "boxed_value.hpp" -namespace chaiscript { - class Function_Params { - public: - constexpr Function_Params(const Boxed_Value *const t_begin, const Boxed_Value *const t_end) - : m_begin(t_begin) - , m_end(t_end) { - } - - explicit Function_Params(const Boxed_Value &bv) - : m_begin(&bv) - , m_end(m_begin + 1) { - } - - explicit Function_Params(const std::vector &vec) - : m_begin(vec.empty() ? nullptr : &vec.front()) - , m_end(vec.empty() ? nullptr : &vec.front() + vec.size()) { - } - - template - constexpr explicit Function_Params(const std::array &a) - : m_begin(&a.front()) - , m_end(&a.front() + Size) { - } - - [[nodiscard]] constexpr const Boxed_Value &operator[](const std::size_t t_i) const noexcept { return m_begin[t_i]; } - - [[nodiscard]] constexpr const Boxed_Value *begin() const noexcept { return m_begin; } +#include - [[nodiscard]] constexpr const Boxed_Value &front() const noexcept { return *m_begin; } - - [[nodiscard]] constexpr const Boxed_Value *end() const noexcept { return m_end; } - - [[nodiscard]] constexpr std::size_t size() const noexcept { return std::size_t(m_end - m_begin); } - - [[nodiscard]] std::vector to_vector() const { return std::vector{m_begin, m_end}; } - - [[nodiscard]] constexpr bool empty() const noexcept { return m_begin == m_end; } - - private: - const Boxed_Value *m_begin = nullptr; - const Boxed_Value *m_end = nullptr; - }; +namespace chaiscript { - // Constructor specialization for array of size 0 - template<> - constexpr Function_Params::Function_Params(const std::array & /* a */) - : m_begin(nullptr) - , m_end(nullptr) { - } + using Function_Params = std::span; } // namespace chaiscript diff --git a/include/chaiscript/dispatchkit/function_signature.hpp b/include/chaiscript/dispatchkit/function_signature.hpp index 174f8576f..a87e60d06 100644 --- a/include/chaiscript/dispatchkit/function_signature.hpp +++ b/include/chaiscript/dispatchkit/function_signature.hpp @@ -64,7 +64,7 @@ namespace chaiscript::dispatch::detail { Function_Signature(Ret (Class::*f)(Param...) volatile &) -> Function_Signature, false, true>; template - Function_Signature(Ret (Class::*f)(Param...) volatile &noexcept) + Function_Signature(Ret (Class::*f)(Param...) volatile & noexcept) -> Function_Signature, true, true>; template @@ -72,20 +72,20 @@ namespace chaiscript::dispatch::detail { -> Function_Signature, false, true>; template - Function_Signature(Ret (Class::*f)(Param...) volatile const &noexcept) + Function_Signature(Ret (Class::*f)(Param...) volatile const & noexcept) -> Function_Signature, true, true>; template Function_Signature(Ret (Class::*f)(Param...) &) -> Function_Signature, false, true>; template - Function_Signature(Ret (Class::*f)(Param...) &noexcept) -> Function_Signature, true, true>; + Function_Signature(Ret (Class::*f)(Param...) & noexcept) -> Function_Signature, true, true>; template Function_Signature(Ret (Class::*f)(Param...) const &) -> Function_Signature, false, true>; template - Function_Signature(Ret (Class::*f)(Param...) const &noexcept) -> Function_Signature, true, true>; + Function_Signature(Ret (Class::*f)(Param...) const & noexcept) -> Function_Signature, true, true>; // && reference specifier @@ -93,7 +93,7 @@ namespace chaiscript::dispatch::detail { Function_Signature(Ret (Class::*f)(Param...) volatile &&) -> Function_Signature, false, true>; template - Function_Signature(Ret (Class::*f)(Param...) volatile &&noexcept) + Function_Signature(Ret (Class::*f)(Param...) volatile && noexcept) -> Function_Signature, true, true>; template @@ -101,20 +101,20 @@ namespace chaiscript::dispatch::detail { -> Function_Signature, false, true>; template - Function_Signature(Ret (Class::*f)(Param...) volatile const &&noexcept) + Function_Signature(Ret (Class::*f)(Param...) volatile const && noexcept) -> Function_Signature, true, true>; template Function_Signature(Ret (Class::*f)(Param...) &&) -> Function_Signature, false, true>; template - Function_Signature(Ret (Class::*f)(Param...) &&noexcept) -> Function_Signature, true, true>; + Function_Signature(Ret (Class::*f)(Param...) && noexcept) -> Function_Signature, true, true>; template Function_Signature(Ret (Class::*f)(Param...) const &&) -> Function_Signature, false, true>; template - Function_Signature(Ret (Class::*f)(Param...) const &&noexcept) + Function_Signature(Ret (Class::*f)(Param...) const && noexcept) -> Function_Signature, true, true>; template diff --git a/include/chaiscript/dispatchkit/handle_return.hpp b/include/chaiscript/dispatchkit/handle_return.hpp index e0a3b1326..2bc296d36 100644 --- a/include/chaiscript/dispatchkit/handle_return.hpp +++ b/include/chaiscript/dispatchkit/handle_return.hpp @@ -126,7 +126,7 @@ namespace chaiscript { struct Handle_Return_Ref { template static Boxed_Value handle(T &&r) { - return Boxed_Value(std::cref(r), true); + return Boxed_Value(std::cref(r), false); } }; @@ -170,8 +170,8 @@ namespace chaiscript { }; /** - * Used internally for handling a return value from a Proxy_Function call - */ + * Used internally for handling a return value from a Proxy_Function call + */ template<> struct Handle_Return { static Boxed_Value handle(const Boxed_Number &r) noexcept { return r.bv; } @@ -182,8 +182,8 @@ namespace chaiscript { }; /** - * Used internally for handling a return value from a Proxy_Function call - */ + * Used internally for handling a return value from a Proxy_Function call + */ template<> struct Handle_Return { static Boxed_Value handle() { return void_var(); } diff --git a/include/chaiscript/dispatchkit/operators.hpp b/include/chaiscript/dispatchkit/operators.hpp index 2545bfceb..d32ee9d6f 100644 --- a/include/chaiscript/dispatchkit/operators.hpp +++ b/include/chaiscript/dispatchkit/operators.hpp @@ -15,168 +15,168 @@ #include "register_function.hpp" namespace chaiscript::bootstrap::operators { - template - void assign(Module &m) { + template + void assign(ModuleType &m) { m.add(chaiscript::fun([](T &lhs, const T &rhs) -> T & { return lhs = rhs; }), "="); } - template - void assign_bitwise_and(Module &m) { + template + void assign_bitwise_and(ModuleType &m) { m.add(chaiscript::fun([](T &lhs, const T &rhs) -> T & { return lhs &= rhs; }), "&="); } - template - void assign_xor(Module &m) { + template + void assign_xor(ModuleType &m) { m.add(chaiscript::fun([](T &lhs, const T &rhs) -> T & { return lhs ^= rhs; }), "^="); } - template - void assign_bitwise_or(Module &m) { + template + void assign_bitwise_or(ModuleType &m) { m.add(chaiscript::fun([](T &lhs, const T &rhs) -> T & { return lhs |= rhs; }), "|="); } - template - void assign_difference(Module &m) { + template + void assign_difference(ModuleType &m) { m.add(chaiscript::fun([](T &lhs, const T &rhs) -> T & { return lhs -= rhs; }), "-="); } - template - void assign_left_shift(Module &m) { + template + void assign_left_shift(ModuleType &m) { m.add(chaiscript::fun([](T &lhs, const T &rhs) -> T & { return lhs <<= rhs; }), "<<="); } - template - void assign_product(Module &m) { + template + void assign_product(ModuleType &m) { m.add(chaiscript::fun([](T &lhs, const T &rhs) -> T & { return lhs <<= rhs; }), "*="); } - template - void assign_quotient(Module &m) { + template + void assign_quotient(ModuleType &m) { m.add(chaiscript::fun([](T &lhs, const T &rhs) -> T & { return lhs /= rhs; }), "/="); } - template - void assign_remainder(Module &m) { + template + void assign_remainder(ModuleType &m) { m.add(chaiscript::fun([](T &lhs, const T &rhs) -> T & { return lhs %= rhs; }), "%="); } - template - void assign_right_shift(Module &m) { + template + void assign_right_shift(ModuleType &m) { m.add(chaiscript::fun([](T &lhs, const T &rhs) -> T & { return lhs >>= rhs; }), ">>="); } - template - void assign_sum(Module &m) { + template + void assign_sum(ModuleType &m) { m.add(chaiscript::fun([](T &lhs, const T &rhs) -> T & { return lhs += rhs; }), "+="); } - template - void prefix_decrement(Module &m) { + template + void prefix_decrement(ModuleType &m) { m.add(chaiscript::fun([](T &lhs) -> T & { return --lhs; }), "--"); } - template - void prefix_increment(Module &m) { + template + void prefix_increment(ModuleType &m) { m.add(chaiscript::fun([](T &lhs) -> T & { return ++lhs; }), "++"); } - template - void equal(Module &m) { + template + void equal(ModuleType &m) { m.add(chaiscript::fun([](const T &lhs, const T &rhs) { return lhs == rhs; }), "=="); } - template - void greater_than(Module &m) { + template + void greater_than(ModuleType &m) { m.add(chaiscript::fun([](const T &lhs, const T &rhs) { return lhs > rhs; }), ">"); } - template - void greater_than_equal(Module &m) { + template + void greater_than_equal(ModuleType &m) { m.add(chaiscript::fun([](const T &lhs, const T &rhs) { return lhs >= rhs; }), ">="); } - template - void less_than(Module &m) { + template + void less_than(ModuleType &m) { m.add(chaiscript::fun([](const T &lhs, const T &rhs) { return lhs < rhs; }), "<"); } - template - void less_than_equal(Module &m) { + template + void less_than_equal(ModuleType &m) { m.add(chaiscript::fun([](const T &lhs, const T &rhs) { return lhs <= rhs; }), "<="); } - template - void logical_compliment(Module &m) { + template + void logical_compliment(ModuleType &m) { m.add(chaiscript::fun([](const T &lhs) { return !lhs; }), "!"); } - template - void not_equal(Module &m) { + template + void not_equal(ModuleType &m) { m.add(chaiscript::fun([](const T &lhs, const T &rhs) { return lhs != rhs; }), "!="); } - template - void addition(Module &m) { + template + void addition(ModuleType &m) { m.add(chaiscript::fun([](const T &lhs, const T &rhs) { return lhs + rhs; }), "+"); } - template - void unary_plus(Module &m) { + template + void unary_plus(ModuleType &m) { m.add(chaiscript::fun([](const T &lhs) { return +lhs; }), "+"); } - template - void subtraction(Module &m) { + template + void subtraction(ModuleType &m) { m.add(chaiscript::fun([](const T &lhs, const T &rhs) { return lhs - rhs; }), "-"); } - template - void unary_minus(Module &m) { + template + void unary_minus(ModuleType &m) { m.add(chaiscript::fun([](const T &lhs) { return -lhs; }), "-"); } - template - void bitwise_and(Module &m) { + template + void bitwise_and(ModuleType &m) { m.add(chaiscript::fun([](const T &lhs, const T &rhs) { return lhs & rhs; }), "&"); } - template - void bitwise_compliment(Module &m) { + template + void bitwise_compliment(ModuleType &m) { m.add(chaiscript::fun([](const T &lhs) { return ~lhs; }), "~"); } - template - void bitwise_xor(Module &m) { + template + void bitwise_xor(ModuleType &m) { m.add(chaiscript::fun([](const T &lhs, const T &rhs) { return lhs ^ rhs; }), "^"); } - template - void bitwise_or(Module &m) { + template + void bitwise_or(ModuleType &m) { m.add(chaiscript::fun([](const T &lhs, const T &rhs) { return lhs | rhs; }), "|"); } - template - void division(Module &m) { + template + void division(ModuleType &m) { m.add(chaiscript::fun([](const T &lhs, const T &rhs) { return lhs / rhs; }), "/"); } - template - void left_shift(Module &m) { + template + void left_shift(ModuleType &m) { m.add(chaiscript::fun([](const T &lhs, const T &rhs) { return lhs << rhs; }), "<<"); } - template - void multiplication(Module &m) { + template + void multiplication(ModuleType &m) { m.add(chaiscript::fun([](const T &lhs, const T &rhs) { return lhs * rhs; }), "*"); } - template - void remainder(Module &m) { + template + void remainder(ModuleType &m) { m.add(chaiscript::fun([](const T &lhs, const T &rhs) { return lhs % rhs; }), "%"); } - template - void right_shift(Module &m) { + template + void right_shift(ModuleType &m) { m.add(chaiscript::fun([](const T &lhs, const T &rhs) { return lhs >> rhs; }), ">>"); } } // namespace chaiscript::bootstrap::operators diff --git a/include/chaiscript/dispatchkit/proxy_functions.hpp b/include/chaiscript/dispatchkit/proxy_functions.hpp index a481e2c64..e0e101637 100644 --- a/include/chaiscript/dispatchkit/proxy_functions.hpp +++ b/include/chaiscript/dispatchkit/proxy_functions.hpp @@ -65,7 +65,7 @@ namespace chaiscript { bool operator==(const Param_Types &t_rhs) const noexcept { return m_types == t_rhs.m_types; } std::vector convert(Function_Params t_params, const Type_Conversions_State &t_conversions) const { - auto vals = t_params.to_vector(); + auto vals = std::vector{t_params.begin(), t_params.end()}; const auto dynamic_object_type_info = user_type(); for (size_t i = 0; i < vals.size(); ++i) { const auto &name = m_types[i].first; @@ -120,7 +120,7 @@ namespace chaiscript { if (bv.get_type_info().bare_equal(dynamic_object_type_info)) { try { const Dynamic_Object &d = boxed_cast(bv, &t_conversions); - if (!(name == "Dynamic_Object" || d.get_type_name() == name)) { + if (!(name == "Dynamic_Object" || Dynamic_Object::type_matches(d.get_type_name(), name))) { return std::make_pair(false, false); } } catch (const std::bad_cast &) { @@ -165,13 +165,13 @@ namespace chaiscript { }; /** - * Pure virtual base class for all Proxy_Function implementations - * Proxy_Functions are a type erasure of type safe C++ - * function calls. At runtime parameter types are expected to be - * tested against passed in types. - * Dispatch_Engine only knows how to work with Proxy_Function, no other - * function classes. - */ + * Pure virtual base class for all Proxy_Function implementations + * Proxy_Functions are a type erasure of type safe C++ + * function calls. At runtime parameter types are expected to be + * tested against passed in types. + * Dispatch_Engine only knows how to work with Proxy_Function, no other + * function classes. + */ class Proxy_Function_Base { public: virtual ~Proxy_Function_Base() = default; @@ -233,6 +233,12 @@ namespace chaiscript { } } + /// Returns the Dynamic_Object type name this function is bound to, or empty string if not a Dynamic_Object function + virtual const std::string &dynamic_object_type_name() const noexcept { + static const std::string empty; + return empty; + } + virtual bool compare_first_type(const Boxed_Value &bv, const Type_Conversions_State &t_conversions) const noexcept { /// TODO is m_types guaranteed to be at least 2?? return compare_type_to_param(m_types[1], bv, t_conversions); @@ -374,7 +380,15 @@ namespace chaiscript { for (const auto &t : t_types.types()) { if (t.second.is_undef()) { - types.push_back(chaiscript::detail::Get_Type_Info::get()); + if (!t.first.empty()) { + // Named type without C++ type_info — assumed to be a Dynamic_Object subtype. + // Using Dynamic_Object type_info ensures correct dispatch priority so that + // user-defined overrides on specific subtypes are tried before generic + // Dynamic_Object built-in functions. + types.push_back(user_type()); + } else { + types.push_back(chaiscript::detail::Get_Type_Info::get()); + } } else { types.push_back(t.second); } @@ -668,13 +682,13 @@ namespace chaiscript { public: dispatch_error(const Function_Params &t_parameters, std::vector t_functions) : std::runtime_error("Error with function dispatch") - , parameters(t_parameters.to_vector()) + , parameters(t_parameters.begin(), t_parameters.end()) , functions(std::move(t_functions)) { } dispatch_error(const Function_Params &t_parameters, std::vector t_functions, const std::string &t_desc) : std::runtime_error(t_desc) - , parameters(t_parameters.to_vector()) + , parameters(t_parameters.begin(), t_parameters.end()) , functions(std::move(t_functions)) { } @@ -723,11 +737,24 @@ namespace chaiscript { if (matching_func == end) { matching_func = begin; } else { - // handle const members vs non-const member, which is not really ambiguous const auto &mat_fun_param_types = matching_func->second->get_param_types(); const auto &next_fun_param_types = begin->second->get_param_types(); - if (plist[0].is_const() && !mat_fun_param_types[1].is_const() && next_fun_param_types[1].is_const()) { + // Prefer the candidate whose first parameter (receiver) matches the + // actual receiver type exactly over one that needs a type conversion. + // Conversions on the receiver create temporaries, so any mutation + // would be silently lost; this mirrors the deprioritization in + // dispatch() and resolves cases like myString.substr(int, int) when + // both string::substr and string_view::substr are registered. + const bool plist_empty = plist.empty(); + const bool mat_receiver_exact = !plist_empty && mat_fun_param_types[1].bare_equal(plist[0].get_type_info()); + const bool next_receiver_exact = !plist_empty && next_fun_param_types[1].bare_equal(plist[0].get_type_info()); + + if (mat_receiver_exact && !next_receiver_exact) { + // keep the old one, it has the better receiver match + } else if (!mat_receiver_exact && next_receiver_exact) { + matching_func = begin; // keep the new one, it has the better receiver match + } else if (plist[0].is_const() && !mat_fun_param_types[1].is_const() && next_fun_param_types[1].is_const()) { matching_func = begin; // keep the new one, the const/non-const matchup is correct } else if (!plist[0].is_const() && !mat_fun_param_types[1].is_const() && next_fun_param_types[1].is_const()) { // keep the old one, it has a better const/non-const matchup @@ -792,6 +819,38 @@ namespace chaiscript { ++numdiffs; } } + + // Deprioritize functions whose first parameter (object/receiver) requires + // type conversion: conversions create temporaries, so mutations on the + // converted object are silently lost. + if (plist.size() > 1 && !func->get_param_types()[1].bare_equal(plist[0].get_type_info())) { + numdiffs = plist.size(); + } + + // Deprioritize C++ registered generic Dynamic_Object functions when the + // actual first argument is a specific Dynamic_Object subtype. This allows + // user-defined operator overrides (e.g. `[]`) on a subtype to take precedence + // over the built-in Dynamic_Object operations such as get_attr. + // Only deprioritize non-dynamic (C++ registered) functions — ChaiScript-defined + // functions (Dynamic_Proxy_Function) have their own type matching via Param_Types. + if (!plist.empty() + && dynamic_cast(func.get()) == nullptr) { + static const auto dynamic_object_ti = user_type(); + if (func->get_param_types().size() > 1 + && func->get_param_types()[1].bare_equal(dynamic_object_ti) + && plist[0].get_type_info().bare_equal(dynamic_object_ti) + && func->dynamic_object_type_name().empty()) { + try { + const auto &d = boxed_cast(plist[0], &t_conversions); + if (d.get_type_name() != "Dynamic_Object") { + numdiffs = plist.size(); + } + } catch (const std::bad_cast &) { + // not a Dynamic_Object, ignore + } + } + } + ordered_funcs.emplace_back(numdiffs, func.get()); } } diff --git a/include/chaiscript/dispatchkit/proxy_functions_detail.hpp b/include/chaiscript/dispatchkit/proxy_functions_detail.hpp index d43d4a137..3ae59880c 100644 --- a/include/chaiscript/dispatchkit/proxy_functions_detail.hpp +++ b/include/chaiscript/dispatchkit/proxy_functions_detail.hpp @@ -32,9 +32,9 @@ namespace chaiscript { namespace chaiscript { namespace exception { /** - * Exception thrown when there is a mismatch in number of - * parameters during Proxy_Function execution - */ + * Exception thrown when there is a mismatch in number of + * parameters during Proxy_Function execution + */ struct arity_error : std::range_error { arity_error(int t_got, int t_expected) : std::range_error("Function dispatch arity mismatch") @@ -54,9 +54,9 @@ namespace chaiscript { namespace dispatch { namespace detail { /** - * Used by Proxy_Function_Impl to return a list of all param types - * it contains. - */ + * Used by Proxy_Function_Impl to return a list of all param types + * it contains. + */ template std::vector build_param_type_list(Ret (*)(Params...)) { /// \note somehow this is responsible for a large part of the code generation @@ -64,10 +64,10 @@ namespace chaiscript { } /** - * Used by Proxy_Function_Impl to determine if it is equivalent to another - * Proxy_Function_Impl object. This function is primarily used to prevent - * registration of two functions with the exact same signatures - */ + * Used by Proxy_Function_Impl to determine if it is equivalent to another + * Proxy_Function_Impl object. This function is primarily used to prevent + * registration of two functions with the exact same signatures + */ template bool compare_types_cast(Ret (*)(Params...), const chaiscript::Function_Params ¶ms, const Type_Conversions_State &t_conversions) noexcept { try { @@ -80,11 +80,11 @@ namespace chaiscript { } template - Ret call_func(Ret (*)(Params...), - std::index_sequence, - const Callable &f, - [[maybe_unused]] const chaiscript::Function_Params ¶ms, - [[maybe_unused]] const Type_Conversions_State &t_conversions) { + Ret call_func_impl(Ret (*)(Params...), + std::index_sequence, + const Callable &f, + [[maybe_unused]] const chaiscript::Function_Params ¶ms, + [[maybe_unused]] const Type_Conversions_State &t_conversions) { return f(boxed_cast(params[I], &t_conversions)...); } @@ -95,13 +95,23 @@ namespace chaiscript { template Boxed_Value call_func(Ret (*sig)(Params...), const Callable &f, const chaiscript::Function_Params ¶ms, const Type_Conversions_State &t_conversions) { - if constexpr (std::is_same_v) { - call_func(sig, std::index_sequence_for{}, f, params, t_conversions); - return Handle_Return::handle(); - } else { - return Handle_Return::handle(call_func(sig, std::index_sequence_for{}, f, params, t_conversions)); - } + return Handle_Return::handle(call_func_impl(sig, std::index_sequence_for{}, f, params, t_conversions)); + } + +// MSVC has a broken warning for unreachable code in this block +#ifdef _MSC_VER +#pragma warning(push) +#pragma warning(disable : 4702) +#endif + template + Boxed_Value + call_func(void (*sig)(Params...), const Callable &f, const chaiscript::Function_Params ¶ms, const Type_Conversions_State &t_conversions) { + call_func_impl(sig, std::index_sequence_for{}, f, params, t_conversions); + return Handle_Return::handle(); } +#ifdef _MSC_VER +#pragma warning(pop) +#endif } // namespace detail } // namespace dispatch diff --git a/include/chaiscript/dispatchkit/register_function.hpp b/include/chaiscript/dispatchkit/register_function.hpp index a22280a92..b8c2b00aa 100644 --- a/include/chaiscript/dispatchkit/register_function.hpp +++ b/include/chaiscript/dispatchkit/register_function.hpp @@ -82,6 +82,43 @@ namespace chaiscript { return dispatch::detail::make_callable(std::forward(t), dispatch::detail::function_signature(t)); } + /// \brief Creates a new Proxy_Function object from an overloaded free function, with the + /// signature specified explicitly to disambiguate the overload. + /// + /// \b Example: + /// \code + /// int overloaded(int); + /// double overloaded(double); + /// + /// chai.add(chaiscript::fun(&overloaded), "overloaded"); + /// chai.add(chaiscript::fun(&overloaded), "overloaded"); + /// \endcode + template, int> = 0> + Proxy_Function fun(std::type_identity_t *f) { + return dispatch::detail::make_callable(f, dispatch::detail::function_signature(f)); + } + + /// \brief Creates a new Proxy_Function object from an overloaded member function, with the + /// signature specified explicitly to disambiguate the overload. + /// + /// \b Example: + /// \code + /// class MyClass { + /// public: + /// int overloaded(int); + /// double overloaded(double); + /// int const_overloaded() const; + /// }; + /// + /// chai.add(chaiscript::fun(&MyClass::overloaded), "overloaded"); + /// chai.add(chaiscript::fun(&MyClass::overloaded), "overloaded"); + /// chai.add(chaiscript::fun(&MyClass::const_overloaded), "const_overloaded"); + /// \endcode + template, int> = 0> + Proxy_Function fun(std::type_identity_t Class::*f) { + return dispatch::detail::make_callable(f, dispatch::detail::function_signature(f)); + } + /// \brief Creates a new Proxy_Function object from a free function, member function or data member and binds the first parameter of it /// \param[in] t Function / member to expose /// \param[in] q Value to bind to first parameter diff --git a/include/chaiscript/dispatchkit/short_alloc.hpp b/include/chaiscript/dispatchkit/short_alloc.hpp deleted file mode 100644 index 64f574464..000000000 --- a/include/chaiscript/dispatchkit/short_alloc.hpp +++ /dev/null @@ -1,137 +0,0 @@ -#ifndef SHORT_ALLOC_H -#define SHORT_ALLOC_H - -// The MIT License (MIT) -// -// Copyright (c) 2015 Howard Hinnant -// -// 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. - -#include -#include - -template -class arena { - alignas(alignment) char buf_[N]; - char *ptr_; - -public: - ~arena() { ptr_ = nullptr; } - arena() noexcept - : ptr_(buf_) { - } - arena(const arena &) = delete; - arena &operator=(const arena &) = delete; - - template - char *allocate(std::size_t n); - void deallocate(char *p, std::size_t n) noexcept; - - static constexpr std::size_t size() noexcept { return N; } - std::size_t used() const noexcept { return static_cast(ptr_ - buf_); } - void reset() noexcept { ptr_ = buf_; } - -private: - static std::size_t align_up(std::size_t n) noexcept { return (n + (alignment - 1)) & ~(alignment - 1); } - - bool pointer_in_buffer(char *p) noexcept { return buf_ <= p && p <= buf_ + N; } -}; - -template -template -char *arena::allocate(std::size_t n) { - static_assert(ReqAlign <= alignment, "alignment is too small for this arena"); - assert(pointer_in_buffer(ptr_) && "short_alloc has outlived arena"); - auto const aligned_n = align_up(n); - if (static_cast(buf_ + N - ptr_) >= aligned_n) { - char *r = ptr_; - ptr_ += aligned_n; - return r; - } - - static_assert(alignment <= alignof(std::max_align_t), - "you've chosen an " - "alignment that is larger than alignof(std::max_align_t), and " - "cannot be guaranteed by normal operator new"); - return static_cast(::operator new(n)); -} - -template -void arena::deallocate(char *p, std::size_t n) noexcept { - assert(pointer_in_buffer(ptr_) && "short_alloc has outlived arena"); - if (pointer_in_buffer(p)) { - n = align_up(n); - if (p + n == ptr_) { - ptr_ = p; - } - } else { - ::operator delete(p); - } -} - -template -class short_alloc { -public: - using value_type = T; - static auto constexpr alignment = Align; - static auto constexpr size = N; - using arena_type = arena; - -private: - arena_type &a_; - -public: - short_alloc(const short_alloc &) = default; - short_alloc &operator=(const short_alloc &) = delete; - - explicit short_alloc(arena_type &a) noexcept - : a_(a) { - static_assert(size % alignment == 0, "size N needs to be a multiple of alignment Align"); - } - template - explicit short_alloc(const short_alloc &a) noexcept - : a_(a.a_) { - } - - template - struct rebind { - using other = short_alloc<_Up, N, alignment>; - }; - - T *allocate(std::size_t n) { return reinterpret_cast(a_.template allocate(n * sizeof(T))); } - void deallocate(T *p, std::size_t n) noexcept { a_.deallocate(reinterpret_cast(p), n * sizeof(T)); } - - template - friend bool operator==(const short_alloc &x, const short_alloc &y) noexcept; - - template - friend class short_alloc; -}; - -template -inline bool operator==(const short_alloc &x, const short_alloc &y) noexcept { - return N == M && A1 == A2 && &x.a_ == &y.a_; -} - -template -inline bool operator!=(const short_alloc &x, const short_alloc &y) noexcept { - return !(x == y); -} - -#endif // SHORT_ALLOC_HPP diff --git a/include/chaiscript/dispatchkit/type_conversions.hpp b/include/chaiscript/dispatchkit/type_conversions.hpp index e47afbf73..30f134895 100644 --- a/include/chaiscript/dispatchkit/type_conversions.hpp +++ b/include/chaiscript/dispatchkit/type_conversions.hpp @@ -498,6 +498,29 @@ namespace chaiscript { func); } + namespace detail { + template + struct is_std_vector : std::false_type {}; + + template + struct is_std_vector> : std::true_type {}; + + template + T convert_vector_element(const Boxed_Value &bv) { + if constexpr (is_std_vector::value) { + const auto &inner = Cast_Helper &>::cast(bv, nullptr); + T result; + result.reserve(inner.size()); + for (const Boxed_Value &elem : inner) { + result.push_back(convert_vector_element(elem)); + } + return result; + } else { + return Cast_Helper::cast(bv, nullptr); + } + } + } // namespace detail + template Type_Conversion vector_conversion() { auto func = [](const Boxed_Value &t_bv) -> Boxed_Value { @@ -506,7 +529,7 @@ namespace chaiscript { To vec; vec.reserve(from_vec.size()); for (const Boxed_Value &bv : from_vec) { - vec.push_back(detail::Cast_Helper::cast(bv, nullptr)); + vec.push_back(detail::convert_vector_element(bv)); } return Boxed_Value(std::move(vec)); @@ -534,6 +557,23 @@ namespace chaiscript { return chaiscript::make_shared>( user_type>(), user_type(), func); } + + template + Type_Conversion pair_conversion() { + auto func = [](const Boxed_Value &t_bv) -> Boxed_Value { + const std::pair &from_pair + = detail::Cast_Helper &>::cast(t_bv, nullptr); + + auto pair = std::make_pair( + detail::Cast_Helper::cast(from_pair.first, nullptr), + detail::Cast_Helper::cast(from_pair.second, nullptr)); + + return Boxed_Value(std::move(pair)); + }; + + return chaiscript::make_shared>( + user_type>(), user_type>(), func); + } } // namespace chaiscript #endif diff --git a/include/chaiscript/dispatchkit/type_info.hpp b/include/chaiscript/dispatchkit/type_info.hpp index 2f21ee583..c77e9facc 100644 --- a/include/chaiscript/dispatchkit/type_info.hpp +++ b/include/chaiscript/dispatchkit/type_info.hpp @@ -85,6 +85,8 @@ namespace chaiscript { constexpr const std::type_info *bare_type_info() const noexcept { return m_bare_type_info; } + void make_const() noexcept { m_flags |= (1 << is_const_flag); } + private: struct Unknown_Type { }; diff --git a/include/chaiscript/language/chaiscript_algebraic.hpp b/include/chaiscript/language/chaiscript_algebraic.hpp index 085928029..49cbf7554 100644 --- a/include/chaiscript/language/chaiscript_algebraic.hpp +++ b/include/chaiscript/language/chaiscript_algebraic.hpp @@ -12,6 +12,7 @@ #include "../utility/hash.hpp" +#include #include namespace chaiscript { @@ -52,10 +53,10 @@ namespace chaiscript { invalid }; - constexpr static const char *to_string(Opers t_oper) noexcept { - constexpr const char *opers[] + constexpr static std::string_view to_string(Opers t_oper) noexcept { + constexpr const std::array opers = {"", "==", "<", ">", "<=", ">=", "!=", "", "=", "++", "--", "*=", "+=", "/=", "-=", "", "&=", "|=", "<<=", ">>=", "%=", "^=", "", "<<", ">>", "%", "&", "|", "^", "~", "", "+", "/", "*", "-", "+", "-", ""}; - return opers[static_cast(t_oper)]; + return opers[static_cast(t_oper)]; } constexpr static Opers to_operator(std::string_view t_str, bool t_is_unary = false) noexcept { diff --git a/include/chaiscript/language/chaiscript_common.hpp b/include/chaiscript/language/chaiscript_common.hpp index f4d51a199..0d80b8cad 100644 --- a/include/chaiscript/language/chaiscript_common.hpp +++ b/include/chaiscript/language/chaiscript_common.hpp @@ -33,7 +33,7 @@ namespace chaiscript { template static bool is_reserved_word(const T &s) noexcept { const static std::unordered_set - words{utility::hash("def"), utility::hash("fun"), utility::hash("while"), utility::hash("for"), utility::hash("if"), utility::hash("else"), utility::hash("&&"), utility::hash("||"), utility::hash(","), utility::hash("auto"), utility::hash("return"), utility::hash("break"), utility::hash("true"), utility::hash("false"), utility::hash("class"), utility::hash("attr"), utility::hash("var"), utility::hash("global"), utility::hash("GLOBAL"), utility::hash("_"), utility::hash("__LINE__"), utility::hash("__FILE__"), utility::hash("__FUNC__"), utility::hash("__CLASS__")}; + words{utility::hash("def"), utility::hash("fun"), utility::hash("while"), utility::hash("for"), utility::hash("if"), utility::hash("else"), utility::hash("&&"), utility::hash("||"), utility::hash(","), utility::hash("auto"), utility::hash("return"), utility::hash("break"), utility::hash("true"), utility::hash("false"), utility::hash("class"), utility::hash("attr"), utility::hash("var"), utility::hash("global"), utility::hash("GLOBAL"), utility::hash("_"), utility::hash("__LINE__"), utility::hash("__FILE__"), utility::hash("__FUNC__"), utility::hash("__CLASS__"), utility::hash("const"), utility::hash("using"), utility::hash("enum")}; return words.count(utility::hash(s)) == 1; } @@ -104,7 +104,12 @@ namespace chaiscript { Arg, Global_Decl, Constant, - Compiled + Compiled, + Const_Var_Decl, + Const_Assign_Decl, + Using, + Enum, + Namespace_Block }; enum class Operator_Precedence { @@ -125,9 +130,9 @@ namespace chaiscript { namespace { /// Helper lookup to get the name of each node type constexpr const char *ast_node_type_to_string(AST_Node_Type ast_node_type) noexcept { - constexpr const char *const ast_node_types[] = {"Id", "Fun_Call", "Unused_Return_Fun_Call", "Arg_List", "Equation", "Var_Decl", "Assign_Decl", "Array_Call", "Dot_Access", "Lambda", "Block", "Scopeless_Block", "Def", "While", "If", "For", "Ranged_For", "Inline_Array", "Inline_Map", "Return", "File", "Prefix", "Break", "Continue", "Map_Pair", "Value_Range", "Inline_Range", "Try", "Catch", "Finally", "Method", "Attr_Decl", "Logical_And", "Logical_Or", "Reference", "Switch", "Case", "Default", "Noop", "Class", "Binary", "Arg", "Global_Decl", "Constant", "Compiled"}; + constexpr std::array ast_node_types = {"Id", "Fun_Call", "Unused_Return_Fun_Call", "Arg_List", "Equation", "Var_Decl", "Assign_Decl", "Array_Call", "Dot_Access", "Lambda", "Block", "Scopeless_Block", "Def", "While", "If", "For", "Ranged_For", "Inline_Array", "Inline_Map", "Return", "File", "Prefix", "Break", "Continue", "Map_Pair", "Value_Range", "Inline_Range", "Try", "Catch", "Finally", "Method", "Attr_Decl", "Logical_And", "Logical_Or", "Reference", "Switch", "Case", "Default", "Noop", "Class", "Binary", "Arg", "Global_Decl", "Constant", "Compiled", "Const_Var_Decl", "Const_Assign_Decl", "Using", "Enum", "Namespace_Block"}; - return ast_node_types[static_cast(ast_node_type)]; + return ast_node_types[static_cast(ast_node_type)]; } } // namespace @@ -166,11 +171,104 @@ namespace chaiscript { std::shared_ptr filename; }; + /// \brief Struct that doubles as both a parser ast_node and an AST node. + struct AST_Node { + public: + const AST_Node_Type identifier; + const std::string text; + Parse_Location location; + + const std::string &filename() const noexcept { return *location.filename; } + + const File_Position &start() const noexcept { return location.start; } + + const File_Position &end() const noexcept { return location.end; } + + std::string pretty_print() const { + std::ostringstream oss; + + oss << text; + + for (auto &elem : get_children()) { + oss << elem.get().pretty_print() << ' '; + } + + return oss.str(); + } + + virtual std::vector> get_children() const = 0; + virtual Boxed_Value eval(const chaiscript::detail::Dispatch_State &t_e) const = 0; + + /// Prints the contents of an AST node, including its children, recursively + std::string to_string(const std::string &t_prepend = "") const { + std::ostringstream oss; + + oss << t_prepend << "(" << ast_node_type_to_string(this->identifier) << ") " << this->text << " : " << this->location.start.line + << ", " << this->location.start.column << '\n'; + + for (auto &elem : get_children()) { + oss << elem.get().to_string(t_prepend + " "); + } + return oss.str(); + } + + static inline bool get_bool_condition(const Boxed_Value &t_bv, const chaiscript::detail::Dispatch_State &t_ss); + + virtual ~AST_Node() noexcept = default; + AST_Node(AST_Node &&) = default; + AST_Node &operator=(AST_Node &&) = delete; + AST_Node(const AST_Node &) = delete; + AST_Node &operator=(const AST_Node &) = delete; + + protected: + AST_Node(std::string t_ast_node_text, AST_Node_Type t_id, Parse_Location t_loc) + : identifier(t_id) + , text(std::move(t_ast_node_text)) + , location(std::move(t_loc)) { + } + }; + /// \brief Typedef for pointers to AST_Node objects. Used in building of the AST_Node tree using AST_NodePtr = std::unique_ptr; using AST_NodePtr_Const = std::unique_ptr; - struct AST_Node_Trace; + struct AST_Node_Trace { + const AST_Node_Type identifier; + const std::string text; + Parse_Location location; + + const std::string &filename() const noexcept { return *location.filename; } + + const File_Position &start() const noexcept { return location.start; } + + const File_Position &end() const noexcept { return location.end; } + + std::string pretty_print() const { + std::ostringstream oss; + + oss << text; + + for (const auto &elem : children) { + oss << elem.pretty_print() << ' '; + } + + return oss.str(); + } + + std::vector get_children(const AST_Node &node) { + const auto node_children = node.get_children(); + return std::vector(node_children.begin(), node_children.end()); + } + + AST_Node_Trace(const AST_Node &node) + : identifier(node.identifier) + , text(node.text) + , location(node.location) + , children(get_children(node)) { + } + + std::vector children; + }; /// \brief Classes which may be thrown during error cases when ChaiScript is executing. namespace exception { @@ -495,106 +593,14 @@ namespace chaiscript { } // namespace exception - /// \brief Struct that doubles as both a parser ast_node and an AST node. - struct AST_Node { - public: - const AST_Node_Type identifier; - const std::string text; - Parse_Location location; - - const std::string &filename() const noexcept { return *location.filename; } - - const File_Position &start() const noexcept { return location.start; } - - const File_Position &end() const noexcept { return location.end; } - - std::string pretty_print() const { - std::ostringstream oss; - - oss << text; - - for (auto &elem : get_children()) { - oss << elem.get().pretty_print() << ' '; - } - - return oss.str(); + // static + bool AST_Node::get_bool_condition(const Boxed_Value &t_bv, const chaiscript::detail::Dispatch_State &t_ss) { + try { + return t_ss->boxed_cast(t_bv); + } catch (const exception::bad_boxed_cast &) { + throw exception::eval_error("Condition not boolean"); } - - virtual std::vector> get_children() const = 0; - virtual Boxed_Value eval(const chaiscript::detail::Dispatch_State &t_e) const = 0; - - /// Prints the contents of an AST node, including its children, recursively - std::string to_string(const std::string &t_prepend = "") const { - std::ostringstream oss; - - oss << t_prepend << "(" << ast_node_type_to_string(this->identifier) << ") " << this->text << " : " << this->location.start.line - << ", " << this->location.start.column << '\n'; - - for (auto &elem : get_children()) { - oss << elem.get().to_string(t_prepend + " "); - } - return oss.str(); - } - - static bool get_bool_condition(const Boxed_Value &t_bv, const chaiscript::detail::Dispatch_State &t_ss) { - try { - return t_ss->boxed_cast(t_bv); - } catch (const exception::bad_boxed_cast &) { - throw exception::eval_error("Condition not boolean"); - } - } - - virtual ~AST_Node() noexcept = default; - AST_Node(AST_Node &&) = default; - AST_Node &operator=(AST_Node &&) = delete; - AST_Node(const AST_Node &) = delete; - AST_Node &operator=(const AST_Node &) = delete; - - protected: - AST_Node(std::string t_ast_node_text, AST_Node_Type t_id, Parse_Location t_loc) - : identifier(t_id) - , text(std::move(t_ast_node_text)) - , location(std::move(t_loc)) { - } - }; - - struct AST_Node_Trace { - const AST_Node_Type identifier; - const std::string text; - Parse_Location location; - - const std::string &filename() const noexcept { return *location.filename; } - - const File_Position &start() const noexcept { return location.start; } - - const File_Position &end() const noexcept { return location.end; } - - std::string pretty_print() const { - std::ostringstream oss; - - oss << text; - - for (const auto &elem : children) { - oss << elem.pretty_print() << ' '; - } - - return oss.str(); - } - - std::vector get_children(const AST_Node &node) { - const auto node_children = node.get_children(); - return std::vector(node_children.begin(), node_children.end()); - } - - AST_Node_Trace(const AST_Node &node) - : identifier(node.identifier) - , text(node.text) - , location(node.location) - , children(get_children(node)) { - } - - std::vector children; - }; + } namespace parser { class ChaiScript_Parser_Base { diff --git a/include/chaiscript/language/chaiscript_engine.hpp b/include/chaiscript/language/chaiscript_engine.hpp index 9950ec50f..48f57f4c1 100644 --- a/include/chaiscript/language/chaiscript_engine.hpp +++ b/include/chaiscript/language/chaiscript_engine.hpp @@ -15,6 +15,7 @@ #include #include #include +#include #include #include #include @@ -78,6 +79,9 @@ namespace chaiscript { std::map> m_namespace_generators; + std::function m_print_handler = [](const std::string &) noexcept {}; + std::function m_file_reader; + /// Evaluates the given string in by parsing it and running the results through the evaluator Boxed_Value do_eval(const std::string &t_input, const std::string &t_filename = "__EVAL__", bool /* t_internal*/ = false) { try { @@ -118,11 +122,30 @@ namespace chaiscript { chaiscript::detail::Dispatch_Engine &get_eval_engine() noexcept { return m_engine; } /// Builds all the requirements for ChaiScript, including its evaluator and a run of its prelude. - void build_eval_system(const ModulePtr &t_lib, const std::vector &t_opts) { + void build_eval_system(const ModulePtr &t_lib, const std::vector &t_opts, const bool t_no_io) { if (t_lib) { add(t_lib); } + if (!t_no_io) { + m_print_handler = [](const std::string &s) noexcept { + fwrite(s.c_str(), 1, s.size(), stdout); + }; + } + + m_engine.add(fun([this](const std::string &s) { m_print_handler(s); }), "print_string"); + m_engine.add(fun([this](const std::string &s) { m_print_handler(s + "\n"); }), "println_string"); + + m_engine.add(fun([this](const std::function &t_handler) { + m_print_handler = t_handler; + }), + "set_print_handler"); + + m_engine.add(fun([this](const std::function &t_reader) { + m_file_reader = t_reader; + }), + "set_file_reader"); + m_engine.add(fun([this]() { m_engine.dump_system(); }), "dump_system"); m_engine.add(fun([this](const Boxed_Value &t_bv) { m_engine.dump_object(t_bv); }), "dump_object"); m_engine.add(fun([this](const Boxed_Value &t_bv, const std::string &t_type) { return m_engine.is_type(t_bv, t_type); }), "is_type"); @@ -172,13 +195,39 @@ namespace chaiscript { m_engine.add(fun([this](const Boxed_Value &t_bv, const std::string &t_name) { add_global(t_bv, t_name); }), "add_global"); m_engine.add(fun([this](const Boxed_Value &t_bv, const std::string &t_name) { set_global(t_bv, t_name); }), "set_global"); - // why this unused parameter to Namespace? m_engine.add(fun([this](const std::string &t_namespace_name) { - register_namespace([](Namespace & /*space*/) noexcept {}, t_namespace_name); - import(t_namespace_name); + if (!m_namespace_generators.count(t_namespace_name)) { + register_namespace([](Namespace & /*space*/) noexcept {}, t_namespace_name); + } + const auto sep_pos = t_namespace_name.find("::"); + const std::string root_name = (sep_pos != std::string::npos) ? t_namespace_name.substr(0, sep_pos) : t_namespace_name; + if (!m_engine.get_scripting_objects().count(root_name)) { + import(root_name); + } else if (m_namespace_generators.count(root_name)) { + nest_children(root_name, m_namespace_generators[root_name]()); + } }), "namespace"); m_engine.add(fun([this](const std::string &t_namespace_name) { import(t_namespace_name); }), "import"); + +#ifndef CHAISCRIPT_NO_THREADS + // Register async() with thread tracking so the engine can join all + // async threads before destroying shared state (issues #632, #636). + m_engine.add(chaiscript::fun( + [this](const std::function &t_func) { + auto promise_ptr = std::make_shared>(); + auto future = promise_ptr->get_future(); + m_engine.track_async_thread(std::thread([promise_ptr, t_func]() { + try { + promise_ptr->set_value(t_func()); + } catch (...) { + promise_ptr->set_exception(std::current_exception()); + } + })); + return future; + }), + "async"); +#endif } /// Skip BOM at the beginning of file @@ -201,7 +250,15 @@ namespace chaiscript { } /// Helper function for loading a file - static std::string load_file(const std::string &t_filename) { + std::string load_file(const std::string &t_filename) const { + if (m_file_reader) { + return m_file_reader(t_filename); + } + + return load_file_default(t_filename); + } + + static std::string load_file_default(const std::string &t_filename) { std::ifstream infile(t_filename.c_str(), std::ios::in | std::ios::ate | std::ios::binary); if (!infile.is_open()) { @@ -236,6 +293,21 @@ namespace chaiscript { } public: + /// \brief Set a custom handler for print output, used by both print_string and println_string + /// \param[in] t_handler Function to call with the string to print + void set_print_handler(std::function t_handler) { + m_print_handler = std::move(t_handler); + } + + /// \brief Set a custom handler for reading files, used by eval_file, use, and internal_eval_file + /// \param[in] t_reader Function to call with the filename, returning the file contents as a string + void set_file_reader(std::function t_reader) { + m_file_reader = std::move(t_reader); + } + + /// \brief Virtual destructor for ChaiScript + virtual ~ChaiScript_Basic() = default; + /// \brief Constructor for ChaiScript /// \param[in] t_lib Standard library to apply to this ChaiScript instance /// \param[in] t_modulepaths Vector of paths to search when attempting to load a binary module @@ -244,7 +316,8 @@ namespace chaiscript { std::unique_ptr &&parser, std::vector t_module_paths = {}, std::vector t_use_paths = {}, - const std::vector &t_opts = chaiscript::default_options()) + const std::vector &t_opts = chaiscript::default_options(), + const bool t_no_io = false) : m_module_paths(ensure_minimum_path_vec(std::move(t_module_paths))) , m_use_paths(ensure_minimum_path_vec(std::move(t_use_paths))) , m_parser(std::move(parser)) @@ -279,7 +352,7 @@ namespace chaiscript { m_module_paths.insert(m_module_paths.begin(), dllpath + "/"); } #endif - build_eval_system(t_lib, t_opts); + build_eval_system(t_lib, t_opts, t_no_io); } #ifndef CHAISCRIPT_NO_DYNLOAD @@ -469,6 +542,12 @@ namespace chaiscript { /// \returns All values in the local thread state, added through the add() function std::map get_locals() const { return m_engine.get_locals(); } + /// \returns All accessible function objects, as a map of name to Boxed_Value + std::map get_function_objects() const { return m_engine.get_function_objects(); } + + /// \returns All accessible scripting objects (locals + globals), as a map of name to Boxed_Value + std::map get_scripting_objects() const { return m_engine.get_scripting_objects(); } + /// \brief Sets all of the locals for the current thread state. /// /// \param[in] t_locals The map set of variables to replace the current state with @@ -532,7 +611,7 @@ namespace chaiscript { /// (the symbol mentioned above), an exception is thrown. /// /// \throw chaiscript::exception::load_module_error In the event that no matching module can be found. - std::string load_module(const std::string &t_module_name) { + std::string load_module([[maybe_unused]] const std::string &t_module_name) { #ifdef CHAISCRIPT_NO_DYNLOAD throw chaiscript::exception::load_module_error("Loadable module support was disabled (CHAISCRIPT_NO_DYNLOAD)"); #else @@ -678,28 +757,59 @@ namespace chaiscript { if (m_engine.get_scripting_objects().count(t_namespace_name)) { throw std::runtime_error("Namespace: " + t_namespace_name + " was already defined"); } else if (m_namespace_generators.count(t_namespace_name)) { - m_engine.add_global(var(std::ref(m_namespace_generators[t_namespace_name]())), t_namespace_name); + auto &ns = m_namespace_generators[t_namespace_name](); + nest_children(t_namespace_name, ns); + m_engine.add_global(var(std::ref(ns)), t_namespace_name); } else { throw std::runtime_error("No registered namespace: " + t_namespace_name); } } /// \brief Registers a namespace generator, which delays generation of the namespace until it is imported, saving memory if it is never - /// used. \param[in] t_namespace_generator Namespace generator function. \param[in] t_namespace_name Name of the Namespace function - /// being registered. \throw std::runtime_error In the case that the namespace name was already registered. + /// used. Supports C++-style nested names (e.g. "constants::si") for nested namespaces; parent namespaces are auto-registered if absent. + /// \param[in] t_namespace_generator Namespace generator function. + /// \param[in] t_namespace_name Name of the Namespace function being registered (may contain :: for nesting). + /// \throw std::runtime_error In the case that the namespace name was already registered. void register_namespace(const std::function &t_namespace_generator, const std::string &t_namespace_name) { chaiscript::detail::threading::unique_lock l(m_use_mutex); - if (!m_namespace_generators.count(t_namespace_name)) { - // contain the namespace object memory within the m_namespace_generators map - m_namespace_generators.emplace(std::make_pair(t_namespace_name, [=, space = Namespace()]() mutable -> Namespace & { - t_namespace_generator(space); - return space; - })); - } else { + if (m_namespace_generators.count(t_namespace_name)) { throw std::runtime_error("Namespace: " + t_namespace_name + " was already registered."); } + + m_namespace_generators.emplace(std::make_pair(t_namespace_name, [=, space = Namespace()]() mutable noexcept -> Namespace & { + t_namespace_generator(space); + return space; + })); + + auto pos = t_namespace_name.rfind("::"); + while (pos != std::string::npos) { + const std::string parent = t_namespace_name.substr(0, pos); + if (!m_namespace_generators.count(parent)) { + m_namespace_generators.emplace(std::make_pair(parent, [space = Namespace()]() mutable noexcept -> Namespace & { + return space; + })); + } + pos = parent.rfind("::"); + } + } + + private: + void nest_children(const std::string &t_parent_name, Namespace &t_parent) { + const std::string prefix = t_parent_name + "::"; + for (auto &[name, generator] : m_namespace_generators) { + if (name.size() > prefix.size() && name.compare(0, prefix.size(), prefix) == 0) { + const std::string remainder = name.substr(prefix.size()); + if (remainder.find("::") == std::string::npos) { + auto &child_ns = generator(); + nest_children(name, child_ns); + t_parent[remainder] = var(std::ref(child_ns)); + } + } + } } + + public: }; } // namespace chaiscript diff --git a/include/chaiscript/language/chaiscript_eval.hpp b/include/chaiscript/language/chaiscript_eval.hpp index 4997d6671..635aac3fa 100644 --- a/include/chaiscript/language/chaiscript_eval.hpp +++ b/include/chaiscript/language/chaiscript_eval.hpp @@ -10,6 +10,7 @@ #ifndef CHAISCRIPT_EVAL_HPP_ #define CHAISCRIPT_EVAL_HPP_ +#include #include #include #include @@ -108,6 +109,169 @@ namespace chaiscript { return incoming; } } + class Strong_Typedef_Binary_Op final : public dispatch::Proxy_Function_Base { + public: + Strong_Typedef_Binary_Op( + std::string t_type_name, + std::string t_op_name, + Operators::Opers t_oper, + bool t_rewrap, + chaiscript::detail::Dispatch_Engine &t_engine) + : Proxy_Function_Base( + {chaiscript::detail::Get_Type_Info::get(), + user_type(), + user_type()}, + 2) + , m_type_name(std::move(t_type_name)) + , m_op_name(std::move(t_op_name)) + , m_oper(t_oper) + , m_rewrap(t_rewrap) + , m_engine(t_engine) { + } + + bool operator==(const Proxy_Function_Base &f) const noexcept override { + if (const auto *other = dynamic_cast(&f)) { + return m_type_name == other->m_type_name && m_op_name == other->m_op_name; + } + return false; + } + + bool call_match(const Function_Params &vals, const Type_Conversions_State &t_conversions) const noexcept override { + return vals.size() == 2 + && type_matches(vals[0], t_conversions) + && type_matches(vals[1], t_conversions); + } + + protected: + Boxed_Value do_call(const Function_Params ¶ms, const Type_Conversions_State &t_conversions) const override { + if (!call_match(params, t_conversions)) { + throw chaiscript::exception::guard_error(); + } + + const auto &lhs = boxed_cast(params[0], &t_conversions); + const auto &rhs = boxed_cast(params[1], &t_conversions); + const auto lhs_val = lhs.get_attr("__value"); + const auto rhs_val = rhs.get_attr("__value"); + + Boxed_Value result; + if (m_oper != Operators::Opers::invalid + && lhs_val.get_type_info().is_arithmetic() + && rhs_val.get_type_info().is_arithmetic()) { + result = Boxed_Number::do_oper(m_oper, lhs_val, rhs_val); + } else { + std::array underlying_params{lhs_val, rhs_val}; + result = m_engine.call_function(m_op_name, m_loc, Function_Params(underlying_params), t_conversions); + } + + if (m_rewrap) { + auto bv = Boxed_Value(dispatch::Dynamic_Object(m_type_name), true); + auto *obj = static_cast(bv.get_ptr()); + obj->get_attr("__value") = result; + return bv; + } + return result; + } + + private: + bool type_matches(const Boxed_Value &bv, const Type_Conversions_State &t_conversions) const noexcept { + if (!bv.get_type_info().bare_equal(user_type())) { + return false; + } + try { + const auto &d = boxed_cast(bv, &t_conversions); + return d.get_type_name() == m_type_name; + } catch (...) { + return false; + } + } + + std::string m_type_name; + std::string m_op_name; + Operators::Opers m_oper; + bool m_rewrap; + chaiscript::detail::Dispatch_Engine &m_engine; + mutable std::atomic_uint_fast32_t m_loc{0}; + }; + + class Strong_Typedef_Compound_Assign_Op final : public dispatch::Proxy_Function_Base { + public: + Strong_Typedef_Compound_Assign_Op( + std::string t_type_name, + std::string t_op_name, + Operators::Opers t_base_oper, + std::string t_base_op_name, + chaiscript::detail::Dispatch_Engine &t_engine) + : Proxy_Function_Base( + {user_type(), + user_type(), + user_type()}, + 2) + , m_type_name(std::move(t_type_name)) + , m_op_name(std::move(t_op_name)) + , m_base_oper(t_base_oper) + , m_base_op_name(std::move(t_base_op_name)) + , m_engine(t_engine) { + } + + bool operator==(const Proxy_Function_Base &f) const noexcept override { + if (const auto *other = dynamic_cast(&f)) { + return m_type_name == other->m_type_name && m_op_name == other->m_op_name; + } + return false; + } + + bool call_match(const Function_Params &vals, const Type_Conversions_State &t_conversions) const noexcept override { + return vals.size() == 2 + && type_matches(vals[0], t_conversions) + && type_matches(vals[1], t_conversions); + } + + protected: + Boxed_Value do_call(const Function_Params ¶ms, const Type_Conversions_State &t_conversions) const override { + if (!call_match(params, t_conversions)) { + throw chaiscript::exception::guard_error(); + } + + auto &lhs = boxed_cast(params[0], &t_conversions); + const auto &rhs = boxed_cast(params[1], &t_conversions); + const auto lhs_val = lhs.get_attr("__value"); + const auto rhs_val = rhs.get_attr("__value"); + + Boxed_Value result; + if (m_base_oper != Operators::Opers::invalid + && lhs_val.get_type_info().is_arithmetic() + && rhs_val.get_type_info().is_arithmetic()) { + result = Boxed_Number::do_oper(m_base_oper, lhs_val, rhs_val); + } else { + std::array underlying_params{lhs_val, rhs_val}; + result = m_engine.call_function(m_base_op_name, m_loc, Function_Params(underlying_params), t_conversions); + } + + lhs.get_attr("__value") = result; + return params[0]; + } + + private: + bool type_matches(const Boxed_Value &bv, const Type_Conversions_State &t_conversions) const noexcept { + if (!bv.get_type_info().bare_equal(user_type())) { + return false; + } + try { + const auto &d = boxed_cast(bv, &t_conversions); + return d.get_type_name() == m_type_name; + } catch (...) { + return false; + } + } + + std::string m_type_name; + std::string m_op_name; + Operators::Opers m_base_oper; + std::string m_base_op_name; + chaiscript::detail::Dispatch_Engine &m_engine; + mutable std::atomic_uint_fast32_t m_loc{0}; + }; + } // namespace detail template @@ -220,15 +384,16 @@ namespace chaiscript { Boxed_Value eval_internal(const chaiscript::detail::Dispatch_State &t_ss) const override { auto lhs = this->children[0]->eval(t_ss); auto rhs = this->children[1]->eval(t_ss); - return do_oper(t_ss, m_oper, this->text, lhs, rhs); + return do_oper(t_ss, m_oper, this->text, lhs, rhs, m_loc); } - protected: - Boxed_Value do_oper(const chaiscript::detail::Dispatch_State &t_ss, - Operators::Opers t_oper, - const std::string &t_oper_string, - const Boxed_Value &t_lhs, - const Boxed_Value &t_rhs) const { + // static and public so we can use this to process Switch_AST_Node case equality + static Boxed_Value do_oper(const chaiscript::detail::Dispatch_State &t_ss, + Operators::Opers t_oper, + const std::string &t_oper_string, + const Boxed_Value &t_lhs, + const Boxed_Value &t_rhs, + std::atomic_uint_fast32_t &t_loc) { try { if (t_oper != Operators::Opers::invalid && t_lhs.get_type_info().is_arithmetic() && t_rhs.get_type_info().is_arithmetic()) { // If it's an arithmetic operation we want to short circuit dispatch @@ -243,7 +408,7 @@ namespace chaiscript { chaiscript::eval::detail::Function_Push_Pop fpp(t_ss); std::array params{t_lhs, t_rhs}; fpp.save_params(Function_Params(params)); - return t_ss->call_function(t_oper_string, m_loc, Function_Params(params), t_ss.conversions()); + return t_ss->call_function(t_oper_string, t_loc, Function_Params(params), t_ss.conversions()); } } catch (const exception::dispatch_error &e) { throw exception::eval_error("Can not find appropriate '" + t_oper_string + "' operator.", e.parameters, e.functions, false, *t_ss); @@ -437,6 +602,8 @@ namespace chaiscript { if (m_oper != Operators::Opers::invalid && params[0].get_type_info().is_arithmetic() && params[1].get_type_info().is_arithmetic()) { try { return Boxed_Number::do_oper(m_oper, params[0], params[1]); + } catch (const chaiscript::exception::arithmetic_error &) { + throw; } catch (const std::exception &) { throw exception::eval_error("Error with unsupported arithmetic assignment operation."); } @@ -501,13 +668,7 @@ namespace chaiscript { } Boxed_Value eval_internal(const chaiscript::detail::Dispatch_State &t_ss) const override { - const std::string &idname = [&]() -> const std::string & { - if (this->children[0]->identifier == AST_Node_Type::Reference) { - return this->children[0]->children[0]->text; - } else { - return this->children[0]->text; - } - }(); + const std::string &idname = (this->children[0]->identifier == AST_Node_Type::Reference) ? this->children[0]->children[0]->text : this->children[0]->text; return t_ss->add_global_no_throw(Boxed_Value(), idname); } @@ -555,6 +716,41 @@ namespace chaiscript { mutable std::atomic_uint_fast32_t m_loc = {0}; }; + template + struct Const_Var_Decl_AST_Node final : AST_Node_Impl { + Const_Var_Decl_AST_Node(std::string t_ast_node_text, Parse_Location t_loc, std::vector> t_children) + : AST_Node_Impl(std::move(t_ast_node_text), AST_Node_Type::Const_Var_Decl, std::move(t_loc), std::move(t_children)) { + } + + Boxed_Value eval_internal(const chaiscript::detail::Dispatch_State &) const override { + throw exception::eval_error("const variables must be initialized at declaration"); + } + }; + + template + struct Const_Assign_Decl_AST_Node final : AST_Node_Impl { + Const_Assign_Decl_AST_Node(std::string t_ast_node_text, Parse_Location t_loc, std::vector> t_children) + : AST_Node_Impl(std::move(t_ast_node_text), AST_Node_Type::Const_Assign_Decl, std::move(t_loc), std::move(t_children)) { + } + + Boxed_Value eval_internal(const chaiscript::detail::Dispatch_State &t_ss) const override { + const std::string &idname = this->children[0]->text; + + try { + Boxed_Value bv(detail::clone_if_necessary(this->children[1]->eval(t_ss), m_loc, t_ss)); + bv.reset_return_value(); + bv.make_const(); + t_ss.add_object(idname, bv); + return bv; + } catch (const exception::name_conflict_error &e) { + throw exception::eval_error("Variable redefined '" + e.name() + "'"); + } + } + + private: + mutable std::atomic_uint_fast32_t m_loc = {0}; + }; + template struct Array_Call_AST_Node final : AST_Node_Impl { Array_Call_AST_Node(std::string t_ast_node_text, Parse_Location t_loc, std::vector> t_children) @@ -756,40 +952,43 @@ namespace chaiscript { return false; } - Boxed_Value eval_internal(const chaiscript::detail::Dispatch_State &t_ss) const override { + static std::shared_ptr make_proxy_function( + const Def_AST_Node &t_node, const chaiscript::detail::Dispatch_State &t_ss) { std::vector t_param_names; size_t numparams = 0; dispatch::Param_Types param_types; - if ((this->children.size() > 1) && (this->children[1]->identifier == AST_Node_Type::Arg_List)) { - numparams = this->children[1]->children.size(); - t_param_names = Arg_List_AST_Node::get_arg_names(*this->children[1]); - param_types = Arg_List_AST_Node::get_arg_types(*this->children[1], t_ss); + if ((t_node.children.size() > 1) && (t_node.children[1]->identifier == AST_Node_Type::Arg_List)) { + numparams = t_node.children[1]->children.size(); + t_param_names = Arg_List_AST_Node::get_arg_names(*t_node.children[1]); + param_types = Arg_List_AST_Node::get_arg_types(*t_node.children[1], t_ss); } std::reference_wrapper engine(*t_ss); std::shared_ptr guard; - if (m_guard_node) { + if (t_node.m_guard_node) { guard = dispatch::make_dynamic_proxy_function( - [engine, guardnode = m_guard_node, t_param_names](const Function_Params &t_params) { + [engine, guardnode = t_node.m_guard_node, t_param_names](const Function_Params &t_params) { return detail::eval_function(engine, *guardnode, t_param_names, t_params); }, static_cast(numparams), - m_guard_node); + t_node.m_guard_node); } + return dispatch::make_dynamic_proxy_function( + [engine, func_node = t_node.m_body_node, t_param_names](const Function_Params &t_params) { + return detail::eval_function(engine, *func_node, t_param_names, t_params); + }, + static_cast(numparams), + t_node.m_body_node, + param_types, + guard); + } + + Boxed_Value eval_internal(const chaiscript::detail::Dispatch_State &t_ss) const override { try { - const std::string &l_function_name = this->children[0]->text; - t_ss->add(dispatch::make_dynamic_proxy_function( - [engine, func_node = m_body_node, t_param_names](const Function_Params &t_params) { - return detail::eval_function(engine, *func_node, t_param_names, t_params); - }, - static_cast(numparams), - m_body_node, - param_types, - guard), - l_function_name); + t_ss->add(make_proxy_function(*this, t_ss), this->children[0]->text); } catch (const exception::name_conflict_error &e) { throw exception::eval_error("Function redefined '" + e.name() + "'"); } @@ -833,16 +1032,290 @@ namespace chaiscript { Boxed_Value eval_internal(const chaiscript::detail::Dispatch_State &t_ss) const override { chaiscript::eval::detail::Scope_Push_Pop spp(t_ss); + const auto &class_name = this->children[0]->text; + /// \todo do this better // put class name in current scope so it can be looked up by the attrs and methods - t_ss.add_object("_current_class_name", const_var(this->children[0]->text)); + t_ss.add_object("_current_class_name", const_var(class_name)); - this->children[1]->eval(t_ss); + const bool has_base_class = (this->children.size() == 3); + const auto &block = has_base_class ? this->children[2] : this->children[1]; + + // Register inheritance before evaluating the class body so that + // function dispatch ordering can account for the relationship + if (has_base_class) { + const auto &base_name = this->children[1]->text; + dispatch::Dynamic_Object::register_inheritance(class_name, base_name); + } + + block->eval(t_ss); + + return void_var(); + } + }; + + template + struct Using_AST_Node final : AST_Node_Impl { + Using_AST_Node(std::string t_ast_node_text, Parse_Location t_loc, std::vector> t_children) + : AST_Node_Impl(std::move(t_ast_node_text), AST_Node_Type::Using, std::move(t_loc), std::move(t_children)) { + assert(this->children.size() == 2); + } + + Boxed_Value eval_internal(const chaiscript::detail::Dispatch_State &t_ss) const override { + const auto &new_type_name = this->children[0]->text; + const auto &base_type_name = this->children[1]->text; + + const auto base_type = t_ss->get_type(base_type_name, true); + + t_ss->add(user_type(), new_type_name); + + dispatch::Param_Types param_types(std::vector>{ + {new_type_name, Type_Info()}, + {base_type_name, base_type}}); + + auto ctor_body = dispatch::make_dynamic_proxy_function( + [](const Function_Params &t_params) -> Boxed_Value { + auto *obj = static_cast(t_params[0].get_ptr()); + obj->get_attr("__value") = t_params[1]; + return void_var(); + }, + 2, + std::shared_ptr(), + param_types); + + try { + t_ss->add(std::make_shared(new_type_name, ctor_body), new_type_name); + } catch (const exception::name_conflict_error &e) { + throw exception::eval_error("Type alias redefined '" + e.name() + "'"); + } + + dispatch::Param_Types to_underlying_param_types(std::vector>{ + {new_type_name, user_type()}}); + + auto to_underlying_body = dispatch::make_dynamic_proxy_function( + [](const Function_Params &t_params) -> Boxed_Value { + const auto *obj = static_cast(t_params[0].get_const_ptr()); + return obj->get_attr("__value"); + }, + 1, + std::shared_ptr(), + to_underlying_param_types); + + t_ss->add(to_underlying_body, "to_underlying"); + + auto &engine = *t_ss; + + struct Op_Entry { + const char *name; + Operators::Opers oper; + bool rewrap; + }; + + static constexpr Op_Entry ops[] = { + {"+", Operators::Opers::sum, true}, + {"-", Operators::Opers::difference, true}, + {"*", Operators::Opers::product, true}, + {"/", Operators::Opers::quotient, true}, + {"%", Operators::Opers::remainder, true}, + {"<<", Operators::Opers::shift_left, true}, + {">>", Operators::Opers::shift_right, true}, + {"&", Operators::Opers::bitwise_and, true}, + {"|", Operators::Opers::bitwise_or, true}, + {"^", Operators::Opers::bitwise_xor, true}, + {"<", Operators::Opers::less_than, false}, + {">", Operators::Opers::greater_than, false}, + {"<=", Operators::Opers::less_than_equal, false}, + {">=", Operators::Opers::greater_than_equal, false}, + {"==", Operators::Opers::equals, false}, + {"!=", Operators::Opers::not_equal, false}, + }; + + for (const auto &op : ops) { + t_ss->add( + chaiscript::make_shared( + new_type_name, std::string(op.name), op.oper, op.rewrap, engine), + op.name); + } + + struct Compound_Op_Entry { + const char *name; + Operators::Opers base_oper; + const char *base_op_name; + }; + + static constexpr Compound_Op_Entry compound_ops[] = { + {"+=", Operators::Opers::sum, "+"}, + {"-=", Operators::Opers::difference, "-"}, + {"*=", Operators::Opers::product, "*"}, + {"/=", Operators::Opers::quotient, "/"}, + {"%=", Operators::Opers::remainder, "%"}, + {"<<=", Operators::Opers::shift_left, "<<"}, + {">>=", Operators::Opers::shift_right, ">>"}, + {"&=", Operators::Opers::bitwise_and, "&"}, + {"|=", Operators::Opers::bitwise_or, "|"}, + {"^=", Operators::Opers::bitwise_xor, "^"}, + }; + + for (const auto &op : compound_ops) { + t_ss->add( + chaiscript::make_shared( + new_type_name, std::string(op.name), op.base_oper, std::string(op.base_op_name), engine), + op.name); + } return void_var(); } }; + template + struct Enum_AST_Node final : AST_Node_Impl { + Enum_AST_Node(std::string t_ast_node_text, Parse_Location t_loc, std::vector> t_children) + : AST_Node_Impl(std::move(t_ast_node_text), AST_Node_Type::Enum, std::move(t_loc), std::move(t_children)) { + } + + Boxed_Value eval_internal(const chaiscript::detail::Dispatch_State &t_ss) const override { + const auto &enum_name = this->children[0]->text; + const auto &underlying_type_name = this->children[1]->text; + const auto underlying_ti = t_ss->get_type(underlying_type_name); + + dispatch::Dynamic_Object container(enum_name); + std::vector valid_values; + + for (size_t i = 2; i < this->children.size(); i += 2) { + const auto &val_name = this->children[i]->text; + const auto val_bv = Boxed_Number(this->children[i + 1]->eval(t_ss)).get_as(underlying_ti).bv; + valid_values.push_back(val_bv); + + dispatch::Dynamic_Object dobj(enum_name); + dobj.get_attr("value") = val_bv; + dobj.set_explicit(true); + container[val_name] = const_var(dobj); + } + + auto shared_valid = std::make_shared>(std::move(valid_values)); + + container[enum_name] = var( + fun([shared_valid, enum_name, underlying_ti](const Boxed_Number &t_val) -> Boxed_Value { + const auto converted = t_val.get_as(underlying_ti); + for (const auto &v : *shared_valid) { + if (Boxed_Number::equals(Boxed_Number(v), converted)) { + dispatch::Dynamic_Object dobj(enum_name); + dobj.get_attr("value") = converted.bv; + dobj.set_explicit(true); + return const_var(dobj); + } + } + throw exception::eval_error("Value is not valid for enum '" + enum_name + "'"); + })); + + t_ss->add_global_const(const_var(container), enum_name); + + t_ss->add( + std::make_shared( + enum_name, + fun([](const dispatch::Dynamic_Object &lhs, const dispatch::Dynamic_Object &rhs) { + return Boxed_Number::equals(Boxed_Number(lhs.get_attr("value")), Boxed_Number(rhs.get_attr("value"))); + })), + "=="); + + t_ss->add( + std::make_shared( + enum_name, + fun([](const dispatch::Dynamic_Object &lhs, const dispatch::Dynamic_Object &rhs) { + return !Boxed_Number::equals(Boxed_Number(lhs.get_attr("value")), Boxed_Number(rhs.get_attr("value"))); + })), + "!="); + + t_ss->add( + std::make_shared( + enum_name, + fun([](const dispatch::Dynamic_Object &obj) { return obj.get_attr("value"); })), + "to_underlying"); + + return void_var(); + } + }; + + template + struct Namespace_Block_AST_Node final : AST_Node_Impl { + Namespace_Block_AST_Node(std::string t_ast_node_text, Parse_Location t_loc, std::vector> t_children) + : AST_Node_Impl(std::move(t_ast_node_text), AST_Node_Type::Namespace_Block, std::move(t_loc), std::move(t_children)) { + } + + Boxed_Value eval_internal(const chaiscript::detail::Dispatch_State &t_ss) const override { + const auto &ns_name = this->children[0]->text; + + auto ns_name_bv = const_var(ns_name); + t_ss->call_function("namespace", m_ns_loc, Function_Params{&ns_name_bv, 1}, t_ss.conversions()); + + std::vector parts; + { + std::string::size_type start = 0; + std::string::size_type pos = 0; + while ((pos = ns_name.find("::", start)) != std::string::npos) { + parts.push_back(ns_name.substr(start, pos - start)); + start = pos + 2; + } + parts.push_back(ns_name.substr(start)); + } + + Boxed_Value ns_bv = t_ss.get_object(parts[0], m_root_loc); + + for (size_t i = 1; i < parts.size(); ++i) { + auto &parent_ns = boxed_cast(ns_bv); + ns_bv = parent_ns.get_attr(parts[i]); + } + + auto &target_ns = boxed_cast(ns_bv); + + const auto process_statement = [&](const AST_Node_Impl &stmt) { + if (stmt.identifier == AST_Node_Type::Def) { + const auto &def_node = static_cast &>(stmt); + target_ns[def_node.children[0]->text] = Boxed_Value(Def_AST_Node::make_proxy_function(def_node, t_ss)); + } else if (stmt.identifier == AST_Node_Type::Assign_Decl + || stmt.identifier == AST_Node_Type::Const_Assign_Decl) { + const auto &var_name = stmt.children[0]->text; + auto value = detail::clone_if_necessary(stmt.children[1]->eval(t_ss), m_clone_loc, t_ss); + value.reset_return_value(); + if (stmt.identifier == AST_Node_Type::Const_Assign_Decl) { + value.make_const(); + } + target_ns[var_name] = std::move(value); + } else if (stmt.identifier == AST_Node_Type::Equation + && !stmt.children.empty() + && (stmt.children[0]->identifier == AST_Node_Type::Var_Decl + || stmt.children[0]->identifier == AST_Node_Type::Const_Var_Decl)) { + const auto &var_name = stmt.children[0]->children[0]->text; + auto value = detail::clone_if_necessary(stmt.children[1]->eval(t_ss), m_clone_loc, t_ss); + value.reset_return_value(); + target_ns[var_name] = std::move(value); + } else if (stmt.identifier == AST_Node_Type::Var_Decl) { + const auto &var_name = stmt.children[0]->text; + target_ns[var_name] = Boxed_Value(); + } else { + throw exception::eval_error("Only declarations (def, var, auto, global) are allowed inside namespace blocks"); + } + }; + + const auto &body = this->children[1]; + if (body->identifier == AST_Node_Type::Block + || body->identifier == AST_Node_Type::Scopeless_Block) { + for (const auto &child : body->children) { + process_statement(*child); + } + } else { + process_statement(*body); + } + + return void_var(); + } + + private: + mutable std::atomic_uint_fast32_t m_ns_loc = {0}; + mutable std::atomic_uint_fast32_t m_root_loc = {0}; + mutable std::atomic_uint_fast32_t m_clone_loc = {0}; + }; + template struct If_AST_Node final : AST_Node_Impl { If_AST_Node(std::string t_ast_node_text, Parse_Location t_loc, std::vector> t_children) @@ -877,7 +1350,7 @@ namespace chaiscript { }; const auto call_function = [&t_ss](const auto &t_funcs, const Boxed_Value &t_param) { - return dispatch::dispatch(*t_funcs, Function_Params{t_param}, t_ss.conversions()); + return dispatch::dispatch(*t_funcs, Function_Params{&t_param, 1}, t_ss.conversions()); }; const std::string &loop_var_name = this->children[0]->text; @@ -991,8 +1464,7 @@ namespace chaiscript { if (this->children[currentCase]->identifier == AST_Node_Type::Case) { // This is a little odd, but because want to see both the switch and the case simultaneously, I do a downcast here. try { - std::array p{match_value, this->children[currentCase]->children[0]->eval(t_ss)}; - if (hasMatched || boxed_cast(t_ss->call_function("==", m_loc, Function_Params{p}, t_ss.conversions()))) { + if (hasMatched || boxed_cast(Binary_Operator_AST_Node::do_oper(t_ss, Operators::Opers::equals, "==", match_value, this->children[currentCase]->children[0]->eval(t_ss), m_loc))) { this->children[currentCase]->eval(t_ss); hasMatched = true; } @@ -1174,8 +1646,8 @@ namespace chaiscript { return Boxed_Number::do_oper(m_oper, bv); } else { chaiscript::eval::detail::Function_Push_Pop fpp(t_ss); - fpp.save_params(Function_Params{bv}); - return t_ss->call_function(this->text, m_loc, Function_Params{bv}, t_ss.conversions()); + fpp.save_params(Function_Params{&bv, 1}); + return t_ss->call_function(this->text, m_loc, Function_Params{&bv, 1}, t_ss.conversions()); } } catch (const exception::dispatch_error &e) { throw exception::eval_error("Error with prefix operator evaluation: '" + this->text + "'", e.parameters, e.functions, false, *t_ss); @@ -1262,6 +1734,7 @@ namespace chaiscript { Boxed_Value handle_exception(const chaiscript::detail::Dispatch_State &t_ss, const Boxed_Value &t_except) const { Boxed_Value retval; + bool handled = false; size_t end_point = this->children.size(); if (this->children.back()->identifier == AST_Node_Type::Finally) { @@ -1275,30 +1748,33 @@ namespace chaiscript { if (catch_block.children.size() == 1) { // No variable capture retval = catch_block.children[0]->eval(t_ss); + handled = true; break; } else if (catch_block.children.size() == 2 || catch_block.children.size() == 3) { const auto name = Arg_List_AST_Node::get_arg_name(*catch_block.children[0]); if (dispatch::Param_Types( std::vector>{Arg_List_AST_Node::get_arg_type(*catch_block.children[0], t_ss)}) - .match(Function_Params{t_except}, t_ss.conversions()) + .match(Function_Params{&t_except, 1}, t_ss.conversions()) .first) { t_ss.add_object(name, t_except); if (catch_block.children.size() == 2) { // Variable capture retval = catch_block.children[1]->eval(t_ss); + handled = true; break; } } } else { - if (this->children.back()->identifier == AST_Node_Type::Finally) { - this->children.back()->children[0]->eval(t_ss); - } throw exception::eval_error("Internal error: catch block size unrecognized"); } } + if (!handled) { + throw; + } + return retval; } @@ -1308,17 +1784,19 @@ namespace chaiscript { chaiscript::eval::detail::Scope_Push_Pop spp(t_ss); try { - retval = this->children[0]->eval(t_ss); - } catch (const exception::eval_error &e) { - retval = handle_exception(t_ss, Boxed_Value(std::ref(e))); - } catch (const std::runtime_error &e) { - retval = handle_exception(t_ss, Boxed_Value(std::ref(e))); - } catch (const std::out_of_range &e) { - retval = handle_exception(t_ss, Boxed_Value(std::ref(e))); - } catch (const std::exception &e) { - retval = handle_exception(t_ss, Boxed_Value(std::ref(e))); - } catch (Boxed_Value &e) { - retval = handle_exception(t_ss, e); + try { + retval = this->children[0]->eval(t_ss); + } catch (const exception::eval_error &e) { + retval = handle_exception(t_ss, Boxed_Value(std::ref(e))); + } catch (const std::runtime_error &e) { + retval = handle_exception(t_ss, Boxed_Value(std::ref(e))); + } catch (const std::out_of_range &e) { + retval = handle_exception(t_ss, Boxed_Value(std::ref(e))); + } catch (const std::exception &e) { + retval = handle_exception(t_ss, Boxed_Value(std::ref(e))); + } catch (Boxed_Value &e) { + retval = handle_exception(t_ss, e); + } } catch (...) { if (this->children.back()->identifier == AST_Node_Type::Finally) { this->children.back()->children[0]->eval(t_ss); diff --git a/include/chaiscript/language/chaiscript_optimizer.hpp b/include/chaiscript/language/chaiscript_optimizer.hpp index d9f706b60..b35dd31b4 100644 --- a/include/chaiscript/language/chaiscript_optimizer.hpp +++ b/include/chaiscript/language/chaiscript_optimizer.hpp @@ -89,6 +89,7 @@ namespace chaiscript { template bool contains_var_decl_in_scope(const eval::AST_Node_Impl &node) noexcept { if (node.identifier == AST_Node_Type::Var_Decl || node.identifier == AST_Node_Type::Assign_Decl + || node.identifier == AST_Node_Type::Const_Var_Decl || node.identifier == AST_Node_Type::Const_Assign_Decl || node.identifier == AST_Node_Type::Reference) { return true; } @@ -202,6 +203,14 @@ namespace chaiscript { return chaiscript::make_unique, eval::Assign_Decl_AST_Node>(node->text, node->location, std::move(new_children)); + } else if ((node->identifier == AST_Node_Type::Equation) && node->text == "=" && node->children.size() == 2 + && node->children[0]->identifier == AST_Node_Type::Const_Var_Decl) { + std::vector> new_children; + new_children.push_back(std::move(node->children[0]->children[0])); + new_children.push_back(std::move(node->children[1])); + return chaiscript::make_unique, eval::Const_Assign_Decl_AST_Node>(node->text, + node->location, + std::move(new_children)); } return node; @@ -388,8 +397,9 @@ namespace chaiscript { assert(children.size() == 1); chaiscript::eval::detail::Scope_Push_Pop spp(t_ss); - int i = start_int; - t_ss.add_object(id, var(&i)); + Boxed_Value bv_i(start_int); + auto &i = *static_cast(bv_i.get_ptr()); + t_ss.add_object(id, bv_i); try { for (; i < end_int; ++i) { diff --git a/include/chaiscript/language/chaiscript_parser.hpp b/include/chaiscript/language/chaiscript_parser.hpp index df64f9b93..715f6ffe0 100644 --- a/include/chaiscript/language/chaiscript_parser.hpp +++ b/include/chaiscript/language/chaiscript_parser.hpp @@ -14,6 +14,7 @@ #include #include #include +#include #include #include #include @@ -22,15 +23,11 @@ #include "../dispatchkit/boxed_value.hpp" #include "../utility/hash.hpp" #include "../utility/static_string.hpp" +#include "../utility/unicode.hpp" #include "chaiscript_common.hpp" #include "chaiscript_optimizer.hpp" #include "chaiscript_tracer.hpp" -#if defined(CHAISCRIPT_UTF16_UTF32) -#include -#include -#endif - #if defined(CHAISCRIPT_MSVC) && defined(max) && defined(min) #define CHAISCRIPT_PUSHED_MIN_MAX #pragma push_macro("max") // Why Microsoft? why? This is worse than bad @@ -64,41 +61,26 @@ namespace chaiscript { // Generic for u16, u32 and wchar template struct Char_Parser_Helper { - // common for all implementations - static std::string u8str_from_ll(long long val) { - using char_type = std::string::value_type; - - char_type c[2]; - c[1] = char_type(val); - c[0] = char_type(val >> 8); - - if (c[0] == 0) { - return std::string(1, c[1]); // size, character - } - - return std::string(c, 2); // char buffer, size - } - static string_type str_from_ll(long long val) { - using target_char_type = typename string_type::value_type; -#if defined(CHAISCRIPT_UTF16_UTF32) - // prepare converter - std::wstring_convert, target_char_type> converter; - // convert - return converter.from_bytes(u8str_from_ll(val)); -#else - // no conversion available, just put value as character - return string_type(1, target_char_type(val)); // size, character -#endif + string_type out; + utility::unicode::append_codepoint(out, static_cast(val)); + return out; } }; - // Specialization for char AKA UTF-8 + // Specialization for char AKA UTF-8: preserve raw two-byte packing + // of multi-character literals. template<> struct Char_Parser_Helper { static std::string str_from_ll(long long val) { - // little SFINAE trick to avoid base class - return Char_Parser_Helper::u8str_from_ll(val); + using char_type = std::string::value_type; + char_type c[2]; + c[1] = char_type(val); + c[0] = char_type(val >> 8); + if (c[0] == 0) { + return std::string(1, c[1]); + } + return std::string(c, 2); } }; } // namespace detail @@ -128,9 +110,7 @@ namespace chaiscript { template constexpr static void set_alphabet(Array2D &array, const First first, const Second second) noexcept { - auto *first_ptr = &std::get<0>(array) + static_cast(first); - auto *second_ptr = &std::get<0>(*first_ptr) + static_cast(second); - *second_ptr = true; + array[static_cast(first)][static_cast(second)] = true; } constexpr static std::array, detail::max_alphabet> build_alphabet() noexcept { @@ -360,13 +340,13 @@ namespace chaiscript { ++col; } - ++m_pos; + std::advance(m_pos, 1); } return *this; } constexpr Position &operator--() noexcept { - --m_pos; + std::advance(m_pos, -1); if (*m_pos == '\n') { --line; col = m_last_col; @@ -377,7 +357,7 @@ namespace chaiscript { } constexpr Position &operator+=(size_t t_distance) noexcept { - *this = (*this) + t_distance; + *this = *this + t_distance; return *this; } @@ -406,9 +386,9 @@ namespace chaiscript { constexpr bool operator!=(const Position &t_rhs) const noexcept { return m_pos != t_rhs.m_pos; } - constexpr bool has_more() const noexcept { return m_pos != m_end; } + [[nodiscard]] constexpr bool has_more() const noexcept { return m_pos != m_end; } - constexpr size_t remaining() const noexcept { return static_cast(m_end - m_pos); } + [[nodiscard]] constexpr size_t remaining() const noexcept { return static_cast(m_end - m_pos); } constexpr const char &operator*() const noexcept { if (m_pos == m_end) { @@ -458,7 +438,7 @@ namespace chaiscript { constexpr static Operator_Matches m_operator_matches{}; /// test a char in an m_alphabet - constexpr bool char_in_alphabet(char c, detail::Alphabet a) const noexcept { return m_alphabet[a][static_cast(c)]; } + [[nodiscard]] constexpr bool char_in_alphabet(char c, detail::Alphabet a) const noexcept { return m_alphabet[a][static_cast(c)]; } /// Prints the parsed ast_nodes as a tree void debug_print(const AST_Node &t, std::string prepend = "") const override { @@ -509,7 +489,7 @@ namespace chaiscript { if (m_position.remaining() >= len) { const char *file_pos = &(*m_position); for (size_t pos = 0; pos < len; ++pos) { - if (sym.c_str()[pos] != file_pos[pos]) { + if (sym[pos] != *std::next(file_pos, static_cast(pos))) { return false; } } @@ -530,20 +510,9 @@ namespace chaiscript { } } return true; - } else if (Symbol_(m_singleline_comment)) { - while (m_position.has_more()) { - if (Symbol_(m_cr_lf)) { - m_position -= 2; - break; - } else if (Char_('\n')) { - --m_position; - break; - } else { - ++m_position; - } - } - return true; - } else if (Symbol_(m_annotation)) { + } + + if (Symbol_(m_singleline_comment) || Symbol_(m_annotation)) { while (m_position.has_more()) { if (Symbol_(m_cr_lf)) { m_position -= 2; @@ -1060,7 +1029,7 @@ namespace chaiscript { template struct Char_Parser { string_type &match; - using char_type = typename string_type::value_type; + using char_type = string_type::value_type; bool is_escaped = false; bool is_interpolated = false; bool saw_interpolation_marker = false; @@ -1119,40 +1088,20 @@ namespace chaiscript { } void process_unicode() { - const auto ch = static_cast(std::stoi(hex_matches, nullptr, 16)); + const auto ch = static_cast(std::stoi(hex_matches, nullptr, 16)); const auto match_size = hex_matches.size(); hex_matches.clear(); is_escaped = false; const auto u_size = unicode_size; unicode_size = 0; - char buf[4]; if (u_size != match_size) { throw exception::eval_error("Incomplete unicode escape sequence"); } - if (u_size == 4 && ch >= 0xD800 && ch <= 0xDFFF) { + if (u_size == 4 && utility::unicode::is_surrogate(ch)) { throw exception::eval_error("Invalid 16 bit universal character"); } - - if (ch < 0x80) { - match += static_cast(ch); - } else if (ch < 0x800) { - buf[0] = static_cast(0xC0 | (ch >> 6)); - buf[1] = static_cast(0x80 | (ch & 0x3F)); - match.append(buf, 2); - } else if (ch < 0x10000) { - buf[0] = static_cast(0xE0 | (ch >> 12)); - buf[1] = static_cast(0x80 | ((ch >> 6) & 0x3F)); - buf[2] = static_cast(0x80 | (ch & 0x3F)); - match.append(buf, 3); - } else if (ch < 0x200000) { - buf[0] = static_cast(0xF0 | (ch >> 18)); - buf[1] = static_cast(0x80 | ((ch >> 12) & 0x3F)); - buf[2] = static_cast(0x80 | ((ch >> 6) & 0x3F)); - buf[3] = static_cast(0x80 | (ch & 0x3F)); - match.append(buf, 4); - } else { - // this must be an invalid escape sequence? + if (utility::unicode::append_utf8(match, ch) == 0) { throw exception::eval_error("Invalid 32 bit universal character"); } } @@ -1363,6 +1312,109 @@ namespace chaiscript { } } + /// Reads a raw string from input R"delimiter(content)delimiter", without skipping initial whitespace + bool Raw_String_() { + if (m_position.has_more() && (*m_position == 'R') && m_position.remaining() > 1 && *(m_position + 1) == '"') { + auto s = m_position + 2; + + // Read the delimiter (may be empty) + std::string delimiter; + while (s.has_more() && *s != '(' && *s != '\n') { + delimiter.push_back(*s); + ++s; + } + + if (!s.has_more() || *s != '(') { + return false; + } + + // Skip the opening '(' + ++s; + + // Build the closing sequence: )delimiter" + std::string close_seq = ")"; + close_seq += delimiter; + close_seq += '"'; + + // Search for the closing sequence + while (s.has_more()) { + if (*s == close_seq[0]) { + // Check if we match the full closing sequence + auto t = s; + std::size_t i = 0; + while (t.has_more() && i < close_seq.size() && *t == close_seq[i]) { + ++t; + ++i; + } + if (i == close_seq.size()) { + // Found the closing sequence + m_position = t; + return true; + } + } + if (*s == '\n') { + m_position.col = 1; + } + ++s; + } + + throw exception::eval_error("Unclosed raw string", File_Position(m_position.line, m_position.col), *m_filename); + } + return false; + } + + /// Reads and captures a raw string from input. No escape processing or interpolation. + bool Raw_String() { + Depth_Counter dc{this}; + SkipWS(); + + const auto start = m_position; + + if (Raw_String_()) { + // Extract content between R"delimiter( and )delimiter" + auto s = start + 2; // skip R" + + // Skip delimiter + std::string delimiter; + while (*s != '(') { + delimiter.push_back(*s); + ++s; + } + ++s; // skip '(' + + // Build closing sequence + std::string close_seq = ")"; + close_seq += delimiter; + close_seq += '"'; + + // Extract raw content up to closing sequence + std::string match; + auto end = m_position; // m_position is already past the closing sequence + + // Content is from s up to (end - close_seq.size()) + // We need to find the closing sequence from s + while (s.has_more()) { + if (*s == close_seq[0]) { + auto t = s; + std::size_t i = 0; + while (t.has_more() && i < close_seq.size() && *t == close_seq[i]) { + ++t; + ++i; + } + if (i == close_seq.size() && t == end) { + break; + } + } + match.push_back(*s); + ++s; + } + + m_match_stack.push_back(make_node>(match, start.line, start.col, const_var(match))); + return true; + } + return false; + } + /// Reads a character group from input, without skipping initial whitespace bool Single_Quoted_String_() { bool retval = false; @@ -1445,7 +1497,7 @@ namespace chaiscript { if (m_position.remaining() >= len) { auto tmp = m_position; for (size_t i = 0; tmp.has_more() && i < len; ++i) { - if (*tmp != t_s.c_str()[i]) { + if (*tmp != t_s[i]) { return false; } ++tmp; @@ -1472,7 +1524,7 @@ namespace chaiscript { return retval; } - bool is_operator(std::string_view t_s) const noexcept { return m_operator_matches.is_match(t_s); } + [[nodiscard]] bool is_operator(std::string_view t_s) const noexcept { return m_operator_matches.is_match(t_s); } /// Reads (and potentially captures) a symbol group from input if it matches the parameter bool Symbol(const utility::Static_String &t_s, const bool t_disallow_prevention = false) { @@ -1887,6 +1939,44 @@ namespace chaiscript { } /// Reads a class block from input + bool Namespace_Block() { + Depth_Counter dc{this}; + const auto prev_stack_top = m_match_stack.size(); + const auto prev_pos = m_position; + + if (Keyword("namespace")) { + if (Id(true)) { + std::string ns_name = m_match_stack.back()->text; + + while (Symbol("::")) { + if (!Id(true)) { + throw exception::eval_error("Incomplete namespace name after '::'", + File_Position(m_position.line, m_position.col), + *m_filename); + } + ns_name += "::" + m_match_stack.back()->text; + m_match_stack.pop_back(); + } + + m_match_stack.back() = make_node>(ns_name, prev_pos.line, prev_pos.col); + + while (Eol()) { + } + + if (Block()) { + build_match>(prev_stack_top); + return true; + } + } + + m_position = prev_pos; + while (prev_stack_top != m_match_stack.size()) { + m_match_stack.pop_back(); + } + } + return false; + } + bool Class(const bool t_class_allowed) { Depth_Counter dc{this}; bool retval = false; @@ -1908,6 +1998,13 @@ namespace chaiscript { const auto class_name = m_match_stack.back()->text; + // Optionally parse ': BaseClassName' for inheritance + if (Char(':')) { + if (!Id(true)) { + throw exception::eval_error("Missing base class name in definition", File_Position(m_position.line, m_position.col), *m_filename); + } + } + while (Eol()) { } @@ -1921,6 +2018,134 @@ namespace chaiscript { return retval; } + bool Using(const bool t_class_allowed) { + Depth_Counter dc{this}; + + const auto prev_stack_top = m_match_stack.size(); + + if (Keyword("using")) { + if (!t_class_allowed) { + throw exception::eval_error("Type alias definitions only allowed at top scope", + File_Position(m_position.line, m_position.col), + *m_filename); + } + + if (!Id(true)) { + throw exception::eval_error("Missing type name in 'using' declaration", + File_Position(m_position.line, m_position.col), + *m_filename); + } + + if (!Symbol("=", true)) { + throw exception::eval_error("Missing '=' in 'using' declaration", + File_Position(m_position.line, m_position.col), + *m_filename); + } + + if (!Id(true)) { + throw exception::eval_error("Missing base type name in 'using' declaration", + File_Position(m_position.line, m_position.col), + *m_filename); + } + + build_match>(prev_stack_top); + return true; + } + + return false; + } + + bool Enum(const bool t_allowed) { + Depth_Counter dc{this}; + bool retval = false; + + const auto prev_stack_top = m_match_stack.size(); + + if (Keyword("enum")) { + if (!Keyword("class") && !Keyword("struct")) { + throw exception::eval_error("Expected 'class' or 'struct' after 'enum' (only 'enum class'/'enum struct' is supported)", + File_Position(m_position.line, m_position.col), + *m_filename); + } + + if (!t_allowed) { + throw exception::eval_error("Enum definitions only allowed at top scope", + File_Position(m_position.line, m_position.col), + *m_filename); + } + + retval = true; + + if (!Id(true)) { + throw exception::eval_error("Missing enum class name in definition", File_Position(m_position.line, m_position.col), *m_filename); + } + + std::string underlying_type = "int"; + if (Char(':')) { + if (!Id(false)) { + throw exception::eval_error("Expected underlying type after ':'", + File_Position(m_position.line, m_position.col), + *m_filename); + } + underlying_type = m_match_stack.back()->text; + m_match_stack.pop_back(); + } + + m_match_stack.push_back( + make_node>(underlying_type, m_position.line, m_position.col, const_var(underlying_type))); + + if (!Char('{')) { + throw exception::eval_error("Expected '{' after enum class declaration", File_Position(m_position.line, m_position.col), *m_filename); + } + + int next_value = 0; + + while (Eol()) { + } + + if (!Char('}')) { + do { + while (Eol()) { + } + + if (!Id(true)) { + throw exception::eval_error("Expected enum value name", File_Position(m_position.line, m_position.col), *m_filename); + } + + if (Symbol("=")) { + if (!Num()) { + throw exception::eval_error("Expected integer after '=' in enum definition", + File_Position(m_position.line, m_position.col), + *m_filename); + } + next_value = static_cast(std::stoi(m_match_stack.back()->text)); + m_match_stack.pop_back(); + } + + m_match_stack.push_back( + make_node>(std::to_string(next_value), m_position.line, m_position.col, const_var(next_value))); + ++next_value; + + while (Eol()) { + } + } while (Char(',') && !Char('}')); + + while (Eol()) { + } + + if (!Char('}')) { + throw exception::eval_error("Expected '}' to close enum class definition", + File_Position(m_position.line, m_position.col), + *m_filename); + } + } + + build_match>(prev_stack_top); + } + + return retval; + } + /// Reads a while block from input bool While() { Depth_Counter dc{this}; @@ -2172,7 +2397,7 @@ namespace chaiscript { const auto prev_stack_top = m_match_stack.size(); if (Keyword("return")) { - Operator(); + Equation(); build_match>(prev_stack_top); return true; } else { @@ -2212,7 +2437,7 @@ namespace chaiscript { bool retval = false; const auto prev_stack_top = m_match_stack.size(); - if (Lambda() || Num() || Quoted_String() || Single_Quoted_String() || Paren_Expression() || Inline_Container() || Id(false)) { + if (Lambda() || Num() || Raw_String() || Quoted_String() || Single_Quoted_String() || Paren_Expression() || Inline_Container() || Id(false)) { retval = true; bool has_more = true; @@ -2269,7 +2494,7 @@ namespace chaiscript { } build_match>(prev_stack_top); - } else if (Symbol(".")) { + } else if (Symbol(".") || Symbol("::")) { has_more = true; if (!(Id(true))) { throw exception::eval_error("Incomplete dot access fun call", File_Position(m_position.line, m_position.col), *m_filename); @@ -2324,6 +2549,18 @@ namespace chaiscript { throw exception::eval_error("Incomplete variable declaration", File_Position(m_position.line, m_position.col), *m_filename); } + } else if (Keyword("const")) { + retval = true; + + // consume optional 'var' or 'auto' after 'const' + Keyword("var") || Keyword("auto"); + + if (Id(true)) { + build_match>(prev_stack_top); + } else { + throw exception::eval_error("Incomplete const variable declaration", File_Position(m_position.line, m_position.col), *m_filename); + } + } else if (Keyword("global")) { retval = true; @@ -2461,7 +2698,7 @@ namespace chaiscript { bool retval = false; const auto prev_stack_top = m_match_stack.size(); - if (m_operators[t_precedence] != Operator_Precedence::Prefix) { + if (t_precedence < m_operators.size() && m_operators[t_precedence] < Operator_Precedence::Prefix) { if (Operator(t_precedence + 1)) { retval = true; std::string oper; @@ -2475,7 +2712,7 @@ namespace chaiscript { } switch (m_operators[t_precedence]) { - case (Operator_Precedence::Ternary_Cond): + case Operator_Precedence::Ternary_Cond: if (Symbol(":")) { if (!Operator(t_precedence + 1)) { throw exception::eval_error("Incomplete '" + oper + "' expression", @@ -2490,24 +2727,24 @@ namespace chaiscript { } break; - case (Operator_Precedence::Addition): - case (Operator_Precedence::Multiplication): - case (Operator_Precedence::Shift): - case (Operator_Precedence::Equality): - case (Operator_Precedence::Bitwise_And): - case (Operator_Precedence::Bitwise_Xor): - case (Operator_Precedence::Bitwise_Or): - case (Operator_Precedence::Comparison): + case Operator_Precedence::Addition: + case Operator_Precedence::Multiplication: + case Operator_Precedence::Shift: + case Operator_Precedence::Equality: + case Operator_Precedence::Bitwise_And: + case Operator_Precedence::Bitwise_Xor: + case Operator_Precedence::Bitwise_Or: + case Operator_Precedence::Comparison: build_match>(prev_stack_top, oper); break; - case (Operator_Precedence::Logical_And): + case Operator_Precedence::Logical_And: build_match>(prev_stack_top, oper); break; - case (Operator_Precedence::Logical_Or): + case Operator_Precedence::Logical_Or: build_match>(prev_stack_top, oper); break; - case (Operator_Precedence::Prefix): + case Operator_Precedence::Prefix: assert(false); // cannot reach here because of if() statement at the top break; @@ -2614,7 +2851,7 @@ namespace chaiscript { while (has_more) { const auto start = m_position; - if (Def(true, t_class_name) || Var_Decl(true, t_class_name)) { + if (Def(true, t_class_name)) { if (!saw_eol) { throw exception::eval_error("Two function definitions missing line separator", File_Position(start.line, start.col), @@ -2623,6 +2860,15 @@ namespace chaiscript { has_more = true; retval = true; saw_eol = true; + } else if (Var_Decl(true, t_class_name)) { + if (!saw_eol) { + throw exception::eval_error("Two expressions missing line separator", + File_Position(start.line, start.col), + *m_filename); + } + has_more = true; + retval = true; + saw_eol = false; } else if (Eol()) { has_more = true; retval = true; @@ -2645,7 +2891,7 @@ namespace chaiscript { while (has_more) { const auto start = m_position; - if (Def() || Try() || If() || While() || Class(t_class_allowed) || For() || Switch()) { + if (Def() || Try() || If() || While() || Namespace_Block() || Class(t_class_allowed) || Using(t_class_allowed) || Enum(t_class_allowed) || For() || Switch()) { if (!saw_eol) { throw exception::eval_error("Two function definitions missing line separator", File_Position(start.line, start.col), @@ -2695,7 +2941,7 @@ namespace chaiscript { /// Parses the given input string, tagging parsed ast_nodes with the given m_filename. AST_NodePtr parse_internal(const std::string &t_input, std::string t_fname) { const auto begin = t_input.empty() ? nullptr : &t_input.front(); - const auto end = begin == nullptr ? nullptr : begin + t_input.size(); + const auto end = begin == nullptr ? nullptr : std::next(begin, std::ssize(t_input)); m_position = Position(begin, end); m_filename = std::make_shared(std::move(t_fname)); diff --git a/include/chaiscript/language/chaiscript_prelude_docs.hpp b/include/chaiscript/language/chaiscript_prelude_docs.hpp index c7fe40c7e..6dc13797e 100644 --- a/include/chaiscript/language/chaiscript_prelude_docs.hpp +++ b/include/chaiscript/language/chaiscript_prelude_docs.hpp @@ -426,6 +426,9 @@ namespace ChaiScript_Language { /// \brief Returns true if the type is "void" bool is_type_void() const; + /// \brief Returns true if the type is an arithmetic type (int, double, etc.) + bool is_type_arithmetic() const; + /// \brief Returns the ChaiScript registered name for the type if one exists. string name() const; }; @@ -764,6 +767,90 @@ namespace ChaiScript_Language { /// \endcode bool call_exists(Function f, ...); + /// \brief Returns the type name of the given object as a string + /// + /// Example: + /// \code + /// eval> type_name(1) + /// int + /// eval> type_name("hello") + /// string + /// \endcode + /// + /// \sa Type_Info::name() + /// \sa Object::get_type_info() + string type_name(Object o); + + /// \brief Returns true if the object is of the named type + /// + /// Example: + /// \code + /// eval> is_type(1, "int") + /// true + /// eval> is_type(1, "string") + /// false + /// \endcode + /// + /// \sa Object::is_type() + bool is_type(Object o, string type_name); + + /// \brief Returns true if a function with the given name is registered + /// + /// Example: + /// \code + /// eval> function_exists("print") + /// true + /// eval> function_exists("nonexistent") + /// false + /// \endcode + bool function_exists(string name); + + /// \brief Returns a Map of all registered functions, keyed by function name + /// + /// Example: + /// \code + /// eval> var funcs = get_functions() + /// eval> funcs["print"].get_arity() + /// 1 + /// \endcode + /// + /// \sa Function + Map get_functions(); + + /// \brief Returns a Map of all scripting objects (variables), keyed by name + /// + /// Example: + /// \code + /// eval> var x = 42 + /// eval> var objs = get_objects() + /// eval> objs.count("x") + /// 1 + /// \endcode + Map get_objects(); + + /// \brief Returns a Type_Info for the named type + /// + /// Example: + /// \code + /// eval> type("int").name() + /// int + /// \endcode + /// + /// \param type_name The name of the type to look up + /// \param throw_on_fail If true (default), throws if the type is not found + /// \sa Type_Info + Type_Info type(string type_name, bool throw_on_fail = true); + + /// \brief Prints all registered functions to stdout + /// + /// Useful for debugging. Outputs a list of all functions registered in the system. + void dump_system(); + + /// \brief Prints information about the given object to stdout + /// + /// Useful for debugging. Outputs the type and value of the object. + void dump_object(Object o); + /// \brief Reverses a Range object so that the elements are accessed in reverse Range retro(Range); diff --git a/include/chaiscript/utility/hash.hpp b/include/chaiscript/utility/hash.hpp index 15bc4d9cb..06cef3a09 100644 --- a/include/chaiscript/utility/hash.hpp +++ b/include/chaiscript/utility/hash.hpp @@ -8,7 +8,9 @@ #define CHAISCRIPT_UTILITY_FNV1A_HPP_ #include "../chaiscript_defines.hpp" + #include +#include namespace chaiscript { namespace utility { @@ -28,7 +30,7 @@ namespace chaiscript { while (begin != end) { h = (h ^ (*begin)) * 0x01000193; - ++begin; + std::advance(begin, 1); } return h; @@ -43,7 +45,7 @@ namespace chaiscript { template static constexpr std::uint32_t hash(const char (&str)[N]) noexcept { - return hash(std::begin(str), std::end(str) - 1); + return hash(std::begin(str), std::prev(std::end(str))); } static constexpr std::uint32_t hash(std::string_view sv) noexcept { @@ -64,7 +66,7 @@ namespace chaiscript { hash += std::uint32_t(*begin); hash += hash << 10; hash ^= hash >> 6; - ++begin; + std::advance(begin, 1); } hash += hash << 3; diff --git a/include/chaiscript/utility/json.hpp b/include/chaiscript/utility/json.hpp index 25be353ad..cb5fdffe2 100644 --- a/include/chaiscript/utility/json.hpp +++ b/include/chaiscript/utility/json.hpp @@ -7,6 +7,7 @@ #include "../chaiscript_defines.hpp" #include "quick_flat_map.hpp" +#include "unicode.hpp" #include #include #include @@ -44,21 +45,17 @@ namespace chaiscript::json { = std::variant, std::vector, std::string, double, std::int64_t, bool>; struct Internal { - Internal(std::nullptr_t) - : d(nullptr) { - } - Internal() - : d(nullptr) { - } - Internal(Class c) + explicit Internal(std::nullptr_t) {} + Internal() = default; + Internal(const Class c) : d(make_type(c)) { } template - Internal(T t) + explicit Internal(T t) : d(std::move(t)) { } - static Data make_type(Class c) { + static Data make_type(const Class c) { switch (c) { case Class::Null: return nullptr; @@ -84,7 +81,7 @@ namespace chaiscript::json { } } - Class type() const noexcept { return Class(d.index()); } + [[nodiscard]] Class type() const noexcept { return Class(d.index()); } template decltype(auto) visit_or(Visitor &&visitor, Or &&other) const { @@ -108,14 +105,14 @@ namespace chaiscript::json { auto &Float() { return get_set_type(); } auto &Bool() { return get_set_type(); } - auto Map() const noexcept { return std::get_if(Class::Object)>(&d); } - auto Vector() const noexcept { return std::get_if(Class::Array)>(&d); } - auto String() const noexcept { return std::get_if(Class::String)>(&d); } - auto Int() const noexcept { return std::get_if(Class::Integral)>(&d); } - auto Float() const noexcept { return std::get_if(Class::Floating)>(&d); } - auto Bool() const noexcept { return std::get_if(Class::Boolean)>(&d); } + [[nodiscard]] auto Map() const noexcept { return std::get_if(Class::Object)>(&d); } + [[nodiscard]] auto Vector() const noexcept { return std::get_if(Class::Array)>(&d); } + [[nodiscard]] auto String() const noexcept { return std::get_if(Class::String)>(&d); } + [[nodiscard]] auto Int() const noexcept { return std::get_if(Class::Integral)>(&d); } + [[nodiscard]] auto Float() const noexcept { return std::get_if(Class::Floating)>(&d); } + [[nodiscard]] auto Bool() const noexcept { return std::get_if(Class::Boolean)>(&d); } - Data d; + Data d{nullptr}; }; Internal internal; @@ -131,10 +128,10 @@ namespace chaiscript::json { } JSONWrapper(std::nullptr_t) {} - typename Container::iterator begin() { return object ? object->begin() : typename Container::iterator(); } - typename Container::iterator end() { return object ? object->end() : typename Container::iterator(); } - typename Container::const_iterator begin() const { return object ? object->begin() : typename Container::iterator(); } - typename Container::const_iterator end() const { return object ? object->end() : typename Container::iterator(); } + Container::iterator begin() { return object ? object->begin() : typename Container::iterator(); } + Container::iterator end() { return object ? object->end() : typename Container::iterator(); } + [[nodiscard]] Container::const_iterator begin() const { return object ? object->begin() : typename Container::iterator(); } + [[nodiscard]] Container::const_iterator end() const { return object ? object->end() : typename Container::iterator(); } }; template @@ -147,10 +144,10 @@ namespace chaiscript::json { } JSONConstWrapper(std::nullptr_t) {} - typename Container::const_iterator begin() const noexcept { + [[nodiscard]] Container::const_iterator begin() const noexcept { return object ? object->begin() : typename Container::const_iterator(); } - typename Container::const_iterator end() const noexcept { return object ? object->end() : typename Container::const_iterator(); } + [[nodiscard]] Container::const_iterator end() const noexcept { return object ? object->end() : typename Container::const_iterator(); } }; JSON() = default; @@ -162,7 +159,9 @@ namespace chaiscript::json { JSON(initializer_list list) : internal(Class::Object) { - for (auto i = list.begin(), e = list.end(); i != e; ++i, ++i) { + for (auto i = list.begin(), e = list.end(); + i != e; + std::advance(i, 2)) { operator[](i->to_string()) = *std::next(i); } } @@ -460,17 +459,21 @@ namespace chaiscript::json { val += '\t'; break; case 'u': { - val += "\\u"; + std::string hex_matches; for (size_t i = 1; i <= 4; ++i) { c = str.at(offset + i); if ((c >= '0' && c <= '9') || (c >= 'a' && c <= 'f') || (c >= 'A' && c <= 'F')) { - val += c; + hex_matches += c; } else { throw std::runtime_error( std::string("JSON ERROR: String: Expected hex character in unicode escape, found '") + c + "'"); } } offset += 4; + const auto ch = static_cast(std::stoi(hex_matches, nullptr, 16)); + if (chaiscript::utility::unicode::append_utf8(val, ch) == 0) { + throw std::runtime_error(std::string("JSON ERROR: String: Invalid 32 bit universal character")); + } } break; default: val += '\\'; diff --git a/include/chaiscript/utility/json_wrap.hpp b/include/chaiscript/utility/json_wrap.hpp index d478f407e..82658a6bb 100644 --- a/include/chaiscript/utility/json_wrap.hpp +++ b/include/chaiscript/utility/json_wrap.hpp @@ -1,6 +1,7 @@ #ifndef CHAISCRIPT_SIMPLEJSON_WRAP_HPP #define CHAISCRIPT_SIMPLEJSON_WRAP_HPP +#include "../dispatchkit/dynamic_object.hpp" #include "json.hpp" namespace chaiscript { @@ -8,7 +9,10 @@ namespace chaiscript { public: static Module &library(Module &m) { m.add(chaiscript::fun([](const std::string &t_str) { return from_json(t_str); }), "from_json"); + m.add(chaiscript::fun([](const std::string &t_str) { return object_from_json(t_str); }), "object_from_json"); m.add(chaiscript::fun(&json_wrap::to_json), "to_json"); + m.add(chaiscript::fun(&json_wrap::map_to_object), "map_to_object"); + m.add(chaiscript::fun(&json_wrap::object_to_map), "object_to_map"); return m; } @@ -57,6 +61,63 @@ namespace chaiscript { } } + static Boxed_Value object_from_json(const json::JSON &t_json) { + switch (t_json.JSONType()) { + case json::JSON::Class::Null: + return Boxed_Value(); + case json::JSON::Class::Object: { + auto obj = dispatch::Dynamic_Object("JSON_Object"); + + for (const auto &p : t_json.object_range()) { + obj.get_attr(p.first) = object_from_json(p.second); + } + + return Boxed_Value(std::move(obj)); + } + case json::JSON::Class::Array: { + std::vector vec; + + for (const auto &p : t_json.array_range()) { + vec.emplace_back(object_from_json(p)); + } + + return Boxed_Value(vec); + } + case json::JSON::Class::String: + return Boxed_Value(t_json.to_string()); + case json::JSON::Class::Floating: + return Boxed_Value(t_json.to_float()); + case json::JSON::Class::Integral: + return Boxed_Value(t_json.to_int()); + case json::JSON::Class::Boolean: + return Boxed_Value(t_json.to_bool()); + } + + throw std::runtime_error("Unknown JSON type"); + } + + static Boxed_Value object_from_json(const std::string &t_json) { + try { + return object_from_json(json::JSON::Load(t_json)); + } catch (const std::out_of_range &) { + throw std::runtime_error("Unparsed JSON input"); + } + } + + static Boxed_Value map_to_object(const std::map &t_map) { + auto obj = dispatch::Dynamic_Object("JSON_Object"); + + for (const auto &p : t_map) { + obj.get_attr(p.first) = p.second; + } + + return Boxed_Value(std::move(obj)); + } + + static std::map object_to_map(const dispatch::Dynamic_Object &t_obj) { + return t_obj.get_attrs(); + } + static std::string to_json(const Boxed_Value &t_bv) { return to_json_object(t_bv).dump(); } static json::JSON to_json_object(const Boxed_Value &t_bv) { diff --git a/include/chaiscript/utility/static_string.hpp b/include/chaiscript/utility/static_string.hpp index 8613fd31b..39ceb5f52 100644 --- a/include/chaiscript/utility/static_string.hpp +++ b/include/chaiscript/utility/static_string.hpp @@ -7,6 +7,8 @@ #ifndef CHAISCRIPT_UTILITY_STATIC_STRING_HPP_ #define CHAISCRIPT_UTILITY_STATIC_STRING_HPP_ +#include + namespace chaiscript::utility { struct Static_String { template @@ -21,30 +23,17 @@ namespace chaiscript::utility { constexpr auto begin() const noexcept { return data; } - constexpr auto end() const noexcept { return data + m_size; } + constexpr auto end() const noexcept { return std::next(data, static_cast(m_size)); } + + constexpr auto operator[](const std::size_t idx) const noexcept { + return *std::next(data, static_cast(idx)); + } constexpr bool operator==(std::string_view other) const noexcept { - // return std::string_view(data, m_size) == other; - auto b1 = begin(); - const auto e1 = end(); - auto b2 = other.begin(); - const auto e2 = other.end(); - - if (e1 - b1 != e2 - b2) { - return false; - } - - while (b1 != e1) { - if (*b1 != *b2) { - return false; - } - ++b1; - ++b2; - } - return true; + return std::string_view(data, m_size) == other; } - bool operator==(const std::string &t_str) const noexcept { return std::equal(begin(), end(), std::cbegin(t_str), std::cend(t_str)); } + constexpr bool operator==(const std::string &t_str) const noexcept { return std::equal(begin(), end(), std::cbegin(t_str), std::cend(t_str)); } const size_t m_size; const char *data = nullptr; diff --git a/include/chaiscript/utility/unicode.hpp b/include/chaiscript/utility/unicode.hpp new file mode 100644 index 000000000..36363a311 --- /dev/null +++ b/include/chaiscript/utility/unicode.hpp @@ -0,0 +1,93 @@ +// This file is distributed under the BSD License. +// See "license.txt" for details. +// http://www.chaiscript.com + +#ifndef CHAISCRIPT_UTILITY_UNICODE_HPP_ +#define CHAISCRIPT_UTILITY_UNICODE_HPP_ + +#include +#include +#include + +namespace chaiscript { + namespace utility { + namespace unicode { + + inline constexpr std::uint32_t max_codepoint = 0x10FFFF; + + constexpr bool is_surrogate(std::uint32_t cp) noexcept { + return cp >= 0xD800 && cp <= 0xDFFF; + } + + // Append cp to out as UTF-8. Returns bytes written, or 0 if cp >= 0x200000. + // Surrogates are not rejected here; callers that care check is_surrogate() first. + inline std::size_t append_utf8(std::string &out, std::uint32_t cp) { + if (cp < 0x80) { + out += static_cast(cp); + return 1; + } + if (cp < 0x800) { + out += static_cast(0xC0 | (cp >> 6)); + out += static_cast(0x80 | (cp & 0x3F)); + return 2; + } + if (cp < 0x10000) { + out += static_cast(0xE0 | (cp >> 12)); + out += static_cast(0x80 | ((cp >> 6) & 0x3F)); + out += static_cast(0x80 | (cp & 0x3F)); + return 3; + } + if (cp < 0x200000) { + out += static_cast(0xF0 | (cp >> 18)); + out += static_cast(0x80 | ((cp >> 12) & 0x3F)); + out += static_cast(0x80 | ((cp >> 6) & 0x3F)); + out += static_cast(0x80 | (cp & 0x3F)); + return 4; + } + return 0; + } + + // Append cp to out as UTF-16. Returns code units written, or 0 if cp is + // a surrogate or > max_codepoint. + template + inline std::size_t append_utf16(std::basic_string &out, std::uint32_t cp) { + if (is_surrogate(cp) || cp > max_codepoint) { + return 0; + } + if (cp < 0x10000) { + out += static_cast(cp); + return 1; + } + const std::uint32_t v = cp - 0x10000; + out += static_cast(0xD800 | (v >> 10)); + out += static_cast(0xDC00 | (v & 0x3FF)); + return 2; + } + + // Append cp to a basic_string. Dispatches on sizeof(CharT): + // 1 byte -> UTF-8, 2 bytes -> UTF-16, 4 bytes -> UTF-32. + // Returns code units written, or 0 if the codepoint is invalid. + template + inline std::size_t append_codepoint(std::basic_string &out, std::uint32_t cp) { + if constexpr (sizeof(CharT) == 1) { + std::string tmp; + const auto n = append_utf8(tmp, cp); + out.append(tmp.begin(), tmp.end()); + return n; + } else if constexpr (sizeof(CharT) == 2) { + return append_utf16(out, cp); + } else { + static_assert(sizeof(CharT) == 4, "append_codepoint: unsupported CharT size"); + if (is_surrogate(cp) || cp > max_codepoint) { + return 0; + } + out += static_cast(cp); + return 1; + } + } + + } // namespace unicode + } // namespace utility +} // namespace chaiscript + +#endif diff --git a/readme.md b/readme.md index f36788a97..6b57f329d 100644 --- a/readme.md +++ b/readme.md @@ -91,6 +91,16 @@ the doxygen documentation in the build folder or see the website http://www.chaiscript.com. +Grammar +======= + +A formal EBNF grammar for ChaiScript is available in +[grammar/chaiscript.ebnf](grammar/chaiscript.ebnf). To view it as a railroad +diagram, paste the grammar into +[mingodad's railroad diagram generator](https://mingodad.github.io/plgh/json2ebnf.html) +or [bottlecaps.de/rr](https://www.bottlecaps.de/rr/ui). + + The shortest complete example possible follows: ```C++ diff --git a/releasenotes.md b/releasenotes.md index a622566eb..f1b2b53a9 100644 --- a/releasenotes.md +++ b/releasenotes.md @@ -4,6 +4,7 @@ Current Version: 6.1.1 ### Changes since 6.1.0 + * Add `set_file_reader` callback for custom file loading #116 — allows overriding how `eval_file` and `use` read files * Handle the returning of `&` to `*` types. This specifically comes up with `std::vector` and similar containers * Update CMake to use `LIBDIR` instead of `lib` #502 by @guoyunhe * Add documentation for installing ChaiScript with vcpkg #500 by @grdowns diff --git a/samples/fun_call_performance.cpp b/samples/fun_call_performance.cpp index da5417cee..0a9926799 100644 --- a/samples/fun_call_performance.cpp +++ b/samples/fun_call_performance.cpp @@ -30,9 +30,8 @@ char *mystrdup(const char *s) { #ifdef CHAISCRIPT_MSVC strcpy_s(d, len + 1, s); // Copy the characters #else - strncpy(d, s, len); // Copy the characters + strncpy(d, s, len + 1); // Copy the characters #endif - d[len] = '\0'; return d; // Return the new string } @@ -297,7 +296,7 @@ int main(int argc, char *argv[]) { ++i; } - std::string arg(i ? argv[i] : "--interactive"); + std::string arg(i ? *std::next(argv, i) : "--interactive"); enum { eInteractive, @@ -311,7 +310,7 @@ int main(int argc, char *argv[]) { std::cout << "insufficient input following " << arg << std::endl; return EXIT_FAILURE; } else { - arg = argv[++i]; + arg = *std::next(argv, ++i); } } else if (arg == "-" || arg == "--stdin") { arg = ""; diff --git a/src/libfuzzer_client.cpp b/src/libfuzzer_client.cpp index 39ebe2f4a..257266110 100644 --- a/src/libfuzzer_client.cpp +++ b/src/libfuzzer_client.cpp @@ -35,9 +35,8 @@ char *mystrdup(const char *s) { #ifdef CHAISCRIPT_MSVC strcpy_s(d, len + 1, s); // Copy the characters #else - strncpy(d, s, len); // Copy the characters + strncpy(d, s, len + 1); // Copy the characters #endif - d[len] = '\0'; return d; // Return the new string } diff --git a/src/main.cpp b/src/main.cpp index 6ec746fba..52db4b1d8 100644 --- a/src/main.cpp +++ b/src/main.cpp @@ -7,6 +7,8 @@ // This is an open source non-commercial project. Dear PVS-Studio, please check it. // PVS-Studio Static Code Analyzer for C, C++ and C#: http://www.viva64.com +#include +#include #include #include #include @@ -33,9 +35,8 @@ char *mystrdup(const char *s) { #ifdef CHAISCRIPT_MSVC strcpy_s(d, len + 1, s); // Copy the characters #else - strncpy(d, s, len); // Copy the characters + strncpy(d, s, len + 1); // Copy the characters #endif - d[len] = '\0'; return d; // Return the new string } @@ -282,7 +283,7 @@ int main(int argc, char *argv[]) { ++i; } - std::string arg(i != 0 ? argv[i] : "--interactive"); + std::string arg(i != 0 ? *std::next(argv, i) : "--interactive"); enum { eInteractive, @@ -296,7 +297,7 @@ int main(int argc, char *argv[]) { std::cout << "insufficient input following " << arg << '\n'; return EXIT_FAILURE; } - arg = argv[++i]; + arg = *std::next(argv, ++i); } else if (arg == "-" || arg == "--stdin") { arg = ""; diff --git a/src/sha3.h b/src/sha3.h index 31c0bc440..13767e72f 100644 --- a/src/sha3.h +++ b/src/sha3.h @@ -6,20 +6,19 @@ #pragma once -//#include "hash.h" +// #include "hash.h" #include // define fixed size integer types #ifdef _MSC_VER // Windows -typedef unsigned __int8 uint8_t; +typedef unsigned __int8 uint8_t; typedef unsigned __int64 uint64_t; #else // GCC #include #endif - /// compute SHA3 hash /** Usage: SHA3 sha3; @@ -37,18 +36,21 @@ class SHA3 //: public Hash { public: /// algorithm variants - enum Bits { Bits224 = 224, Bits256 = 256, Bits384 = 384, Bits512 = 512 }; + enum Bits { Bits224 = 224, + Bits256 = 256, + Bits384 = 384, + Bits512 = 512 }; /// same as reset() explicit SHA3(Bits bits = Bits256); /// compute hash of a memory block - std::string operator()(const void* data, size_t numBytes); + std::string operator()(const void *data, size_t numBytes); /// compute hash of a string, excluding final zero - std::string operator()(const std::string& text); + std::string operator()(const std::string &text); /// add arbitrary number of bytes - void add(const void* data, size_t numBytes); + void add(const void *data, size_t numBytes); /// return latest hash as hex characters std::string getHash(); @@ -58,24 +60,24 @@ class SHA3 //: public Hash private: /// process a full block - void processBlock(const void* data); + void processBlock(const void *data); /// process everything left in the internal buffer void processBuffer(); /// 1600 bits, stored as 25x64 bit, BlockSize is no more than 1152 bits (Keccak224) - enum { StateSize = 1600 / (8 * 8), - MaxBlockSize = 200 - 2 * (224 / 8) }; + enum { StateSize = 1600 / (8 * 8), + MaxBlockSize = 200 - 2 * (224 / 8) }; /// hash uint64_t m_hash[StateSize]; /// size of processed data in bytes uint64_t m_numBytes; /// block size (less or equal to MaxBlockSize) - size_t m_blockSize; + size_t m_blockSize; /// valid bytes in m_buffer - size_t m_bufferSize; + size_t m_bufferSize; /// bytes not processed yet - uint8_t m_buffer[MaxBlockSize]; + uint8_t m_buffer[MaxBlockSize]; /// variant - Bits m_bits; + Bits m_bits; }; diff --git a/src/test_module.cpp b/src/test_module.cpp index 6568b9b24..5da2d3890 100644 --- a/src/test_module.cpp +++ b/src/test_module.cpp @@ -5,30 +5,30 @@ class TestBaseType { public: - TestBaseType() + TestBaseType() noexcept : val(10) , const_val(15) , mdarray{} { } - TestBaseType(int) + TestBaseType(int) noexcept : val(10) , const_val(15) , mdarray{} { } - TestBaseType(int *) + TestBaseType(int *) noexcept : val(10) , const_val(15) , mdarray{} { } - TestBaseType(const TestBaseType &other) + TestBaseType(const TestBaseType &other) noexcept : val(other.val) , const_val(other.const_val) , const_val_ptr(&const_val) , func_member(other.func_member) { } - TestBaseType(TestBaseType &&other) + TestBaseType(TestBaseType &&other) noexcept : val(other.val) , const_val(other.const_val) , const_val_ptr(&const_val) @@ -59,7 +59,7 @@ class TestBaseType { class Type2 { public: - Type2(TestBaseType t_bt) + Type2(TestBaseType t_bt) noexcept : m_bt(std::move(t_bt)) , m_str("Hello World") { } diff --git a/unittests/add_vs_add_global.chai b/unittests/add_vs_add_global.chai new file mode 100644 index 000000000..545d880af --- /dev/null +++ b/unittests/add_vs_add_global.chai @@ -0,0 +1,43 @@ +// Test demonstrating the difference between add (local scope) and add_global/set_global (global scope) +// See issue #554 + +// --- Local variables (var / add) are scoped --- +// A local variable defined in a block is not visible outside that block +var local_val = 10 +assert_equal(local_val, 10) + +// Local variables can be reassigned in scope +local_val = 20 +assert_equal(local_val, 20) + +// Local variables inside a function are not visible outside +def set_local() { + var func_local = 99 + assert_equal(func_local, 99) +} +set_local() + +// --- Globals (add_global) are visible everywhere, including inside functions --- +var g_val = 42 +add_global(g_val, "my_global") + +def check_global() { + assert_equal(my_global, 42) +} +check_global() + +// add_global throws if the global already exists +assert_throws("Name already exists in current context my_global", fun() { add_global(1, "my_global") }) + +// --- set_global overwrites an existing global --- +set_global(100, "my_global") +assert_equal(my_global, 100) + +def check_global_updated() { + assert_equal(my_global, 100) +} +check_global_updated() + +// set_global can also create a new global if it doesn't exist +set_global(77, "new_global") +assert_equal(new_global, 77) diff --git a/unittests/assign_no_aliasing.chai b/unittests/assign_no_aliasing.chai new file mode 100644 index 000000000..0f0276004 --- /dev/null +++ b/unittests/assign_no_aliasing.chai @@ -0,0 +1,41 @@ +// Issue #635: optimized for loop with := must not produce dangling values +// := is reference-assign, so ret aliases i and sees the loop exit value + +// Basic: capture loop variable via := +var ret = 0 +for (var i = 0; i < 100; ++i) { + ret := i +} +assert_equal(100, ret) + +// Return from function containing optimized for loop +def loop_result() { + var ret = 0 + for (var i = 0; i < 200; ++i) { + ret := i + } + return ret +} +assert_equal(200, loop_result()) + +// Multiple calls return consistent results +assert_equal(loop_result(), loop_result()) + +// Nested optimized for loops +var outer_val = 0 +var inner_val = 0 +for (var i = 0; i < 10; ++i) { + for (var j = 0; j < 10; ++j) { + inner_val := j + } + outer_val := i +} +assert_equal(10, outer_val) +assert_equal(10, inner_val) + +// Value-assign (=) captures last body-iteration value, not exit value +var ret2 = 0 +for (var i = 0; i < 100; ++i) { + ret2 = i +} +assert_equal(99, ret2) diff --git a/unittests/async_engine_lifetime.chai b/unittests/async_engine_lifetime.chai new file mode 100644 index 000000000..94ee70662 --- /dev/null +++ b/unittests/async_engine_lifetime.chai @@ -0,0 +1,17 @@ +// Regression test for #632 and #636: Heap-use-after-free in async threads +// Async threads must complete before the engine is destroyed. + +var func = fun(){ + var ret = 0; + for (var i = 0; i < 1000; ++i) { + ret += i; + } + return ret; +} + +var fut1 = async(func); +var fut2 = async(func); + +// Wait for results to verify correctness +assert_equal(fut1.get(), 499500); +assert_equal(fut2.get(), 499500); diff --git a/unittests/async_engine_lifetime_test.cpp b/unittests/async_engine_lifetime_test.cpp new file mode 100644 index 000000000..698f64c03 --- /dev/null +++ b/unittests/async_engine_lifetime_test.cpp @@ -0,0 +1,46 @@ +// Regression test for #632 and #636: +// Heap-use-after-free when async threads outlive the ChaiScript engine. +// The engine must join all outstanding async threads before destroying shared state. + +#include + +int main() { + // Test 1: Async threads still running when engine is destroyed. + // Without the fix, this triggers heap-use-after-free under ASAN/TSAN. + for (int iter = 0; iter < 3; ++iter) { + { + chaiscript::ChaiScript chai; + try { + chai.eval(R"( + var func = fun(){ + var ret = 0; + for (var i = 0; i < 5000; ++i) { + ret += i; + } + return ret; + } + + var fut1 = async(func); + var fut2 = async(func); + )"); + // Engine destroyed here without waiting for futures. + } catch (const std::exception &) { + // Exceptions are fine + } + } + } + + // Test 2: Verify async still works correctly (results are accessible). + { + chaiscript::ChaiScript chai; + auto result = chai.eval(R"( + var f = async(fun() { return 42; }); + f.get(); + )"); + if (result != 42) { + return EXIT_FAILURE; + } + } + + return EXIT_SUCCESS; +} diff --git a/unittests/async_return_value.chai b/unittests/async_return_value.chai new file mode 100644 index 000000000..71bebbc8e --- /dev/null +++ b/unittests/async_return_value.chai @@ -0,0 +1,25 @@ +// Issue #635: async + optimized for loop must not segfault from dangling pointer +// := is reference-assign, so ret aliases i and sees the loop exit value + +var func = fun(){ + var ret = 0; + for (var i = 0; i < 1000; ++i) { + ret := i; + } + return ret; +} + +var&fut1 = async(func); +var fut2 = async(func); + +assert_equal(1000, fut1.get()) +assert_equal(1000, fut2.get()) + +// Multiple concurrent async calls to stress the scenario +var results = [] +for (var n = 0; n < 5; ++n) { + results.push_back(async(func)) +} +for (var n = 0; n < 5; ++n) { + assert_equal(1000, results[n].get()) +} diff --git a/unittests/catch.hpp b/unittests/catch.hpp deleted file mode 100644 index 2e78ece81..000000000 --- a/unittests/catch.hpp +++ /dev/null @@ -1,18073 +0,0 @@ -/* - * Catch v2.13.6 - * Generated: 2021-04-16 18:23:38.044268 - * ---------------------------------------------------------- - * This file has been merged from multiple headers. Please don't edit it directly - * Copyright (c) 2021 Two Blue Cubes Ltd. All rights reserved. - * - * Distributed under the Boost Software License, Version 1.0. (See accompanying - * file LICENSE_1_0.txt or copy at http://www.boost.org/LICENSE_1_0.txt) - */ -#ifndef TWOBLUECUBES_SINGLE_INCLUDE_CATCH_HPP_INCLUDED -#define TWOBLUECUBES_SINGLE_INCLUDE_CATCH_HPP_INCLUDED -// start catch.hpp - -#define CATCH_VERSION_MAJOR 2 -#define CATCH_VERSION_MINOR 13 -#define CATCH_VERSION_PATCH 6 - -#ifdef __clang__ -#pragma clang system_header -#elif defined __GNUC__ -#pragma GCC system_header -#endif - -// start catch_suppress_warnings.h - -#ifdef __clang__ -#ifdef __ICC // icpc defines the __clang__ macro -#pragma warning(push) -#pragma warning(disable : 161 1682) -#else // __ICC -#pragma clang diagnostic push -#pragma clang diagnostic ignored "-Wpadded" -#pragma clang diagnostic ignored "-Wswitch-enum" -#pragma clang diagnostic ignored "-Wcovered-switch-default" -#endif -#elif defined __GNUC__ -// Because REQUIREs trigger GCC's -Wparentheses, and because still -// supported version of g++ have only buggy support for _Pragmas, -// Wparentheses have to be suppressed globally. -#pragma GCC diagnostic ignored "-Wparentheses" // See #674 for details - -#pragma GCC diagnostic push -#pragma GCC diagnostic ignored "-Wunused-variable" -#pragma GCC diagnostic ignored "-Wpadded" -#endif -// end catch_suppress_warnings.h -#if defined(CATCH_CONFIG_MAIN) || defined(CATCH_CONFIG_RUNNER) -#define CATCH_IMPL -#define CATCH_CONFIG_ALL_PARTS -#endif - -// In the impl file, we want to have access to all parts of the headers -// Can also be used to sanely support PCHs -#if defined(CATCH_CONFIG_ALL_PARTS) -#define CATCH_CONFIG_EXTERNAL_INTERFACES -#if defined(CATCH_CONFIG_DISABLE_MATCHERS) -#undef CATCH_CONFIG_DISABLE_MATCHERS -#endif -#if !defined(CATCH_CONFIG_ENABLE_CHRONO_STRINGMAKER) -#define CATCH_CONFIG_ENABLE_CHRONO_STRINGMAKER -#endif -#endif - -#if !defined(CATCH_CONFIG_IMPL_ONLY) -// start catch_platform.h - -// See e.g.: -// https://opensource.apple.com/source/CarbonHeaders/CarbonHeaders-18.1/TargetConditionals.h.auto.html -#ifdef __APPLE__ -#include -#if (defined(TARGET_OS_OSX) && TARGET_OS_OSX == 1) || (defined(TARGET_OS_MAC) && TARGET_OS_MAC == 1) -#define CATCH_PLATFORM_MAC -#elif (defined(TARGET_OS_IPHONE) && TARGET_OS_IPHONE == 1) -#define CATCH_PLATFORM_IPHONE -#endif - -#elif defined(linux) || defined(__linux) || defined(__linux__) -#define CATCH_PLATFORM_LINUX - -#elif defined(WIN32) || defined(__WIN32__) || defined(_WIN32) || defined(_MSC_VER) || defined(__MINGW32__) -#define CATCH_PLATFORM_WINDOWS -#endif - -// end catch_platform.h - -#ifdef CATCH_IMPL -#ifndef CLARA_CONFIG_MAIN -#define CLARA_CONFIG_MAIN_NOT_DEFINED -#define CLARA_CONFIG_MAIN -#endif -#endif - -// start catch_user_interfaces.h - -namespace Catch { - unsigned int rngSeed(); -} - -// end catch_user_interfaces.h -// start catch_tag_alias_autoregistrar.h - -// start catch_common.h - -// start catch_compiler_capabilities.h - -// Detect a number of compiler features - by compiler -// The following features are defined: -// -// CATCH_CONFIG_COUNTER : is the __COUNTER__ macro supported? -// CATCH_CONFIG_WINDOWS_SEH : is Windows SEH supported? -// CATCH_CONFIG_POSIX_SIGNALS : are POSIX signals supported? -// CATCH_CONFIG_DISABLE_EXCEPTIONS : Are exceptions enabled? -// **************** -// Note to maintainers: if new toggles are added please document them -// in configuration.md, too -// **************** - -// In general each macro has a _NO_ form -// (e.g. CATCH_CONFIG_NO_POSIX_SIGNALS) which disables the feature. -// Many features, at point of detection, define an _INTERNAL_ macro, so they -// can be combined, en-mass, with the _NO_ forms later. - -#ifdef __cplusplus - -#if (__cplusplus >= 201402L) || (defined(_MSVC_LANG) && _MSVC_LANG >= 201402L) -#define CATCH_CPP14_OR_GREATER -#endif - -#if (__cplusplus >= 201703L) || (defined(_MSVC_LANG) && _MSVC_LANG >= 201703L) -#define CATCH_CPP17_OR_GREATER -#endif - -#endif - -// Only GCC compiler should be used in this block, so other compilers trying to -// mask themselves as GCC should be ignored. -#if defined(__GNUC__) && !defined(__clang__) && !defined(__ICC) && !defined(__CUDACC__) && !defined(__LCC__) -#define CATCH_INTERNAL_START_WARNINGS_SUPPRESSION _Pragma("GCC diagnostic push") -#define CATCH_INTERNAL_STOP_WARNINGS_SUPPRESSION _Pragma("GCC diagnostic pop") - -#define CATCH_INTERNAL_IGNORE_BUT_WARN(...) (void)__builtin_constant_p(__VA_ARGS__) - -#endif - -#if defined(__clang__) - -#define CATCH_INTERNAL_START_WARNINGS_SUPPRESSION _Pragma("clang diagnostic push") -#define CATCH_INTERNAL_STOP_WARNINGS_SUPPRESSION _Pragma("clang diagnostic pop") - -// As of this writing, IBM XL's implementation of __builtin_constant_p has a bug -// which results in calls to destructors being emitted for each temporary, -// without a matching initialization. In practice, this can result in something -// like `std::string::~string` being called on an uninitialized value. -// -// For example, this code will likely segfault under IBM XL: -// ``` -// REQUIRE(std::string("12") + "34" == "1234") -// ``` -// -// Therefore, `CATCH_INTERNAL_IGNORE_BUT_WARN` is not implemented. -#if !defined(__ibmxl__) && !defined(__CUDACC__) -#define CATCH_INTERNAL_IGNORE_BUT_WARN(...) \ - (void)__builtin_constant_p(__VA_ARGS__) /* NOLINT(cppcoreguidelines-pro-type-vararg, hicpp-vararg) */ -#endif - -#define CATCH_INTERNAL_SUPPRESS_GLOBALS_WARNINGS \ - _Pragma("clang diagnostic ignored \"-Wexit-time-destructors\"") _Pragma("clang diagnostic ignored \"-Wglobal-constructors\"") - -#define CATCH_INTERNAL_SUPPRESS_PARENTHESES_WARNINGS _Pragma("clang diagnostic ignored \"-Wparentheses\"") - -#define CATCH_INTERNAL_SUPPRESS_UNUSED_WARNINGS _Pragma("clang diagnostic ignored \"-Wunused-variable\"") - -#define CATCH_INTERNAL_SUPPRESS_ZERO_VARIADIC_WARNINGS _Pragma("clang diagnostic ignored \"-Wgnu-zero-variadic-macro-arguments\"") - -#define CATCH_INTERNAL_SUPPRESS_UNUSED_TEMPLATE_WARNINGS _Pragma("clang diagnostic ignored \"-Wunused-template\"") - -#endif // __clang__ - -//////////////////////////////////////////////////////////////////////////////// -// Assume that non-Windows platforms support posix signals by default -#if !defined(CATCH_PLATFORM_WINDOWS) -#define CATCH_INTERNAL_CONFIG_POSIX_SIGNALS -#endif - -//////////////////////////////////////////////////////////////////////////////// -// We know some environments not to support full POSIX signals -#if defined(__CYGWIN__) || defined(__QNX__) || defined(__EMSCRIPTEN__) || defined(__DJGPP__) -#define CATCH_INTERNAL_CONFIG_NO_POSIX_SIGNALS -#endif - -#ifdef __OS400__ -#define CATCH_INTERNAL_CONFIG_NO_POSIX_SIGNALS -#define CATCH_CONFIG_COLOUR_NONE -#endif - -//////////////////////////////////////////////////////////////////////////////// -// Android somehow still does not support std::to_string -#if defined(__ANDROID__) -#define CATCH_INTERNAL_CONFIG_NO_CPP11_TO_STRING -#define CATCH_INTERNAL_CONFIG_ANDROID_LOGWRITE -#endif - -//////////////////////////////////////////////////////////////////////////////// -// Not all Windows environments support SEH properly -#if defined(__MINGW32__) -#define CATCH_INTERNAL_CONFIG_NO_WINDOWS_SEH -#endif - -//////////////////////////////////////////////////////////////////////////////// -// PS4 -#if defined(__ORBIS__) -#define CATCH_INTERNAL_CONFIG_NO_NEW_CAPTURE -#endif - -//////////////////////////////////////////////////////////////////////////////// -// Cygwin -#ifdef __CYGWIN__ - -// Required for some versions of Cygwin to declare gettimeofday -// see: http://stackoverflow.com/questions/36901803/gettimeofday-not-declared-in-this-scope-cygwin -#define _BSD_SOURCE -// some versions of cygwin (most) do not support std::to_string. Use the libstd check. -// https://gcc.gnu.org/onlinedocs/gcc-4.8.2/libstdc++/api/a01053_source.html line 2812-2813 -#if !((__cplusplus >= 201103L) && defined(_GLIBCXX_USE_C99) && !defined(_GLIBCXX_HAVE_BROKEN_VSWPRINTF)) - -#define CATCH_INTERNAL_CONFIG_NO_CPP11_TO_STRING - -#endif -#endif // __CYGWIN__ - -//////////////////////////////////////////////////////////////////////////////// -// Visual C++ -#if defined(_MSC_VER) - -#define CATCH_INTERNAL_START_WARNINGS_SUPPRESSION __pragma(warning(push)) -#define CATCH_INTERNAL_STOP_WARNINGS_SUPPRESSION __pragma(warning(pop)) - -// Universal Windows platform does not support SEH -// Or console colours (or console at all...) -#if defined(WINAPI_FAMILY) && (WINAPI_FAMILY == WINAPI_FAMILY_APP) -#define CATCH_CONFIG_COLOUR_NONE -#else -#define CATCH_INTERNAL_CONFIG_WINDOWS_SEH -#endif - -// MSVC traditional preprocessor needs some workaround for __VA_ARGS__ -// _MSVC_TRADITIONAL == 0 means new conformant preprocessor -// _MSVC_TRADITIONAL == 1 means old traditional non-conformant preprocessor -#if !defined(__clang__) // Handle Clang masquerading for msvc -#if !defined(_MSVC_TRADITIONAL) || (defined(_MSVC_TRADITIONAL) && _MSVC_TRADITIONAL) -#define CATCH_INTERNAL_CONFIG_TRADITIONAL_MSVC_PREPROCESSOR -#endif // MSVC_TRADITIONAL -#endif // __clang__ - -#endif // _MSC_VER - -#if defined(_REENTRANT) || defined(_MSC_VER) -// Enable async processing, as -pthread is specified or no additional linking is required -#define CATCH_INTERNAL_CONFIG_USE_ASYNC -#endif // _MSC_VER - -//////////////////////////////////////////////////////////////////////////////// -// Check if we are compiled with -fno-exceptions or equivalent -#if defined(__EXCEPTIONS) || defined(__cpp_exceptions) || defined(_CPPUNWIND) -#define CATCH_INTERNAL_CONFIG_EXCEPTIONS_ENABLED -#endif - -//////////////////////////////////////////////////////////////////////////////// -// DJGPP -#ifdef __DJGPP__ -#define CATCH_INTERNAL_CONFIG_NO_WCHAR -#endif // __DJGPP__ - -//////////////////////////////////////////////////////////////////////////////// -// Embarcadero C++Build -#if defined(__BORLANDC__) -#define CATCH_INTERNAL_CONFIG_POLYFILL_ISNAN -#endif - -//////////////////////////////////////////////////////////////////////////////// - -// Use of __COUNTER__ is suppressed during code analysis in -// CLion/AppCode 2017.2.x and former, because __COUNTER__ is not properly -// handled by it. -// Otherwise all supported compilers support COUNTER macro, -// but user still might want to turn it off -#if (!defined(__JETBRAINS_IDE__) || __JETBRAINS_IDE__ >= 20170300L) -#define CATCH_INTERNAL_CONFIG_COUNTER -#endif - -//////////////////////////////////////////////////////////////////////////////// - -// RTX is a special version of Windows that is real time. -// This means that it is detected as Windows, but does not provide -// the same set of capabilities as real Windows does. -#if defined(UNDER_RTSS) || defined(RTX64_BUILD) -#define CATCH_INTERNAL_CONFIG_NO_WINDOWS_SEH -#define CATCH_INTERNAL_CONFIG_NO_ASYNC -#define CATCH_CONFIG_COLOUR_NONE -#endif - -#if !defined(_GLIBCXX_USE_C99_MATH_TR1) -#define CATCH_INTERNAL_CONFIG_GLOBAL_NEXTAFTER -#endif - -// Various stdlib support checks that require __has_include -#if defined(__has_include) -// Check if string_view is available and usable -#if __has_include() && defined(CATCH_CPP17_OR_GREATER) -#define CATCH_INTERNAL_CONFIG_CPP17_STRING_VIEW -#endif - -// Check if optional is available and usable -#if __has_include() && defined(CATCH_CPP17_OR_GREATER) -#define CATCH_INTERNAL_CONFIG_CPP17_OPTIONAL -#endif // __has_include() && defined(CATCH_CPP17_OR_GREATER) - -// Check if byte is available and usable -#if __has_include() && defined(CATCH_CPP17_OR_GREATER) -#include -#if __cpp_lib_byte > 0 -#define CATCH_INTERNAL_CONFIG_CPP17_BYTE -#endif -#endif // __has_include() && defined(CATCH_CPP17_OR_GREATER) - -// Check if variant is available and usable -#if __has_include() && defined(CATCH_CPP17_OR_GREATER) -#if defined(__clang__) && (__clang_major__ < 8) -// work around clang bug with libstdc++ https://bugs.llvm.org/show_bug.cgi?id=31852 -// fix should be in clang 8, workaround in libstdc++ 8.2 -#include -#if defined(__GLIBCXX__) && defined(_GLIBCXX_RELEASE) && (_GLIBCXX_RELEASE < 9) -#define CATCH_CONFIG_NO_CPP17_VARIANT -#else -#define CATCH_INTERNAL_CONFIG_CPP17_VARIANT -#endif // defined(__GLIBCXX__) && defined(_GLIBCXX_RELEASE) && (_GLIBCXX_RELEASE < 9) -#else -#define CATCH_INTERNAL_CONFIG_CPP17_VARIANT -#endif // defined(__clang__) && (__clang_major__ < 8) -#endif // __has_include() && defined(CATCH_CPP17_OR_GREATER) -#endif // defined(__has_include) - -#if defined(CATCH_INTERNAL_CONFIG_COUNTER) && !defined(CATCH_CONFIG_NO_COUNTER) && !defined(CATCH_CONFIG_COUNTER) -#define CATCH_CONFIG_COUNTER -#endif -#if defined(CATCH_INTERNAL_CONFIG_WINDOWS_SEH) && !defined(CATCH_CONFIG_NO_WINDOWS_SEH) && !defined(CATCH_CONFIG_WINDOWS_SEH) \ - && !defined(CATCH_INTERNAL_CONFIG_NO_WINDOWS_SEH) -#define CATCH_CONFIG_WINDOWS_SEH -#endif -// This is set by default, because we assume that unix compilers are posix-signal-compatible by default. -#if defined(CATCH_INTERNAL_CONFIG_POSIX_SIGNALS) && !defined(CATCH_INTERNAL_CONFIG_NO_POSIX_SIGNALS) \ - && !defined(CATCH_CONFIG_NO_POSIX_SIGNALS) && !defined(CATCH_CONFIG_POSIX_SIGNALS) -#define CATCH_CONFIG_POSIX_SIGNALS -#endif -// This is set by default, because we assume that compilers with no wchar_t support are just rare exceptions. -#if !defined(CATCH_INTERNAL_CONFIG_NO_WCHAR) && !defined(CATCH_CONFIG_NO_WCHAR) && !defined(CATCH_CONFIG_WCHAR) -#define CATCH_CONFIG_WCHAR -#endif - -#if !defined(CATCH_INTERNAL_CONFIG_NO_CPP11_TO_STRING) && !defined(CATCH_CONFIG_NO_CPP11_TO_STRING) \ - && !defined(CATCH_CONFIG_CPP11_TO_STRING) -#define CATCH_CONFIG_CPP11_TO_STRING -#endif - -#if defined(CATCH_INTERNAL_CONFIG_CPP17_OPTIONAL) && !defined(CATCH_CONFIG_NO_CPP17_OPTIONAL) && !defined(CATCH_CONFIG_CPP17_OPTIONAL) -#define CATCH_CONFIG_CPP17_OPTIONAL -#endif - -#if defined(CATCH_INTERNAL_CONFIG_CPP17_STRING_VIEW) && !defined(CATCH_CONFIG_NO_CPP17_STRING_VIEW) \ - && !defined(CATCH_CONFIG_CPP17_STRING_VIEW) -#define CATCH_CONFIG_CPP17_STRING_VIEW -#endif - -#if defined(CATCH_INTERNAL_CONFIG_CPP17_VARIANT) && !defined(CATCH_CONFIG_NO_CPP17_VARIANT) && !defined(CATCH_CONFIG_CPP17_VARIANT) -#define CATCH_CONFIG_CPP17_VARIANT -#endif - -#if defined(CATCH_INTERNAL_CONFIG_CPP17_BYTE) && !defined(CATCH_CONFIG_NO_CPP17_BYTE) && !defined(CATCH_CONFIG_CPP17_BYTE) -#define CATCH_CONFIG_CPP17_BYTE -#endif - -#if defined(CATCH_CONFIG_EXPERIMENTAL_REDIRECT) -#define CATCH_INTERNAL_CONFIG_NEW_CAPTURE -#endif - -#if defined(CATCH_INTERNAL_CONFIG_NEW_CAPTURE) && !defined(CATCH_INTERNAL_CONFIG_NO_NEW_CAPTURE) \ - && !defined(CATCH_CONFIG_NO_NEW_CAPTURE) && !defined(CATCH_CONFIG_NEW_CAPTURE) -#define CATCH_CONFIG_NEW_CAPTURE -#endif - -#if !defined(CATCH_INTERNAL_CONFIG_EXCEPTIONS_ENABLED) && !defined(CATCH_CONFIG_DISABLE_EXCEPTIONS) -#define CATCH_CONFIG_DISABLE_EXCEPTIONS -#endif - -#if defined(CATCH_INTERNAL_CONFIG_POLYFILL_ISNAN) && !defined(CATCH_CONFIG_NO_POLYFILL_ISNAN) && !defined(CATCH_CONFIG_POLYFILL_ISNAN) -#define CATCH_CONFIG_POLYFILL_ISNAN -#endif - -#if defined(CATCH_INTERNAL_CONFIG_USE_ASYNC) && !defined(CATCH_INTERNAL_CONFIG_NO_ASYNC) && !defined(CATCH_CONFIG_NO_USE_ASYNC) \ - && !defined(CATCH_CONFIG_USE_ASYNC) -#define CATCH_CONFIG_USE_ASYNC -#endif - -#if defined(CATCH_INTERNAL_CONFIG_ANDROID_LOGWRITE) && !defined(CATCH_CONFIG_NO_ANDROID_LOGWRITE) \ - && !defined(CATCH_CONFIG_ANDROID_LOGWRITE) -#define CATCH_CONFIG_ANDROID_LOGWRITE -#endif - -#if defined(CATCH_INTERNAL_CONFIG_GLOBAL_NEXTAFTER) && !defined(CATCH_CONFIG_NO_GLOBAL_NEXTAFTER) \ - && !defined(CATCH_CONFIG_GLOBAL_NEXTAFTER) -#define CATCH_CONFIG_GLOBAL_NEXTAFTER -#endif - -// Even if we do not think the compiler has that warning, we still have -// to provide a macro that can be used by the code. -#if !defined(CATCH_INTERNAL_START_WARNINGS_SUPPRESSION) -#define CATCH_INTERNAL_START_WARNINGS_SUPPRESSION -#endif -#if !defined(CATCH_INTERNAL_STOP_WARNINGS_SUPPRESSION) -#define CATCH_INTERNAL_STOP_WARNINGS_SUPPRESSION -#endif -#if !defined(CATCH_INTERNAL_SUPPRESS_PARENTHESES_WARNINGS) -#define CATCH_INTERNAL_SUPPRESS_PARENTHESES_WARNINGS -#endif -#if !defined(CATCH_INTERNAL_SUPPRESS_GLOBALS_WARNINGS) -#define CATCH_INTERNAL_SUPPRESS_GLOBALS_WARNINGS -#endif -#if !defined(CATCH_INTERNAL_SUPPRESS_UNUSED_WARNINGS) -#define CATCH_INTERNAL_SUPPRESS_UNUSED_WARNINGS -#endif -#if !defined(CATCH_INTERNAL_SUPPRESS_ZERO_VARIADIC_WARNINGS) -#define CATCH_INTERNAL_SUPPRESS_ZERO_VARIADIC_WARNINGS -#endif - -// The goal of this macro is to avoid evaluation of the arguments, but -// still have the compiler warn on problems inside... -#if !defined(CATCH_INTERNAL_IGNORE_BUT_WARN) -#define CATCH_INTERNAL_IGNORE_BUT_WARN(...) -#endif - -#if defined(__APPLE__) && defined(__apple_build_version__) && (__clang_major__ < 10) -#undef CATCH_INTERNAL_SUPPRESS_UNUSED_TEMPLATE_WARNINGS -#elif defined(__clang__) && (__clang_major__ < 5) -#undef CATCH_INTERNAL_SUPPRESS_UNUSED_TEMPLATE_WARNINGS -#endif - -#if !defined(CATCH_INTERNAL_SUPPRESS_UNUSED_TEMPLATE_WARNINGS) -#define CATCH_INTERNAL_SUPPRESS_UNUSED_TEMPLATE_WARNINGS -#endif - -#if defined(CATCH_CONFIG_DISABLE_EXCEPTIONS) -#define CATCH_TRY if ((true)) -#define CATCH_CATCH_ALL if ((false)) -#define CATCH_CATCH_ANON(type) if ((false)) -#else -#define CATCH_TRY try -#define CATCH_CATCH_ALL catch (...) -#define CATCH_CATCH_ANON(type) catch (type) -#endif - -#if defined(CATCH_INTERNAL_CONFIG_TRADITIONAL_MSVC_PREPROCESSOR) && !defined(CATCH_CONFIG_NO_TRADITIONAL_MSVC_PREPROCESSOR) \ - && !defined(CATCH_CONFIG_TRADITIONAL_MSVC_PREPROCESSOR) -#define CATCH_CONFIG_TRADITIONAL_MSVC_PREPROCESSOR -#endif - -// end catch_compiler_capabilities.h -#define INTERNAL_CATCH_UNIQUE_NAME_LINE2(name, line) name##line -#define INTERNAL_CATCH_UNIQUE_NAME_LINE(name, line) INTERNAL_CATCH_UNIQUE_NAME_LINE2(name, line) -#ifdef CATCH_CONFIG_COUNTER -#define INTERNAL_CATCH_UNIQUE_NAME(name) INTERNAL_CATCH_UNIQUE_NAME_LINE(name, __COUNTER__) -#else -#define INTERNAL_CATCH_UNIQUE_NAME(name) INTERNAL_CATCH_UNIQUE_NAME_LINE(name, __LINE__) -#endif - -#include -#include -#include - -// We need a dummy global operator<< so we can bring it into Catch namespace later -struct Catch_global_namespace_dummy { -}; -std::ostream &operator<<(std::ostream &, Catch_global_namespace_dummy); - -namespace Catch { - struct CaseSensitive { - enum Choice { - Yes, - No - }; - }; - - class NonCopyable { - NonCopyable(NonCopyable const &) = delete; - NonCopyable(NonCopyable &&) = delete; - NonCopyable &operator=(NonCopyable const &) = delete; - NonCopyable &operator=(NonCopyable &&) = delete; - - protected: - NonCopyable(); - virtual ~NonCopyable(); - }; - - struct SourceLineInfo { - SourceLineInfo() = delete; - SourceLineInfo(char const *_file, std::size_t _line) noexcept - : file(_file) - , line(_line) { - } - - SourceLineInfo(SourceLineInfo const &other) = default; - SourceLineInfo &operator=(SourceLineInfo const &) = default; - SourceLineInfo(SourceLineInfo &&) noexcept = default; - SourceLineInfo &operator=(SourceLineInfo &&) noexcept = default; - - bool empty() const noexcept { return file[0] == '\0'; } - bool operator==(SourceLineInfo const &other) const noexcept; - bool operator<(SourceLineInfo const &other) const noexcept; - - char const *file; - std::size_t line; - }; - - std::ostream &operator<<(std::ostream &os, SourceLineInfo const &info); - - // Bring in operator<< from global namespace into Catch namespace - // This is necessary because the overload of operator<< above makes - // lookup stop at namespace Catch - using ::operator<<; - - // Use this in variadic streaming macros to allow - // >> +StreamEndStop - // as well as - // >> stuff +StreamEndStop - struct StreamEndStop { - std::string operator+() const; - }; - template - T const &operator+(T const &value, StreamEndStop) { - return value; - } -} // namespace Catch - -#define CATCH_INTERNAL_LINEINFO ::Catch::SourceLineInfo(__FILE__, static_cast(__LINE__)) - -// end catch_common.h -namespace Catch { - struct RegistrarForTagAliases { - RegistrarForTagAliases(char const *alias, char const *tag, SourceLineInfo const &lineInfo); - }; - -} // end namespace Catch - -#define CATCH_REGISTER_TAG_ALIAS(alias, spec) \ - CATCH_INTERNAL_START_WARNINGS_SUPPRESSION \ - CATCH_INTERNAL_SUPPRESS_GLOBALS_WARNINGS \ - namespace { \ - Catch::RegistrarForTagAliases INTERNAL_CATCH_UNIQUE_NAME(AutoRegisterTagAlias)(alias, spec, CATCH_INTERNAL_LINEINFO); \ - } \ - CATCH_INTERNAL_STOP_WARNINGS_SUPPRESSION - -// end catch_tag_alias_autoregistrar.h -// start catch_test_registry.h - -// start catch_interfaces_testcase.h - -#include - -namespace Catch { - class TestSpec; - - struct ITestInvoker { - virtual void invoke() const = 0; - virtual ~ITestInvoker(); - }; - - class TestCase; - struct IConfig; - - struct ITestCaseRegistry { - virtual ~ITestCaseRegistry(); - virtual std::vector const &getAllTests() const = 0; - virtual std::vector const &getAllTestsSorted(IConfig const &config) const = 0; - }; - - bool isThrowSafe(TestCase const &testCase, IConfig const &config); - bool matchTest(TestCase const &testCase, TestSpec const &testSpec, IConfig const &config); - std::vector filterTests(std::vector const &testCases, TestSpec const &testSpec, IConfig const &config); - std::vector const &getAllTestCasesSorted(IConfig const &config); - -} // namespace Catch - -// end catch_interfaces_testcase.h -// start catch_stringref.h - -#include -#include -#include -#include - -namespace Catch { - /// A non-owning string class (similar to the forthcoming std::string_view) - /// Note that, because a StringRef may be a substring of another string, - /// it may not be null terminated. - class StringRef { - public: - using size_type = std::size_t; - using const_iterator = const char *; - - private: - static constexpr char const *const s_empty = ""; - - char const *m_start = s_empty; - size_type m_size = 0; - - public: // construction - constexpr StringRef() noexcept = default; - - StringRef(char const *rawChars) noexcept; - - constexpr StringRef(char const *rawChars, size_type size) noexcept - : m_start(rawChars) - , m_size(size) { - } - - StringRef(std::string const &stdString) noexcept - : m_start(stdString.c_str()) - , m_size(stdString.size()) { - } - - explicit operator std::string() const { return std::string(m_start, m_size); } - - public: // operators - auto operator==(StringRef const &other) const noexcept -> bool; - auto operator!=(StringRef const &other) const noexcept -> bool { return !(*this == other); } - - auto operator[](size_type index) const noexcept -> char { - assert(index < m_size); - return m_start[index]; - } - - public: // named queries - constexpr auto empty() const noexcept -> bool { return m_size == 0; } - constexpr auto size() const noexcept -> size_type { return m_size; } - - // Returns the current start pointer. If the StringRef is not - // null-terminated, throws std::domain_exception - auto c_str() const -> char const *; - - public: // substrings and searches - // Returns a substring of [start, start + length). - // If start + length > size(), then the substring is [start, size()). - // If start > size(), then the substring is empty. - auto substr(size_type start, size_type length) const noexcept -> StringRef; - - // Returns the current start pointer. May not be null-terminated. - auto data() const noexcept -> char const *; - - constexpr auto isNullTerminated() const noexcept -> bool { return m_start[m_size] == '\0'; } - - public: // iterators - constexpr const_iterator begin() const { return m_start; } - constexpr const_iterator end() const { return m_start + m_size; } - }; - - auto operator+=(std::string &lhs, StringRef const &sr) -> std::string &; - auto operator<<(std::ostream &os, StringRef const &sr) -> std::ostream &; - - constexpr auto operator"" _sr(char const *rawChars, std::size_t size) noexcept -> StringRef { - return StringRef(rawChars, size); - } -} // namespace Catch - -constexpr auto operator"" _catch_sr(char const *rawChars, std::size_t size) noexcept -> Catch::StringRef { - return Catch::StringRef(rawChars, size); -} - -// end catch_stringref.h -// start catch_preprocessor.hpp - -#define CATCH_RECURSION_LEVEL0(...) __VA_ARGS__ -#define CATCH_RECURSION_LEVEL1(...) CATCH_RECURSION_LEVEL0(CATCH_RECURSION_LEVEL0(CATCH_RECURSION_LEVEL0(__VA_ARGS__))) -#define CATCH_RECURSION_LEVEL2(...) CATCH_RECURSION_LEVEL1(CATCH_RECURSION_LEVEL1(CATCH_RECURSION_LEVEL1(__VA_ARGS__))) -#define CATCH_RECURSION_LEVEL3(...) CATCH_RECURSION_LEVEL2(CATCH_RECURSION_LEVEL2(CATCH_RECURSION_LEVEL2(__VA_ARGS__))) -#define CATCH_RECURSION_LEVEL4(...) CATCH_RECURSION_LEVEL3(CATCH_RECURSION_LEVEL3(CATCH_RECURSION_LEVEL3(__VA_ARGS__))) -#define CATCH_RECURSION_LEVEL5(...) CATCH_RECURSION_LEVEL4(CATCH_RECURSION_LEVEL4(CATCH_RECURSION_LEVEL4(__VA_ARGS__))) - -#ifdef CATCH_CONFIG_TRADITIONAL_MSVC_PREPROCESSOR -#define INTERNAL_CATCH_EXPAND_VARGS(...) __VA_ARGS__ -// MSVC needs more evaluations -#define CATCH_RECURSION_LEVEL6(...) CATCH_RECURSION_LEVEL5(CATCH_RECURSION_LEVEL5(CATCH_RECURSION_LEVEL5(__VA_ARGS__))) -#define CATCH_RECURSE(...) CATCH_RECURSION_LEVEL6(CATCH_RECURSION_LEVEL6(__VA_ARGS__)) -#else -#define CATCH_RECURSE(...) CATCH_RECURSION_LEVEL5(__VA_ARGS__) -#endif - -#define CATCH_REC_END(...) -#define CATCH_REC_OUT - -#define CATCH_EMPTY() -#define CATCH_DEFER(id) id CATCH_EMPTY() - -#define CATCH_REC_GET_END2() 0, CATCH_REC_END -#define CATCH_REC_GET_END1(...) CATCH_REC_GET_END2 -#define CATCH_REC_GET_END(...) CATCH_REC_GET_END1 -#define CATCH_REC_NEXT0(test, next, ...) next CATCH_REC_OUT -#define CATCH_REC_NEXT1(test, next) \ - CATCH_DEFER(CATCH_REC_NEXT0) \ - (test, next, 0) -#define CATCH_REC_NEXT(test, next) CATCH_REC_NEXT1(CATCH_REC_GET_END test, next) - -#define CATCH_REC_LIST0(f, x, peek, ...) , f(x) CATCH_DEFER(CATCH_REC_NEXT(peek, CATCH_REC_LIST1))(f, peek, __VA_ARGS__) -#define CATCH_REC_LIST1(f, x, peek, ...) , f(x) CATCH_DEFER(CATCH_REC_NEXT(peek, CATCH_REC_LIST0))(f, peek, __VA_ARGS__) -#define CATCH_REC_LIST2(f, x, peek, ...) f(x) CATCH_DEFER(CATCH_REC_NEXT(peek, CATCH_REC_LIST1))(f, peek, __VA_ARGS__) - -#define CATCH_REC_LIST0_UD(f, userdata, x, peek, ...) \ - , f(userdata, x) CATCH_DEFER(CATCH_REC_NEXT(peek, CATCH_REC_LIST1_UD))(f, userdata, peek, __VA_ARGS__) -#define CATCH_REC_LIST1_UD(f, userdata, x, peek, ...) \ - , f(userdata, x) CATCH_DEFER(CATCH_REC_NEXT(peek, CATCH_REC_LIST0_UD))(f, userdata, peek, __VA_ARGS__) -#define CATCH_REC_LIST2_UD(f, userdata, x, peek, ...) \ - f(userdata, x) CATCH_DEFER(CATCH_REC_NEXT(peek, CATCH_REC_LIST1_UD))(f, userdata, peek, __VA_ARGS__) - -// Applies the function macro `f` to each of the remaining parameters, inserts commas between the results, -// and passes userdata as the first parameter to each invocation, -// e.g. CATCH_REC_LIST_UD(f, x, a, b, c) evaluates to f(x, a), f(x, b), f(x, c) -#define CATCH_REC_LIST_UD(f, userdata, ...) CATCH_RECURSE(CATCH_REC_LIST2_UD(f, userdata, __VA_ARGS__, ()()(), ()()(), ()()(), 0)) - -#define CATCH_REC_LIST(f, ...) CATCH_RECURSE(CATCH_REC_LIST2(f, __VA_ARGS__, ()()(), ()()(), ()()(), 0)) - -#define INTERNAL_CATCH_EXPAND1(param) INTERNAL_CATCH_EXPAND2(param) -#define INTERNAL_CATCH_EXPAND2(...) INTERNAL_CATCH_NO##__VA_ARGS__ -#define INTERNAL_CATCH_DEF(...) INTERNAL_CATCH_DEF __VA_ARGS__ -#define INTERNAL_CATCH_NOINTERNAL_CATCH_DEF -#define INTERNAL_CATCH_STRINGIZE(...) INTERNAL_CATCH_STRINGIZE2(__VA_ARGS__) -#ifndef CATCH_CONFIG_TRADITIONAL_MSVC_PREPROCESSOR -#define INTERNAL_CATCH_STRINGIZE2(...) #__VA_ARGS__ -#define INTERNAL_CATCH_STRINGIZE_WITHOUT_PARENS(param) INTERNAL_CATCH_STRINGIZE(INTERNAL_CATCH_REMOVE_PARENS(param)) -#else -// MSVC is adding extra space and needs another indirection to expand INTERNAL_CATCH_NOINTERNAL_CATCH_DEF -#define INTERNAL_CATCH_STRINGIZE2(...) INTERNAL_CATCH_STRINGIZE3(__VA_ARGS__) -#define INTERNAL_CATCH_STRINGIZE3(...) #__VA_ARGS__ -#define INTERNAL_CATCH_STRINGIZE_WITHOUT_PARENS(param) (INTERNAL_CATCH_STRINGIZE(INTERNAL_CATCH_REMOVE_PARENS(param)) + 1) -#endif - -#define INTERNAL_CATCH_MAKE_NAMESPACE2(...) ns_##__VA_ARGS__ -#define INTERNAL_CATCH_MAKE_NAMESPACE(name) INTERNAL_CATCH_MAKE_NAMESPACE2(name) - -#define INTERNAL_CATCH_REMOVE_PARENS(...) INTERNAL_CATCH_EXPAND1(INTERNAL_CATCH_DEF __VA_ARGS__) - -#ifndef CATCH_CONFIG_TRADITIONAL_MSVC_PREPROCESSOR -#define INTERNAL_CATCH_MAKE_TYPE_LIST2(...) decltype(get_wrapper()) -#define INTERNAL_CATCH_MAKE_TYPE_LIST(...) INTERNAL_CATCH_MAKE_TYPE_LIST2(INTERNAL_CATCH_REMOVE_PARENS(__VA_ARGS__)) -#else -#define INTERNAL_CATCH_MAKE_TYPE_LIST2(...) \ - INTERNAL_CATCH_EXPAND_VARGS(decltype(get_wrapper())) -#define INTERNAL_CATCH_MAKE_TYPE_LIST(...) \ - INTERNAL_CATCH_EXPAND_VARGS(INTERNAL_CATCH_MAKE_TYPE_LIST2(INTERNAL_CATCH_REMOVE_PARENS(__VA_ARGS__))) -#endif - -#define INTERNAL_CATCH_MAKE_TYPE_LISTS_FROM_TYPES(...) CATCH_REC_LIST(INTERNAL_CATCH_MAKE_TYPE_LIST, __VA_ARGS__) - -#define INTERNAL_CATCH_REMOVE_PARENS_1_ARG(_0) INTERNAL_CATCH_REMOVE_PARENS(_0) -#define INTERNAL_CATCH_REMOVE_PARENS_2_ARG(_0, _1) INTERNAL_CATCH_REMOVE_PARENS(_0), INTERNAL_CATCH_REMOVE_PARENS_1_ARG(_1) -#define INTERNAL_CATCH_REMOVE_PARENS_3_ARG(_0, _1, _2) INTERNAL_CATCH_REMOVE_PARENS(_0), INTERNAL_CATCH_REMOVE_PARENS_2_ARG(_1, _2) -#define INTERNAL_CATCH_REMOVE_PARENS_4_ARG(_0, _1, _2, _3) \ - INTERNAL_CATCH_REMOVE_PARENS(_0), INTERNAL_CATCH_REMOVE_PARENS_3_ARG(_1, _2, _3) -#define INTERNAL_CATCH_REMOVE_PARENS_5_ARG(_0, _1, _2, _3, _4) \ - INTERNAL_CATCH_REMOVE_PARENS(_0), INTERNAL_CATCH_REMOVE_PARENS_4_ARG(_1, _2, _3, _4) -#define INTERNAL_CATCH_REMOVE_PARENS_6_ARG(_0, _1, _2, _3, _4, _5) \ - INTERNAL_CATCH_REMOVE_PARENS(_0), INTERNAL_CATCH_REMOVE_PARENS_5_ARG(_1, _2, _3, _4, _5) -#define INTERNAL_CATCH_REMOVE_PARENS_7_ARG(_0, _1, _2, _3, _4, _5, _6) \ - INTERNAL_CATCH_REMOVE_PARENS(_0), INTERNAL_CATCH_REMOVE_PARENS_6_ARG(_1, _2, _3, _4, _5, _6) -#define INTERNAL_CATCH_REMOVE_PARENS_8_ARG(_0, _1, _2, _3, _4, _5, _6, _7) \ - INTERNAL_CATCH_REMOVE_PARENS(_0), INTERNAL_CATCH_REMOVE_PARENS_7_ARG(_1, _2, _3, _4, _5, _6, _7) -#define INTERNAL_CATCH_REMOVE_PARENS_9_ARG(_0, _1, _2, _3, _4, _5, _6, _7, _8) \ - INTERNAL_CATCH_REMOVE_PARENS(_0), INTERNAL_CATCH_REMOVE_PARENS_8_ARG(_1, _2, _3, _4, _5, _6, _7, _8) -#define INTERNAL_CATCH_REMOVE_PARENS_10_ARG(_0, _1, _2, _3, _4, _5, _6, _7, _8, _9) \ - INTERNAL_CATCH_REMOVE_PARENS(_0), INTERNAL_CATCH_REMOVE_PARENS_9_ARG(_1, _2, _3, _4, _5, _6, _7, _8, _9) -#define INTERNAL_CATCH_REMOVE_PARENS_11_ARG(_0, _1, _2, _3, _4, _5, _6, _7, _8, _9, _10) \ - INTERNAL_CATCH_REMOVE_PARENS(_0), INTERNAL_CATCH_REMOVE_PARENS_10_ARG(_1, _2, _3, _4, _5, _6, _7, _8, _9, _10) - -#define INTERNAL_CATCH_VA_NARGS_IMPL(_0, _1, _2, _3, _4, _5, _6, _7, _8, _9, _10, N, ...) N - -#define INTERNAL_CATCH_TYPE_GEN \ - template \ - struct TypeList { \ - }; \ - template \ - constexpr auto get_wrapper() noexcept->TypeList { \ - return {}; \ - } \ - template class...> \ - struct TemplateTypeList { \ - }; \ - template class... Cs> \ - constexpr auto get_wrapper() noexcept->TemplateTypeList { \ - return {}; \ - } \ - template \ - struct append; \ - template \ - struct rewrap; \ - template class, typename...> \ - struct create; \ - template class, typename> \ - struct convert; \ - \ - template \ - struct append { \ - using type = T; \ - }; \ - template class L1, typename... E1, template class L2, typename... E2, typename... Rest> \ - struct append, L2, Rest...> { \ - using type = typename append, Rest...>::type; \ - }; \ - template class L1, typename... E1, typename... Rest> \ - struct append, TypeList, Rest...> { \ - using type = L1; \ - }; \ - \ - template class Container, template class List, typename... elems> \ - struct rewrap, List> { \ - using type = TypeList>; \ - }; \ - template class Container, template class List, class... Elems, typename... Elements> \ - struct rewrap, List, Elements...> { \ - using type = typename append>, typename rewrap, Elements...>::type>::type; \ - }; \ - \ - template class Final, template class... Containers, typename... Types> \ - struct create, TypeList> { \ - using type = typename append, typename rewrap, Types...>::type...>::type; \ - }; \ - template class Final, template class List, typename... Ts> \ - struct convert> { \ - using type = typename append, TypeList...>::type; \ - }; - -#define INTERNAL_CATCH_NTTP_1(signature, ...) \ - template \ - struct Nttp { \ - }; \ - template \ - constexpr auto get_wrapper() noexcept->Nttp<__VA_ARGS__> { \ - return {}; \ - } \ - template class...> \ - struct NttpTemplateTypeList { \ - }; \ - template class... Cs> \ - constexpr auto get_wrapper() noexcept->NttpTemplateTypeList { \ - return {}; \ - } \ - \ - template class Container, \ - template \ - class List, \ - INTERNAL_CATCH_REMOVE_PARENS(signature)> \ - struct rewrap, List<__VA_ARGS__>> { \ - using type = TypeList>; \ - }; \ - template class Container, \ - template \ - class List, \ - INTERNAL_CATCH_REMOVE_PARENS(signature), \ - typename... Elements> \ - struct rewrap, List<__VA_ARGS__>, Elements...> { \ - using type = \ - typename append>, typename rewrap, Elements...>::type>::type; \ - }; \ - template class Final, template class... Containers, typename... Types> \ - struct create, TypeList> { \ - using type = typename append, typename rewrap, Types...>::type...>::type; \ - }; - -#define INTERNAL_CATCH_DECLARE_SIG_TEST0(TestName) -#define INTERNAL_CATCH_DECLARE_SIG_TEST1(TestName, signature) \ - template \ - static void TestName() -#define INTERNAL_CATCH_DECLARE_SIG_TEST_X(TestName, signature, ...) \ - template \ - static void TestName() - -#define INTERNAL_CATCH_DEFINE_SIG_TEST0(TestName) -#define INTERNAL_CATCH_DEFINE_SIG_TEST1(TestName, signature) \ - template \ - static void TestName() -#define INTERNAL_CATCH_DEFINE_SIG_TEST_X(TestName, signature, ...) \ - template \ - static void TestName() - -#define INTERNAL_CATCH_NTTP_REGISTER0(TestFunc, signature) \ - template \ - void reg_test(TypeList, Catch::NameAndTags nameAndTags) { \ - Catch::AutoReg(Catch::makeTestInvoker(&TestFunc), CATCH_INTERNAL_LINEINFO, Catch::StringRef(), nameAndTags); \ - } - -#define INTERNAL_CATCH_NTTP_REGISTER(TestFunc, signature, ...) \ - template \ - void reg_test(Nttp<__VA_ARGS__>, Catch::NameAndTags nameAndTags) { \ - Catch::AutoReg(Catch::makeTestInvoker(&TestFunc<__VA_ARGS__>), CATCH_INTERNAL_LINEINFO, Catch::StringRef(), nameAndTags); \ - } - -#define INTERNAL_CATCH_NTTP_REGISTER_METHOD0(TestName, signature, ...) \ - template \ - void reg_test(TypeList, Catch::StringRef className, Catch::NameAndTags nameAndTags) { \ - Catch::AutoReg(Catch::makeTestInvoker(&TestName::test), CATCH_INTERNAL_LINEINFO, className, nameAndTags); \ - } - -#define INTERNAL_CATCH_NTTP_REGISTER_METHOD(TestName, signature, ...) \ - template \ - void reg_test(Nttp<__VA_ARGS__>, Catch::StringRef className, Catch::NameAndTags nameAndTags) { \ - Catch::AutoReg(Catch::makeTestInvoker(&TestName<__VA_ARGS__>::test), CATCH_INTERNAL_LINEINFO, className, nameAndTags); \ - } - -#define INTERNAL_CATCH_DECLARE_SIG_TEST_METHOD0(TestName, ClassName) -#define INTERNAL_CATCH_DECLARE_SIG_TEST_METHOD1(TestName, ClassName, signature) \ - template \ - struct TestName : INTERNAL_CATCH_REMOVE_PARENS(ClassName) { \ - void test(); \ - } - -#define INTERNAL_CATCH_DECLARE_SIG_TEST_METHOD_X(TestName, ClassName, signature, ...) \ - template \ - struct TestName : INTERNAL_CATCH_REMOVE_PARENS(ClassName)<__VA_ARGS__> { \ - void test(); \ - } - -#define INTERNAL_CATCH_DEFINE_SIG_TEST_METHOD0(TestName) -#define INTERNAL_CATCH_DEFINE_SIG_TEST_METHOD1(TestName, signature) \ - template \ - void INTERNAL_CATCH_MAKE_NAMESPACE(TestName)::TestName::test() -#define INTERNAL_CATCH_DEFINE_SIG_TEST_METHOD_X(TestName, signature, ...) \ - template \ - void INTERNAL_CATCH_MAKE_NAMESPACE(TestName)::TestName<__VA_ARGS__>::test() - -#ifndef CATCH_CONFIG_TRADITIONAL_MSVC_PREPROCESSOR -#define INTERNAL_CATCH_NTTP_0 -#define INTERNAL_CATCH_NTTP_GEN(...) \ - INTERNAL_CATCH_VA_NARGS_IMPL(__VA_ARGS__, \ - INTERNAL_CATCH_NTTP_1(__VA_ARGS__), \ - INTERNAL_CATCH_NTTP_1(__VA_ARGS__), \ - INTERNAL_CATCH_NTTP_1(__VA_ARGS__), \ - INTERNAL_CATCH_NTTP_1(__VA_ARGS__), \ - INTERNAL_CATCH_NTTP_1(__VA_ARGS__), \ - INTERNAL_CATCH_NTTP_1(__VA_ARGS__), \ - INTERNAL_CATCH_NTTP_1(__VA_ARGS__), \ - INTERNAL_CATCH_NTTP_1(__VA_ARGS__), \ - INTERNAL_CATCH_NTTP_1(__VA_ARGS__), \ - INTERNAL_CATCH_NTTP_1(__VA_ARGS__), \ - INTERNAL_CATCH_NTTP_0) -#define INTERNAL_CATCH_DEFINE_SIG_TEST_METHOD(TestName, ...) \ - INTERNAL_CATCH_VA_NARGS_IMPL("dummy", \ - __VA_ARGS__, \ - INTERNAL_CATCH_DEFINE_SIG_TEST_METHOD_X, \ - INTERNAL_CATCH_DEFINE_SIG_TEST_METHOD_X, \ - INTERNAL_CATCH_DEFINE_SIG_TEST_METHOD_X, \ - INTERNAL_CATCH_DEFINE_SIG_TEST_METHOD_X, \ - INTERNAL_CATCH_DEFINE_SIG_TEST_METHOD_X, \ - INTERNAL_CATCH_DEFINE_SIG_TEST_METHOD_X, \ - INTERNAL_CATCH_DEFINE_SIG_TEST_METHOD_X, \ - INTERNAL_CATCH_DEFINE_SIG_TEST_METHOD_X, \ - INTERNAL_CATCH_DEFINE_SIG_TEST_METHOD_X, \ - INTERNAL_CATCH_DEFINE_SIG_TEST_METHOD1, \ - INTERNAL_CATCH_DEFINE_SIG_TEST_METHOD0) \ - (TestName, __VA_ARGS__) -#define INTERNAL_CATCH_DECLARE_SIG_TEST_METHOD(TestName, ClassName, ...) \ - INTERNAL_CATCH_VA_NARGS_IMPL("dummy", \ - __VA_ARGS__, \ - INTERNAL_CATCH_DECLARE_SIG_TEST_METHOD_X, \ - INTERNAL_CATCH_DECLARE_SIG_TEST_METHOD_X, \ - INTERNAL_CATCH_DECLARE_SIG_TEST_METHOD_X, \ - INTERNAL_CATCH_DECLARE_SIG_TEST_METHOD_X, \ - INTERNAL_CATCH_DECLARE_SIG_TEST_METHOD_X, \ - INTERNAL_CATCH_DECLARE_SIG_TEST_METHOD_X, \ - INTERNAL_CATCH_DECLARE_SIG_TEST_METHOD_X, \ - INTERNAL_CATCH_DECLARE_SIG_TEST_METHOD_X, \ - INTERNAL_CATCH_DECLARE_SIG_TEST_METHOD_X, \ - INTERNAL_CATCH_DECLARE_SIG_TEST_METHOD1, \ - INTERNAL_CATCH_DECLARE_SIG_TEST_METHOD0) \ - (TestName, ClassName, __VA_ARGS__) -#define INTERNAL_CATCH_NTTP_REG_METHOD_GEN(TestName, ...) \ - INTERNAL_CATCH_VA_NARGS_IMPL("dummy", \ - __VA_ARGS__, \ - INTERNAL_CATCH_NTTP_REGISTER_METHOD, \ - INTERNAL_CATCH_NTTP_REGISTER_METHOD, \ - INTERNAL_CATCH_NTTP_REGISTER_METHOD, \ - INTERNAL_CATCH_NTTP_REGISTER_METHOD, \ - INTERNAL_CATCH_NTTP_REGISTER_METHOD, \ - INTERNAL_CATCH_NTTP_REGISTER_METHOD, \ - INTERNAL_CATCH_NTTP_REGISTER_METHOD, \ - INTERNAL_CATCH_NTTP_REGISTER_METHOD, \ - INTERNAL_CATCH_NTTP_REGISTER_METHOD, \ - INTERNAL_CATCH_NTTP_REGISTER_METHOD0, \ - INTERNAL_CATCH_NTTP_REGISTER_METHOD0) \ - (TestName, __VA_ARGS__) -#define INTERNAL_CATCH_NTTP_REG_GEN(TestFunc, ...) \ - INTERNAL_CATCH_VA_NARGS_IMPL("dummy", \ - __VA_ARGS__, \ - INTERNAL_CATCH_NTTP_REGISTER, \ - INTERNAL_CATCH_NTTP_REGISTER, \ - INTERNAL_CATCH_NTTP_REGISTER, \ - INTERNAL_CATCH_NTTP_REGISTER, \ - INTERNAL_CATCH_NTTP_REGISTER, \ - INTERNAL_CATCH_NTTP_REGISTER, \ - INTERNAL_CATCH_NTTP_REGISTER, \ - INTERNAL_CATCH_NTTP_REGISTER, \ - INTERNAL_CATCH_NTTP_REGISTER, \ - INTERNAL_CATCH_NTTP_REGISTER0, \ - INTERNAL_CATCH_NTTP_REGISTER0) \ - (TestFunc, __VA_ARGS__) -#define INTERNAL_CATCH_DEFINE_SIG_TEST(TestName, ...) \ - INTERNAL_CATCH_VA_NARGS_IMPL("dummy", \ - __VA_ARGS__, \ - INTERNAL_CATCH_DEFINE_SIG_TEST_X, \ - INTERNAL_CATCH_DEFINE_SIG_TEST_X, \ - INTERNAL_CATCH_DEFINE_SIG_TEST_X, \ - INTERNAL_CATCH_DEFINE_SIG_TEST_X, \ - INTERNAL_CATCH_DEFINE_SIG_TEST_X, \ - INTERNAL_CATCH_DEFINE_SIG_TEST_X, \ - INTERNAL_CATCH_DEFINE_SIG_TEST_X, \ - INTERNAL_CATCH_DEFINE_SIG_TEST_X, \ - INTERNAL_CATCH_DEFINE_SIG_TEST_X, \ - INTERNAL_CATCH_DEFINE_SIG_TEST1, \ - INTERNAL_CATCH_DEFINE_SIG_TEST0) \ - (TestName, __VA_ARGS__) -#define INTERNAL_CATCH_DECLARE_SIG_TEST(TestName, ...) \ - INTERNAL_CATCH_VA_NARGS_IMPL("dummy", \ - __VA_ARGS__, \ - INTERNAL_CATCH_DECLARE_SIG_TEST_X, \ - INTERNAL_CATCH_DECLARE_SIG_TEST_X, \ - INTERNAL_CATCH_DECLARE_SIG_TEST_X, \ - INTERNAL_CATCH_DECLARE_SIG_TEST_X, \ - INTERNAL_CATCH_DECLARE_SIG_TEST_X, \ - INTERNAL_CATCH_DECLARE_SIG_TEST_X, \ - INTERNAL_CATCH_DEFINE_SIG_TEST_X, \ - INTERNAL_CATCH_DECLARE_SIG_TEST_X, \ - INTERNAL_CATCH_DECLARE_SIG_TEST_X, \ - INTERNAL_CATCH_DECLARE_SIG_TEST1, \ - INTERNAL_CATCH_DECLARE_SIG_TEST0) \ - (TestName, __VA_ARGS__) -#define INTERNAL_CATCH_REMOVE_PARENS_GEN(...) \ - INTERNAL_CATCH_VA_NARGS_IMPL(__VA_ARGS__, \ - INTERNAL_CATCH_REMOVE_PARENS_11_ARG, \ - INTERNAL_CATCH_REMOVE_PARENS_10_ARG, \ - INTERNAL_CATCH_REMOVE_PARENS_9_ARG, \ - INTERNAL_CATCH_REMOVE_PARENS_8_ARG, \ - INTERNAL_CATCH_REMOVE_PARENS_7_ARG, \ - INTERNAL_CATCH_REMOVE_PARENS_6_ARG, \ - INTERNAL_CATCH_REMOVE_PARENS_5_ARG, \ - INTERNAL_CATCH_REMOVE_PARENS_4_ARG, \ - INTERNAL_CATCH_REMOVE_PARENS_3_ARG, \ - INTERNAL_CATCH_REMOVE_PARENS_2_ARG, \ - INTERNAL_CATCH_REMOVE_PARENS_1_ARG) \ - (__VA_ARGS__) -#else -#define INTERNAL_CATCH_NTTP_0(signature) -#define INTERNAL_CATCH_NTTP_GEN(...) \ - INTERNAL_CATCH_EXPAND_VARGS(INTERNAL_CATCH_VA_NARGS_IMPL(__VA_ARGS__, \ - INTERNAL_CATCH_NTTP_1, \ - INTERNAL_CATCH_NTTP_1, \ - INTERNAL_CATCH_NTTP_1, \ - INTERNAL_CATCH_NTTP_1, \ - INTERNAL_CATCH_NTTP_1, \ - INTERNAL_CATCH_NTTP_1, \ - INTERNAL_CATCH_NTTP_1, \ - INTERNAL_CATCH_NTTP_1, \ - INTERNAL_CATCH_NTTP_1, \ - INTERNAL_CATCH_NTTP_1, \ - INTERNAL_CATCH_NTTP_0)(__VA_ARGS__)) -#define INTERNAL_CATCH_DEFINE_SIG_TEST_METHOD(TestName, ...) \ - INTERNAL_CATCH_EXPAND_VARGS(INTERNAL_CATCH_VA_NARGS_IMPL("dummy", \ - __VA_ARGS__, \ - INTERNAL_CATCH_DEFINE_SIG_TEST_METHOD_X, \ - INTERNAL_CATCH_DEFINE_SIG_TEST_METHOD_X, \ - INTERNAL_CATCH_DEFINE_SIG_TEST_METHOD_X, \ - INTERNAL_CATCH_DEFINE_SIG_TEST_METHOD_X, \ - INTERNAL_CATCH_DEFINE_SIG_TEST_METHOD_X, \ - INTERNAL_CATCH_DEFINE_SIG_TEST_METHOD_X, \ - INTERNAL_CATCH_DEFINE_SIG_TEST_METHOD_X, \ - INTERNAL_CATCH_DEFINE_SIG_TEST_METHOD_X, \ - INTERNAL_CATCH_DEFINE_SIG_TEST_METHOD_X, \ - INTERNAL_CATCH_DEFINE_SIG_TEST_METHOD1, \ - INTERNAL_CATCH_DEFINE_SIG_TEST_METHOD0)(TestName, __VA_ARGS__)) -#define INTERNAL_CATCH_DECLARE_SIG_TEST_METHOD(TestName, ClassName, ...) \ - INTERNAL_CATCH_EXPAND_VARGS(INTERNAL_CATCH_VA_NARGS_IMPL("dummy", \ - __VA_ARGS__, \ - INTERNAL_CATCH_DECLARE_SIG_TEST_METHOD_X, \ - INTERNAL_CATCH_DECLARE_SIG_TEST_METHOD_X, \ - INTERNAL_CATCH_DECLARE_SIG_TEST_METHOD_X, \ - INTERNAL_CATCH_DECLARE_SIG_TEST_METHOD_X, \ - INTERNAL_CATCH_DECLARE_SIG_TEST_METHOD_X, \ - INTERNAL_CATCH_DECLARE_SIG_TEST_METHOD_X, \ - INTERNAL_CATCH_DECLARE_SIG_TEST_METHOD_X, \ - INTERNAL_CATCH_DECLARE_SIG_TEST_METHOD_X, \ - INTERNAL_CATCH_DECLARE_SIG_TEST_METHOD_X, \ - INTERNAL_CATCH_DECLARE_SIG_TEST_METHOD1, \ - INTERNAL_CATCH_DECLARE_SIG_TEST_METHOD0)(TestName, ClassName, __VA_ARGS__)) -#define INTERNAL_CATCH_NTTP_REG_METHOD_GEN(TestName, ...) \ - INTERNAL_CATCH_EXPAND_VARGS(INTERNAL_CATCH_VA_NARGS_IMPL("dummy", \ - __VA_ARGS__, \ - INTERNAL_CATCH_NTTP_REGISTER_METHOD, \ - INTERNAL_CATCH_NTTP_REGISTER_METHOD, \ - INTERNAL_CATCH_NTTP_REGISTER_METHOD, \ - INTERNAL_CATCH_NTTP_REGISTER_METHOD, \ - INTERNAL_CATCH_NTTP_REGISTER_METHOD, \ - INTERNAL_CATCH_NTTP_REGISTER_METHOD, \ - INTERNAL_CATCH_NTTP_REGISTER_METHOD, \ - INTERNAL_CATCH_NTTP_REGISTER_METHOD, \ - INTERNAL_CATCH_NTTP_REGISTER_METHOD, \ - INTERNAL_CATCH_NTTP_REGISTER_METHOD0, \ - INTERNAL_CATCH_NTTP_REGISTER_METHOD0)(TestName, __VA_ARGS__)) -#define INTERNAL_CATCH_NTTP_REG_GEN(TestFunc, ...) \ - INTERNAL_CATCH_EXPAND_VARGS(INTERNAL_CATCH_VA_NARGS_IMPL("dummy", \ - __VA_ARGS__, \ - INTERNAL_CATCH_NTTP_REGISTER, \ - INTERNAL_CATCH_NTTP_REGISTER, \ - INTERNAL_CATCH_NTTP_REGISTER, \ - INTERNAL_CATCH_NTTP_REGISTER, \ - INTERNAL_CATCH_NTTP_REGISTER, \ - INTERNAL_CATCH_NTTP_REGISTER, \ - INTERNAL_CATCH_NTTP_REGISTER, \ - INTERNAL_CATCH_NTTP_REGISTER, \ - INTERNAL_CATCH_NTTP_REGISTER, \ - INTERNAL_CATCH_NTTP_REGISTER0, \ - INTERNAL_CATCH_NTTP_REGISTER0)(TestFunc, __VA_ARGS__)) -#define INTERNAL_CATCH_DEFINE_SIG_TEST(TestName, ...) \ - INTERNAL_CATCH_EXPAND_VARGS(INTERNAL_CATCH_VA_NARGS_IMPL("dummy", \ - __VA_ARGS__, \ - INTERNAL_CATCH_DEFINE_SIG_TEST_X, \ - INTERNAL_CATCH_DEFINE_SIG_TEST_X, \ - INTERNAL_CATCH_DEFINE_SIG_TEST_X, \ - INTERNAL_CATCH_DEFINE_SIG_TEST_X, \ - INTERNAL_CATCH_DEFINE_SIG_TEST_X, \ - INTERNAL_CATCH_DEFINE_SIG_TEST_X, \ - INTERNAL_CATCH_DEFINE_SIG_TEST_X, \ - INTERNAL_CATCH_DEFINE_SIG_TEST_X, \ - INTERNAL_CATCH_DEFINE_SIG_TEST_X, \ - INTERNAL_CATCH_DEFINE_SIG_TEST1, \ - INTERNAL_CATCH_DEFINE_SIG_TEST0)(TestName, __VA_ARGS__)) -#define INTERNAL_CATCH_DECLARE_SIG_TEST(TestName, ...) \ - INTERNAL_CATCH_EXPAND_VARGS(INTERNAL_CATCH_VA_NARGS_IMPL("dummy", \ - __VA_ARGS__, \ - INTERNAL_CATCH_DECLARE_SIG_TEST_X, \ - INTERNAL_CATCH_DECLARE_SIG_TEST_X, \ - INTERNAL_CATCH_DECLARE_SIG_TEST_X, \ - INTERNAL_CATCH_DECLARE_SIG_TEST_X, \ - INTERNAL_CATCH_DECLARE_SIG_TEST_X, \ - INTERNAL_CATCH_DECLARE_SIG_TEST_X, \ - INTERNAL_CATCH_DEFINE_SIG_TEST_X, \ - INTERNAL_CATCH_DECLARE_SIG_TEST_X, \ - INTERNAL_CATCH_DECLARE_SIG_TEST_X, \ - INTERNAL_CATCH_DECLARE_SIG_TEST1, \ - INTERNAL_CATCH_DECLARE_SIG_TEST0)(TestName, __VA_ARGS__)) -#define INTERNAL_CATCH_REMOVE_PARENS_GEN(...) \ - INTERNAL_CATCH_EXPAND_VARGS(INTERNAL_CATCH_VA_NARGS_IMPL(__VA_ARGS__, \ - INTERNAL_CATCH_REMOVE_PARENS_11_ARG, \ - INTERNAL_CATCH_REMOVE_PARENS_10_ARG, \ - INTERNAL_CATCH_REMOVE_PARENS_9_ARG, \ - INTERNAL_CATCH_REMOVE_PARENS_8_ARG, \ - INTERNAL_CATCH_REMOVE_PARENS_7_ARG, \ - INTERNAL_CATCH_REMOVE_PARENS_6_ARG, \ - INTERNAL_CATCH_REMOVE_PARENS_5_ARG, \ - INTERNAL_CATCH_REMOVE_PARENS_4_ARG, \ - INTERNAL_CATCH_REMOVE_PARENS_3_ARG, \ - INTERNAL_CATCH_REMOVE_PARENS_2_ARG, \ - INTERNAL_CATCH_REMOVE_PARENS_1_ARG)(__VA_ARGS__)) -#endif - -// end catch_preprocessor.hpp -// start catch_meta.hpp - -#include - -namespace Catch { - template - struct always_false : std::false_type { - }; - - template - struct true_given : std::true_type { - }; - struct is_callable_tester { - template - true_given()(std::declval()...))> static test(int); - template - std::false_type static test(...); - }; - - template - struct is_callable; - - template - struct is_callable : decltype(is_callable_tester::test(0)) { - }; - -#if defined(__cpp_lib_is_invocable) && __cpp_lib_is_invocable >= 201703 - // std::result_of is deprecated in C++17 and removed in C++20. Hence, it is - // replaced with std::invoke_result here. - template - using FunctionReturnType = std::remove_reference_t>>; -#else - // Keep ::type here because we still support C++11 - template - using FunctionReturnType = typename std::remove_reference::type>::type>::type; -#endif - -} // namespace Catch - -namespace mpl_ { - struct na; -} - -// end catch_meta.hpp -namespace Catch { - template - class TestInvokerAsMethod : public ITestInvoker { - void (C::*m_testAsMethod)(); - - public: - TestInvokerAsMethod(void (C::*testAsMethod)()) noexcept - : m_testAsMethod(testAsMethod) { - } - - void invoke() const override { - C obj; - (obj.*m_testAsMethod)(); - } - }; - - auto makeTestInvoker(void (*testAsFunction)()) noexcept -> ITestInvoker *; - - template - auto makeTestInvoker(void (C::*testAsMethod)()) noexcept -> ITestInvoker * { - return new (std::nothrow) TestInvokerAsMethod(testAsMethod); - } - - struct NameAndTags { - NameAndTags(StringRef const &name_ = StringRef(), StringRef const &tags_ = StringRef()) noexcept; - StringRef name; - StringRef tags; - }; - - struct AutoReg : NonCopyable { - AutoReg(ITestInvoker *invoker, SourceLineInfo const &lineInfo, StringRef const &classOrMethod, NameAndTags const &nameAndTags) noexcept; - ~AutoReg(); - }; - -} // end namespace Catch - -#if defined(CATCH_CONFIG_DISABLE) -#define INTERNAL_CATCH_TESTCASE_NO_REGISTRATION(TestName, ...) static void TestName() -#define INTERNAL_CATCH_TESTCASE_METHOD_NO_REGISTRATION(TestName, ClassName, ...) \ - namespace { \ - struct TestName : INTERNAL_CATCH_REMOVE_PARENS(ClassName) { \ - void test(); \ - }; \ - } \ - void TestName::test() -#define INTERNAL_CATCH_TEMPLATE_TEST_CASE_NO_REGISTRATION_2(TestName, TestFunc, Name, Tags, Signature, ...) \ - INTERNAL_CATCH_DEFINE_SIG_TEST(TestFunc, INTERNAL_CATCH_REMOVE_PARENS(Signature)) -#define INTERNAL_CATCH_TEMPLATE_TEST_CASE_METHOD_NO_REGISTRATION_2(TestNameClass, TestName, ClassName, Name, Tags, Signature, ...) \ - namespace { \ - namespace INTERNAL_CATCH_MAKE_NAMESPACE(TestName) { \ - INTERNAL_CATCH_DECLARE_SIG_TEST_METHOD(TestName, ClassName, INTERNAL_CATCH_REMOVE_PARENS(Signature)); \ - } \ - } \ - INTERNAL_CATCH_DEFINE_SIG_TEST_METHOD(TestName, INTERNAL_CATCH_REMOVE_PARENS(Signature)) - -#ifndef CATCH_CONFIG_TRADITIONAL_MSVC_PREPROCESSOR -#define INTERNAL_CATCH_TEMPLATE_TEST_CASE_NO_REGISTRATION(Name, Tags, ...) \ - INTERNAL_CATCH_TEMPLATE_TEST_CASE_NO_REGISTRATION_2(INTERNAL_CATCH_UNIQUE_NAME(____C_A_T_C_H____T_E_M_P_L_A_T_E____T_E_S_T____), \ - INTERNAL_CATCH_UNIQUE_NAME( \ - ____C_A_T_C_H____T_E_M_P_L_A_T_E____T_E_S_T____F_U_N_C____), \ - Name, \ - Tags, \ - typename TestType, \ - __VA_ARGS__) -#else -#define INTERNAL_CATCH_TEMPLATE_TEST_CASE_NO_REGISTRATION(Name, Tags, ...) \ - INTERNAL_CATCH_EXPAND_VARGS(INTERNAL_CATCH_TEMPLATE_TEST_CASE_NO_REGISTRATION_2( \ - INTERNAL_CATCH_UNIQUE_NAME(____C_A_T_C_H____T_E_M_P_L_A_T_E____T_E_S_T____), \ - INTERNAL_CATCH_UNIQUE_NAME(____C_A_T_C_H____T_E_M_P_L_A_T_E____T_E_S_T____F_U_N_C____), \ - Name, \ - Tags, \ - typename TestType, \ - __VA_ARGS__)) -#endif - -#ifndef CATCH_CONFIG_TRADITIONAL_MSVC_PREPROCESSOR -#define INTERNAL_CATCH_TEMPLATE_TEST_CASE_SIG_NO_REGISTRATION(Name, Tags, Signature, ...) \ - INTERNAL_CATCH_TEMPLATE_TEST_CASE_NO_REGISTRATION_2(INTERNAL_CATCH_UNIQUE_NAME(____C_A_T_C_H____T_E_M_P_L_A_T_E____T_E_S_T____), \ - INTERNAL_CATCH_UNIQUE_NAME( \ - ____C_A_T_C_H____T_E_M_P_L_A_T_E____T_E_S_T____F_U_N_C____), \ - Name, \ - Tags, \ - Signature, \ - __VA_ARGS__) -#else -#define INTERNAL_CATCH_TEMPLATE_TEST_CASE_SIG_NO_REGISTRATION(Name, Tags, Signature, ...) \ - INTERNAL_CATCH_EXPAND_VARGS(INTERNAL_CATCH_TEMPLATE_TEST_CASE_NO_REGISTRATION_2( \ - INTERNAL_CATCH_UNIQUE_NAME(____C_A_T_C_H____T_E_M_P_L_A_T_E____T_E_S_T____), \ - INTERNAL_CATCH_UNIQUE_NAME(____C_A_T_C_H____T_E_M_P_L_A_T_E____T_E_S_T____F_U_N_C____), \ - Name, \ - Tags, \ - Signature, \ - __VA_ARGS__)) -#endif - -#ifndef CATCH_CONFIG_TRADITIONAL_MSVC_PREPROCESSOR -#define INTERNAL_CATCH_TEMPLATE_TEST_CASE_METHOD_NO_REGISTRATION(ClassName, Name, Tags, ...) \ - INTERNAL_CATCH_TEMPLATE_TEST_CASE_METHOD_NO_REGISTRATION_2(INTERNAL_CATCH_UNIQUE_NAME( \ - ____C_A_T_C_H____T_E_M_P_L_A_T_E____T_E_S_T____C_L_A_S_S____), \ - INTERNAL_CATCH_UNIQUE_NAME( \ - ____C_A_T_C_H____T_E_M_P_L_A_T_E____T_E_S_T____), \ - ClassName, \ - Name, \ - Tags, \ - typename T, \ - __VA_ARGS__) -#else -#define INTERNAL_CATCH_TEMPLATE_TEST_CASE_METHOD_NO_REGISTRATION(ClassName, Name, Tags, ...) \ - INTERNAL_CATCH_EXPAND_VARGS(INTERNAL_CATCH_TEMPLATE_TEST_CASE_METHOD_NO_REGISTRATION_2( \ - INTERNAL_CATCH_UNIQUE_NAME(____C_A_T_C_H____T_E_M_P_L_A_T_E____T_E_S_T____C_L_A_S_S____), \ - INTERNAL_CATCH_UNIQUE_NAME(____C_A_T_C_H____T_E_M_P_L_A_T_E____T_E_S_T____), \ - ClassName, \ - Name, \ - Tags, \ - typename T, \ - __VA_ARGS__)) -#endif - -#ifndef CATCH_CONFIG_TRADITIONAL_MSVC_PREPROCESSOR -#define INTERNAL_CATCH_TEMPLATE_TEST_CASE_METHOD_SIG_NO_REGISTRATION(ClassName, Name, Tags, Signature, ...) \ - INTERNAL_CATCH_TEMPLATE_TEST_CASE_METHOD_NO_REGISTRATION_2(INTERNAL_CATCH_UNIQUE_NAME( \ - ____C_A_T_C_H____T_E_M_P_L_A_T_E____T_E_S_T____C_L_A_S_S____), \ - INTERNAL_CATCH_UNIQUE_NAME( \ - ____C_A_T_C_H____T_E_M_P_L_A_T_E____T_E_S_T____), \ - ClassName, \ - Name, \ - Tags, \ - Signature, \ - __VA_ARGS__) -#else -#define INTERNAL_CATCH_TEMPLATE_TEST_CASE_METHOD_SIG_NO_REGISTRATION(ClassName, Name, Tags, Signature, ...) \ - INTERNAL_CATCH_EXPAND_VARGS(INTERNAL_CATCH_TEMPLATE_TEST_CASE_METHOD_NO_REGISTRATION_2( \ - INTERNAL_CATCH_UNIQUE_NAME(____C_A_T_C_H____T_E_M_P_L_A_T_E____T_E_S_T____C_L_A_S_S____), \ - INTERNAL_CATCH_UNIQUE_NAME(____C_A_T_C_H____T_E_M_P_L_A_T_E____T_E_S_T____), \ - ClassName, \ - Name, \ - Tags, \ - Signature, \ - __VA_ARGS__)) -#endif -#endif - -/////////////////////////////////////////////////////////////////////////////// -#define INTERNAL_CATCH_TESTCASE2(TestName, ...) \ - static void TestName(); \ - CATCH_INTERNAL_START_WARNINGS_SUPPRESSION \ - CATCH_INTERNAL_SUPPRESS_GLOBALS_WARNINGS \ - namespace { \ - Catch::AutoReg INTERNAL_CATCH_UNIQUE_NAME(autoRegistrar)(Catch::makeTestInvoker(&TestName), \ - CATCH_INTERNAL_LINEINFO, \ - Catch::StringRef(), \ - Catch::NameAndTags{__VA_ARGS__}); \ - } /* NOLINT */ \ - CATCH_INTERNAL_STOP_WARNINGS_SUPPRESSION \ - static void TestName() -#define INTERNAL_CATCH_TESTCASE(...) INTERNAL_CATCH_TESTCASE2(INTERNAL_CATCH_UNIQUE_NAME(____C_A_T_C_H____T_E_S_T____), __VA_ARGS__) - -/////////////////////////////////////////////////////////////////////////////// -#define INTERNAL_CATCH_METHOD_AS_TEST_CASE(QualifiedMethod, ...) \ - CATCH_INTERNAL_START_WARNINGS_SUPPRESSION \ - CATCH_INTERNAL_SUPPRESS_GLOBALS_WARNINGS \ - namespace { \ - Catch::AutoReg INTERNAL_CATCH_UNIQUE_NAME(autoRegistrar)(Catch::makeTestInvoker(&QualifiedMethod), \ - CATCH_INTERNAL_LINEINFO, \ - "&" #QualifiedMethod, \ - Catch::NameAndTags{__VA_ARGS__}); \ - } /* NOLINT */ \ - CATCH_INTERNAL_STOP_WARNINGS_SUPPRESSION - -/////////////////////////////////////////////////////////////////////////////// -#define INTERNAL_CATCH_TEST_CASE_METHOD2(TestName, ClassName, ...) \ - CATCH_INTERNAL_START_WARNINGS_SUPPRESSION \ - CATCH_INTERNAL_SUPPRESS_GLOBALS_WARNINGS \ - namespace { \ - struct TestName : INTERNAL_CATCH_REMOVE_PARENS(ClassName) { \ - void test(); \ - }; \ - Catch::AutoReg INTERNAL_CATCH_UNIQUE_NAME(autoRegistrar)(Catch::makeTestInvoker(&TestName::test), \ - CATCH_INTERNAL_LINEINFO, \ - #ClassName, \ - Catch::NameAndTags{__VA_ARGS__}); /* NOLINT */ \ - } \ - CATCH_INTERNAL_STOP_WARNINGS_SUPPRESSION \ - void TestName::test() -#define INTERNAL_CATCH_TEST_CASE_METHOD(ClassName, ...) \ - INTERNAL_CATCH_TEST_CASE_METHOD2(INTERNAL_CATCH_UNIQUE_NAME(____C_A_T_C_H____T_E_S_T____), ClassName, __VA_ARGS__) - -/////////////////////////////////////////////////////////////////////////////// -#define INTERNAL_CATCH_REGISTER_TESTCASE(Function, ...) \ - CATCH_INTERNAL_START_WARNINGS_SUPPRESSION \ - CATCH_INTERNAL_SUPPRESS_GLOBALS_WARNINGS \ - Catch::AutoReg INTERNAL_CATCH_UNIQUE_NAME( \ - autoRegistrar)(Catch::makeTestInvoker(Function), CATCH_INTERNAL_LINEINFO, Catch::StringRef(), Catch::NameAndTags{__VA_ARGS__}); /* NOLINT \ - */ \ - CATCH_INTERNAL_STOP_WARNINGS_SUPPRESSION - -/////////////////////////////////////////////////////////////////////////////// -#define INTERNAL_CATCH_TEMPLATE_TEST_CASE_2(TestName, TestFunc, Name, Tags, Signature, ...) \ - CATCH_INTERNAL_START_WARNINGS_SUPPRESSION \ - CATCH_INTERNAL_SUPPRESS_GLOBALS_WARNINGS \ - CATCH_INTERNAL_SUPPRESS_ZERO_VARIADIC_WARNINGS \ - CATCH_INTERNAL_SUPPRESS_UNUSED_TEMPLATE_WARNINGS \ - INTERNAL_CATCH_DECLARE_SIG_TEST(TestFunc, INTERNAL_CATCH_REMOVE_PARENS(Signature)); \ - namespace { \ - namespace INTERNAL_CATCH_MAKE_NAMESPACE(TestName) { \ - INTERNAL_CATCH_TYPE_GEN \ - INTERNAL_CATCH_NTTP_GEN(INTERNAL_CATCH_REMOVE_PARENS(Signature)) \ - INTERNAL_CATCH_NTTP_REG_GEN(TestFunc, INTERNAL_CATCH_REMOVE_PARENS(Signature)) \ - template \ - struct TestName { \ - TestName() { \ - int index = 0; \ - constexpr char const *tmpl_types[] = {CATCH_REC_LIST(INTERNAL_CATCH_STRINGIZE_WITHOUT_PARENS, __VA_ARGS__)}; \ - using expander = int[]; \ - (void)expander{(reg_test(Types{}, Catch::NameAndTags{Name " - " + std::string(tmpl_types[index]), Tags}), index++)...}; /* NOLINT \ - */ \ - } \ - }; \ - static int INTERNAL_CATCH_UNIQUE_NAME(globalRegistrar) = []() { \ - TestName(); \ - return 0; \ - }(); \ - } \ - } \ - CATCH_INTERNAL_STOP_WARNINGS_SUPPRESSION \ - INTERNAL_CATCH_DEFINE_SIG_TEST(TestFunc, INTERNAL_CATCH_REMOVE_PARENS(Signature)) - -#ifndef CATCH_CONFIG_TRADITIONAL_MSVC_PREPROCESSOR -#define INTERNAL_CATCH_TEMPLATE_TEST_CASE(Name, Tags, ...) \ - INTERNAL_CATCH_TEMPLATE_TEST_CASE_2(INTERNAL_CATCH_UNIQUE_NAME(____C_A_T_C_H____T_E_M_P_L_A_T_E____T_E_S_T____), \ - INTERNAL_CATCH_UNIQUE_NAME(____C_A_T_C_H____T_E_M_P_L_A_T_E____T_E_S_T____F_U_N_C____), \ - Name, \ - Tags, \ - typename TestType, \ - __VA_ARGS__) -#else -#define INTERNAL_CATCH_TEMPLATE_TEST_CASE(Name, Tags, ...) \ - INTERNAL_CATCH_EXPAND_VARGS( \ - INTERNAL_CATCH_TEMPLATE_TEST_CASE_2(INTERNAL_CATCH_UNIQUE_NAME(____C_A_T_C_H____T_E_M_P_L_A_T_E____T_E_S_T____), \ - INTERNAL_CATCH_UNIQUE_NAME(____C_A_T_C_H____T_E_M_P_L_A_T_E____T_E_S_T____F_U_N_C____), \ - Name, \ - Tags, \ - typename TestType, \ - __VA_ARGS__)) -#endif - -#ifndef CATCH_CONFIG_TRADITIONAL_MSVC_PREPROCESSOR -#define INTERNAL_CATCH_TEMPLATE_TEST_CASE_SIG(Name, Tags, Signature, ...) \ - INTERNAL_CATCH_TEMPLATE_TEST_CASE_2(INTERNAL_CATCH_UNIQUE_NAME(____C_A_T_C_H____T_E_M_P_L_A_T_E____T_E_S_T____), \ - INTERNAL_CATCH_UNIQUE_NAME(____C_A_T_C_H____T_E_M_P_L_A_T_E____T_E_S_T____F_U_N_C____), \ - Name, \ - Tags, \ - Signature, \ - __VA_ARGS__) -#else -#define INTERNAL_CATCH_TEMPLATE_TEST_CASE_SIG(Name, Tags, Signature, ...) \ - INTERNAL_CATCH_EXPAND_VARGS( \ - INTERNAL_CATCH_TEMPLATE_TEST_CASE_2(INTERNAL_CATCH_UNIQUE_NAME(____C_A_T_C_H____T_E_M_P_L_A_T_E____T_E_S_T____), \ - INTERNAL_CATCH_UNIQUE_NAME(____C_A_T_C_H____T_E_M_P_L_A_T_E____T_E_S_T____F_U_N_C____), \ - Name, \ - Tags, \ - Signature, \ - __VA_ARGS__)) -#endif - -#define INTERNAL_CATCH_TEMPLATE_PRODUCT_TEST_CASE2(TestName, TestFuncName, Name, Tags, Signature, TmplTypes, TypesList) \ - CATCH_INTERNAL_START_WARNINGS_SUPPRESSION \ - CATCH_INTERNAL_SUPPRESS_GLOBALS_WARNINGS \ - CATCH_INTERNAL_SUPPRESS_ZERO_VARIADIC_WARNINGS \ - CATCH_INTERNAL_SUPPRESS_UNUSED_TEMPLATE_WARNINGS \ - template \ - static void TestFuncName(); \ - namespace { \ - namespace INTERNAL_CATCH_MAKE_NAMESPACE(TestName) { \ - INTERNAL_CATCH_TYPE_GEN \ - INTERNAL_CATCH_NTTP_GEN(INTERNAL_CATCH_REMOVE_PARENS(Signature)) \ - template \ - struct TestName { \ - void reg_tests() { \ - int index = 0; \ - using expander = int[]; \ - constexpr char const *tmpl_types[] \ - = {CATCH_REC_LIST(INTERNAL_CATCH_STRINGIZE_WITHOUT_PARENS, INTERNAL_CATCH_REMOVE_PARENS(TmplTypes))}; \ - constexpr char const *types_list[] \ - = {CATCH_REC_LIST(INTERNAL_CATCH_STRINGIZE_WITHOUT_PARENS, INTERNAL_CATCH_REMOVE_PARENS(TypesList))}; \ - constexpr auto num_types = sizeof(types_list) / sizeof(types_list[0]); \ - (void)expander{(Catch::AutoReg(Catch::makeTestInvoker(&TestFuncName), \ - CATCH_INTERNAL_LINEINFO, \ - Catch::StringRef(), \ - Catch::NameAndTags{Name " - " + std::string(tmpl_types[index / num_types]) + "<" \ - + std::string(types_list[index % num_types]) + ">", \ - Tags}), \ - index++)...}; /* NOLINT */ \ - } \ - }; \ - static int INTERNAL_CATCH_UNIQUE_NAME(globalRegistrar) = []() { \ - using TestInit = \ - typename create()), \ - TypeList>::type; \ - TestInit t; \ - t.reg_tests(); \ - return 0; \ - }(); \ - } \ - } \ - CATCH_INTERNAL_STOP_WARNINGS_SUPPRESSION \ - template \ - static void TestFuncName() - -#ifndef CATCH_CONFIG_TRADITIONAL_MSVC_PREPROCESSOR -#define INTERNAL_CATCH_TEMPLATE_PRODUCT_TEST_CASE(Name, Tags, ...) \ - INTERNAL_CATCH_TEMPLATE_PRODUCT_TEST_CASE2(INTERNAL_CATCH_UNIQUE_NAME(____C_A_T_C_H____T_E_M_P_L_A_T_E____T_E_S_T____), \ - INTERNAL_CATCH_UNIQUE_NAME(____C_A_T_C_H____T_E_M_P_L_A_T_E____T_E_S_T____F_U_N_C____), \ - Name, \ - Tags, \ - typename T, \ - __VA_ARGS__) -#else -#define INTERNAL_CATCH_TEMPLATE_PRODUCT_TEST_CASE(Name, Tags, ...) \ - INTERNAL_CATCH_EXPAND_VARGS( \ - INTERNAL_CATCH_TEMPLATE_PRODUCT_TEST_CASE2(INTERNAL_CATCH_UNIQUE_NAME(____C_A_T_C_H____T_E_M_P_L_A_T_E____T_E_S_T____), \ - INTERNAL_CATCH_UNIQUE_NAME(____C_A_T_C_H____T_E_M_P_L_A_T_E____T_E_S_T____F_U_N_C____), \ - Name, \ - Tags, \ - typename T, \ - __VA_ARGS__)) -#endif - -#ifndef CATCH_CONFIG_TRADITIONAL_MSVC_PREPROCESSOR -#define INTERNAL_CATCH_TEMPLATE_PRODUCT_TEST_CASE_SIG(Name, Tags, Signature, ...) \ - INTERNAL_CATCH_TEMPLATE_PRODUCT_TEST_CASE2(INTERNAL_CATCH_UNIQUE_NAME(____C_A_T_C_H____T_E_M_P_L_A_T_E____T_E_S_T____), \ - INTERNAL_CATCH_UNIQUE_NAME(____C_A_T_C_H____T_E_M_P_L_A_T_E____T_E_S_T____F_U_N_C____), \ - Name, \ - Tags, \ - Signature, \ - __VA_ARGS__) -#else -#define INTERNAL_CATCH_TEMPLATE_PRODUCT_TEST_CASE_SIG(Name, Tags, Signature, ...) \ - INTERNAL_CATCH_EXPAND_VARGS( \ - INTERNAL_CATCH_TEMPLATE_PRODUCT_TEST_CASE2(INTERNAL_CATCH_UNIQUE_NAME(____C_A_T_C_H____T_E_M_P_L_A_T_E____T_E_S_T____), \ - INTERNAL_CATCH_UNIQUE_NAME(____C_A_T_C_H____T_E_M_P_L_A_T_E____T_E_S_T____F_U_N_C____), \ - Name, \ - Tags, \ - Signature, \ - __VA_ARGS__)) -#endif - -#define INTERNAL_CATCH_TEMPLATE_LIST_TEST_CASE_2(TestName, TestFunc, Name, Tags, TmplList) \ - CATCH_INTERNAL_START_WARNINGS_SUPPRESSION \ - CATCH_INTERNAL_SUPPRESS_GLOBALS_WARNINGS \ - CATCH_INTERNAL_SUPPRESS_UNUSED_TEMPLATE_WARNINGS \ - template \ - static void TestFunc(); \ - namespace { \ - namespace INTERNAL_CATCH_MAKE_NAMESPACE(TestName) { \ - INTERNAL_CATCH_TYPE_GEN \ - template \ - struct TestName { \ - void reg_tests() { \ - int index = 0; \ - using expander = int[]; \ - (void)expander{(Catch::AutoReg(Catch::makeTestInvoker(&TestFunc), \ - CATCH_INTERNAL_LINEINFO, \ - Catch::StringRef(), \ - Catch::NameAndTags{Name " - " + std::string(INTERNAL_CATCH_STRINGIZE(TmplList)) + " - " \ - + std::to_string(index), \ - Tags}), \ - index++)...}; /* NOLINT */ \ - } \ - }; \ - static int INTERNAL_CATCH_UNIQUE_NAME(globalRegistrar) = []() { \ - using TestInit = typename convert::type; \ - TestInit t; \ - t.reg_tests(); \ - return 0; \ - }(); \ - } \ - } \ - CATCH_INTERNAL_STOP_WARNINGS_SUPPRESSION \ - template \ - static void TestFunc() - -#define INTERNAL_CATCH_TEMPLATE_LIST_TEST_CASE(Name, Tags, TmplList) \ - INTERNAL_CATCH_TEMPLATE_LIST_TEST_CASE_2(INTERNAL_CATCH_UNIQUE_NAME(____C_A_T_C_H____T_E_M_P_L_A_T_E____T_E_S_T____), \ - INTERNAL_CATCH_UNIQUE_NAME(____C_A_T_C_H____T_E_M_P_L_A_T_E____T_E_S_T____F_U_N_C____), \ - Name, \ - Tags, \ - TmplList) - -#define INTERNAL_CATCH_TEMPLATE_TEST_CASE_METHOD_2(TestNameClass, TestName, ClassName, Name, Tags, Signature, ...) \ - CATCH_INTERNAL_START_WARNINGS_SUPPRESSION \ - CATCH_INTERNAL_SUPPRESS_GLOBALS_WARNINGS \ - CATCH_INTERNAL_SUPPRESS_ZERO_VARIADIC_WARNINGS \ - CATCH_INTERNAL_SUPPRESS_UNUSED_TEMPLATE_WARNINGS \ - namespace { \ - namespace INTERNAL_CATCH_MAKE_NAMESPACE(TestName) { \ - INTERNAL_CATCH_TYPE_GEN \ - INTERNAL_CATCH_NTTP_GEN(INTERNAL_CATCH_REMOVE_PARENS(Signature)) \ - INTERNAL_CATCH_DECLARE_SIG_TEST_METHOD(TestName, ClassName, INTERNAL_CATCH_REMOVE_PARENS(Signature)); \ - INTERNAL_CATCH_NTTP_REG_METHOD_GEN(TestName, INTERNAL_CATCH_REMOVE_PARENS(Signature)) \ - template \ - struct TestNameClass { \ - TestNameClass() { \ - int index = 0; \ - constexpr char const *tmpl_types[] = {CATCH_REC_LIST(INTERNAL_CATCH_STRINGIZE_WITHOUT_PARENS, __VA_ARGS__)}; \ - using expander = int[]; \ - (void)expander{(reg_test(Types{}, #ClassName, Catch::NameAndTags{Name " - " + std::string(tmpl_types[index]), Tags}), \ - index++)...}; /* NOLINT */ \ - } \ - }; \ - static int INTERNAL_CATCH_UNIQUE_NAME(globalRegistrar) = []() { \ - TestNameClass(); \ - return 0; \ - }(); \ - } \ - } \ - CATCH_INTERNAL_STOP_WARNINGS_SUPPRESSION \ - INTERNAL_CATCH_DEFINE_SIG_TEST_METHOD(TestName, INTERNAL_CATCH_REMOVE_PARENS(Signature)) - -#ifndef CATCH_CONFIG_TRADITIONAL_MSVC_PREPROCESSOR -#define INTERNAL_CATCH_TEMPLATE_TEST_CASE_METHOD(ClassName, Name, Tags, ...) \ - INTERNAL_CATCH_TEMPLATE_TEST_CASE_METHOD_2(INTERNAL_CATCH_UNIQUE_NAME(____C_A_T_C_H____T_E_M_P_L_A_T_E____T_E_S_T____C_L_A_S_S____), \ - INTERNAL_CATCH_UNIQUE_NAME(____C_A_T_C_H____T_E_M_P_L_A_T_E____T_E_S_T____), \ - ClassName, \ - Name, \ - Tags, \ - typename T, \ - __VA_ARGS__) -#else -#define INTERNAL_CATCH_TEMPLATE_TEST_CASE_METHOD(ClassName, Name, Tags, ...) \ - INTERNAL_CATCH_EXPAND_VARGS( \ - INTERNAL_CATCH_TEMPLATE_TEST_CASE_METHOD_2(INTERNAL_CATCH_UNIQUE_NAME(____C_A_T_C_H____T_E_M_P_L_A_T_E____T_E_S_T____C_L_A_S_S____), \ - INTERNAL_CATCH_UNIQUE_NAME(____C_A_T_C_H____T_E_M_P_L_A_T_E____T_E_S_T____), \ - ClassName, \ - Name, \ - Tags, \ - typename T, \ - __VA_ARGS__)) -#endif - -#ifndef CATCH_CONFIG_TRADITIONAL_MSVC_PREPROCESSOR -#define INTERNAL_CATCH_TEMPLATE_TEST_CASE_METHOD_SIG(ClassName, Name, Tags, Signature, ...) \ - INTERNAL_CATCH_TEMPLATE_TEST_CASE_METHOD_2(INTERNAL_CATCH_UNIQUE_NAME(____C_A_T_C_H____T_E_M_P_L_A_T_E____T_E_S_T____C_L_A_S_S____), \ - INTERNAL_CATCH_UNIQUE_NAME(____C_A_T_C_H____T_E_M_P_L_A_T_E____T_E_S_T____), \ - ClassName, \ - Name, \ - Tags, \ - Signature, \ - __VA_ARGS__) -#else -#define INTERNAL_CATCH_TEMPLATE_TEST_CASE_METHOD_SIG(ClassName, Name, Tags, Signature, ...) \ - INTERNAL_CATCH_EXPAND_VARGS( \ - INTERNAL_CATCH_TEMPLATE_TEST_CASE_METHOD_2(INTERNAL_CATCH_UNIQUE_NAME(____C_A_T_C_H____T_E_M_P_L_A_T_E____T_E_S_T____C_L_A_S_S____), \ - INTERNAL_CATCH_UNIQUE_NAME(____C_A_T_C_H____T_E_M_P_L_A_T_E____T_E_S_T____), \ - ClassName, \ - Name, \ - Tags, \ - Signature, \ - __VA_ARGS__)) -#endif - -#define INTERNAL_CATCH_TEMPLATE_PRODUCT_TEST_CASE_METHOD_2(TestNameClass, TestName, ClassName, Name, Tags, Signature, TmplTypes, TypesList) \ - CATCH_INTERNAL_START_WARNINGS_SUPPRESSION \ - CATCH_INTERNAL_SUPPRESS_GLOBALS_WARNINGS \ - CATCH_INTERNAL_SUPPRESS_ZERO_VARIADIC_WARNINGS \ - CATCH_INTERNAL_SUPPRESS_UNUSED_TEMPLATE_WARNINGS \ - template \ - struct TestName : INTERNAL_CATCH_REMOVE_PARENS(ClassName) { \ - void test(); \ - }; \ - namespace { \ - namespace INTERNAL_CATCH_MAKE_NAMESPACE(TestNameClass) { \ - INTERNAL_CATCH_TYPE_GEN \ - INTERNAL_CATCH_NTTP_GEN(INTERNAL_CATCH_REMOVE_PARENS(Signature)) \ - template \ - struct TestNameClass { \ - void reg_tests() { \ - int index = 0; \ - using expander = int[]; \ - constexpr char const *tmpl_types[] \ - = {CATCH_REC_LIST(INTERNAL_CATCH_STRINGIZE_WITHOUT_PARENS, INTERNAL_CATCH_REMOVE_PARENS(TmplTypes))}; \ - constexpr char const *types_list[] \ - = {CATCH_REC_LIST(INTERNAL_CATCH_STRINGIZE_WITHOUT_PARENS, INTERNAL_CATCH_REMOVE_PARENS(TypesList))}; \ - constexpr auto num_types = sizeof(types_list) / sizeof(types_list[0]); \ - (void)expander{(Catch::AutoReg(Catch::makeTestInvoker(&TestName::test), \ - CATCH_INTERNAL_LINEINFO, \ - #ClassName, \ - Catch::NameAndTags{Name " - " + std::string(tmpl_types[index / num_types]) + "<" \ - + std::string(types_list[index % num_types]) + ">", \ - Tags}), \ - index++)...}; /* NOLINT */ \ - } \ - }; \ - static int INTERNAL_CATCH_UNIQUE_NAME(globalRegistrar) = []() { \ - using TestInit = \ - typename create()), \ - TypeList>::type; \ - TestInit t; \ - t.reg_tests(); \ - return 0; \ - }(); \ - } \ - } \ - CATCH_INTERNAL_STOP_WARNINGS_SUPPRESSION \ - template \ - void TestName::test() - -#ifndef CATCH_CONFIG_TRADITIONAL_MSVC_PREPROCESSOR -#define INTERNAL_CATCH_TEMPLATE_PRODUCT_TEST_CASE_METHOD(ClassName, Name, Tags, ...) \ - INTERNAL_CATCH_TEMPLATE_PRODUCT_TEST_CASE_METHOD_2(INTERNAL_CATCH_UNIQUE_NAME(____C_A_T_C_H____T_E_M_P_L_A_T_E____T_E_S_T____), \ - INTERNAL_CATCH_UNIQUE_NAME( \ - ____C_A_T_C_H____T_E_M_P_L_A_T_E____T_E_S_T____F_U_N_C____), \ - ClassName, \ - Name, \ - Tags, \ - typename T, \ - __VA_ARGS__) -#else -#define INTERNAL_CATCH_TEMPLATE_PRODUCT_TEST_CASE_METHOD(ClassName, Name, Tags, ...) \ - INTERNAL_CATCH_EXPAND_VARGS( \ - INTERNAL_CATCH_TEMPLATE_PRODUCT_TEST_CASE_METHOD_2(INTERNAL_CATCH_UNIQUE_NAME(____C_A_T_C_H____T_E_M_P_L_A_T_E____T_E_S_T____), \ - INTERNAL_CATCH_UNIQUE_NAME( \ - ____C_A_T_C_H____T_E_M_P_L_A_T_E____T_E_S_T____F_U_N_C____), \ - ClassName, \ - Name, \ - Tags, \ - typename T, \ - __VA_ARGS__)) -#endif - -#ifndef CATCH_CONFIG_TRADITIONAL_MSVC_PREPROCESSOR -#define INTERNAL_CATCH_TEMPLATE_PRODUCT_TEST_CASE_METHOD_SIG(ClassName, Name, Tags, Signature, ...) \ - INTERNAL_CATCH_TEMPLATE_PRODUCT_TEST_CASE_METHOD_2(INTERNAL_CATCH_UNIQUE_NAME(____C_A_T_C_H____T_E_M_P_L_A_T_E____T_E_S_T____), \ - INTERNAL_CATCH_UNIQUE_NAME( \ - ____C_A_T_C_H____T_E_M_P_L_A_T_E____T_E_S_T____F_U_N_C____), \ - ClassName, \ - Name, \ - Tags, \ - Signature, \ - __VA_ARGS__) -#else -#define INTERNAL_CATCH_TEMPLATE_PRODUCT_TEST_CASE_METHOD_SIG(ClassName, Name, Tags, Signature, ...) \ - INTERNAL_CATCH_EXPAND_VARGS( \ - INTERNAL_CATCH_TEMPLATE_PRODUCT_TEST_CASE_METHOD_2(INTERNAL_CATCH_UNIQUE_NAME(____C_A_T_C_H____T_E_M_P_L_A_T_E____T_E_S_T____), \ - INTERNAL_CATCH_UNIQUE_NAME( \ - ____C_A_T_C_H____T_E_M_P_L_A_T_E____T_E_S_T____F_U_N_C____), \ - ClassName, \ - Name, \ - Tags, \ - Signature, \ - __VA_ARGS__)) -#endif - -#define INTERNAL_CATCH_TEMPLATE_LIST_TEST_CASE_METHOD_2(TestNameClass, TestName, ClassName, Name, Tags, TmplList) \ - CATCH_INTERNAL_START_WARNINGS_SUPPRESSION \ - CATCH_INTERNAL_SUPPRESS_GLOBALS_WARNINGS \ - CATCH_INTERNAL_SUPPRESS_UNUSED_TEMPLATE_WARNINGS \ - template \ - struct TestName : INTERNAL_CATCH_REMOVE_PARENS(ClassName) { \ - void test(); \ - }; \ - namespace { \ - namespace INTERNAL_CATCH_MAKE_NAMESPACE(TestName) { \ - INTERNAL_CATCH_TYPE_GEN \ - template \ - struct TestNameClass { \ - void reg_tests() { \ - int index = 0; \ - using expander = int[]; \ - (void)expander{(Catch::AutoReg(Catch::makeTestInvoker(&TestName::test), \ - CATCH_INTERNAL_LINEINFO, \ - #ClassName, \ - Catch::NameAndTags{Name " - " + std::string(INTERNAL_CATCH_STRINGIZE(TmplList)) + " - " \ - + std::to_string(index), \ - Tags}), \ - index++)...}; /* NOLINT */ \ - } \ - }; \ - static int INTERNAL_CATCH_UNIQUE_NAME(globalRegistrar) = []() { \ - using TestInit = typename convert::type; \ - TestInit t; \ - t.reg_tests(); \ - return 0; \ - }(); \ - } \ - } \ - CATCH_INTERNAL_STOP_WARNINGS_SUPPRESSION \ - template \ - void TestName::test() - -#define INTERNAL_CATCH_TEMPLATE_LIST_TEST_CASE_METHOD(ClassName, Name, Tags, TmplList) \ - INTERNAL_CATCH_TEMPLATE_LIST_TEST_CASE_METHOD_2(INTERNAL_CATCH_UNIQUE_NAME(____C_A_T_C_H____T_E_M_P_L_A_T_E____T_E_S_T____), \ - INTERNAL_CATCH_UNIQUE_NAME(____C_A_T_C_H____T_E_M_P_L_A_T_E____T_E_S_T____F_U_N_C____), \ - ClassName, \ - Name, \ - Tags, \ - TmplList) - -// end catch_test_registry.h -// start catch_capture.hpp - -// start catch_assertionhandler.h - -// start catch_assertioninfo.h - -// start catch_result_type.h - -namespace Catch { - // ResultWas::OfType enum - struct ResultWas { - enum OfType { - Unknown = -1, - Ok = 0, - Info = 1, - Warning = 2, - - FailureBit = 0x10, - - ExpressionFailed = FailureBit | 1, - ExplicitFailure = FailureBit | 2, - - Exception = 0x100 | FailureBit, - - ThrewException = Exception | 1, - DidntThrowException = Exception | 2, - - FatalErrorCondition = 0x200 | FailureBit - - }; - }; - - bool isOk(ResultWas::OfType resultType); - bool isJustInfo(int flags); - - // ResultDisposition::Flags enum - struct ResultDisposition { - enum Flags { - Normal = 0x01, - - ContinueOnFailure = 0x02, // Failures fail test, but execution continues - FalseTest = 0x04, // Prefix expression with ! - SuppressFail = 0x08 // Failures are reported but do not fail the test - }; - }; - - ResultDisposition::Flags operator|(ResultDisposition::Flags lhs, ResultDisposition::Flags rhs); - - bool shouldContinueOnFailure(int flags); - inline bool isFalseTest(int flags) { - return (flags & ResultDisposition::FalseTest) != 0; - } - bool shouldSuppressFailure(int flags); - -} // end namespace Catch - -// end catch_result_type.h -namespace Catch { - struct AssertionInfo { - StringRef macroName; - SourceLineInfo lineInfo; - StringRef capturedExpression; - ResultDisposition::Flags resultDisposition; - - // We want to delete this constructor but a compiler bug in 4.8 means - // the struct is then treated as non-aggregate - // AssertionInfo() = delete; - }; - -} // end namespace Catch - -// end catch_assertioninfo.h -// start catch_decomposer.h - -// start catch_tostring.h - -#include -#include -#include -#include -// start catch_stream.h - -#include -#include -#include - -namespace Catch { - std::ostream &cout(); - std::ostream &cerr(); - std::ostream &clog(); - - class StringRef; - - struct IStream { - virtual ~IStream(); - virtual std::ostream &stream() const = 0; - }; - - auto makeStream(StringRef const &filename) -> IStream const *; - - class ReusableStringStream : NonCopyable { - std::size_t m_index; - std::ostream *m_oss; - - public: - ReusableStringStream(); - ~ReusableStringStream(); - - auto str() const -> std::string; - - template - auto operator<<(T const &value) -> ReusableStringStream & { - *m_oss << value; - return *this; - } - auto get() -> std::ostream & { return *m_oss; } - }; -} // namespace Catch - -// end catch_stream.h -// start catch_interfaces_enum_values_registry.h - -#include - -namespace Catch { - namespace Detail { - struct EnumInfo { - StringRef m_name; - std::vector> m_values; - - ~EnumInfo(); - - StringRef lookup(int value) const; - }; - } // namespace Detail - - struct IMutableEnumValuesRegistry { - virtual ~IMutableEnumValuesRegistry(); - - virtual Detail::EnumInfo const ®isterEnum(StringRef enumName, StringRef allEnums, std::vector const &values) = 0; - - template - Detail::EnumInfo const ®isterEnum(StringRef enumName, StringRef allEnums, std::initializer_list values) { - static_assert(sizeof(int) >= sizeof(E), "Cannot serialize enum to int"); - std::vector intValues; - intValues.reserve(values.size()); - for (auto enumValue : values) - intValues.push_back(static_cast(enumValue)); - return registerEnum(enumName, allEnums, intValues); - } - }; - -} // namespace Catch - -// end catch_interfaces_enum_values_registry.h - -#ifdef CATCH_CONFIG_CPP17_STRING_VIEW -#include -#endif - -#ifdef __OBJC__ -// start catch_objc_arc.hpp - -#import - -#ifdef __has_feature -#define CATCH_ARC_ENABLED __has_feature(objc_arc) -#else -#define CATCH_ARC_ENABLED 0 -#endif - -void arcSafeRelease(NSObject *obj); -id performOptionalSelector(id obj, SEL sel); - -#if !CATCH_ARC_ENABLED -inline void arcSafeRelease(NSObject *obj) { - [obj release]; -} -inline id performOptionalSelector(id obj, SEL sel) { - if ([obj respondsToSelector:sel]) - return [obj performSelector:sel]; - return nil; -} -#define CATCH_UNSAFE_UNRETAINED -#define CATCH_ARC_STRONG -#else -inline void arcSafeRelease(NSObject *) { -} -inline id performOptionalSelector(id obj, SEL sel) { -#ifdef __clang__ -#pragma clang diagnostic push -#pragma clang diagnostic ignored "-Warc-performSelector-leaks" -#endif - if ([obj respondsToSelector:sel]) - return [obj performSelector:sel]; -#ifdef __clang__ -#pragma clang diagnostic pop -#endif - return nil; -} -#define CATCH_UNSAFE_UNRETAINED __unsafe_unretained -#define CATCH_ARC_STRONG __strong -#endif - -// end catch_objc_arc.hpp -#endif - -#ifdef _MSC_VER -#pragma warning(push) -#pragma warning(disable : 4180) // We attempt to stream a function (address) by const&, which MSVC complains about but is harmless -#endif - -namespace Catch { - namespace Detail { - extern const std::string unprintableString; - - std::string rawMemoryToString(const void *object, std::size_t size); - - template - std::string rawMemoryToString(const T &object) { - return rawMemoryToString(&object, sizeof(object)); - } - - template - class IsStreamInsertable { - template - static auto test(int) -> decltype(std::declval() << std::declval(), std::true_type()); - - template - static auto test(...) -> std::false_type; - - public: - static const bool value = decltype(test(0))::value; - }; - - template - std::string convertUnknownEnumToString(E e); - - template - typename std::enable_if::value && !std::is_base_of::value, std::string>::type convertUnstreamable(T const &) { - return Detail::unprintableString; - } - template - typename std::enable_if::value && std::is_base_of::value, std::string>::type convertUnstreamable(T const &ex) { - return ex.what(); - } - - template - typename std::enable_if::value, std::string>::type convertUnstreamable(T const &value) { - return convertUnknownEnumToString(value); - } - -#if defined(_MANAGED) - //! Convert a CLR string to a utf8 std::string - template - std::string clrReferenceToString(T ^ ref) { - if (ref == nullptr) - return std::string("null"); - auto bytes = System::Text::Encoding::UTF8->GetBytes(ref->ToString()); - cli::pin_ptr p = &bytes[0]; - return std::string(reinterpret_cast(p), bytes->Length); - } -#endif - - } // namespace Detail - - // If we decide for C++14, change these to enable_if_ts - template - struct StringMaker { - template - static typename std::enable_if<::Catch::Detail::IsStreamInsertable::value, std::string>::type convert(const Fake &value) { - ReusableStringStream rss; - // NB: call using the function-like syntax to avoid ambiguity with - // user-defined templated operator<< under clang. - rss.operator<<(value); - return rss.str(); - } - - template - static typename std::enable_if::value, std::string>::type convert(const Fake &value) { -#if !defined(CATCH_CONFIG_FALLBACK_STRINGIFIER) - return Detail::convertUnstreamable(value); -#else - return CATCH_CONFIG_FALLBACK_STRINGIFIER(value); -#endif - } - }; - - namespace Detail { - // This function dispatches all stringification requests inside of Catch. - // Should be preferably called fully qualified, like ::Catch::Detail::stringify - template - std::string stringify(const T &e) { - return ::Catch::StringMaker::type>::type>::convert(e); - } - - template - std::string convertUnknownEnumToString(E e) { - return ::Catch::Detail::stringify(static_cast::type>(e)); - } - -#if defined(_MANAGED) - template - std::string stringify(T ^ e) { - return ::Catch::StringMaker::convert(e); - } -#endif - - } // namespace Detail - - // Some predefined specializations - - template<> - struct StringMaker { - static std::string convert(const std::string &str); - }; - -#ifdef CATCH_CONFIG_CPP17_STRING_VIEW - template<> - struct StringMaker { - static std::string convert(std::string_view str); - }; -#endif - - template<> - struct StringMaker { - static std::string convert(char const *str); - }; - template<> - struct StringMaker { - static std::string convert(char *str); - }; - -#ifdef CATCH_CONFIG_WCHAR - template<> - struct StringMaker { - static std::string convert(const std::wstring &wstr); - }; - -#ifdef CATCH_CONFIG_CPP17_STRING_VIEW - template<> - struct StringMaker { - static std::string convert(std::wstring_view str); - }; -#endif - - template<> - struct StringMaker { - static std::string convert(wchar_t const *str); - }; - template<> - struct StringMaker { - static std::string convert(wchar_t *str); - }; -#endif - - // TBD: Should we use `strnlen` to ensure that we don't go out of the buffer, - // while keeping string semantics? - template - struct StringMaker { - static std::string convert(char const *str) { return ::Catch::Detail::stringify(std::string{str}); } - }; - template - struct StringMaker { - static std::string convert(signed char const *str) { - return ::Catch::Detail::stringify(std::string{reinterpret_cast(str)}); - } - }; - template - struct StringMaker { - static std::string convert(unsigned char const *str) { - return ::Catch::Detail::stringify(std::string{reinterpret_cast(str)}); - } - }; - -#if defined(CATCH_CONFIG_CPP17_BYTE) - template<> - struct StringMaker { - static std::string convert(std::byte value); - }; -#endif // defined(CATCH_CONFIG_CPP17_BYTE) - template<> - struct StringMaker { - static std::string convert(int value); - }; - template<> - struct StringMaker { - static std::string convert(long value); - }; - template<> - struct StringMaker { - static std::string convert(long long value); - }; - template<> - struct StringMaker { - static std::string convert(unsigned int value); - }; - template<> - struct StringMaker { - static std::string convert(unsigned long value); - }; - template<> - struct StringMaker { - static std::string convert(unsigned long long value); - }; - - template<> - struct StringMaker { - static std::string convert(bool b); - }; - - template<> - struct StringMaker { - static std::string convert(char c); - }; - template<> - struct StringMaker { - static std::string convert(signed char c); - }; - template<> - struct StringMaker { - static std::string convert(unsigned char c); - }; - - template<> - struct StringMaker { - static std::string convert(std::nullptr_t); - }; - - template<> - struct StringMaker { - static std::string convert(float value); - static int precision; - }; - - template<> - struct StringMaker { - static std::string convert(double value); - static int precision; - }; - - template - struct StringMaker { - template - static std::string convert(U *p) { - if (p) { - return ::Catch::Detail::rawMemoryToString(p); - } else { - return "nullptr"; - } - } - }; - - template - struct StringMaker { - static std::string convert(R C::*p) { - if (p) { - return ::Catch::Detail::rawMemoryToString(p); - } else { - return "nullptr"; - } - } - }; - -#if defined(_MANAGED) - template - struct StringMaker { - static std::string convert(T ^ ref) { return ::Catch::Detail::clrReferenceToString(ref); } - }; -#endif - - namespace Detail { - template - std::string rangeToString(InputIterator first, Sentinel last) { - ReusableStringStream rss; - rss << "{ "; - if (first != last) { - rss << ::Catch::Detail::stringify(*first); - for (++first; first != last; ++first) - rss << ", " << ::Catch::Detail::stringify(*first); - } - rss << " }"; - return rss.str(); - } - } // namespace Detail - -#ifdef __OBJC__ - template<> - struct StringMaker { - static std::string convert(NSString *nsstring) { - if (!nsstring) - return "nil"; - return std::string("@") + [nsstring UTF8String]; - } - }; - template<> - struct StringMaker { - static std::string convert(NSObject *nsObject) { return ::Catch::Detail::stringify([nsObject description]); } - }; - namespace Detail { - inline std::string stringify(NSString *nsstring) { - return StringMaker::convert(nsstring); - } - - } // namespace Detail -#endif // __OBJC__ - -} // namespace Catch - -////////////////////////////////////////////////////// -// Separate std-lib types stringification, so it can be selectively enabled -// This means that we do not bring in - -#if defined(CATCH_CONFIG_ENABLE_ALL_STRINGMAKERS) -#define CATCH_CONFIG_ENABLE_PAIR_STRINGMAKER -#define CATCH_CONFIG_ENABLE_TUPLE_STRINGMAKER -#define CATCH_CONFIG_ENABLE_VARIANT_STRINGMAKER -#define CATCH_CONFIG_ENABLE_CHRONO_STRINGMAKER -#define CATCH_CONFIG_ENABLE_OPTIONAL_STRINGMAKER -#endif - -// Separate std::pair specialization -#if defined(CATCH_CONFIG_ENABLE_PAIR_STRINGMAKER) -#include -namespace Catch { - template - struct StringMaker> { - static std::string convert(const std::pair &pair) { - ReusableStringStream rss; - rss << "{ " << ::Catch::Detail::stringify(pair.first) << ", " << ::Catch::Detail::stringify(pair.second) << " }"; - return rss.str(); - } - }; -} // namespace Catch -#endif // CATCH_CONFIG_ENABLE_PAIR_STRINGMAKER - -#if defined(CATCH_CONFIG_ENABLE_OPTIONAL_STRINGMAKER) && defined(CATCH_CONFIG_CPP17_OPTIONAL) -#include -namespace Catch { - template - struct StringMaker> { - static std::string convert(const std::optional &optional) { - ReusableStringStream rss; - if (optional.has_value()) { - rss << ::Catch::Detail::stringify(*optional); - } else { - rss << "{ }"; - } - return rss.str(); - } - }; -} // namespace Catch -#endif // CATCH_CONFIG_ENABLE_OPTIONAL_STRINGMAKER - -// Separate std::tuple specialization -#if defined(CATCH_CONFIG_ENABLE_TUPLE_STRINGMAKER) -#include -namespace Catch { - namespace Detail { - template::value)> - struct TupleElementPrinter { - static void print(const Tuple &tuple, std::ostream &os) { - os << (N ? ", " : " ") << ::Catch::Detail::stringify(std::get(tuple)); - TupleElementPrinter::print(tuple, os); - } - }; - - template - struct TupleElementPrinter { - static void print(const Tuple &, std::ostream &) {} - }; - - } // namespace Detail - - template - struct StringMaker> { - static std::string convert(const std::tuple &tuple) { - ReusableStringStream rss; - rss << '{'; - Detail::TupleElementPrinter>::print(tuple, rss.get()); - rss << " }"; - return rss.str(); - } - }; -} // namespace Catch -#endif // CATCH_CONFIG_ENABLE_TUPLE_STRINGMAKER - -#if defined(CATCH_CONFIG_ENABLE_VARIANT_STRINGMAKER) && defined(CATCH_CONFIG_CPP17_VARIANT) -#include -namespace Catch { - template<> - struct StringMaker { - static std::string convert(const std::monostate &) { return "{ }"; } - }; - - template - struct StringMaker> { - static std::string convert(const std::variant &variant) { - if (variant.valueless_by_exception()) { - return "{valueless variant}"; - } else { - return std::visit([](const auto &value) { return ::Catch::Detail::stringify(value); }, variant); - } - } - }; -} // namespace Catch -#endif // CATCH_CONFIG_ENABLE_VARIANT_STRINGMAKER - -namespace Catch { - // Import begin/ end from std here - using std::begin; - using std::end; - - namespace detail { - template - struct void_type { - using type = void; - }; - - template - struct is_range_impl : std::false_type { - }; - - template - struct is_range_impl()))>::type> : std::true_type { - }; - } // namespace detail - - template - struct is_range : detail::is_range_impl { - }; - -#if defined(_MANAGED) // Managed types are never ranges - template - struct is_range { - static const bool value = false; - }; -#endif - - template - std::string rangeToString(Range const &range) { - return ::Catch::Detail::rangeToString(begin(range), end(range)); - } - - // Handle vector specially - template - std::string rangeToString(std::vector const &v) { - ReusableStringStream rss; - rss << "{ "; - bool first = true; - for (bool b : v) { - if (first) - first = false; - else - rss << ", "; - rss << ::Catch::Detail::stringify(b); - } - rss << " }"; - return rss.str(); - } - - template - struct StringMaker::value && !::Catch::Detail::IsStreamInsertable::value>::type> { - static std::string convert(R const &range) { return rangeToString(range); } - }; - - template - struct StringMaker { - static std::string convert(T const (&arr)[SZ]) { return rangeToString(arr); } - }; - -} // namespace Catch - -// Separate std::chrono::duration specialization -#if defined(CATCH_CONFIG_ENABLE_CHRONO_STRINGMAKER) -#include -#include -#include - -namespace Catch { - template - struct ratio_string { - static std::string symbol(); - }; - - template - std::string ratio_string::symbol() { - Catch::ReusableStringStream rss; - rss << '[' << Ratio::num << '/' << Ratio::den << ']'; - return rss.str(); - } - template<> - struct ratio_string { - static std::string symbol(); - }; - template<> - struct ratio_string { - static std::string symbol(); - }; - template<> - struct ratio_string { - static std::string symbol(); - }; - template<> - struct ratio_string { - static std::string symbol(); - }; - template<> - struct ratio_string { - static std::string symbol(); - }; - template<> - struct ratio_string { - static std::string symbol(); - }; - - //////////// - // std::chrono::duration specializations - template - struct StringMaker> { - static std::string convert(std::chrono::duration const &duration) { - ReusableStringStream rss; - rss << duration.count() << ' ' << ratio_string::symbol() << 's'; - return rss.str(); - } - }; - template - struct StringMaker>> { - static std::string convert(std::chrono::duration> const &duration) { - ReusableStringStream rss; - rss << duration.count() << " s"; - return rss.str(); - } - }; - template - struct StringMaker>> { - static std::string convert(std::chrono::duration> const &duration) { - ReusableStringStream rss; - rss << duration.count() << " m"; - return rss.str(); - } - }; - template - struct StringMaker>> { - static std::string convert(std::chrono::duration> const &duration) { - ReusableStringStream rss; - rss << duration.count() << " h"; - return rss.str(); - } - }; - - //////////// - // std::chrono::time_point specialization - // Generic time_point cannot be specialized, only std::chrono::time_point - template - struct StringMaker> { - static std::string convert(std::chrono::time_point const &time_point) { - return ::Catch::Detail::stringify(time_point.time_since_epoch()) + " since epoch"; - } - }; - // std::chrono::time_point specialization - template - struct StringMaker> { - static std::string convert(std::chrono::time_point const &time_point) { - auto converted = std::chrono::system_clock::to_time_t(time_point); - -#ifdef _MSC_VER - std::tm timeInfo = {}; - gmtime_s(&timeInfo, &converted); -#else - std::tm *timeInfo = std::gmtime(&converted); -#endif - - auto const timeStampSize = sizeof("2017-01-16T17:06:45Z"); - char timeStamp[timeStampSize]; - const char *const fmt = "%Y-%m-%dT%H:%M:%SZ"; - -#ifdef _MSC_VER - std::strftime(timeStamp, timeStampSize, fmt, &timeInfo); -#else - std::strftime(timeStamp, timeStampSize, fmt, timeInfo); -#endif - return std::string(timeStamp); - } - }; -} // namespace Catch -#endif // CATCH_CONFIG_ENABLE_CHRONO_STRINGMAKER - -#define INTERNAL_CATCH_REGISTER_ENUM(enumName, ...) \ - namespace Catch { \ - template<> \ - struct StringMaker { \ - static std::string convert(enumName value) { \ - static const auto &enumInfo \ - = ::Catch::getMutableRegistryHub().getMutableEnumValuesRegistry().registerEnum(#enumName, #__VA_ARGS__, {__VA_ARGS__}); \ - return static_cast(enumInfo.lookup(static_cast(value))); \ - } \ - }; \ - } - -#define CATCH_REGISTER_ENUM(enumName, ...) INTERNAL_CATCH_REGISTER_ENUM(enumName, __VA_ARGS__) - -#ifdef _MSC_VER -#pragma warning(pop) -#endif - -// end catch_tostring.h -#include - -#ifdef _MSC_VER -#pragma warning(push) -#pragma warning(disable : 4389) // '==' : signed/unsigned mismatch -#pragma warning(disable : 4018) // more "signed/unsigned mismatch" -#pragma warning(disable : 4312) // Converting int to T* using reinterpret_cast (issue on x64 platform) -#pragma warning(disable : 4180) // qualifier applied to function type has no meaning -#pragma warning(disable : 4800) // Forcing result to true or false -#endif - -namespace Catch { - struct ITransientExpression { - auto isBinaryExpression() const -> bool { return m_isBinaryExpression; } - auto getResult() const -> bool { return m_result; } - virtual void streamReconstructedExpression(std::ostream &os) const = 0; - - ITransientExpression(bool isBinaryExpression, bool result) - : m_isBinaryExpression(isBinaryExpression) - , m_result(result) { - } - - // We don't actually need a virtual destructor, but many static analysers - // complain if it's not here :-( - virtual ~ITransientExpression(); - - bool m_isBinaryExpression; - bool m_result; - }; - - void formatReconstructedExpression(std::ostream &os, std::string const &lhs, StringRef op, std::string const &rhs); - - template - class BinaryExpr : public ITransientExpression { - LhsT m_lhs; - StringRef m_op; - RhsT m_rhs; - - void streamReconstructedExpression(std::ostream &os) const override { - formatReconstructedExpression(os, Catch::Detail::stringify(m_lhs), m_op, Catch::Detail::stringify(m_rhs)); - } - - public: - BinaryExpr(bool comparisonResult, LhsT lhs, StringRef op, RhsT rhs) - : ITransientExpression{true, comparisonResult} - , m_lhs(lhs) - , m_op(op) - , m_rhs(rhs) { - } - - template - auto operator&&(T) const -> BinaryExpr const { - static_assert(always_false::value, - "chained comparisons are not supported inside assertions, " - "wrap the expression inside parentheses, or decompose it"); - } - - template - auto operator||(T) const -> BinaryExpr const { - static_assert(always_false::value, - "chained comparisons are not supported inside assertions, " - "wrap the expression inside parentheses, or decompose it"); - } - - template - auto operator==(T) const -> BinaryExpr const { - static_assert(always_false::value, - "chained comparisons are not supported inside assertions, " - "wrap the expression inside parentheses, or decompose it"); - } - - template - auto operator!=(T) const -> BinaryExpr const { - static_assert(always_false::value, - "chained comparisons are not supported inside assertions, " - "wrap the expression inside parentheses, or decompose it"); - } - - template - auto operator>(T) const -> BinaryExpr const { - static_assert(always_false::value, - "chained comparisons are not supported inside assertions, " - "wrap the expression inside parentheses, or decompose it"); - } - - template - auto operator<(T) const -> BinaryExpr const { - static_assert(always_false::value, - "chained comparisons are not supported inside assertions, " - "wrap the expression inside parentheses, or decompose it"); - } - - template - auto operator>=(T) const -> BinaryExpr const { - static_assert(always_false::value, - "chained comparisons are not supported inside assertions, " - "wrap the expression inside parentheses, or decompose it"); - } - - template - auto operator<=(T) const -> BinaryExpr const { - static_assert(always_false::value, - "chained comparisons are not supported inside assertions, " - "wrap the expression inside parentheses, or decompose it"); - } - }; - - template - class UnaryExpr : public ITransientExpression { - LhsT m_lhs; - - void streamReconstructedExpression(std::ostream &os) const override { os << Catch::Detail::stringify(m_lhs); } - - public: - explicit UnaryExpr(LhsT lhs) - : ITransientExpression{false, static_cast(lhs)} - , m_lhs(lhs) { - } - }; - - // Specialised comparison functions to handle equality comparisons between ints and pointers (NULL deduces as an int) - template - auto compareEqual(LhsT const &lhs, RhsT const &rhs) -> bool { - return static_cast(lhs == rhs); - } - template - auto compareEqual(T *const &lhs, int rhs) -> bool { - return lhs == reinterpret_cast(rhs); - } - template - auto compareEqual(T *const &lhs, long rhs) -> bool { - return lhs == reinterpret_cast(rhs); - } - template - auto compareEqual(int lhs, T *const &rhs) -> bool { - return reinterpret_cast(lhs) == rhs; - } - template - auto compareEqual(long lhs, T *const &rhs) -> bool { - return reinterpret_cast(lhs) == rhs; - } - - template - auto compareNotEqual(LhsT const &lhs, RhsT &&rhs) -> bool { - return static_cast(lhs != rhs); - } - template - auto compareNotEqual(T *const &lhs, int rhs) -> bool { - return lhs != reinterpret_cast(rhs); - } - template - auto compareNotEqual(T *const &lhs, long rhs) -> bool { - return lhs != reinterpret_cast(rhs); - } - template - auto compareNotEqual(int lhs, T *const &rhs) -> bool { - return reinterpret_cast(lhs) != rhs; - } - template - auto compareNotEqual(long lhs, T *const &rhs) -> bool { - return reinterpret_cast(lhs) != rhs; - } - - template - class ExprLhs { - LhsT m_lhs; - - public: - explicit ExprLhs(LhsT lhs) - : m_lhs(lhs) { - } - - template - auto operator==(RhsT const &rhs) -> BinaryExpr const { - return {compareEqual(m_lhs, rhs), m_lhs, "==", rhs}; - } - auto operator==(bool rhs) -> BinaryExpr const { return {m_lhs == rhs, m_lhs, "==", rhs}; } - - template - auto operator!=(RhsT const &rhs) -> BinaryExpr const { - return {compareNotEqual(m_lhs, rhs), m_lhs, "!=", rhs}; - } - auto operator!=(bool rhs) -> BinaryExpr const { return {m_lhs != rhs, m_lhs, "!=", rhs}; } - - template - auto operator>(RhsT const &rhs) -> BinaryExpr const { - return {static_cast(m_lhs > rhs), m_lhs, ">", rhs}; - } - template - auto operator<(RhsT const &rhs) -> BinaryExpr const { - return {static_cast(m_lhs < rhs), m_lhs, "<", rhs}; - } - template - auto operator>=(RhsT const &rhs) -> BinaryExpr const { - return {static_cast(m_lhs >= rhs), m_lhs, ">=", rhs}; - } - template - auto operator<=(RhsT const &rhs) -> BinaryExpr const { - return {static_cast(m_lhs <= rhs), m_lhs, "<=", rhs}; - } - template - auto operator|(RhsT const &rhs) -> BinaryExpr const { - return {static_cast(m_lhs | rhs), m_lhs, "|", rhs}; - } - template - auto operator&(RhsT const &rhs) -> BinaryExpr const { - return {static_cast(m_lhs & rhs), m_lhs, "&", rhs}; - } - template - auto operator^(RhsT const &rhs) -> BinaryExpr const { - return {static_cast(m_lhs ^ rhs), m_lhs, "^", rhs}; - } - - template - auto operator&&(RhsT const &) -> BinaryExpr const { - static_assert(always_false::value, - "operator&& is not supported inside assertions, " - "wrap the expression inside parentheses, or decompose it"); - } - - template - auto operator||(RhsT const &) -> BinaryExpr const { - static_assert(always_false::value, - "operator|| is not supported inside assertions, " - "wrap the expression inside parentheses, or decompose it"); - } - - auto makeUnaryExpr() const -> UnaryExpr { return UnaryExpr{m_lhs}; } - }; - - void handleExpression(ITransientExpression const &expr); - - template - void handleExpression(ExprLhs const &expr) { - handleExpression(expr.makeUnaryExpr()); - } - - struct Decomposer { - template - auto operator<=(T const &lhs) -> ExprLhs { - return ExprLhs{lhs}; - } - - auto operator<=(bool value) -> ExprLhs { return ExprLhs{value}; } - }; - -} // end namespace Catch - -#ifdef _MSC_VER -#pragma warning(pop) -#endif - -// end catch_decomposer.h -// start catch_interfaces_capture.h - -#include -#include - -namespace Catch { - class AssertionResult; - struct AssertionInfo; - struct SectionInfo; - struct SectionEndInfo; - struct MessageInfo; - struct MessageBuilder; - struct Counts; - struct AssertionReaction; - struct SourceLineInfo; - - struct ITransientExpression; - struct IGeneratorTracker; - -#if defined(CATCH_CONFIG_ENABLE_BENCHMARKING) - struct BenchmarkInfo; - template> - struct BenchmarkStats; -#endif // CATCH_CONFIG_ENABLE_BENCHMARKING - - struct IResultCapture { - virtual ~IResultCapture(); - - virtual bool sectionStarted(SectionInfo const §ionInfo, Counts &assertions) = 0; - virtual void sectionEnded(SectionEndInfo const &endInfo) = 0; - virtual void sectionEndedEarly(SectionEndInfo const &endInfo) = 0; - - virtual auto acquireGeneratorTracker(StringRef generatorName, SourceLineInfo const &lineInfo) -> IGeneratorTracker & = 0; - -#if defined(CATCH_CONFIG_ENABLE_BENCHMARKING) - virtual void benchmarkPreparing(std::string const &name) = 0; - virtual void benchmarkStarting(BenchmarkInfo const &info) = 0; - virtual void benchmarkEnded(BenchmarkStats<> const &stats) = 0; - virtual void benchmarkFailed(std::string const &error) = 0; -#endif // CATCH_CONFIG_ENABLE_BENCHMARKING - - virtual void pushScopedMessage(MessageInfo const &message) = 0; - virtual void popScopedMessage(MessageInfo const &message) = 0; - - virtual void emplaceUnscopedMessage(MessageBuilder const &builder) = 0; - - virtual void handleFatalErrorCondition(StringRef message) = 0; - - virtual void handleExpr(AssertionInfo const &info, ITransientExpression const &expr, AssertionReaction &reaction) = 0; - virtual void handleMessage(AssertionInfo const &info, ResultWas::OfType resultType, StringRef const &message, AssertionReaction &reaction) - = 0; - virtual void handleUnexpectedExceptionNotThrown(AssertionInfo const &info, AssertionReaction &reaction) = 0; - virtual void handleUnexpectedInflightException(AssertionInfo const &info, std::string const &message, AssertionReaction &reaction) = 0; - virtual void handleIncomplete(AssertionInfo const &info) = 0; - virtual void handleNonExpr(AssertionInfo const &info, ResultWas::OfType resultType, AssertionReaction &reaction) = 0; - - virtual bool lastAssertionPassed() = 0; - virtual void assertionPassed() = 0; - - // Deprecated, do not use: - virtual std::string getCurrentTestName() const = 0; - virtual const AssertionResult *getLastResult() const = 0; - virtual void exceptionEarlyReported() = 0; - }; - - IResultCapture &getResultCapture(); -} // namespace Catch - -// end catch_interfaces_capture.h -namespace Catch { - struct TestFailureException { - }; - struct AssertionResultData; - struct IResultCapture; - class RunContext; - - class LazyExpression { - friend class AssertionHandler; - friend struct AssertionStats; - friend class RunContext; - - ITransientExpression const *m_transientExpression = nullptr; - bool m_isNegated; - - public: - LazyExpression(bool isNegated); - LazyExpression(LazyExpression const &other); - LazyExpression &operator=(LazyExpression const &) = delete; - - explicit operator bool() const; - - friend auto operator<<(std::ostream &os, LazyExpression const &lazyExpr) -> std::ostream &; - }; - - struct AssertionReaction { - bool shouldDebugBreak = false; - bool shouldThrow = false; - }; - - class AssertionHandler { - AssertionInfo m_assertionInfo; - AssertionReaction m_reaction; - bool m_completed = false; - IResultCapture &m_resultCapture; - - public: - AssertionHandler(StringRef const ¯oName, - SourceLineInfo const &lineInfo, - StringRef capturedExpression, - ResultDisposition::Flags resultDisposition); - ~AssertionHandler() { - if (!m_completed) { - m_resultCapture.handleIncomplete(m_assertionInfo); - } - } - - template - void handleExpr(ExprLhs const &expr) { - handleExpr(expr.makeUnaryExpr()); - } - void handleExpr(ITransientExpression const &expr); - - void handleMessage(ResultWas::OfType resultType, StringRef const &message); - - void handleExceptionThrownAsExpected(); - void handleUnexpectedExceptionNotThrown(); - void handleExceptionNotThrownAsExpected(); - void handleThrowingCallSkipped(); - void handleUnexpectedInflightException(); - - void complete(); - void setCompleted(); - - // query - auto allowThrows() const -> bool; - }; - - void handleExceptionMatchExpr(AssertionHandler &handler, std::string const &str, StringRef const &matcherString); - -} // namespace Catch - -// end catch_assertionhandler.h -// start catch_message.h - -#include -#include - -namespace Catch { - struct MessageInfo { - MessageInfo(StringRef const &_macroName, SourceLineInfo const &_lineInfo, ResultWas::OfType _type); - - StringRef macroName; - std::string message; - SourceLineInfo lineInfo; - ResultWas::OfType type; - unsigned int sequence; - - bool operator==(MessageInfo const &other) const; - bool operator<(MessageInfo const &other) const; - - private: - static unsigned int globalCount; - }; - - struct MessageStream { - template - MessageStream &operator<<(T const &value) { - m_stream << value; - return *this; - } - - ReusableStringStream m_stream; - }; - - struct MessageBuilder : MessageStream { - MessageBuilder(StringRef const ¯oName, SourceLineInfo const &lineInfo, ResultWas::OfType type); - - template - MessageBuilder &operator<<(T const &value) { - m_stream << value; - return *this; - } - - MessageInfo m_info; - }; - - class ScopedMessage { - public: - explicit ScopedMessage(MessageBuilder const &builder); - ScopedMessage(ScopedMessage &duplicate) = delete; - ScopedMessage(ScopedMessage &&old); - ~ScopedMessage(); - - MessageInfo m_info; - bool m_moved; - }; - - class Capturer { - std::vector m_messages; - IResultCapture &m_resultCapture = getResultCapture(); - size_t m_captured = 0; - - public: - Capturer(StringRef macroName, SourceLineInfo const &lineInfo, ResultWas::OfType resultType, StringRef names); - ~Capturer(); - - void captureValue(size_t index, std::string const &value); - - template - void captureValues(size_t index, T const &value) { - captureValue(index, Catch::Detail::stringify(value)); - } - - template - void captureValues(size_t index, T const &value, Ts const &...values) { - captureValue(index, Catch::Detail::stringify(value)); - captureValues(index + 1, values...); - } - }; - -} // end namespace Catch - -// end catch_message.h -#if !defined(CATCH_CONFIG_DISABLE) - -#if !defined(CATCH_CONFIG_DISABLE_STRINGIFICATION) -#define CATCH_INTERNAL_STRINGIFY(...) #__VA_ARGS__ -#else -#define CATCH_INTERNAL_STRINGIFY(...) "Disabled by CATCH_CONFIG_DISABLE_STRINGIFICATION" -#endif - -#if defined(CATCH_CONFIG_FAST_COMPILE) || defined(CATCH_CONFIG_DISABLE_EXCEPTIONS) - -/////////////////////////////////////////////////////////////////////////////// -// Another way to speed-up compilation is to omit local try-catch for REQUIRE* -// macros. -#define INTERNAL_CATCH_TRY -#define INTERNAL_CATCH_CATCH(capturer) - -#else // CATCH_CONFIG_FAST_COMPILE - -#define INTERNAL_CATCH_TRY try -#define INTERNAL_CATCH_CATCH(handler) \ - catch (...) { \ - handler.handleUnexpectedInflightException(); \ - } - -#endif - -#define INTERNAL_CATCH_REACT(handler) handler.complete(); - -/////////////////////////////////////////////////////////////////////////////// -#define INTERNAL_CATCH_TEST(macroName, resultDisposition, ...) \ - do { \ - CATCH_INTERNAL_IGNORE_BUT_WARN(__VA_ARGS__); \ - Catch::AssertionHandler catchAssertionHandler(macroName##_catch_sr, \ - CATCH_INTERNAL_LINEINFO, \ - CATCH_INTERNAL_STRINGIFY(__VA_ARGS__), \ - resultDisposition); \ - INTERNAL_CATCH_TRY { \ - CATCH_INTERNAL_START_WARNINGS_SUPPRESSION \ - CATCH_INTERNAL_SUPPRESS_PARENTHESES_WARNINGS \ - catchAssertionHandler.handleExpr(Catch::Decomposer() <= __VA_ARGS__); \ - CATCH_INTERNAL_STOP_WARNINGS_SUPPRESSION \ - } \ - INTERNAL_CATCH_CATCH(catchAssertionHandler) \ - INTERNAL_CATCH_REACT(catchAssertionHandler) \ - } while ((void)0, (false) && static_cast(!!(__VA_ARGS__))) - -/////////////////////////////////////////////////////////////////////////////// -#define INTERNAL_CATCH_IF(macroName, resultDisposition, ...) \ - INTERNAL_CATCH_TEST(macroName, resultDisposition, __VA_ARGS__); \ - if (Catch::getResultCapture().lastAssertionPassed()) - -/////////////////////////////////////////////////////////////////////////////// -#define INTERNAL_CATCH_ELSE(macroName, resultDisposition, ...) \ - INTERNAL_CATCH_TEST(macroName, resultDisposition, __VA_ARGS__); \ - if (!Catch::getResultCapture().lastAssertionPassed()) - -/////////////////////////////////////////////////////////////////////////////// -#define INTERNAL_CATCH_NO_THROW(macroName, resultDisposition, ...) \ - do { \ - Catch::AssertionHandler catchAssertionHandler(macroName##_catch_sr, \ - CATCH_INTERNAL_LINEINFO, \ - CATCH_INTERNAL_STRINGIFY(__VA_ARGS__), \ - resultDisposition); \ - try { \ - static_cast(__VA_ARGS__); \ - catchAssertionHandler.handleExceptionNotThrownAsExpected(); \ - } catch (...) { \ - catchAssertionHandler.handleUnexpectedInflightException(); \ - } \ - INTERNAL_CATCH_REACT(catchAssertionHandler) \ - } while (false) - -/////////////////////////////////////////////////////////////////////////////// -#define INTERNAL_CATCH_THROWS(macroName, resultDisposition, ...) \ - do { \ - Catch::AssertionHandler catchAssertionHandler(macroName##_catch_sr, \ - CATCH_INTERNAL_LINEINFO, \ - CATCH_INTERNAL_STRINGIFY(__VA_ARGS__), \ - resultDisposition); \ - if (catchAssertionHandler.allowThrows()) \ - try { \ - static_cast(__VA_ARGS__); \ - catchAssertionHandler.handleUnexpectedExceptionNotThrown(); \ - } catch (...) { \ - catchAssertionHandler.handleExceptionThrownAsExpected(); \ - } \ - else \ - catchAssertionHandler.handleThrowingCallSkipped(); \ - INTERNAL_CATCH_REACT(catchAssertionHandler) \ - } while (false) - -/////////////////////////////////////////////////////////////////////////////// -#define INTERNAL_CATCH_THROWS_AS(macroName, exceptionType, resultDisposition, expr) \ - do { \ - Catch::AssertionHandler catchAssertionHandler(macroName##_catch_sr, \ - CATCH_INTERNAL_LINEINFO, \ - CATCH_INTERNAL_STRINGIFY(expr) ", " CATCH_INTERNAL_STRINGIFY(exceptionType), \ - resultDisposition); \ - if (catchAssertionHandler.allowThrows()) \ - try { \ - static_cast(expr); \ - catchAssertionHandler.handleUnexpectedExceptionNotThrown(); \ - } catch (exceptionType const &) { \ - catchAssertionHandler.handleExceptionThrownAsExpected(); \ - } catch (...) { \ - catchAssertionHandler.handleUnexpectedInflightException(); \ - } \ - else \ - catchAssertionHandler.handleThrowingCallSkipped(); \ - INTERNAL_CATCH_REACT(catchAssertionHandler) \ - } while (false) - -/////////////////////////////////////////////////////////////////////////////// -#define INTERNAL_CATCH_MSG(macroName, messageType, resultDisposition, ...) \ - do { \ - Catch::AssertionHandler catchAssertionHandler(macroName##_catch_sr, CATCH_INTERNAL_LINEINFO, Catch::StringRef(), resultDisposition); \ - catchAssertionHandler.handleMessage(messageType, \ - (Catch::MessageStream() << __VA_ARGS__ + ::Catch::StreamEndStop()).m_stream.str()); \ - INTERNAL_CATCH_REACT(catchAssertionHandler) \ - } while (false) - -/////////////////////////////////////////////////////////////////////////////// -#define INTERNAL_CATCH_CAPTURE(varName, macroName, ...) \ - auto varName = Catch::Capturer(macroName, CATCH_INTERNAL_LINEINFO, Catch::ResultWas::Info, #__VA_ARGS__); \ - varName.captureValues(0, __VA_ARGS__) - -/////////////////////////////////////////////////////////////////////////////// -#define INTERNAL_CATCH_INFO(macroName, log) \ - Catch::ScopedMessage INTERNAL_CATCH_UNIQUE_NAME(scopedMessage)( \ - Catch::MessageBuilder(macroName##_catch_sr, CATCH_INTERNAL_LINEINFO, Catch::ResultWas::Info) << log); - -/////////////////////////////////////////////////////////////////////////////// -#define INTERNAL_CATCH_UNSCOPED_INFO(macroName, log) \ - Catch::getResultCapture().emplaceUnscopedMessage( \ - Catch::MessageBuilder(macroName##_catch_sr, CATCH_INTERNAL_LINEINFO, Catch::ResultWas::Info) << log) - -/////////////////////////////////////////////////////////////////////////////// -// Although this is matcher-based, it can be used with just a string -#define INTERNAL_CATCH_THROWS_STR_MATCHES(macroName, resultDisposition, matcher, ...) \ - do { \ - Catch::AssertionHandler catchAssertionHandler(macroName##_catch_sr, \ - CATCH_INTERNAL_LINEINFO, \ - CATCH_INTERNAL_STRINGIFY(__VA_ARGS__) ", " CATCH_INTERNAL_STRINGIFY(matcher), \ - resultDisposition); \ - if (catchAssertionHandler.allowThrows()) \ - try { \ - static_cast(__VA_ARGS__); \ - catchAssertionHandler.handleUnexpectedExceptionNotThrown(); \ - } catch (...) { \ - Catch::handleExceptionMatchExpr(catchAssertionHandler, matcher, #matcher##_catch_sr); \ - } \ - else \ - catchAssertionHandler.handleThrowingCallSkipped(); \ - INTERNAL_CATCH_REACT(catchAssertionHandler) \ - } while (false) - -#endif // CATCH_CONFIG_DISABLE - -// end catch_capture.hpp -// start catch_section.h - -// start catch_section_info.h - -// start catch_totals.h - -#include - -namespace Catch { - struct Counts { - Counts operator-(Counts const &other) const; - Counts &operator+=(Counts const &other); - - std::size_t total() const; - bool allPassed() const; - bool allOk() const; - - std::size_t passed = 0; - std::size_t failed = 0; - std::size_t failedButOk = 0; - }; - - struct Totals { - Totals operator-(Totals const &other) const; - Totals &operator+=(Totals const &other); - - Totals delta(Totals const &prevTotals) const; - - int error = 0; - Counts assertions; - Counts testCases; - }; -} // namespace Catch - -// end catch_totals.h -#include - -namespace Catch { - struct SectionInfo { - SectionInfo(SourceLineInfo const &_lineInfo, std::string const &_name); - - // Deprecated - SectionInfo(SourceLineInfo const &_lineInfo, std::string const &_name, std::string const &) - : SectionInfo(_lineInfo, _name) { - } - - std::string name; - std::string description; // !Deprecated: this will always be empty - SourceLineInfo lineInfo; - }; - - struct SectionEndInfo { - SectionInfo sectionInfo; - Counts prevAssertions; - double durationInSeconds; - }; - -} // end namespace Catch - -// end catch_section_info.h -// start catch_timer.h - -#include - -namespace Catch { - auto getCurrentNanosecondsSinceEpoch() -> uint64_t; - auto getEstimatedClockResolution() -> uint64_t; - - class Timer { - uint64_t m_nanoseconds = 0; - - public: - void start(); - auto getElapsedNanoseconds() const -> uint64_t; - auto getElapsedMicroseconds() const -> uint64_t; - auto getElapsedMilliseconds() const -> unsigned int; - auto getElapsedSeconds() const -> double; - }; - -} // namespace Catch - -// end catch_timer.h -#include - -namespace Catch { - class Section : NonCopyable { - public: - Section(SectionInfo const &info); - ~Section(); - - // This indicates whether the section should be executed or not - explicit operator bool() const; - - private: - SectionInfo m_info; - - std::string m_name; - Counts m_assertions; - bool m_sectionIncluded; - Timer m_timer; - }; - -} // end namespace Catch - -#define INTERNAL_CATCH_SECTION(...) \ - CATCH_INTERNAL_START_WARNINGS_SUPPRESSION \ - CATCH_INTERNAL_SUPPRESS_UNUSED_WARNINGS \ - if (Catch::Section const &INTERNAL_CATCH_UNIQUE_NAME(catch_internal_Section) \ - = Catch::SectionInfo(CATCH_INTERNAL_LINEINFO, __VA_ARGS__)) \ - CATCH_INTERNAL_STOP_WARNINGS_SUPPRESSION - -#define INTERNAL_CATCH_DYNAMIC_SECTION(...) \ - CATCH_INTERNAL_START_WARNINGS_SUPPRESSION \ - CATCH_INTERNAL_SUPPRESS_UNUSED_WARNINGS \ - if (Catch::Section const &INTERNAL_CATCH_UNIQUE_NAME(catch_internal_Section) \ - = Catch::SectionInfo(CATCH_INTERNAL_LINEINFO, (Catch::ReusableStringStream() << __VA_ARGS__).str())) \ - CATCH_INTERNAL_STOP_WARNINGS_SUPPRESSION - -// end catch_section.h -// start catch_interfaces_exception.h - -// start catch_interfaces_registry_hub.h - -#include -#include - -namespace Catch { - class TestCase; - struct ITestCaseRegistry; - struct IExceptionTranslatorRegistry; - struct IExceptionTranslator; - struct IReporterRegistry; - struct IReporterFactory; - struct ITagAliasRegistry; - struct IMutableEnumValuesRegistry; - - class StartupExceptionRegistry; - - using IReporterFactoryPtr = std::shared_ptr; - - struct IRegistryHub { - virtual ~IRegistryHub(); - - virtual IReporterRegistry const &getReporterRegistry() const = 0; - virtual ITestCaseRegistry const &getTestCaseRegistry() const = 0; - virtual ITagAliasRegistry const &getTagAliasRegistry() const = 0; - virtual IExceptionTranslatorRegistry const &getExceptionTranslatorRegistry() const = 0; - - virtual StartupExceptionRegistry const &getStartupExceptionRegistry() const = 0; - }; - - struct IMutableRegistryHub { - virtual ~IMutableRegistryHub(); - virtual void registerReporter(std::string const &name, IReporterFactoryPtr const &factory) = 0; - virtual void registerListener(IReporterFactoryPtr const &factory) = 0; - virtual void registerTest(TestCase const &testInfo) = 0; - virtual void registerTranslator(const IExceptionTranslator *translator) = 0; - virtual void registerTagAlias(std::string const &alias, std::string const &tag, SourceLineInfo const &lineInfo) = 0; - virtual void registerStartupException() noexcept = 0; - virtual IMutableEnumValuesRegistry &getMutableEnumValuesRegistry() = 0; - }; - - IRegistryHub const &getRegistryHub(); - IMutableRegistryHub &getMutableRegistryHub(); - void cleanUp(); - std::string translateActiveException(); - -} // namespace Catch - -// end catch_interfaces_registry_hub.h -#if defined(CATCH_CONFIG_DISABLE) -#define INTERNAL_CATCH_TRANSLATE_EXCEPTION_NO_REG(translatorName, signature) static std::string translatorName(signature) -#endif - -#include -#include -#include - -namespace Catch { - using exceptionTranslateFunction = std::string (*)(); - - struct IExceptionTranslator; - using ExceptionTranslators = std::vector>; - - struct IExceptionTranslator { - virtual ~IExceptionTranslator(); - virtual std::string translate(ExceptionTranslators::const_iterator it, ExceptionTranslators::const_iterator itEnd) const = 0; - }; - - struct IExceptionTranslatorRegistry { - virtual ~IExceptionTranslatorRegistry(); - - virtual std::string translateActiveException() const = 0; - }; - - class ExceptionTranslatorRegistrar { - template - class ExceptionTranslator : public IExceptionTranslator { - public: - ExceptionTranslator(std::string (*translateFunction)(T &)) - : m_translateFunction(translateFunction) { - } - - std::string translate(ExceptionTranslators::const_iterator it, ExceptionTranslators::const_iterator itEnd) const override { -#if defined(CATCH_CONFIG_DISABLE_EXCEPTIONS) - return ""; -#else - try { - if (it == itEnd) - std::rethrow_exception(std::current_exception()); - else - return (*it)->translate(it + 1, itEnd); - } catch (T &ex) { - return m_translateFunction(ex); - } -#endif - } - - protected: - std::string (*m_translateFunction)(T &); - }; - - public: - template - ExceptionTranslatorRegistrar(std::string (*translateFunction)(T &)) { - getMutableRegistryHub().registerTranslator(new ExceptionTranslator(translateFunction)); - } - }; -} // namespace Catch - -/////////////////////////////////////////////////////////////////////////////// -#define INTERNAL_CATCH_TRANSLATE_EXCEPTION2(translatorName, signature) \ - static std::string translatorName(signature); \ - CATCH_INTERNAL_START_WARNINGS_SUPPRESSION \ - CATCH_INTERNAL_SUPPRESS_GLOBALS_WARNINGS \ - namespace { \ - Catch::ExceptionTranslatorRegistrar INTERNAL_CATCH_UNIQUE_NAME(catch_internal_ExceptionRegistrar)(&translatorName); \ - } \ - CATCH_INTERNAL_STOP_WARNINGS_SUPPRESSION \ - static std::string translatorName(signature) - -#define INTERNAL_CATCH_TRANSLATE_EXCEPTION(signature) \ - INTERNAL_CATCH_TRANSLATE_EXCEPTION2(INTERNAL_CATCH_UNIQUE_NAME(catch_internal_ExceptionTranslator), signature) - -// end catch_interfaces_exception.h -// start catch_approx.h - -#include - -namespace Catch { - namespace Detail { - class Approx { - private: - bool equalityComparisonImpl(double other) const; - // Validates the new margin (margin >= 0) - // out-of-line to avoid including stdexcept in the header - void setMargin(double margin); - // Validates the new epsilon (0 < epsilon < 1) - // out-of-line to avoid including stdexcept in the header - void setEpsilon(double epsilon); - - public: - explicit Approx(double value); - - static Approx custom(); - - Approx operator-() const; - - template::value>::type> - Approx operator()(T const &value) { - Approx approx(static_cast(value)); - approx.m_epsilon = m_epsilon; - approx.m_margin = m_margin; - approx.m_scale = m_scale; - return approx; - } - - template::value>::type> - explicit Approx(T const &value) - : Approx(static_cast(value)) { - } - - template::value>::type> - friend bool operator==(const T &lhs, Approx const &rhs) { - auto lhs_v = static_cast(lhs); - return rhs.equalityComparisonImpl(lhs_v); - } - - template::value>::type> - friend bool operator==(Approx const &lhs, const T &rhs) { - return operator==(rhs, lhs); - } - - template::value>::type> - friend bool operator!=(T const &lhs, Approx const &rhs) { - return !operator==(lhs, rhs); - } - - template::value>::type> - friend bool operator!=(Approx const &lhs, T const &rhs) { - return !operator==(rhs, lhs); - } - - template::value>::type> - friend bool operator<=(T const &lhs, Approx const &rhs) { - return static_cast(lhs) < rhs.m_value || lhs == rhs; - } - - template::value>::type> - friend bool operator<=(Approx const &lhs, T const &rhs) { - return lhs.m_value < static_cast(rhs) || lhs == rhs; - } - - template::value>::type> - friend bool operator>=(T const &lhs, Approx const &rhs) { - return static_cast(lhs) > rhs.m_value || lhs == rhs; - } - - template::value>::type> - friend bool operator>=(Approx const &lhs, T const &rhs) { - return lhs.m_value > static_cast(rhs) || lhs == rhs; - } - - template::value>::type> - Approx &epsilon(T const &newEpsilon) { - double epsilonAsDouble = static_cast(newEpsilon); - setEpsilon(epsilonAsDouble); - return *this; - } - - template::value>::type> - Approx &margin(T const &newMargin) { - double marginAsDouble = static_cast(newMargin); - setMargin(marginAsDouble); - return *this; - } - - template::value>::type> - Approx &scale(T const &newScale) { - m_scale = static_cast(newScale); - return *this; - } - - std::string toString() const; - - private: - double m_epsilon; - double m_margin; - double m_scale; - double m_value; - }; - } // end namespace Detail - - namespace literals { - Detail::Approx operator"" _a(long double val); - Detail::Approx operator"" _a(unsigned long long val); - } // end namespace literals - - template<> - struct StringMaker { - static std::string convert(Catch::Detail::Approx const &value); - }; - -} // end namespace Catch - -// end catch_approx.h -// start catch_string_manip.h - -#include -#include -#include - -namespace Catch { - bool startsWith(std::string const &s, std::string const &prefix); - bool startsWith(std::string const &s, char prefix); - bool endsWith(std::string const &s, std::string const &suffix); - bool endsWith(std::string const &s, char suffix); - bool contains(std::string const &s, std::string const &infix); - void toLowerInPlace(std::string &s); - std::string toLower(std::string const &s); - //! Returns a new string without whitespace at the start/end - std::string trim(std::string const &str); - //! Returns a substring of the original ref without whitespace. Beware lifetimes! - StringRef trim(StringRef ref); - - // !!! Be aware, returns refs into original string - make sure original string outlives them - std::vector splitStringRef(StringRef str, char delimiter); - bool replaceInPlace(std::string &str, std::string const &replaceThis, std::string const &withThis); - - struct pluralise { - pluralise(std::size_t count, std::string const &label); - - friend std::ostream &operator<<(std::ostream &os, pluralise const &pluraliser); - - std::size_t m_count; - std::string m_label; - }; -} // namespace Catch - -// end catch_string_manip.h -#ifndef CATCH_CONFIG_DISABLE_MATCHERS -// start catch_capture_matchers.h - -// start catch_matchers.h - -#include -#include - -namespace Catch { - namespace Matchers { - namespace Impl { - template - struct MatchAllOf; - template - struct MatchAnyOf; - template - struct MatchNotOf; - - class MatcherUntypedBase { - public: - MatcherUntypedBase() = default; - MatcherUntypedBase(MatcherUntypedBase const &) = default; - MatcherUntypedBase &operator=(MatcherUntypedBase const &) = delete; - std::string toString() const; - - protected: - virtual ~MatcherUntypedBase(); - virtual std::string describe() const = 0; - mutable std::string m_cachedToString; - }; - -#ifdef __clang__ -#pragma clang diagnostic push -#pragma clang diagnostic ignored "-Wnon-virtual-dtor" -#endif - - template - struct MatcherMethod { - virtual bool match(ObjectT const &arg) const = 0; - }; - -#if defined(__OBJC__) - // Hack to fix Catch GH issue #1661. Could use id for generic Object support. - // use of const for Object pointers is very uncommon and under ARC it causes some kind of signature mismatch that breaks compilation - template<> - struct MatcherMethod { - virtual bool match(NSString *arg) const = 0; - }; -#endif - -#ifdef __clang__ -#pragma clang diagnostic pop -#endif - - template - struct MatcherBase : MatcherUntypedBase, MatcherMethod { - MatchAllOf operator&&(MatcherBase const &other) const; - MatchAnyOf operator||(MatcherBase const &other) const; - MatchNotOf operator!() const; - }; - - template - struct MatchAllOf : MatcherBase { - bool match(ArgT const &arg) const override { - for (auto matcher : m_matchers) { - if (!matcher->match(arg)) - return false; - } - return true; - } - std::string describe() const override { - std::string description; - description.reserve(4 + m_matchers.size() * 32); - description += "( "; - bool first = true; - for (auto matcher : m_matchers) { - if (first) - first = false; - else - description += " and "; - description += matcher->toString(); - } - description += " )"; - return description; - } - - MatchAllOf operator&&(MatcherBase const &other) { - auto copy(*this); - copy.m_matchers.push_back(&other); - return copy; - } - - std::vector const *> m_matchers; - }; - template - struct MatchAnyOf : MatcherBase { - bool match(ArgT const &arg) const override { - for (auto matcher : m_matchers) { - if (matcher->match(arg)) - return true; - } - return false; - } - std::string describe() const override { - std::string description; - description.reserve(4 + m_matchers.size() * 32); - description += "( "; - bool first = true; - for (auto matcher : m_matchers) { - if (first) - first = false; - else - description += " or "; - description += matcher->toString(); - } - description += " )"; - return description; - } - - MatchAnyOf operator||(MatcherBase const &other) { - auto copy(*this); - copy.m_matchers.push_back(&other); - return copy; - } - - std::vector const *> m_matchers; - }; - - template - struct MatchNotOf : MatcherBase { - MatchNotOf(MatcherBase const &underlyingMatcher) - : m_underlyingMatcher(underlyingMatcher) { - } - - bool match(ArgT const &arg) const override { return !m_underlyingMatcher.match(arg); } - - std::string describe() const override { return "not " + m_underlyingMatcher.toString(); } - MatcherBase const &m_underlyingMatcher; - }; - - template - MatchAllOf MatcherBase::operator&&(MatcherBase const &other) const { - return MatchAllOf() && *this && other; - } - template - MatchAnyOf MatcherBase::operator||(MatcherBase const &other) const { - return MatchAnyOf() || *this || other; - } - template - MatchNotOf MatcherBase::operator!() const { - return MatchNotOf(*this); - } - - } // namespace Impl - } // namespace Matchers - - using namespace Matchers; - using Matchers::Impl::MatcherBase; - -} // namespace Catch - -// end catch_matchers.h -// start catch_matchers_exception.hpp - -namespace Catch { - namespace Matchers { - namespace Exception { - class ExceptionMessageMatcher : public MatcherBase { - std::string m_message; - - public: - ExceptionMessageMatcher(std::string const &message) - : m_message(message) { - } - - bool match(std::exception const &ex) const override; - - std::string describe() const override; - }; - - } // namespace Exception - - Exception::ExceptionMessageMatcher Message(std::string const &message); - - } // namespace Matchers -} // namespace Catch - -// end catch_matchers_exception.hpp -// start catch_matchers_floating.h - -namespace Catch { - namespace Matchers { - namespace Floating { - enum class FloatingPointKind : uint8_t; - - struct WithinAbsMatcher : MatcherBase { - WithinAbsMatcher(double target, double margin); - bool match(double const &matchee) const override; - std::string describe() const override; - - private: - double m_target; - double m_margin; - }; - - struct WithinUlpsMatcher : MatcherBase { - WithinUlpsMatcher(double target, uint64_t ulps, FloatingPointKind baseType); - bool match(double const &matchee) const override; - std::string describe() const override; - - private: - double m_target; - uint64_t m_ulps; - FloatingPointKind m_type; - }; - - // Given IEEE-754 format for floats and doubles, we can assume - // that float -> double promotion is lossless. Given this, we can - // assume that if we do the standard relative comparison of - // |lhs - rhs| <= epsilon * max(fabs(lhs), fabs(rhs)), then we get - // the same result if we do this for floats, as if we do this for - // doubles that were promoted from floats. - struct WithinRelMatcher : MatcherBase { - WithinRelMatcher(double target, double epsilon); - bool match(double const &matchee) const override; - std::string describe() const override; - - private: - double m_target; - double m_epsilon; - }; - - } // namespace Floating - - // The following functions create the actual matcher objects. - // This allows the types to be inferred - Floating::WithinUlpsMatcher WithinULP(double target, uint64_t maxUlpDiff); - Floating::WithinUlpsMatcher WithinULP(float target, uint64_t maxUlpDiff); - Floating::WithinAbsMatcher WithinAbs(double target, double margin); - Floating::WithinRelMatcher WithinRel(double target, double eps); - // defaults epsilon to 100*numeric_limits::epsilon() - Floating::WithinRelMatcher WithinRel(double target); - Floating::WithinRelMatcher WithinRel(float target, float eps); - // defaults epsilon to 100*numeric_limits::epsilon() - Floating::WithinRelMatcher WithinRel(float target); - - } // namespace Matchers -} // namespace Catch - -// end catch_matchers_floating.h -// start catch_matchers_generic.hpp - -#include -#include - -namespace Catch { - namespace Matchers { - namespace Generic { - namespace Detail { - std::string finalizeDescription(const std::string &desc); - } - - template - class PredicateMatcher : public MatcherBase { - std::function m_predicate; - std::string m_description; - - public: - PredicateMatcher(std::function const &elem, std::string const &descr) - : m_predicate(std::move(elem)) - , m_description(Detail::finalizeDescription(descr)) { - } - - bool match(T const &item) const override { return m_predicate(item); } - - std::string describe() const override { return m_description; } - }; - - } // namespace Generic - - // The following functions create the actual matcher objects. - // The user has to explicitly specify type to the function, because - // inferring std::function is hard (but possible) and - // requires a lot of TMP. - template - Generic::PredicateMatcher Predicate(std::function const &predicate, std::string const &description = "") { - return Generic::PredicateMatcher(predicate, description); - } - - } // namespace Matchers -} // namespace Catch - -// end catch_matchers_generic.hpp -// start catch_matchers_string.h - -#include - -namespace Catch { - namespace Matchers { - namespace StdString { - struct CasedString { - CasedString(std::string const &str, CaseSensitive::Choice caseSensitivity); - std::string adjustString(std::string const &str) const; - std::string caseSensitivitySuffix() const; - - CaseSensitive::Choice m_caseSensitivity; - std::string m_str; - }; - - struct StringMatcherBase : MatcherBase { - StringMatcherBase(std::string const &operation, CasedString const &comparator); - std::string describe() const override; - - CasedString m_comparator; - std::string m_operation; - }; - - struct EqualsMatcher : StringMatcherBase { - EqualsMatcher(CasedString const &comparator); - bool match(std::string const &source) const override; - }; - struct ContainsMatcher : StringMatcherBase { - ContainsMatcher(CasedString const &comparator); - bool match(std::string const &source) const override; - }; - struct StartsWithMatcher : StringMatcherBase { - StartsWithMatcher(CasedString const &comparator); - bool match(std::string const &source) const override; - }; - struct EndsWithMatcher : StringMatcherBase { - EndsWithMatcher(CasedString const &comparator); - bool match(std::string const &source) const override; - }; - - struct RegexMatcher : MatcherBase { - RegexMatcher(std::string regex, CaseSensitive::Choice caseSensitivity); - bool match(std::string const &matchee) const override; - std::string describe() const override; - - private: - std::string m_regex; - CaseSensitive::Choice m_caseSensitivity; - }; - - } // namespace StdString - - // The following functions create the actual matcher objects. - // This allows the types to be inferred - - StdString::EqualsMatcher Equals(std::string const &str, CaseSensitive::Choice caseSensitivity = CaseSensitive::Yes); - StdString::ContainsMatcher Contains(std::string const &str, CaseSensitive::Choice caseSensitivity = CaseSensitive::Yes); - StdString::EndsWithMatcher EndsWith(std::string const &str, CaseSensitive::Choice caseSensitivity = CaseSensitive::Yes); - StdString::StartsWithMatcher StartsWith(std::string const &str, CaseSensitive::Choice caseSensitivity = CaseSensitive::Yes); - StdString::RegexMatcher Matches(std::string const ®ex, CaseSensitive::Choice caseSensitivity = CaseSensitive::Yes); - - } // namespace Matchers -} // namespace Catch - -// end catch_matchers_string.h -// start catch_matchers_vector.h - -#include - -namespace Catch { - namespace Matchers { - namespace Vector { - template - struct ContainsElementMatcher : MatcherBase> { - ContainsElementMatcher(T const &comparator) - : m_comparator(comparator) { - } - - bool match(std::vector const &v) const override { - for (auto const &el : v) { - if (el == m_comparator) { - return true; - } - } - return false; - } - - std::string describe() const override { return "Contains: " + ::Catch::Detail::stringify(m_comparator); } - - T const &m_comparator; - }; - - template - struct ContainsMatcher : MatcherBase> { - ContainsMatcher(std::vector const &comparator) - : m_comparator(comparator) { - } - - bool match(std::vector const &v) const override { - // !TBD: see note in EqualsMatcher - if (m_comparator.size() > v.size()) - return false; - for (auto const &comparator : m_comparator) { - auto present = false; - for (const auto &el : v) { - if (el == comparator) { - present = true; - break; - } - } - if (!present) { - return false; - } - } - return true; - } - std::string describe() const override { return "Contains: " + ::Catch::Detail::stringify(m_comparator); } - - std::vector const &m_comparator; - }; - - template - struct EqualsMatcher : MatcherBase> { - EqualsMatcher(std::vector const &comparator) - : m_comparator(comparator) { - } - - bool match(std::vector const &v) const override { - // !TBD: This currently works if all elements can be compared using != - // - a more general approach would be via a compare template that defaults - // to using !=. but could be specialised for, e.g. std::vector etc - // - then just call that directly - if (m_comparator.size() != v.size()) - return false; - for (std::size_t i = 0; i < v.size(); ++i) - if (m_comparator[i] != v[i]) - return false; - return true; - } - std::string describe() const override { return "Equals: " + ::Catch::Detail::stringify(m_comparator); } - std::vector const &m_comparator; - }; - - template - struct ApproxMatcher : MatcherBase> { - ApproxMatcher(std::vector const &comparator) - : m_comparator(comparator) { - } - - bool match(std::vector const &v) const override { - if (m_comparator.size() != v.size()) - return false; - for (std::size_t i = 0; i < v.size(); ++i) - if (m_comparator[i] != approx(v[i])) - return false; - return true; - } - std::string describe() const override { return "is approx: " + ::Catch::Detail::stringify(m_comparator); } - template::value>::type> - ApproxMatcher &epsilon(T const &newEpsilon) { - approx.epsilon(newEpsilon); - return *this; - } - template::value>::type> - ApproxMatcher &margin(T const &newMargin) { - approx.margin(newMargin); - return *this; - } - template::value>::type> - ApproxMatcher &scale(T const &newScale) { - approx.scale(newScale); - return *this; - } - - std::vector const &m_comparator; - mutable Catch::Detail::Approx approx = Catch::Detail::Approx::custom(); - }; - - template - struct UnorderedEqualsMatcher : MatcherBase> { - UnorderedEqualsMatcher(std::vector const &target) - : m_target(target) { - } - bool match(std::vector const &vec) const override { - if (m_target.size() != vec.size()) { - return false; - } - return std::is_permutation(m_target.begin(), m_target.end(), vec.begin()); - } - - std::string describe() const override { return "UnorderedEquals: " + ::Catch::Detail::stringify(m_target); } - - private: - std::vector const &m_target; - }; - - } // namespace Vector - - // The following functions create the actual matcher objects. - // This allows the types to be inferred - - template, typename AllocMatch = AllocComp> - Vector::ContainsMatcher Contains(std::vector const &comparator) { - return Vector::ContainsMatcher(comparator); - } - - template> - Vector::ContainsElementMatcher VectorContains(T const &comparator) { - return Vector::ContainsElementMatcher(comparator); - } - - template, typename AllocMatch = AllocComp> - Vector::EqualsMatcher Equals(std::vector const &comparator) { - return Vector::EqualsMatcher(comparator); - } - - template, typename AllocMatch = AllocComp> - Vector::ApproxMatcher Approx(std::vector const &comparator) { - return Vector::ApproxMatcher(comparator); - } - - template, typename AllocMatch = AllocComp> - Vector::UnorderedEqualsMatcher UnorderedEquals(std::vector const &target) { - return Vector::UnorderedEqualsMatcher(target); - } - - } // namespace Matchers -} // namespace Catch - -// end catch_matchers_vector.h -namespace Catch { - template - class MatchExpr : public ITransientExpression { - ArgT const &m_arg; - MatcherT m_matcher; - StringRef m_matcherString; - - public: - MatchExpr(ArgT const &arg, MatcherT const &matcher, StringRef const &matcherString) - : ITransientExpression{true, matcher.match(arg)} - , m_arg(arg) - , m_matcher(matcher) - , m_matcherString(matcherString) { - } - - void streamReconstructedExpression(std::ostream &os) const override { - auto matcherAsString = m_matcher.toString(); - os << Catch::Detail::stringify(m_arg) << ' '; - if (matcherAsString == Detail::unprintableString) - os << m_matcherString; - else - os << matcherAsString; - } - }; - - using StringMatcher = Matchers::Impl::MatcherBase; - - void handleExceptionMatchExpr(AssertionHandler &handler, StringMatcher const &matcher, StringRef const &matcherString); - - template - auto makeMatchExpr(ArgT const &arg, MatcherT const &matcher, StringRef const &matcherString) -> MatchExpr { - return MatchExpr(arg, matcher, matcherString); - } - -} // namespace Catch - -/////////////////////////////////////////////////////////////////////////////// -#define INTERNAL_CHECK_THAT(macroName, matcher, resultDisposition, arg) \ - do { \ - Catch::AssertionHandler catchAssertionHandler(macroName##_catch_sr, \ - CATCH_INTERNAL_LINEINFO, \ - CATCH_INTERNAL_STRINGIFY(arg) ", " CATCH_INTERNAL_STRINGIFY(matcher), \ - resultDisposition); \ - INTERNAL_CATCH_TRY { catchAssertionHandler.handleExpr(Catch::makeMatchExpr(arg, matcher, #matcher##_catch_sr)); } \ - INTERNAL_CATCH_CATCH(catchAssertionHandler) \ - INTERNAL_CATCH_REACT(catchAssertionHandler) \ - } while (false) - -/////////////////////////////////////////////////////////////////////////////// -#define INTERNAL_CATCH_THROWS_MATCHES(macroName, exceptionType, resultDisposition, matcher, ...) \ - do { \ - Catch::AssertionHandler catchAssertionHandler(macroName##_catch_sr, \ - CATCH_INTERNAL_LINEINFO, \ - CATCH_INTERNAL_STRINGIFY(__VA_ARGS__) ", " CATCH_INTERNAL_STRINGIFY( \ - exceptionType) ", " CATCH_INTERNAL_STRINGIFY(matcher), \ - resultDisposition); \ - if (catchAssertionHandler.allowThrows()) \ - try { \ - static_cast(__VA_ARGS__); \ - catchAssertionHandler.handleUnexpectedExceptionNotThrown(); \ - } catch (exceptionType const &ex) { \ - catchAssertionHandler.handleExpr(Catch::makeMatchExpr(ex, matcher, #matcher##_catch_sr)); \ - } catch (...) { \ - catchAssertionHandler.handleUnexpectedInflightException(); \ - } \ - else \ - catchAssertionHandler.handleThrowingCallSkipped(); \ - INTERNAL_CATCH_REACT(catchAssertionHandler) \ - } while (false) - -// end catch_capture_matchers.h -#endif -// start catch_generators.hpp - -// start catch_interfaces_generatortracker.h - -#include - -namespace Catch { - namespace Generators { - class GeneratorUntypedBase { - public: - GeneratorUntypedBase() = default; - virtual ~GeneratorUntypedBase(); - // Attempts to move the generator to the next element - // - // Returns true iff the move succeeded (and a valid element - // can be retrieved). - virtual bool next() = 0; - }; - using GeneratorBasePtr = std::unique_ptr; - - } // namespace Generators - - struct IGeneratorTracker { - virtual ~IGeneratorTracker(); - virtual auto hasGenerator() const -> bool = 0; - virtual auto getGenerator() const -> Generators::GeneratorBasePtr const & = 0; - virtual void setGenerator(Generators::GeneratorBasePtr &&generator) = 0; - }; - -} // namespace Catch - -// end catch_interfaces_generatortracker.h -// start catch_enforce.h - -#include - -namespace Catch { -#if !defined(CATCH_CONFIG_DISABLE_EXCEPTIONS) - template - [[noreturn]] void throw_exception(Ex const &e) { - throw e; - } -#else // ^^ Exceptions are enabled // Exceptions are disabled vv - [[noreturn]] void throw_exception(std::exception const &e); -#endif - - [[noreturn]] void throw_logic_error(std::string const &msg); - [[noreturn]] void throw_domain_error(std::string const &msg); - [[noreturn]] void throw_runtime_error(std::string const &msg); - -} // namespace Catch - -#define CATCH_MAKE_MSG(...) (Catch::ReusableStringStream() << __VA_ARGS__).str() - -#define CATCH_INTERNAL_ERROR(...) \ - Catch::throw_logic_error(CATCH_MAKE_MSG(CATCH_INTERNAL_LINEINFO << ": Internal Catch2 error: " << __VA_ARGS__)) - -#define CATCH_ERROR(...) Catch::throw_domain_error(CATCH_MAKE_MSG(__VA_ARGS__)) - -#define CATCH_RUNTIME_ERROR(...) Catch::throw_runtime_error(CATCH_MAKE_MSG(__VA_ARGS__)) - -#define CATCH_ENFORCE(condition, ...) \ - do { \ - if (!(condition)) \ - CATCH_ERROR(__VA_ARGS__); \ - } while (false) - -// end catch_enforce.h -#include -#include -#include - -#include -#include - -namespace Catch { - class GeneratorException : public std::exception { - const char *const m_msg = ""; - - public: - GeneratorException(const char *msg) - : m_msg(msg) { - } - - const char *what() const noexcept override final; - }; - - namespace Generators { - // !TBD move this into its own location? - namespace pf { - template - std::unique_ptr make_unique(Args &&...args) { - return std::unique_ptr(new T(std::forward(args)...)); - } - } // namespace pf - - template - struct IGenerator : GeneratorUntypedBase { - virtual ~IGenerator() = default; - - // Returns the current element of the generator - // - // \Precondition The generator is either freshly constructed, - // or the last call to `next()` returned true - virtual T const &get() const = 0; - using type = T; - }; - - template - class SingleValueGenerator final : public IGenerator { - T m_value; - - public: - SingleValueGenerator(T &&value) - : m_value(std::move(value)) { - } - - T const &get() const override { return m_value; } - bool next() override { return false; } - }; - - template - class FixedValuesGenerator final : public IGenerator { - static_assert(!std::is_same::value, - "FixedValuesGenerator does not support bools because of std::vector" - "specialization, use SingleValue Generator instead."); - std::vector m_values; - size_t m_idx = 0; - - public: - FixedValuesGenerator(std::initializer_list values) - : m_values(values) { - } - - T const &get() const override { return m_values[m_idx]; } - bool next() override { - ++m_idx; - return m_idx < m_values.size(); - } - }; - - template - class GeneratorWrapper final { - std::unique_ptr> m_generator; - - public: - GeneratorWrapper(std::unique_ptr> generator) - : m_generator(std::move(generator)) { - } - T const &get() const { return m_generator->get(); } - bool next() { return m_generator->next(); } - }; - - template - GeneratorWrapper value(T &&value) { - return GeneratorWrapper(pf::make_unique>(std::forward(value))); - } - template - GeneratorWrapper values(std::initializer_list values) { - return GeneratorWrapper(pf::make_unique>(values)); - } - - template - class Generators : public IGenerator { - std::vector> m_generators; - size_t m_current = 0; - - void populate(GeneratorWrapper &&generator) { m_generators.emplace_back(std::move(generator)); } - void populate(T &&val) { m_generators.emplace_back(value(std::forward(val))); } - template - void populate(U &&val) { - populate(T(std::forward(val))); - } - template - void populate(U &&valueOrGenerator, Gs &&...moreGenerators) { - populate(std::forward(valueOrGenerator)); - populate(std::forward(moreGenerators)...); - } - - public: - template - Generators(Gs &&...moreGenerators) { - m_generators.reserve(sizeof...(Gs)); - populate(std::forward(moreGenerators)...); - } - - T const &get() const override { return m_generators[m_current].get(); } - - bool next() override { - if (m_current >= m_generators.size()) { - return false; - } - const bool current_status = m_generators[m_current].next(); - if (!current_status) { - ++m_current; - } - return m_current < m_generators.size(); - } - }; - - template - GeneratorWrapper> table(std::initializer_list::type...>> tuples) { - return values>(tuples); - } - - // Tag type to signal that a generator sequence should convert arguments to a specific type - template - struct as { - }; - - template - auto makeGenerators(GeneratorWrapper &&generator, Gs &&...moreGenerators) -> Generators { - return Generators(std::move(generator), std::forward(moreGenerators)...); - } - template - auto makeGenerators(GeneratorWrapper &&generator) -> Generators { - return Generators(std::move(generator)); - } - template - auto makeGenerators(T &&val, Gs &&...moreGenerators) -> Generators { - return makeGenerators(value(std::forward(val)), std::forward(moreGenerators)...); - } - template - auto makeGenerators(as, U &&val, Gs &&...moreGenerators) -> Generators { - return makeGenerators(value(T(std::forward(val))), std::forward(moreGenerators)...); - } - - auto acquireGeneratorTracker(StringRef generatorName, SourceLineInfo const &lineInfo) -> IGeneratorTracker &; - - template - // Note: The type after -> is weird, because VS2015 cannot parse - // the expression used in the typedef inside, when it is in - // return type. Yeah. - auto generate(StringRef generatorName, SourceLineInfo const &lineInfo, L const &generatorExpression) - -> decltype(std::declval().get()) { - using UnderlyingType = typename decltype(generatorExpression())::type; - - IGeneratorTracker &tracker = acquireGeneratorTracker(generatorName, lineInfo); - if (!tracker.hasGenerator()) { - tracker.setGenerator(pf::make_unique>(generatorExpression())); - } - - auto const &generator = static_cast const &>(*tracker.getGenerator()); - return generator.get(); - } - - } // namespace Generators -} // namespace Catch - -#define GENERATE(...) \ - Catch::Generators::generate(INTERNAL_CATCH_STRINGIZE(INTERNAL_CATCH_UNIQUE_NAME(generator)), CATCH_INTERNAL_LINEINFO, [] { \ - using namespace Catch::Generators; \ - return makeGenerators(__VA_ARGS__); \ - }) // NOLINT(google-build-using-namespace) -#define GENERATE_COPY(...) \ - Catch::Generators::generate(INTERNAL_CATCH_STRINGIZE(INTERNAL_CATCH_UNIQUE_NAME(generator)), CATCH_INTERNAL_LINEINFO, [=] { \ - using namespace Catch::Generators; \ - return makeGenerators(__VA_ARGS__); \ - }) // NOLINT(google-build-using-namespace) -#define GENERATE_REF(...) \ - Catch::Generators::generate(INTERNAL_CATCH_STRINGIZE(INTERNAL_CATCH_UNIQUE_NAME(generator)), CATCH_INTERNAL_LINEINFO, [&] { \ - using namespace Catch::Generators; \ - return makeGenerators(__VA_ARGS__); \ - }) // NOLINT(google-build-using-namespace) - -// end catch_generators.hpp -// start catch_generators_generic.hpp - -namespace Catch { - namespace Generators { - template - class TakeGenerator : public IGenerator { - GeneratorWrapper m_generator; - size_t m_returned = 0; - size_t m_target; - - public: - TakeGenerator(size_t target, GeneratorWrapper &&generator) - : m_generator(std::move(generator)) - , m_target(target) { - assert(target != 0 && "Empty generators are not allowed"); - } - T const &get() const override { return m_generator.get(); } - bool next() override { - ++m_returned; - if (m_returned >= m_target) { - return false; - } - - const auto success = m_generator.next(); - // If the underlying generator does not contain enough values - // then we cut short as well - if (!success) { - m_returned = m_target; - } - return success; - } - }; - - template - GeneratorWrapper take(size_t target, GeneratorWrapper &&generator) { - return GeneratorWrapper(pf::make_unique>(target, std::move(generator))); - } - - template - class FilterGenerator : public IGenerator { - GeneratorWrapper m_generator; - Predicate m_predicate; - - public: - template - FilterGenerator(P &&pred, GeneratorWrapper &&generator) - : m_generator(std::move(generator)) - , m_predicate(std::forward

(pred)) { - if (!m_predicate(m_generator.get())) { - // It might happen that there are no values that pass the - // filter. In that case we throw an exception. - auto has_initial_value = next(); - if (!has_initial_value) { - Catch::throw_exception(GeneratorException("No valid value found in filtered generator")); - } - } - } - - T const &get() const override { return m_generator.get(); } - - bool next() override { - bool success = m_generator.next(); - if (!success) { - return false; - } - while (!m_predicate(m_generator.get()) && (success = m_generator.next()) == true) - ; - return success; - } - }; - - template - GeneratorWrapper filter(Predicate &&pred, GeneratorWrapper &&generator) { - return GeneratorWrapper( - std::unique_ptr>(pf::make_unique>(std::forward(pred), std::move(generator)))); - } - - template - class RepeatGenerator : public IGenerator { - static_assert(!std::is_same::value, - "RepeatGenerator currently does not support bools" - "because of std::vector specialization"); - GeneratorWrapper m_generator; - mutable std::vector m_returned; - size_t m_target_repeats; - size_t m_current_repeat = 0; - size_t m_repeat_index = 0; - - public: - RepeatGenerator(size_t repeats, GeneratorWrapper &&generator) - : m_generator(std::move(generator)) - , m_target_repeats(repeats) { - assert(m_target_repeats > 0 && "Repeat generator must repeat at least once"); - } - - T const &get() const override { - if (m_current_repeat == 0) { - m_returned.push_back(m_generator.get()); - return m_returned.back(); - } - return m_returned[m_repeat_index]; - } - - bool next() override { - // There are 2 basic cases: - // 1) We are still reading the generator - // 2) We are reading our own cache - - // In the first case, we need to poke the underlying generator. - // If it happily moves, we are left in that state, otherwise it is time to start reading from our cache - if (m_current_repeat == 0) { - const auto success = m_generator.next(); - if (!success) { - ++m_current_repeat; - } - return m_current_repeat < m_target_repeats; - } - - // In the second case, we need to move indices forward and check that we haven't run up against the end - ++m_repeat_index; - if (m_repeat_index == m_returned.size()) { - m_repeat_index = 0; - ++m_current_repeat; - } - return m_current_repeat < m_target_repeats; - } - }; - - template - GeneratorWrapper repeat(size_t repeats, GeneratorWrapper &&generator) { - return GeneratorWrapper(pf::make_unique>(repeats, std::move(generator))); - } - - template - class MapGenerator : public IGenerator { - // TBD: provide static assert for mapping function, for friendly error message - GeneratorWrapper m_generator; - Func m_function; - // To avoid returning dangling reference, we have to save the values - T m_cache; - - public: - template - MapGenerator(F2 &&function, GeneratorWrapper &&generator) - : m_generator(std::move(generator)) - , m_function(std::forward(function)) - , m_cache(m_function(m_generator.get())) { - } - - T const &get() const override { return m_cache; } - bool next() override { - const auto success = m_generator.next(); - if (success) { - m_cache = m_function(m_generator.get()); - } - return success; - } - }; - - template> - GeneratorWrapper map(Func &&function, GeneratorWrapper &&generator) { - return GeneratorWrapper(pf::make_unique>(std::forward(function), std::move(generator))); - } - - template - GeneratorWrapper map(Func &&function, GeneratorWrapper &&generator) { - return GeneratorWrapper(pf::make_unique>(std::forward(function), std::move(generator))); - } - - template - class ChunkGenerator final : public IGenerator> { - std::vector m_chunk; - size_t m_chunk_size; - GeneratorWrapper m_generator; - bool m_used_up = false; - - public: - ChunkGenerator(size_t size, GeneratorWrapper generator) - : m_chunk_size(size) - , m_generator(std::move(generator)) { - m_chunk.reserve(m_chunk_size); - if (m_chunk_size != 0) { - m_chunk.push_back(m_generator.get()); - for (size_t i = 1; i < m_chunk_size; ++i) { - if (!m_generator.next()) { - Catch::throw_exception(GeneratorException("Not enough values to initialize the first chunk")); - } - m_chunk.push_back(m_generator.get()); - } - } - } - std::vector const &get() const override { return m_chunk; } - bool next() override { - m_chunk.clear(); - for (size_t idx = 0; idx < m_chunk_size; ++idx) { - if (!m_generator.next()) { - return false; - } - m_chunk.push_back(m_generator.get()); - } - return true; - } - }; - - template - GeneratorWrapper> chunk(size_t size, GeneratorWrapper &&generator) { - return GeneratorWrapper>(pf::make_unique>(size, std::move(generator))); - } - - } // namespace Generators -} // namespace Catch - -// end catch_generators_generic.hpp -// start catch_generators_specific.hpp - -// start catch_context.h - -#include - -namespace Catch { - struct IResultCapture; - struct IRunner; - struct IConfig; - struct IMutableContext; - - using IConfigPtr = std::shared_ptr; - - struct IContext { - virtual ~IContext(); - - virtual IResultCapture *getResultCapture() = 0; - virtual IRunner *getRunner() = 0; - virtual IConfigPtr const &getConfig() const = 0; - }; - - struct IMutableContext : IContext { - virtual ~IMutableContext(); - virtual void setResultCapture(IResultCapture *resultCapture) = 0; - virtual void setRunner(IRunner *runner) = 0; - virtual void setConfig(IConfigPtr const &config) = 0; - - private: - static IMutableContext *currentContext; - friend IMutableContext &getCurrentMutableContext(); - friend void cleanUpContext(); - static void createContext(); - }; - - inline IMutableContext &getCurrentMutableContext() { - if (!IMutableContext::currentContext) - IMutableContext::createContext(); - // NOLINTNEXTLINE(clang-analyzer-core.uninitialized.UndefReturn) - return *IMutableContext::currentContext; - } - - inline IContext &getCurrentContext() { - return getCurrentMutableContext(); - } - - void cleanUpContext(); - - class SimplePcg32; - SimplePcg32 &rng(); -} // namespace Catch - -// end catch_context.h -// start catch_interfaces_config.h - -// start catch_option.hpp - -namespace Catch { - // An optional type - template - class Option { - public: - Option() - : nullableValue(nullptr) { - } - Option(T const &_value) - : nullableValue(new (storage) T(_value)) { - } - Option(Option const &_other) - : nullableValue(_other ? new (storage) T(*_other) : nullptr) { - } - - ~Option() { reset(); } - - Option &operator=(Option const &_other) { - if (&_other != this) { - reset(); - if (_other) - nullableValue = new (storage) T(*_other); - } - return *this; - } - Option &operator=(T const &_value) { - reset(); - nullableValue = new (storage) T(_value); - return *this; - } - - void reset() { - if (nullableValue) - nullableValue->~T(); - nullableValue = nullptr; - } - - T &operator*() { return *nullableValue; } - T const &operator*() const { return *nullableValue; } - T *operator->() { return nullableValue; } - const T *operator->() const { return nullableValue; } - - T valueOr(T const &defaultValue) const { return nullableValue ? *nullableValue : defaultValue; } - - bool some() const { return nullableValue != nullptr; } - bool none() const { return nullableValue == nullptr; } - - bool operator!() const { return nullableValue == nullptr; } - explicit operator bool() const { return some(); } - - private: - T *nullableValue; - alignas(alignof(T)) char storage[sizeof(T)]; - }; - -} // end namespace Catch - -// end catch_option.hpp -#include -#include -#include -#include -#include - -namespace Catch { - enum class Verbosity { - Quiet = 0, - Normal, - High - }; - - struct WarnAbout { - enum What { - Nothing = 0x00, - NoAssertions = 0x01, - NoTests = 0x02 - }; - }; - - struct ShowDurations { - enum OrNot { - DefaultForReporter, - Always, - Never - }; - }; - struct RunTests { - enum InWhatOrder { - InDeclarationOrder, - InLexicographicalOrder, - InRandomOrder - }; - }; - struct UseColour { - enum YesOrNo { - Auto, - Yes, - No - }; - }; - struct WaitForKeypress { - enum When { - Never, - BeforeStart = 1, - BeforeExit = 2, - BeforeStartAndExit = BeforeStart | BeforeExit - }; - }; - - class TestSpec; - - struct IConfig : NonCopyable { - virtual ~IConfig(); - - virtual bool allowThrows() const = 0; - virtual std::ostream &stream() const = 0; - virtual std::string name() const = 0; - virtual bool includeSuccessfulResults() const = 0; - virtual bool shouldDebugBreak() const = 0; - virtual bool warnAboutMissingAssertions() const = 0; - virtual bool warnAboutNoTests() const = 0; - virtual int abortAfter() const = 0; - virtual bool showInvisibles() const = 0; - virtual ShowDurations::OrNot showDurations() const = 0; - virtual double minDuration() const = 0; - virtual TestSpec const &testSpec() const = 0; - virtual bool hasTestFilters() const = 0; - virtual std::vector const &getTestsOrTags() const = 0; - virtual RunTests::InWhatOrder runOrder() const = 0; - virtual unsigned int rngSeed() const = 0; - virtual UseColour::YesOrNo useColour() const = 0; - virtual std::vector const &getSectionsToRun() const = 0; - virtual Verbosity verbosity() const = 0; - - virtual bool benchmarkNoAnalysis() const = 0; - virtual int benchmarkSamples() const = 0; - virtual double benchmarkConfidenceInterval() const = 0; - virtual unsigned int benchmarkResamples() const = 0; - virtual std::chrono::milliseconds benchmarkWarmupTime() const = 0; - }; - - using IConfigPtr = std::shared_ptr; -} // namespace Catch - -// end catch_interfaces_config.h -// start catch_random_number_generator.h - -#include - -namespace Catch { - // This is a simple implementation of C++11 Uniform Random Number - // Generator. It does not provide all operators, because Catch2 - // does not use it, but it should behave as expected inside stdlib's - // distributions. - // The implementation is based on the PCG family (http://pcg-random.org) - class SimplePcg32 { - using state_type = std::uint64_t; - - public: - using result_type = std::uint32_t; - static constexpr result_type(min)() { return 0; } - static constexpr result_type(max)() { return static_cast(-1); } - - // Provide some default initial state for the default constructor - SimplePcg32() - : SimplePcg32(0xed743cc4U) { - } - - explicit SimplePcg32(result_type seed_); - - void seed(result_type seed_); - void discard(uint64_t skip); - - result_type operator()(); - - private: - friend bool operator==(SimplePcg32 const &lhs, SimplePcg32 const &rhs); - friend bool operator!=(SimplePcg32 const &lhs, SimplePcg32 const &rhs); - - // In theory we also need operator<< and operator>> - // In practice we do not use them, so we will skip them for now - - std::uint64_t m_state; - // This part of the state determines which "stream" of the numbers - // is chosen -- we take it as a constant for Catch2, so we only - // need to deal with seeding the main state. - // Picked by reading 8 bytes from `/dev/random` :-) - static const std::uint64_t s_inc = (0x13ed0cc53f939476ULL << 1ULL) | 1ULL; - }; - -} // end namespace Catch - -// end catch_random_number_generator.h -#include - -namespace Catch { - namespace Generators { - template - class RandomFloatingGenerator final : public IGenerator { - Catch::SimplePcg32 &m_rng; - std::uniform_real_distribution m_dist; - Float m_current_number; - - public: - RandomFloatingGenerator(Float a, Float b) - : m_rng(rng()) - , m_dist(a, b) { - static_cast(next()); - } - - Float const &get() const override { return m_current_number; } - bool next() override { - m_current_number = m_dist(m_rng); - return true; - } - }; - - template - class RandomIntegerGenerator final : public IGenerator { - Catch::SimplePcg32 &m_rng; - std::uniform_int_distribution m_dist; - Integer m_current_number; - - public: - RandomIntegerGenerator(Integer a, Integer b) - : m_rng(rng()) - , m_dist(a, b) { - static_cast(next()); - } - - Integer const &get() const override { return m_current_number; } - bool next() override { - m_current_number = m_dist(m_rng); - return true; - } - }; - - // TODO: Ideally this would be also constrained against the various char types, - // but I don't expect users to run into that in practice. - template - typename std::enable_if::value && !std::is_same::value, GeneratorWrapper>::type random(T a, T b) { - return GeneratorWrapper(pf::make_unique>(a, b)); - } - - template - typename std::enable_if::value, GeneratorWrapper>::type random(T a, T b) { - return GeneratorWrapper(pf::make_unique>(a, b)); - } - - template - class RangeGenerator final : public IGenerator { - T m_current; - T m_end; - T m_step; - bool m_positive; - - public: - RangeGenerator(T const &start, T const &end, T const &step) - : m_current(start) - , m_end(end) - , m_step(step) - , m_positive(m_step > T(0)) { - assert(m_current != m_end && "Range start and end cannot be equal"); - assert(m_step != T(0) && "Step size cannot be zero"); - assert(((m_positive && m_current <= m_end) || (!m_positive && m_current >= m_end)) && "Step moves away from end"); - } - - RangeGenerator(T const &start, T const &end) - : RangeGenerator(start, end, (start < end) ? T(1) : T(-1)) { - } - - T const &get() const override { return m_current; } - - bool next() override { - m_current += m_step; - return (m_positive) ? (m_current < m_end) : (m_current > m_end); - } - }; - - template - GeneratorWrapper range(T const &start, T const &end, T const &step) { - static_assert(std::is_arithmetic::value && !std::is_same::value, "Type must be numeric"); - return GeneratorWrapper(pf::make_unique>(start, end, step)); - } - - template - GeneratorWrapper range(T const &start, T const &end) { - static_assert(std::is_integral::value && !std::is_same::value, "Type must be an integer"); - return GeneratorWrapper(pf::make_unique>(start, end)); - } - - template - class IteratorGenerator final : public IGenerator { - static_assert(!std::is_same::value, - "IteratorGenerator currently does not support bools" - "because of std::vector specialization"); - - std::vector m_elems; - size_t m_current = 0; - - public: - template - IteratorGenerator(InputIterator first, InputSentinel last) - : m_elems(first, last) { - if (m_elems.empty()) { - Catch::throw_exception(GeneratorException("IteratorGenerator received no valid values")); - } - } - - T const &get() const override { return m_elems[m_current]; } - - bool next() override { - ++m_current; - return m_current != m_elems.size(); - } - }; - - template::value_type> - GeneratorWrapper from_range(InputIterator from, InputSentinel to) { - return GeneratorWrapper(pf::make_unique>(from, to)); - } - - template - GeneratorWrapper from_range(Container const &cnt) { - return GeneratorWrapper(pf::make_unique>(cnt.begin(), cnt.end())); - } - - } // namespace Generators -} // namespace Catch - -// end catch_generators_specific.hpp - -// These files are included here so the single_include script doesn't put them -// in the conditionally compiled sections -// start catch_test_case_info.h - -#include -#include -#include - -#ifdef __clang__ -#pragma clang diagnostic push -#pragma clang diagnostic ignored "-Wpadded" -#endif - -namespace Catch { - struct ITestInvoker; - - struct TestCaseInfo { - enum SpecialProperties { - None = 0, - IsHidden = 1 << 1, - ShouldFail = 1 << 2, - MayFail = 1 << 3, - Throws = 1 << 4, - NonPortable = 1 << 5, - Benchmark = 1 << 6 - }; - - TestCaseInfo(std::string const &_name, - std::string const &_className, - std::string const &_description, - std::vector const &_tags, - SourceLineInfo const &_lineInfo); - - friend void setTags(TestCaseInfo &testCaseInfo, std::vector tags); - - bool isHidden() const; - bool throws() const; - bool okToFail() const; - bool expectedToFail() const; - - std::string tagsAsString() const; - - std::string name; - std::string className; - std::string description; - std::vector tags; - std::vector lcaseTags; - SourceLineInfo lineInfo; - SpecialProperties properties; - }; - - class TestCase : public TestCaseInfo { - public: - TestCase(ITestInvoker *testCase, TestCaseInfo &&info); - - TestCase withName(std::string const &_newName) const; - - void invoke() const; - - TestCaseInfo const &getTestCaseInfo() const; - - bool operator==(TestCase const &other) const; - bool operator<(TestCase const &other) const; - - private: - std::shared_ptr test; - }; - - TestCase makeTestCase(ITestInvoker *testCase, std::string const &className, NameAndTags const &nameAndTags, SourceLineInfo const &lineInfo); -} // namespace Catch - -#ifdef __clang__ -#pragma clang diagnostic pop -#endif - -// end catch_test_case_info.h -// start catch_interfaces_runner.h - -namespace Catch { - struct IRunner { - virtual ~IRunner(); - virtual bool aborting() const = 0; - }; -} // namespace Catch - -// end catch_interfaces_runner.h - -#ifdef __OBJC__ -// start catch_objc.hpp - -#import - -#include - -// NB. Any general catch headers included here must be included -// in catch.hpp first to make sure they are included by the single -// header for non obj-usage - -/////////////////////////////////////////////////////////////////////////////// -// This protocol is really only here for (self) documenting purposes, since -// all its methods are optional. -@protocol OcFixture - -@optional - -- (void)setUp; -- (void)tearDown; - -@end - -namespace Catch { - class OcMethod : public ITestInvoker { - public: - OcMethod(Class cls, SEL sel) - : m_cls(cls) - , m_sel(sel) { - } - - virtual void invoke() const { - id obj = [[m_cls alloc] init]; - - performOptionalSelector(obj, @selector(setUp)); - performOptionalSelector(obj, m_sel); - performOptionalSelector(obj, @selector(tearDown)); - - arcSafeRelease(obj); - } - - private: - virtual ~OcMethod() {} - - Class m_cls; - SEL m_sel; - }; - - namespace Detail { - inline std::string getAnnotation(Class cls, std::string const &annotationName, std::string const &testCaseName) { - NSString *selStr = [[NSString alloc] initWithFormat:@"Catch_%s_%s", annotationName.c_str(), testCaseName.c_str()]; - SEL sel = NSSelectorFromString(selStr); - arcSafeRelease(selStr); - id value = performOptionalSelector(cls, sel); - if (value) - return [(NSString *)value UTF8String]; - return ""; - } - } // namespace Detail - - inline std::size_t registerTestMethods() { - std::size_t noTestMethods = 0; - int noClasses = objc_getClassList(nullptr, 0); - - Class *classes = (CATCH_UNSAFE_UNRETAINED Class *)malloc(sizeof(Class) * noClasses); - objc_getClassList(classes, noClasses); - - for (int c = 0; c < noClasses; c++) { - Class cls = classes[c]; - { - u_int count; - Method *methods = class_copyMethodList(cls, &count); - for (u_int m = 0; m < count; m++) { - SEL selector = method_getName(methods[m]); - std::string methodName = sel_getName(selector); - if (startsWith(methodName, "Catch_TestCase_")) { - std::string testCaseName = methodName.substr(15); - std::string name = Detail::getAnnotation(cls, "Name", testCaseName); - std::string desc = Detail::getAnnotation(cls, "Description", testCaseName); - const char *className = class_getName(cls); - - getMutableRegistryHub().registerTest( - makeTestCase(new OcMethod(cls, selector), className, NameAndTags(name.c_str(), desc.c_str()), SourceLineInfo("", 0))); - noTestMethods++; - } - } - free(methods); - } - } - return noTestMethods; - } - -#if !defined(CATCH_CONFIG_DISABLE_MATCHERS) - - namespace Matchers { - namespace Impl { - namespace NSStringMatchers { - struct StringHolder : MatcherBase { - StringHolder(NSString *substr) - : m_substr([substr copy]) { - } - StringHolder(StringHolder const &other) - : m_substr([other.m_substr copy]) { - } - StringHolder() { arcSafeRelease(m_substr); } - - bool match(NSString *str) const override { return false; } - - NSString *CATCH_ARC_STRONG m_substr; - }; - - struct Equals : StringHolder { - Equals(NSString *substr) - : StringHolder(substr) { - } - - bool match(NSString *str) const override { return (str != nil || m_substr == nil) && [str isEqualToString:m_substr]; } - - std::string describe() const override { return "equals string: " + Catch::Detail::stringify(m_substr); } - }; - - struct Contains : StringHolder { - Contains(NSString *substr) - : StringHolder(substr) { - } - - bool match(NSString *str) const override { - return (str != nil || m_substr == nil) && [str rangeOfString:m_substr].location != NSNotFound; - } - - std::string describe() const override { return "contains string: " + Catch::Detail::stringify(m_substr); } - }; - - struct StartsWith : StringHolder { - StartsWith(NSString *substr) - : StringHolder(substr) { - } - - bool match(NSString *str) const override { return (str != nil || m_substr == nil) && [str rangeOfString:m_substr].location == 0; } - - std::string describe() const override { return "starts with: " + Catch::Detail::stringify(m_substr); } - }; - struct EndsWith : StringHolder { - EndsWith(NSString *substr) - : StringHolder(substr) { - } - - bool match(NSString *str) const override { - return (str != nil || m_substr == nil) && [str rangeOfString:m_substr].location == [str length] - [m_substr length]; - } - - std::string describe() const override { return "ends with: " + Catch::Detail::stringify(m_substr); } - }; - - } // namespace NSStringMatchers - } // namespace Impl - - inline Impl::NSStringMatchers::Equals Equals(NSString *substr) { - return Impl::NSStringMatchers::Equals(substr); - } - - inline Impl::NSStringMatchers::Contains Contains(NSString *substr) { - return Impl::NSStringMatchers::Contains(substr); - } - - inline Impl::NSStringMatchers::StartsWith StartsWith(NSString *substr) { - return Impl::NSStringMatchers::StartsWith(substr); - } - - inline Impl::NSStringMatchers::EndsWith EndsWith(NSString *substr) { - return Impl::NSStringMatchers::EndsWith(substr); - } - - } // namespace Matchers - - using namespace Matchers; - -#endif // CATCH_CONFIG_DISABLE_MATCHERS - -} // namespace Catch - -/////////////////////////////////////////////////////////////////////////////// -#define OC_MAKE_UNIQUE_NAME(root, uniqueSuffix) root##uniqueSuffix -#define OC_TEST_CASE2(name, desc, uniqueSuffix) \ - +(NSString *)OC_MAKE_UNIQUE_NAME(Catch_Name_test_, uniqueSuffix) { \ - return @name; \ - } \ - +(NSString *)OC_MAKE_UNIQUE_NAME(Catch_Description_test_, uniqueSuffix) { \ - return @desc; \ - } \ - -(void)OC_MAKE_UNIQUE_NAME(Catch_TestCase_test_, uniqueSuffix) - -#define OC_TEST_CASE(name, desc) OC_TEST_CASE2(name, desc, __LINE__) - -// end catch_objc.hpp -#endif - -// Benchmarking needs the externally-facing parts of reporters to work -#if defined(CATCH_CONFIG_EXTERNAL_INTERFACES) || defined(CATCH_CONFIG_ENABLE_BENCHMARKING) -// start catch_external_interfaces.h - -// start catch_reporter_bases.hpp - -// start catch_interfaces_reporter.h - -// start catch_config.hpp - -// start catch_test_spec_parser.h - -#ifdef __clang__ -#pragma clang diagnostic push -#pragma clang diagnostic ignored "-Wpadded" -#endif - -// start catch_test_spec.h - -#ifdef __clang__ -#pragma clang diagnostic push -#pragma clang diagnostic ignored "-Wpadded" -#endif - -// start catch_wildcard_pattern.h - -namespace Catch { - class WildcardPattern { - enum WildcardPosition { - NoWildcard = 0, - WildcardAtStart = 1, - WildcardAtEnd = 2, - WildcardAtBothEnds = WildcardAtStart | WildcardAtEnd - }; - - public: - WildcardPattern(std::string const &pattern, CaseSensitive::Choice caseSensitivity); - virtual ~WildcardPattern() = default; - virtual bool matches(std::string const &str) const; - - private: - std::string normaliseString(std::string const &str) const; - CaseSensitive::Choice m_caseSensitivity; - WildcardPosition m_wildcard = NoWildcard; - std::string m_pattern; - }; -} // namespace Catch - -// end catch_wildcard_pattern.h -#include -#include -#include - -namespace Catch { - struct IConfig; - - class TestSpec { - class Pattern { - public: - explicit Pattern(std::string const &name); - virtual ~Pattern(); - virtual bool matches(TestCaseInfo const &testCase) const = 0; - std::string const &name() const; - - private: - std::string const m_name; - }; - using PatternPtr = std::shared_ptr; - - class NamePattern : public Pattern { - public: - explicit NamePattern(std::string const &name, std::string const &filterString); - bool matches(TestCaseInfo const &testCase) const override; - - private: - WildcardPattern m_wildcardPattern; - }; - - class TagPattern : public Pattern { - public: - explicit TagPattern(std::string const &tag, std::string const &filterString); - bool matches(TestCaseInfo const &testCase) const override; - - private: - std::string m_tag; - }; - - class ExcludedPattern : public Pattern { - public: - explicit ExcludedPattern(PatternPtr const &underlyingPattern); - bool matches(TestCaseInfo const &testCase) const override; - - private: - PatternPtr m_underlyingPattern; - }; - - struct Filter { - std::vector m_patterns; - - bool matches(TestCaseInfo const &testCase) const; - std::string name() const; - }; - - public: - struct FilterMatch { - std::string name; - std::vector tests; - }; - using Matches = std::vector; - using vectorStrings = std::vector; - - bool hasFilters() const; - bool matches(TestCaseInfo const &testCase) const; - Matches matchesByFilter(std::vector const &testCases, IConfig const &config) const; - const vectorStrings &getInvalidArgs() const; - - private: - std::vector m_filters; - std::vector m_invalidArgs; - friend class TestSpecParser; - }; -} // namespace Catch - -#ifdef __clang__ -#pragma clang diagnostic pop -#endif - -// end catch_test_spec.h -// start catch_interfaces_tag_alias_registry.h - -#include - -namespace Catch { - struct TagAlias; - - struct ITagAliasRegistry { - virtual ~ITagAliasRegistry(); - // Nullptr if not present - virtual TagAlias const *find(std::string const &alias) const = 0; - virtual std::string expandAliases(std::string const &unexpandedTestSpec) const = 0; - - static ITagAliasRegistry const &get(); - }; - -} // end namespace Catch - -// end catch_interfaces_tag_alias_registry.h -namespace Catch { - class TestSpecParser { - enum Mode { - None, - Name, - QuotedName, - Tag, - EscapedName - }; - Mode m_mode = None; - Mode lastMode = None; - bool m_exclusion = false; - std::size_t m_pos = 0; - std::size_t m_realPatternPos = 0; - std::string m_arg; - std::string m_substring; - std::string m_patternName; - std::vector m_escapeChars; - TestSpec::Filter m_currentFilter; - TestSpec m_testSpec; - ITagAliasRegistry const *m_tagAliases = nullptr; - - public: - TestSpecParser(ITagAliasRegistry const &tagAliases); - - TestSpecParser &parse(std::string const &arg); - TestSpec testSpec(); - - private: - bool visitChar(char c); - void startNewMode(Mode mode); - bool processNoneChar(char c); - void processNameChar(char c); - bool processOtherChar(char c); - void endMode(); - void escape(); - bool isControlChar(char c) const; - void saveLastMode(); - void revertBackToLastMode(); - void addFilter(); - bool separate(); - - // Handles common preprocessing of the pattern for name/tag patterns - std::string preprocessPattern(); - // Adds the current pattern as a test name - void addNamePattern(); - // Adds the current pattern as a tag - void addTagPattern(); - - inline void addCharToPattern(char c) { - m_substring += c; - m_patternName += c; - m_realPatternPos++; - } - }; - TestSpec parseTestSpec(std::string const &arg); - -} // namespace Catch - -#ifdef __clang__ -#pragma clang diagnostic pop -#endif - -// end catch_test_spec_parser.h -// Libstdc++ doesn't like incomplete classes for unique_ptr - -#include -#include -#include - -#ifndef CATCH_CONFIG_CONSOLE_WIDTH -#define CATCH_CONFIG_CONSOLE_WIDTH 80 -#endif - -namespace Catch { - struct IStream; - - struct ConfigData { - bool listTests = false; - bool listTags = false; - bool listReporters = false; - bool listTestNamesOnly = false; - - bool showSuccessfulTests = false; - bool shouldDebugBreak = false; - bool noThrow = false; - bool showHelp = false; - bool showInvisibles = false; - bool filenamesAsTags = false; - bool libIdentify = false; - - int abortAfter = -1; - unsigned int rngSeed = 0; - - bool benchmarkNoAnalysis = false; - unsigned int benchmarkSamples = 100; - double benchmarkConfidenceInterval = 0.95; - unsigned int benchmarkResamples = 100000; - std::chrono::milliseconds::rep benchmarkWarmupTime = 100; - - Verbosity verbosity = Verbosity::Normal; - WarnAbout::What warnings = WarnAbout::Nothing; - ShowDurations::OrNot showDurations = ShowDurations::DefaultForReporter; - double minDuration = -1; - RunTests::InWhatOrder runOrder = RunTests::InDeclarationOrder; - UseColour::YesOrNo useColour = UseColour::Auto; - WaitForKeypress::When waitForKeypress = WaitForKeypress::Never; - - std::string outputFilename; - std::string name; - std::string processName; -#ifndef CATCH_CONFIG_DEFAULT_REPORTER -#define CATCH_CONFIG_DEFAULT_REPORTER "console" -#endif - std::string reporterName = CATCH_CONFIG_DEFAULT_REPORTER; -#undef CATCH_CONFIG_DEFAULT_REPORTER - - std::vector testsOrTags; - std::vector sectionsToRun; - }; - - class Config : public IConfig { - public: - Config() = default; - Config(ConfigData const &data); - virtual ~Config() = default; - - std::string const &getFilename() const; - - bool listTests() const; - bool listTestNamesOnly() const; - bool listTags() const; - bool listReporters() const; - - std::string getProcessName() const; - std::string const &getReporterName() const; - - std::vector const &getTestsOrTags() const override; - std::vector const &getSectionsToRun() const override; - - TestSpec const &testSpec() const override; - bool hasTestFilters() const override; - - bool showHelp() const; - - // IConfig interface - bool allowThrows() const override; - std::ostream &stream() const override; - std::string name() const override; - bool includeSuccessfulResults() const override; - bool warnAboutMissingAssertions() const override; - bool warnAboutNoTests() const override; - ShowDurations::OrNot showDurations() const override; - double minDuration() const override; - RunTests::InWhatOrder runOrder() const override; - unsigned int rngSeed() const override; - UseColour::YesOrNo useColour() const override; - bool shouldDebugBreak() const override; - int abortAfter() const override; - bool showInvisibles() const override; - Verbosity verbosity() const override; - bool benchmarkNoAnalysis() const override; - int benchmarkSamples() const override; - double benchmarkConfidenceInterval() const override; - unsigned int benchmarkResamples() const override; - std::chrono::milliseconds benchmarkWarmupTime() const override; - - private: - IStream const *openStream(); - ConfigData m_data; - - std::unique_ptr m_stream; - TestSpec m_testSpec; - bool m_hasTestFilters = false; - }; - -} // end namespace Catch - -// end catch_config.hpp -// start catch_assertionresult.h - -#include - -namespace Catch { - struct AssertionResultData { - AssertionResultData() = delete; - - AssertionResultData(ResultWas::OfType _resultType, LazyExpression const &_lazyExpression); - - std::string message; - mutable std::string reconstructedExpression; - LazyExpression lazyExpression; - ResultWas::OfType resultType; - - std::string reconstructExpression() const; - }; - - class AssertionResult { - public: - AssertionResult() = delete; - AssertionResult(AssertionInfo const &info, AssertionResultData const &data); - - bool isOk() const; - bool succeeded() const; - ResultWas::OfType getResultType() const; - bool hasExpression() const; - bool hasMessage() const; - std::string getExpression() const; - std::string getExpressionInMacro() const; - bool hasExpandedExpression() const; - std::string getExpandedExpression() const; - std::string getMessage() const; - SourceLineInfo getSourceInfo() const; - StringRef getTestMacroName() const; - - // protected: - AssertionInfo m_info; - AssertionResultData m_resultData; - }; - -} // end namespace Catch - -// end catch_assertionresult.h -#if defined(CATCH_CONFIG_ENABLE_BENCHMARKING) -// start catch_estimate.hpp - -// Statistics estimates - -namespace Catch { - namespace Benchmark { - template - struct Estimate { - Duration point; - Duration lower_bound; - Duration upper_bound; - double confidence_interval; - - template - operator Estimate() const { - return {point, lower_bound, upper_bound, confidence_interval}; - } - }; - } // namespace Benchmark -} // namespace Catch - -// end catch_estimate.hpp -// start catch_outlier_classification.hpp - -// Outlier information - -namespace Catch { - namespace Benchmark { - struct OutlierClassification { - int samples_seen = 0; - int low_severe = 0; // more than 3 times IQR below Q1 - int low_mild = 0; // 1.5 to 3 times IQR below Q1 - int high_mild = 0; // 1.5 to 3 times IQR above Q3 - int high_severe = 0; // more than 3 times IQR above Q3 - - int total() const { return low_severe + low_mild + high_mild + high_severe; } - }; - } // namespace Benchmark -} // namespace Catch - -// end catch_outlier_classification.hpp -#endif // CATCH_CONFIG_ENABLE_BENCHMARKING - -#include -#include -#include -#include -#include -#include - -namespace Catch { - struct ReporterConfig { - explicit ReporterConfig(IConfigPtr const &_fullConfig); - - ReporterConfig(IConfigPtr const &_fullConfig, std::ostream &_stream); - - std::ostream &stream() const; - IConfigPtr fullConfig() const; - - private: - std::ostream *m_stream; - IConfigPtr m_fullConfig; - }; - - struct ReporterPreferences { - bool shouldRedirectStdOut = false; - bool shouldReportAllAssertions = false; - }; - - template - struct LazyStat : Option { - LazyStat &operator=(T const &_value) { - Option::operator=(_value); - used = false; - return *this; - } - void reset() { - Option::reset(); - used = false; - } - bool used = false; - }; - - struct TestRunInfo { - TestRunInfo(std::string const &_name); - std::string name; - }; - struct GroupInfo { - GroupInfo(std::string const &_name, std::size_t _groupIndex, std::size_t _groupsCount); - - std::string name; - std::size_t groupIndex; - std::size_t groupsCounts; - }; - - struct AssertionStats { - AssertionStats(AssertionResult const &_assertionResult, std::vector const &_infoMessages, Totals const &_totals); - - AssertionStats(AssertionStats const &) = default; - AssertionStats(AssertionStats &&) = default; - AssertionStats &operator=(AssertionStats const &) = delete; - AssertionStats &operator=(AssertionStats &&) = delete; - virtual ~AssertionStats(); - - AssertionResult assertionResult; - std::vector infoMessages; - Totals totals; - }; - - struct SectionStats { - SectionStats(SectionInfo const &_sectionInfo, Counts const &_assertions, double _durationInSeconds, bool _missingAssertions); - SectionStats(SectionStats const &) = default; - SectionStats(SectionStats &&) = default; - SectionStats &operator=(SectionStats const &) = default; - SectionStats &operator=(SectionStats &&) = default; - virtual ~SectionStats(); - - SectionInfo sectionInfo; - Counts assertions; - double durationInSeconds; - bool missingAssertions; - }; - - struct TestCaseStats { - TestCaseStats(TestCaseInfo const &_testInfo, Totals const &_totals, std::string const &_stdOut, std::string const &_stdErr, bool _aborting); - - TestCaseStats(TestCaseStats const &) = default; - TestCaseStats(TestCaseStats &&) = default; - TestCaseStats &operator=(TestCaseStats const &) = default; - TestCaseStats &operator=(TestCaseStats &&) = default; - virtual ~TestCaseStats(); - - TestCaseInfo testInfo; - Totals totals; - std::string stdOut; - std::string stdErr; - bool aborting; - }; - - struct TestGroupStats { - TestGroupStats(GroupInfo const &_groupInfo, Totals const &_totals, bool _aborting); - TestGroupStats(GroupInfo const &_groupInfo); - - TestGroupStats(TestGroupStats const &) = default; - TestGroupStats(TestGroupStats &&) = default; - TestGroupStats &operator=(TestGroupStats const &) = default; - TestGroupStats &operator=(TestGroupStats &&) = default; - virtual ~TestGroupStats(); - - GroupInfo groupInfo; - Totals totals; - bool aborting; - }; - - struct TestRunStats { - TestRunStats(TestRunInfo const &_runInfo, Totals const &_totals, bool _aborting); - - TestRunStats(TestRunStats const &) = default; - TestRunStats(TestRunStats &&) = default; - TestRunStats &operator=(TestRunStats const &) = default; - TestRunStats &operator=(TestRunStats &&) = default; - virtual ~TestRunStats(); - - TestRunInfo runInfo; - Totals totals; - bool aborting; - }; - -#if defined(CATCH_CONFIG_ENABLE_BENCHMARKING) - struct BenchmarkInfo { - std::string name; - double estimatedDuration; - int iterations; - int samples; - unsigned int resamples; - double clockResolution; - double clockCost; - }; - - template - struct BenchmarkStats { - BenchmarkInfo info; - - std::vector samples; - Benchmark::Estimate mean; - Benchmark::Estimate standardDeviation; - Benchmark::OutlierClassification outliers; - double outlierVariance; - - template - operator BenchmarkStats() const { - std::vector samples2; - samples2.reserve(samples.size()); - std::transform(samples.begin(), samples.end(), std::back_inserter(samples2), [](Duration d) { return Duration2(d); }); - return { - info, - std::move(samples2), - mean, - standardDeviation, - outliers, - outlierVariance, - }; - } - }; -#endif // CATCH_CONFIG_ENABLE_BENCHMARKING - - struct IStreamingReporter { - virtual ~IStreamingReporter() = default; - - // Implementing class must also provide the following static methods: - // static std::string getDescription(); - // static std::set getSupportedVerbosities() - - virtual ReporterPreferences getPreferences() const = 0; - - virtual void noMatchingTestCases(std::string const &spec) = 0; - - virtual void reportInvalidArguments(std::string const &) {} - - virtual void testRunStarting(TestRunInfo const &testRunInfo) = 0; - virtual void testGroupStarting(GroupInfo const &groupInfo) = 0; - - virtual void testCaseStarting(TestCaseInfo const &testInfo) = 0; - virtual void sectionStarting(SectionInfo const §ionInfo) = 0; - -#if defined(CATCH_CONFIG_ENABLE_BENCHMARKING) - virtual void benchmarkPreparing(std::string const &) { - } - virtual void benchmarkStarting(BenchmarkInfo const &) {} - virtual void benchmarkEnded(BenchmarkStats<> const &) {} - virtual void benchmarkFailed(std::string const &) {} -#endif // CATCH_CONFIG_ENABLE_BENCHMARKING - - virtual void assertionStarting(AssertionInfo const &assertionInfo) = 0; - - // The return value indicates if the messages buffer should be cleared: - virtual bool assertionEnded(AssertionStats const &assertionStats) = 0; - - virtual void sectionEnded(SectionStats const §ionStats) = 0; - virtual void testCaseEnded(TestCaseStats const &testCaseStats) = 0; - virtual void testGroupEnded(TestGroupStats const &testGroupStats) = 0; - virtual void testRunEnded(TestRunStats const &testRunStats) = 0; - - virtual void skipTest(TestCaseInfo const &testInfo) = 0; - - // Default empty implementation provided - virtual void fatalErrorEncountered(StringRef name); - - virtual bool isMulti() const; - }; - using IStreamingReporterPtr = std::unique_ptr; - - struct IReporterFactory { - virtual ~IReporterFactory(); - virtual IStreamingReporterPtr create(ReporterConfig const &config) const = 0; - virtual std::string getDescription() const = 0; - }; - using IReporterFactoryPtr = std::shared_ptr; - - struct IReporterRegistry { - using FactoryMap = std::map; - using Listeners = std::vector; - - virtual ~IReporterRegistry(); - virtual IStreamingReporterPtr create(std::string const &name, IConfigPtr const &config) const = 0; - virtual FactoryMap const &getFactories() const = 0; - virtual Listeners const &getListeners() const = 0; - }; - -} // end namespace Catch - -// end catch_interfaces_reporter.h -#include -#include -#include -#include -#include -#include -#include - -namespace Catch { - void prepareExpandedExpression(AssertionResult &result); - - // Returns double formatted as %.3f (format expected on output) - std::string getFormattedDuration(double duration); - - //! Should the reporter show - bool shouldShowDuration(IConfig const &config, double duration); - - std::string serializeFilters(std::vector const &container); - - template - struct StreamingReporterBase : IStreamingReporter { - StreamingReporterBase(ReporterConfig const &_config) - : m_config(_config.fullConfig()) - , stream(_config.stream()) { - m_reporterPrefs.shouldRedirectStdOut = false; - if (!DerivedT::getSupportedVerbosities().count(m_config->verbosity())) - CATCH_ERROR("Verbosity level not supported by this reporter"); - } - - ReporterPreferences getPreferences() const override { return m_reporterPrefs; } - - static std::set getSupportedVerbosities() { return {Verbosity::Normal}; } - - ~StreamingReporterBase() override = default; - - void noMatchingTestCases(std::string const &) override {} - - void reportInvalidArguments(std::string const &) override {} - - void testRunStarting(TestRunInfo const &_testRunInfo) override { currentTestRunInfo = _testRunInfo; } - - void testGroupStarting(GroupInfo const &_groupInfo) override { currentGroupInfo = _groupInfo; } - - void testCaseStarting(TestCaseInfo const &_testInfo) override { currentTestCaseInfo = _testInfo; } - void sectionStarting(SectionInfo const &_sectionInfo) override { m_sectionStack.push_back(_sectionInfo); } - - void sectionEnded(SectionStats const & /* _sectionStats */) override { m_sectionStack.pop_back(); } - void testCaseEnded(TestCaseStats const & /* _testCaseStats */) override { currentTestCaseInfo.reset(); } - void testGroupEnded(TestGroupStats const & /* _testGroupStats */) override { currentGroupInfo.reset(); } - void testRunEnded(TestRunStats const & /* _testRunStats */) override { - currentTestCaseInfo.reset(); - currentGroupInfo.reset(); - currentTestRunInfo.reset(); - } - - void skipTest(TestCaseInfo const &) override { - // Don't do anything with this by default. - // It can optionally be overridden in the derived class. - } - - IConfigPtr m_config; - std::ostream &stream; - - LazyStat currentTestRunInfo; - LazyStat currentGroupInfo; - LazyStat currentTestCaseInfo; - - std::vector m_sectionStack; - ReporterPreferences m_reporterPrefs; - }; - - template - struct CumulativeReporterBase : IStreamingReporter { - template - struct Node { - explicit Node(T const &_value) - : value(_value) { - } - virtual ~Node() {} - - using ChildNodes = std::vector>; - T value; - ChildNodes children; - }; - struct SectionNode { - explicit SectionNode(SectionStats const &_stats) - : stats(_stats) { - } - virtual ~SectionNode() = default; - - bool operator==(SectionNode const &other) const { return stats.sectionInfo.lineInfo == other.stats.sectionInfo.lineInfo; } - bool operator==(std::shared_ptr const &other) const { return operator==(*other); } - - SectionStats stats; - using ChildSections = std::vector>; - using Assertions = std::vector; - ChildSections childSections; - Assertions assertions; - std::string stdOut; - std::string stdErr; - }; - - struct BySectionInfo { - BySectionInfo(SectionInfo const &other) - : m_other(other) { - } - BySectionInfo(BySectionInfo const &other) - : m_other(other.m_other) { - } - bool operator()(std::shared_ptr const &node) const { - return ((node->stats.sectionInfo.name == m_other.name) && (node->stats.sectionInfo.lineInfo == m_other.lineInfo)); - } - void operator=(BySectionInfo const &) = delete; - - private: - SectionInfo const &m_other; - }; - - using TestCaseNode = Node; - using TestGroupNode = Node; - using TestRunNode = Node; - - CumulativeReporterBase(ReporterConfig const &_config) - : m_config(_config.fullConfig()) - , stream(_config.stream()) { - m_reporterPrefs.shouldRedirectStdOut = false; - if (!DerivedT::getSupportedVerbosities().count(m_config->verbosity())) - CATCH_ERROR("Verbosity level not supported by this reporter"); - } - ~CumulativeReporterBase() override = default; - - ReporterPreferences getPreferences() const override { return m_reporterPrefs; } - - static std::set getSupportedVerbosities() { return {Verbosity::Normal}; } - - void testRunStarting(TestRunInfo const &) override {} - void testGroupStarting(GroupInfo const &) override {} - - void testCaseStarting(TestCaseInfo const &) override {} - - void sectionStarting(SectionInfo const §ionInfo) override { - SectionStats incompleteStats(sectionInfo, Counts(), 0, false); - std::shared_ptr node; - if (m_sectionStack.empty()) { - if (!m_rootSection) - m_rootSection = std::make_shared(incompleteStats); - node = m_rootSection; - } else { - SectionNode &parentNode = *m_sectionStack.back(); - auto it = std::find_if(parentNode.childSections.begin(), parentNode.childSections.end(), BySectionInfo(sectionInfo)); - if (it == parentNode.childSections.end()) { - node = std::make_shared(incompleteStats); - parentNode.childSections.push_back(node); - } else - node = *it; - } - m_sectionStack.push_back(node); - m_deepestSection = std::move(node); - } - - void assertionStarting(AssertionInfo const &) override {} - - bool assertionEnded(AssertionStats const &assertionStats) override { - assert(!m_sectionStack.empty()); - // AssertionResult holds a pointer to a temporary DecomposedExpression, - // which getExpandedExpression() calls to build the expression string. - // Our section stack copy of the assertionResult will likely outlive the - // temporary, so it must be expanded or discarded now to avoid calling - // a destroyed object later. - prepareExpandedExpression(const_cast(assertionStats.assertionResult)); - SectionNode §ionNode = *m_sectionStack.back(); - sectionNode.assertions.push_back(assertionStats); - return true; - } - void sectionEnded(SectionStats const §ionStats) override { - assert(!m_sectionStack.empty()); - SectionNode &node = *m_sectionStack.back(); - node.stats = sectionStats; - m_sectionStack.pop_back(); - } - void testCaseEnded(TestCaseStats const &testCaseStats) override { - auto node = std::make_shared(testCaseStats); - assert(m_sectionStack.size() == 0); - node->children.push_back(m_rootSection); - m_testCases.push_back(node); - m_rootSection.reset(); - - assert(m_deepestSection); - m_deepestSection->stdOut = testCaseStats.stdOut; - m_deepestSection->stdErr = testCaseStats.stdErr; - } - void testGroupEnded(TestGroupStats const &testGroupStats) override { - auto node = std::make_shared(testGroupStats); - node->children.swap(m_testCases); - m_testGroups.push_back(node); - } - void testRunEnded(TestRunStats const &testRunStats) override { - auto node = std::make_shared(testRunStats); - node->children.swap(m_testGroups); - m_testRuns.push_back(node); - testRunEndedCumulative(); - } - virtual void testRunEndedCumulative() = 0; - - void skipTest(TestCaseInfo const &) override {} - - IConfigPtr m_config; - std::ostream &stream; - std::vector m_assertions; - std::vector>> m_sections; - std::vector> m_testCases; - std::vector> m_testGroups; - - std::vector> m_testRuns; - - std::shared_ptr m_rootSection; - std::shared_ptr m_deepestSection; - std::vector> m_sectionStack; - ReporterPreferences m_reporterPrefs; - }; - - template - char const *getLineOfChars() { - static char line[CATCH_CONFIG_CONSOLE_WIDTH] = {0}; - if (!*line) { - std::memset(line, C, CATCH_CONFIG_CONSOLE_WIDTH - 1); - line[CATCH_CONFIG_CONSOLE_WIDTH - 1] = 0; - } - return line; - } - - struct TestEventListenerBase : StreamingReporterBase { - TestEventListenerBase(ReporterConfig const &_config); - - static std::set getSupportedVerbosities(); - - void assertionStarting(AssertionInfo const &) override; - bool assertionEnded(AssertionStats const &) override; - }; - -} // end namespace Catch - -// end catch_reporter_bases.hpp -// start catch_console_colour.h - -namespace Catch { - struct Colour { - enum Code { - None = 0, - - White, - Red, - Green, - Blue, - Cyan, - Yellow, - Grey, - - Bright = 0x10, - - BrightRed = Bright | Red, - BrightGreen = Bright | Green, - LightGrey = Bright | Grey, - BrightWhite = Bright | White, - BrightYellow = Bright | Yellow, - - // By intention - FileName = LightGrey, - Warning = BrightYellow, - ResultError = BrightRed, - ResultSuccess = BrightGreen, - ResultExpectedFailure = Warning, - - Error = BrightRed, - Success = Green, - - OriginalExpression = Cyan, - ReconstructedExpression = BrightYellow, - - SecondaryText = LightGrey, - Headers = White - }; - - // Use constructed object for RAII guard - Colour(Code _colourCode); - Colour(Colour &&other) noexcept; - Colour &operator=(Colour &&other) noexcept; - ~Colour(); - - // Use static method for one-shot changes - static void use(Code _colourCode); - - private: - bool m_moved = false; - }; - - std::ostream &operator<<(std::ostream &os, Colour const &); - -} // end namespace Catch - -// end catch_console_colour.h -// start catch_reporter_registrars.hpp - -namespace Catch { - template - class ReporterRegistrar { - class ReporterFactory : public IReporterFactory { - IStreamingReporterPtr create(ReporterConfig const &config) const override { return std::unique_ptr(new T(config)); } - - std::string getDescription() const override { return T::getDescription(); } - }; - - public: - explicit ReporterRegistrar(std::string const &name) { - getMutableRegistryHub().registerReporter(name, std::make_shared()); - } - }; - - template - class ListenerRegistrar { - class ListenerFactory : public IReporterFactory { - IStreamingReporterPtr create(ReporterConfig const &config) const override { return std::unique_ptr(new T(config)); } - std::string getDescription() const override { return std::string(); } - }; - - public: - ListenerRegistrar() { getMutableRegistryHub().registerListener(std::make_shared()); } - }; -} // namespace Catch - -#if !defined(CATCH_CONFIG_DISABLE) - -#define CATCH_REGISTER_REPORTER(name, reporterType) \ - CATCH_INTERNAL_START_WARNINGS_SUPPRESSION \ - CATCH_INTERNAL_SUPPRESS_GLOBALS_WARNINGS \ - namespace { \ - Catch::ReporterRegistrar catch_internal_RegistrarFor##reporterType(name); \ - } \ - CATCH_INTERNAL_STOP_WARNINGS_SUPPRESSION - -#define CATCH_REGISTER_LISTENER(listenerType) \ - CATCH_INTERNAL_START_WARNINGS_SUPPRESSION \ - CATCH_INTERNAL_SUPPRESS_GLOBALS_WARNINGS \ - namespace { \ - Catch::ListenerRegistrar catch_internal_RegistrarFor##listenerType; \ - } \ - CATCH_INTERNAL_STOP_WARNINGS_SUPPRESSION -#else // CATCH_CONFIG_DISABLE - -#define CATCH_REGISTER_REPORTER(name, reporterType) -#define CATCH_REGISTER_LISTENER(listenerType) - -#endif // CATCH_CONFIG_DISABLE - -// end catch_reporter_registrars.hpp -// Allow users to base their work off existing reporters -// start catch_reporter_compact.h - -namespace Catch { - struct CompactReporter : StreamingReporterBase { - using StreamingReporterBase::StreamingReporterBase; - - ~CompactReporter() override; - - static std::string getDescription(); - - void noMatchingTestCases(std::string const &spec) override; - - void assertionStarting(AssertionInfo const &) override; - - bool assertionEnded(AssertionStats const &_assertionStats) override; - - void sectionEnded(SectionStats const &_sectionStats) override; - - void testRunEnded(TestRunStats const &_testRunStats) override; - }; - -} // end namespace Catch - -// end catch_reporter_compact.h -// start catch_reporter_console.h - -#if defined(_MSC_VER) -#pragma warning(push) -#pragma warning(disable : 4061) // Not all labels are EXPLICITLY handled in switch \ - // Note that 4062 (not all labels are handled \ - // and default is missing) is enabled -#endif - -namespace Catch { - // Fwd decls - struct SummaryColumn; - class TablePrinter; - - struct ConsoleReporter : StreamingReporterBase { - std::unique_ptr m_tablePrinter; - - ConsoleReporter(ReporterConfig const &config); - ~ConsoleReporter() override; - static std::string getDescription(); - - void noMatchingTestCases(std::string const &spec) override; - - void reportInvalidArguments(std::string const &arg) override; - - void assertionStarting(AssertionInfo const &) override; - - bool assertionEnded(AssertionStats const &_assertionStats) override; - - void sectionStarting(SectionInfo const &_sectionInfo) override; - void sectionEnded(SectionStats const &_sectionStats) override; - -#if defined(CATCH_CONFIG_ENABLE_BENCHMARKING) - void benchmarkPreparing(std::string const &name) override; - void benchmarkStarting(BenchmarkInfo const &info) override; - void benchmarkEnded(BenchmarkStats<> const &stats) override; - void benchmarkFailed(std::string const &error) override; -#endif // CATCH_CONFIG_ENABLE_BENCHMARKING - - void testCaseEnded(TestCaseStats const &_testCaseStats) override; - void testGroupEnded(TestGroupStats const &_testGroupStats) override; - void testRunEnded(TestRunStats const &_testRunStats) override; - void testRunStarting(TestRunInfo const &_testRunInfo) override; - - private: - void lazyPrint(); - - void lazyPrintWithoutClosingBenchmarkTable(); - void lazyPrintRunInfo(); - void lazyPrintGroupInfo(); - void printTestCaseAndSectionHeader(); - - void printClosedHeader(std::string const &_name); - void printOpenHeader(std::string const &_name); - - // if string has a : in first line will set indent to follow it on - // subsequent lines - void printHeaderString(std::string const &_string, std::size_t indent = 0); - - void printTotals(Totals const &totals); - void printSummaryRow(std::string const &label, std::vector const &cols, std::size_t row); - - void printTotalsDivider(Totals const &totals); - void printSummaryDivider(); - void printTestFilters(); - - private: - bool m_headerPrinted = false; - }; - -} // end namespace Catch - -#if defined(_MSC_VER) -#pragma warning(pop) -#endif - -// end catch_reporter_console.h -// start catch_reporter_junit.h - -// start catch_xmlwriter.h - -#include - -namespace Catch { - enum class XmlFormatting { - None = 0x00, - Indent = 0x01, - Newline = 0x02, - }; - - XmlFormatting operator|(XmlFormatting lhs, XmlFormatting rhs); - XmlFormatting operator&(XmlFormatting lhs, XmlFormatting rhs); - - 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, XmlFormatting fmt); - - ScopedElement(ScopedElement &&other) noexcept; - ScopedElement &operator=(ScopedElement &&other) noexcept; - - ~ScopedElement(); - - ScopedElement &writeText(std::string const &text, XmlFormatting fmt = XmlFormatting::Newline | XmlFormatting::Indent); - - template - ScopedElement &writeAttribute(std::string const &name, T const &attribute) { - m_writer->writeAttribute(name, attribute); - return *this; - } - - private: - mutable XmlWriter *m_writer = nullptr; - XmlFormatting m_fmt; - }; - - XmlWriter(std::ostream &os = Catch::cout()); - ~XmlWriter(); - - XmlWriter(XmlWriter const &) = delete; - XmlWriter &operator=(XmlWriter const &) = delete; - - XmlWriter &startElement(std::string const &name, XmlFormatting fmt = XmlFormatting::Newline | XmlFormatting::Indent); - - ScopedElement scopedElement(std::string const &name, XmlFormatting fmt = XmlFormatting::Newline | XmlFormatting::Indent); - - XmlWriter &endElement(XmlFormatting fmt = XmlFormatting::Newline | XmlFormatting::Indent); - - XmlWriter &writeAttribute(std::string const &name, std::string const &attribute); - - XmlWriter &writeAttribute(std::string const &name, bool attribute); - - template - XmlWriter &writeAttribute(std::string const &name, T const &attribute) { - ReusableStringStream rss; - rss << attribute; - return writeAttribute(name, rss.str()); - } - - XmlWriter &writeText(std::string const &text, XmlFormatting fmt = XmlFormatting::Newline | XmlFormatting::Indent); - - XmlWriter &writeComment(std::string const &text, XmlFormatting fmt = XmlFormatting::Newline | XmlFormatting::Indent); - - void writeStylesheetRef(std::string const &url); - - XmlWriter &writeBlankLine(); - - void ensureTagClosed(); - - private: - void applyFormatting(XmlFormatting fmt); - - void writeDeclaration(); - - void newlineIfNecessary(); - - bool m_tagIsOpen = false; - bool m_needsNewline = false; - std::vector m_tags; - std::string m_indent; - std::ostream &m_os; - }; - -} // namespace Catch - -// end catch_xmlwriter.h -namespace Catch { - class JunitReporter : public CumulativeReporterBase { - public: - JunitReporter(ReporterConfig const &_config); - - ~JunitReporter() override; - - static std::string getDescription(); - - void noMatchingTestCases(std::string const & /*spec*/) override; - - void testRunStarting(TestRunInfo const &runInfo) override; - - void testGroupStarting(GroupInfo const &groupInfo) override; - - void testCaseStarting(TestCaseInfo const &testCaseInfo) override; - bool assertionEnded(AssertionStats const &assertionStats) override; - - void testCaseEnded(TestCaseStats const &testCaseStats) override; - - void testGroupEnded(TestGroupStats const &testGroupStats) override; - - void testRunEndedCumulative() override; - - void writeGroup(TestGroupNode const &groupNode, double suiteTime); - - void writeTestCase(TestCaseNode const &testCaseNode); - - void writeSection(std::string const &className, std::string const &rootName, SectionNode const §ionNode); - - void writeAssertions(SectionNode const §ionNode); - void writeAssertion(AssertionStats const &stats); - - XmlWriter xml; - Timer suiteTimer; - std::string stdOutForSuite; - std::string stdErrForSuite; - unsigned int unexpectedExceptions = 0; - bool m_okToFail = false; - }; - -} // end namespace Catch - -// end catch_reporter_junit.h -// start catch_reporter_xml.h - -namespace Catch { - class XmlReporter : public StreamingReporterBase { - public: - XmlReporter(ReporterConfig const &_config); - - ~XmlReporter() override; - - static std::string getDescription(); - - virtual std::string getStylesheetRef() const; - - void writeSourceInfo(SourceLineInfo const &sourceInfo); - - public: // StreamingReporterBase - void noMatchingTestCases(std::string const &s) override; - - void testRunStarting(TestRunInfo const &testInfo) override; - - void testGroupStarting(GroupInfo const &groupInfo) override; - - void testCaseStarting(TestCaseInfo const &testInfo) override; - - void sectionStarting(SectionInfo const §ionInfo) override; - - void assertionStarting(AssertionInfo const &) override; - - bool assertionEnded(AssertionStats const &assertionStats) override; - - void sectionEnded(SectionStats const §ionStats) override; - - void testCaseEnded(TestCaseStats const &testCaseStats) override; - - void testGroupEnded(TestGroupStats const &testGroupStats) override; - - void testRunEnded(TestRunStats const &testRunStats) override; - -#if defined(CATCH_CONFIG_ENABLE_BENCHMARKING) - void benchmarkPreparing(std::string const &name) override; - void benchmarkStarting(BenchmarkInfo const &) override; - void benchmarkEnded(BenchmarkStats<> const &) override; - void benchmarkFailed(std::string const &) override; -#endif // CATCH_CONFIG_ENABLE_BENCHMARKING - - private: - Timer m_testCaseTimer; - XmlWriter m_xml; - int m_sectionDepth = 0; - }; - -} // end namespace Catch - -// end catch_reporter_xml.h - -// end catch_external_interfaces.h -#endif - -#if defined(CATCH_CONFIG_ENABLE_BENCHMARKING) -// start catch_benchmarking_all.hpp - -// A proxy header that includes all of the benchmarking headers to allow -// concise include of the benchmarking features. You should prefer the -// individual includes in standard use. - -// start catch_benchmark.hpp - -// Benchmark - -// start catch_chronometer.hpp - -// User-facing chronometer - -// start catch_clock.hpp - -// Clocks - -#include -#include - -namespace Catch { - namespace Benchmark { - template - using ClockDuration = typename Clock::duration; - template - using FloatDuration = std::chrono::duration; - - template - using TimePoint = typename Clock::time_point; - - using default_clock = std::chrono::steady_clock; - - template - struct now { - TimePoint operator()() const { return Clock::now(); } - }; - - using fp_seconds = std::chrono::duration>; - } // namespace Benchmark -} // namespace Catch - -// end catch_clock.hpp -// start catch_optimizer.hpp - -// Hinting the optimizer - -#if defined(_MSC_VER) -#include // atomic_thread_fence -#endif - -namespace Catch { - namespace Benchmark { -#if defined(__GNUC__) || defined(__clang__) - template - inline void keep_memory(T *p) { - asm volatile("" - : - : "g"(p) - : "memory"); - } - inline void keep_memory() { - asm volatile("" - : - : - : "memory"); - } - - namespace Detail { - inline void optimizer_barrier() { - keep_memory(); - } - } // namespace Detail -#elif defined(_MSC_VER) - -#pragma optimize("", off) - template - inline void keep_memory(T *p) { - // thanks @milleniumbug - *reinterpret_cast(p) = *reinterpret_cast(p); - } -// TODO equivalent keep_memory() -#pragma optimize("", on) - - namespace Detail { - inline void optimizer_barrier() { - std::atomic_thread_fence(std::memory_order_seq_cst); - } - } // namespace Detail - -#endif - - template - inline void deoptimize_value(T &&x) { - keep_memory(&x); - } - - template - inline auto invoke_deoptimized(Fn &&fn, Args &&...args) -> typename std::enable_if::value>::type { - deoptimize_value(std::forward(fn)(std::forward(args...))); - } - - template - inline auto invoke_deoptimized(Fn &&fn, Args &&...args) -> typename std::enable_if::value>::type { - std::forward(fn)(std::forward(args...)); - } - } // namespace Benchmark -} // namespace Catch - -// end catch_optimizer.hpp -// start catch_complete_invoke.hpp - -// Invoke with a special case for void - -#include -#include - -namespace Catch { - namespace Benchmark { - namespace Detail { - template - struct CompleteType { - using type = T; - }; - template<> - struct CompleteType { - struct type { - }; - }; - - template - using CompleteType_t = typename CompleteType::type; - - template - struct CompleteInvoker { - template - static Result invoke(Fun &&fun, Args &&...args) { - return std::forward(fun)(std::forward(args)...); - } - }; - template<> - struct CompleteInvoker { - template - static CompleteType_t invoke(Fun &&fun, Args &&...args) { - std::forward(fun)(std::forward(args)...); - return {}; - } - }; - - // invoke and not return void :( - template - CompleteType_t> complete_invoke(Fun &&fun, Args &&...args) { - return CompleteInvoker>::invoke(std::forward(fun), std::forward(args)...); - } - - const std::string benchmarkErrorMsg = "a benchmark failed to run successfully"; - } // namespace Detail - - template - Detail::CompleteType_t> user_code(Fun &&fun) { - CATCH_TRY { return Detail::complete_invoke(std::forward(fun)); } - CATCH_CATCH_ALL { - getResultCapture().benchmarkFailed(translateActiveException()); - CATCH_RUNTIME_ERROR(Detail::benchmarkErrorMsg); - } - } - } // namespace Benchmark -} // namespace Catch - -// end catch_complete_invoke.hpp -namespace Catch { - namespace Benchmark { - namespace Detail { - struct ChronometerConcept { - virtual void start() = 0; - virtual void finish() = 0; - virtual ~ChronometerConcept() = default; - }; - template - struct ChronometerModel final : public ChronometerConcept { - void start() override { started = Clock::now(); } - void finish() override { finished = Clock::now(); } - - ClockDuration elapsed() const { return finished - started; } - - TimePoint started; - TimePoint finished; - }; - } // namespace Detail - - struct Chronometer { - public: - template - void measure(Fun &&fun) { - measure(std::forward(fun), is_callable()); - } - - int runs() const { return k; } - - Chronometer(Detail::ChronometerConcept &meter, int k) - : impl(&meter) - , k(k) { - } - - private: - template - void measure(Fun &&fun, std::false_type) { - measure([&fun](int) { return fun(); }, std::true_type()); - } - - template - void measure(Fun &&fun, std::true_type) { - Detail::optimizer_barrier(); - impl->start(); - for (int i = 0; i < k; ++i) - invoke_deoptimized(fun, i); - impl->finish(); - Detail::optimizer_barrier(); - } - - Detail::ChronometerConcept *impl; - int k; - }; - } // namespace Benchmark -} // namespace Catch - -// end catch_chronometer.hpp -// start catch_environment.hpp - -// Environment information - -namespace Catch { - namespace Benchmark { - template - struct EnvironmentEstimate { - Duration mean; - OutlierClassification outliers; - - template - operator EnvironmentEstimate() const { - return {mean, outliers}; - } - }; - template - struct Environment { - using clock_type = Clock; - EnvironmentEstimate> clock_resolution; - EnvironmentEstimate> clock_cost; - }; - } // namespace Benchmark -} // namespace Catch - -// end catch_environment.hpp -// start catch_execution_plan.hpp - -// Execution plan - -// start catch_benchmark_function.hpp - -// Dumb std::function implementation for consistent call overhead - -#include -#include -#include -#include - -namespace Catch { - namespace Benchmark { - namespace Detail { - template - using Decay = typename std::decay::type; - template - struct is_related : std::is_same, Decay> { - }; - - /// We need to reinvent std::function because every piece of code that might add overhead - /// in a measurement context needs to have consistent performance characteristics so that we - /// can account for it in the measurement. - /// Implementations of std::function with optimizations that aren't always applicable, like - /// small buffer optimizations, are not uncommon. - /// This is effectively an implementation of std::function without any such optimizations; - /// it may be slow, but it is consistently slow. - struct BenchmarkFunction { - private: - struct callable { - virtual void call(Chronometer meter) const = 0; - virtual callable *clone() const = 0; - virtual ~callable() = default; - }; - template - struct model : public callable { - model(Fun &&fun) - : fun(std::move(fun)) { - } - model(Fun const &fun) - : fun(fun) { - } - - model *clone() const override { return new model(*this); } - - void call(Chronometer meter) const override { call(meter, is_callable()); } - void call(Chronometer meter, std::true_type) const { fun(meter); } - void call(Chronometer meter, std::false_type) const { meter.measure(fun); } - - Fun fun; - }; - - struct do_nothing { - void operator()() const {} - }; - - template - BenchmarkFunction(model *c) - : f(c) { - } - - public: - BenchmarkFunction() - : f(new model{{}}) { - } - - template::value, int>::type = 0> - BenchmarkFunction(Fun &&fun) - : f(new model::type>(std::forward(fun))) { - } - - BenchmarkFunction(BenchmarkFunction &&that) - : f(std::move(that.f)) { - } - - BenchmarkFunction(BenchmarkFunction const &that) - : f(that.f->clone()) { - } - - BenchmarkFunction &operator=(BenchmarkFunction &&that) { - f = std::move(that.f); - return *this; - } - - BenchmarkFunction &operator=(BenchmarkFunction const &that) { - f.reset(that.f->clone()); - return *this; - } - - void operator()(Chronometer meter) const { f->call(meter); } - - private: - std::unique_ptr f; - }; - } // namespace Detail - } // namespace Benchmark -} // namespace Catch - -// end catch_benchmark_function.hpp -// start catch_repeat.hpp - -// repeat algorithm - -#include -#include - -namespace Catch { - namespace Benchmark { - namespace Detail { - template - struct repeater { - void operator()(int k) const { - for (int i = 0; i < k; ++i) { - fun(); - } - } - Fun fun; - }; - template - repeater::type> repeat(Fun &&fun) { - return {std::forward(fun)}; - } - } // namespace Detail - } // namespace Benchmark -} // namespace Catch - -// end catch_repeat.hpp -// start catch_run_for_at_least.hpp - -// Run a function for a minimum amount of time - -// start catch_measure.hpp - -// Measure - -// start catch_timing.hpp - -// Timing - -#include -#include - -namespace Catch { - namespace Benchmark { - template - struct Timing { - Duration elapsed; - Result result; - int iterations; - }; - template - using TimingOf = Timing, Detail::CompleteType_t>>; - } // namespace Benchmark -} // namespace Catch - -// end catch_timing.hpp -#include - -namespace Catch { - namespace Benchmark { - namespace Detail { - template - TimingOf measure(Fun &&fun, Args &&...args) { - auto start = Clock::now(); - auto &&r = Detail::complete_invoke(fun, std::forward(args)...); - auto end = Clock::now(); - auto delta = end - start; - return {delta, std::forward(r), 1}; - } - } // namespace Detail - } // namespace Benchmark -} // namespace Catch - -// end catch_measure.hpp -#include -#include - -namespace Catch { - namespace Benchmark { - namespace Detail { - template - TimingOf measure_one(Fun &&fun, int iters, std::false_type) { - return Detail::measure(fun, iters); - } - template - TimingOf measure_one(Fun &&fun, int iters, std::true_type) { - Detail::ChronometerModel meter; - auto &&result = Detail::complete_invoke(fun, Chronometer(meter, iters)); - - return {meter.elapsed(), std::move(result), iters}; - } - - template - using run_for_at_least_argument_t = typename std::conditional::value, Chronometer, int>::type; - - struct optimized_away_error : std::exception { - const char *what() const noexcept override { return "could not measure benchmark, maybe it was optimized away"; } - }; - - template - TimingOf> run_for_at_least(ClockDuration how_long, int seed, Fun &&fun) { - auto iters = seed; - while (iters < (1 << 30)) { - auto &&Timing = measure_one(fun, iters, is_callable()); - - if (Timing.elapsed >= how_long) { - return {Timing.elapsed, std::move(Timing.result), iters}; - } - iters *= 2; - } - throw optimized_away_error{}; - } - } // namespace Detail - } // namespace Benchmark -} // namespace Catch - -// end catch_run_for_at_least.hpp -#include - -namespace Catch { - namespace Benchmark { - template - struct ExecutionPlan { - int iterations_per_sample; - Duration estimated_duration; - Detail::BenchmarkFunction benchmark; - Duration warmup_time; - int warmup_iterations; - - template - operator ExecutionPlan() const { - return {iterations_per_sample, estimated_duration, benchmark, warmup_time, warmup_iterations}; - } - - template - std::vector> run(const IConfig &cfg, Environment> env) const { - // warmup a bit - Detail::run_for_at_least(std::chrono::duration_cast>(warmup_time), - warmup_iterations, - Detail::repeat(now{})); - - std::vector> times; - times.reserve(cfg.benchmarkSamples()); - std::generate_n(std::back_inserter(times), cfg.benchmarkSamples(), [this, env] { - Detail::ChronometerModel model; - this->benchmark(Chronometer(model, iterations_per_sample)); - auto sample_time = model.elapsed() - env.clock_cost.mean; - if (sample_time < FloatDuration::zero()) - sample_time = FloatDuration::zero(); - return sample_time / iterations_per_sample; - }); - return times; - } - }; - } // namespace Benchmark -} // namespace Catch - -// end catch_execution_plan.hpp -// start catch_estimate_clock.hpp - -// Environment measurement - -// start catch_stats.hpp - -// Statistical analysis tools - -#include -#include -#include -#include -#include -#include -#include -#include -#include -#include - -namespace Catch { - namespace Benchmark { - namespace Detail { - using sample = std::vector; - - double weighted_average_quantile(int k, int q, std::vector::iterator first, std::vector::iterator last); - - template - OutlierClassification classify_outliers(Iterator first, Iterator last) { - std::vector copy(first, last); - - auto q1 = weighted_average_quantile(1, 4, copy.begin(), copy.end()); - auto q3 = weighted_average_quantile(3, 4, copy.begin(), copy.end()); - auto iqr = q3 - q1; - auto los = q1 - (iqr * 3.); - auto lom = q1 - (iqr * 1.5); - auto him = q3 + (iqr * 1.5); - auto his = q3 + (iqr * 3.); - - OutlierClassification o; - for (; first != last; ++first) { - auto &&t = *first; - if (t < los) - ++o.low_severe; - else if (t < lom) - ++o.low_mild; - else if (t > his) - ++o.high_severe; - else if (t > him) - ++o.high_mild; - ++o.samples_seen; - } - return o; - } - - template - double mean(Iterator first, Iterator last) { - auto count = last - first; - double sum = std::accumulate(first, last, 0.); - return sum / count; - } - - template - sample resample(URng &rng, int resamples, Iterator first, Iterator last, Estimator &estimator) { - auto n = last - first; - std::uniform_int_distribution dist(0, n - 1); - - sample out; - out.reserve(resamples); - std::generate_n(std::back_inserter(out), resamples, [n, first, &estimator, &dist, &rng] { - std::vector resampled; - resampled.reserve(n); - std::generate_n(std::back_inserter(resampled), n, [first, &dist, &rng] { return first[dist(rng)]; }); - return estimator(resampled.begin(), resampled.end()); - }); - std::sort(out.begin(), out.end()); - return out; - } - - template - sample jackknife(Estimator &&estimator, Iterator first, Iterator last) { - auto n = last - first; - auto second = std::next(first); - sample results; - results.reserve(n); - - for (auto it = first; it != last; ++it) { - std::iter_swap(it, first); - results.push_back(estimator(second, last)); - } - - return results; - } - - inline double normal_cdf(double x) { - return std::erfc(-x / std::sqrt(2.0)) / 2.0; - } - - double erfc_inv(double x); - - double normal_quantile(double p); - - template - Estimate bootstrap(double confidence_level, Iterator first, Iterator last, sample const &resample, Estimator &&estimator) { - auto n_samples = last - first; - - double point = estimator(first, last); - // Degenerate case with a single sample - if (n_samples == 1) - return {point, point, point, confidence_level}; - - sample jack = jackknife(estimator, first, last); - double jack_mean = mean(jack.begin(), jack.end()); - double sum_squares, sum_cubes; - std::tie(sum_squares, sum_cubes) = std::accumulate(jack.begin(), - jack.end(), - std::make_pair(0., 0.), - [jack_mean](std::pair sqcb, double x) -> std::pair { - auto d = jack_mean - x; - auto d2 = d * d; - auto d3 = d2 * d; - return {sqcb.first + d2, sqcb.second + d3}; - }); - - double accel = sum_cubes / (6 * std::pow(sum_squares, 1.5)); - int n = static_cast(resample.size()); - double prob_n = std::count_if(resample.begin(), resample.end(), [point](double x) { return x < point; }) / (double)n; - // degenerate case with uniform samples - if (prob_n == 0) - return {point, point, point, confidence_level}; - - double bias = normal_quantile(prob_n); - double z1 = normal_quantile((1. - confidence_level) / 2.); - - auto cumn = [n](double x) -> int { return std::lround(normal_cdf(x) * n); }; - auto a = [bias, accel](double b) { return bias + b / (1. - accel * b); }; - double b1 = bias + z1; - double b2 = bias - z1; - double a1 = a(b1); - double a2 = a(b2); - auto lo = (std::max)(cumn(a1), 0); - auto hi = (std::min)(cumn(a2), n - 1); - - return {point, resample[lo], resample[hi], confidence_level}; - } - - double outlier_variance(Estimate mean, Estimate stddev, int n); - - struct bootstrap_analysis { - Estimate mean; - Estimate standard_deviation; - double outlier_variance; - }; - - bootstrap_analysis - analyse_samples(double confidence_level, int n_resamples, std::vector::iterator first, std::vector::iterator last); - } // namespace Detail - } // namespace Benchmark -} // namespace Catch - -// end catch_stats.hpp -#include -#include -#include -#include -#include - -namespace Catch { - namespace Benchmark { - namespace Detail { - template - std::vector resolution(int k) { - std::vector> times; - times.reserve(k + 1); - std::generate_n(std::back_inserter(times), k + 1, now{}); - - std::vector deltas; - deltas.reserve(k); - std::transform(std::next(times.begin()), times.end(), times.begin(), std::back_inserter(deltas), [](TimePoint a, TimePoint b) { - return static_cast((a - b).count()); - }); - - return deltas; - } - - const auto warmup_iterations = 10000; - const auto warmup_time = std::chrono::milliseconds(100); - const auto minimum_ticks = 1000; - const auto warmup_seed = 10000; - const auto clock_resolution_estimation_time = std::chrono::milliseconds(500); - const auto clock_cost_estimation_time_limit = std::chrono::seconds(1); - const auto clock_cost_estimation_tick_limit = 100000; - const auto clock_cost_estimation_time = std::chrono::milliseconds(10); - const auto clock_cost_estimation_iterations = 10000; - - template - int warmup() { - return run_for_at_least(std::chrono::duration_cast>(warmup_time), warmup_seed, &resolution).iterations; - } - template - EnvironmentEstimate> estimate_clock_resolution(int iterations) { - auto r = run_for_at_least(std::chrono::duration_cast>(clock_resolution_estimation_time), - iterations, - &resolution) - .result; - return { - FloatDuration(mean(r.begin(), r.end())), - classify_outliers(r.begin(), r.end()), - }; - } - template - EnvironmentEstimate> estimate_clock_cost(FloatDuration resolution) { - auto time_limit = (std::min)(resolution * clock_cost_estimation_tick_limit, FloatDuration(clock_cost_estimation_time_limit)); - auto time_clock = [](int k) { - return Detail::measure([k] { - for (int i = 0; i < k; ++i) { - volatile auto ignored = Clock::now(); - (void)ignored; - } - }) - .elapsed; - }; - time_clock(1); - int iters = clock_cost_estimation_iterations; - auto &&r = run_for_at_least(std::chrono::duration_cast>(clock_cost_estimation_time), iters, time_clock); - std::vector times; - int nsamples = static_cast(std::ceil(time_limit / r.elapsed)); - times.reserve(nsamples); - std::generate_n(std::back_inserter(times), nsamples, [time_clock, &r] { - return static_cast((time_clock(r.iterations) / r.iterations).count()); - }); - return { - FloatDuration(mean(times.begin(), times.end())), - classify_outliers(times.begin(), times.end()), - }; - } - - template - Environment> measure_environment() { - static Environment> *env = nullptr; - if (env) { - return *env; - } - - auto iters = Detail::warmup(); - auto resolution = Detail::estimate_clock_resolution(iters); - auto cost = Detail::estimate_clock_cost(resolution.mean); - - env = new Environment>{resolution, cost}; - return *env; - } - } // namespace Detail - } // namespace Benchmark -} // namespace Catch - -// end catch_estimate_clock.hpp -// start catch_analyse.hpp - -// Run and analyse one benchmark - -// start catch_sample_analysis.hpp - -// Benchmark results - -#include -#include -#include -#include - -namespace Catch { - namespace Benchmark { - template - struct SampleAnalysis { - std::vector samples; - Estimate mean; - Estimate standard_deviation; - OutlierClassification outliers; - double outlier_variance; - - template - operator SampleAnalysis() const { - std::vector samples2; - samples2.reserve(samples.size()); - std::transform(samples.begin(), samples.end(), std::back_inserter(samples2), [](Duration d) { return Duration2(d); }); - return { - std::move(samples2), - mean, - standard_deviation, - outliers, - outlier_variance, - }; - } - }; - } // namespace Benchmark -} // namespace Catch - -// end catch_sample_analysis.hpp -#include -#include -#include - -namespace Catch { - namespace Benchmark { - namespace Detail { - template - SampleAnalysis analyse(const IConfig &cfg, Environment, Iterator first, Iterator last) { - if (!cfg.benchmarkNoAnalysis()) { - std::vector samples; - samples.reserve(last - first); - std::transform(first, last, std::back_inserter(samples), [](Duration d) { return d.count(); }); - - auto analysis = Catch::Benchmark::Detail::analyse_samples(cfg.benchmarkConfidenceInterval(), - cfg.benchmarkResamples(), - samples.begin(), - samples.end()); - auto outliers = Catch::Benchmark::Detail::classify_outliers(samples.begin(), samples.end()); - - auto wrap_estimate = [](Estimate e) { - return Estimate{ - Duration(e.point), - Duration(e.lower_bound), - Duration(e.upper_bound), - e.confidence_interval, - }; - }; - std::vector samples2; - samples2.reserve(samples.size()); - std::transform(samples.begin(), samples.end(), std::back_inserter(samples2), [](double d) { return Duration(d); }); - return { - std::move(samples2), - wrap_estimate(analysis.mean), - wrap_estimate(analysis.standard_deviation), - outliers, - analysis.outlier_variance, - }; - } else { - std::vector samples; - samples.reserve(last - first); - - Duration mean = Duration(0); - int i = 0; - for (auto it = first; it < last; ++it, ++i) { - samples.push_back(Duration(*it)); - mean += Duration(*it); - } - mean /= i; - - return {std::move(samples), - Estimate{mean, mean, mean, 0.0}, - Estimate{Duration(0), Duration(0), Duration(0), 0.0}, - OutlierClassification{}, - 0.0}; - } - } - } // namespace Detail - } // namespace Benchmark -} // namespace Catch - -// end catch_analyse.hpp -#include -#include -#include -#include -#include - -namespace Catch { - namespace Benchmark { - struct Benchmark { - Benchmark(std::string &&name) - : name(std::move(name)) { - } - - template - Benchmark(std::string &&name, FUN &&func) - : fun(std::move(func)) - , name(std::move(name)) { - } - - template - ExecutionPlan> prepare(const IConfig &cfg, Environment> env) const { - auto min_time = env.clock_resolution.mean * Detail::minimum_ticks; - auto run_time = std::max(min_time, std::chrono::duration_cast(cfg.benchmarkWarmupTime())); - auto &&test = Detail::run_for_at_least(std::chrono::duration_cast>(run_time), 1, fun); - int new_iters = static_cast(std::ceil(min_time * test.iterations / test.elapsed)); - return {new_iters, - test.elapsed / test.iterations * new_iters * cfg.benchmarkSamples(), - fun, - std::chrono::duration_cast>(cfg.benchmarkWarmupTime()), - Detail::warmup_iterations}; - } - - template - void run() { - IConfigPtr cfg = getCurrentContext().getConfig(); - - auto env = Detail::measure_environment(); - - getResultCapture().benchmarkPreparing(name); - CATCH_TRY { - auto plan = user_code([&] { return prepare(*cfg, env); }); - - BenchmarkInfo info{name, - plan.estimated_duration.count(), - plan.iterations_per_sample, - cfg->benchmarkSamples(), - cfg->benchmarkResamples(), - env.clock_resolution.mean.count(), - env.clock_cost.mean.count()}; - - getResultCapture().benchmarkStarting(info); - - auto samples = user_code([&] { return plan.template run(*cfg, env); }); - - auto analysis = Detail::analyse(*cfg, env, samples.begin(), samples.end()); - BenchmarkStats> stats{info, - analysis.samples, - analysis.mean, - analysis.standard_deviation, - analysis.outliers, - analysis.outlier_variance}; - getResultCapture().benchmarkEnded(stats); - } - CATCH_CATCH_ALL { - if (translateActiveException() != Detail::benchmarkErrorMsg) // benchmark errors have been reported, otherwise rethrow. - std::rethrow_exception(std::current_exception()); - } - } - - // sets lambda to be used in fun *and* executes benchmark! - template::value, int>::type = 0> - Benchmark &operator=(Fun func) { - fun = Detail::BenchmarkFunction(func); - run(); - return *this; - } - - explicit operator bool() { return true; } - - private: - Detail::BenchmarkFunction fun; - std::string name; - }; - } // namespace Benchmark -} // namespace Catch - -#define INTERNAL_CATCH_GET_1_ARG(arg1, arg2, ...) arg1 -#define INTERNAL_CATCH_GET_2_ARG(arg1, arg2, ...) arg2 - -#define INTERNAL_CATCH_BENCHMARK(BenchmarkName, name, benchmarkIndex) \ - if (Catch::Benchmark::Benchmark BenchmarkName{name}) \ - BenchmarkName = [&](int benchmarkIndex) - -#define INTERNAL_CATCH_BENCHMARK_ADVANCED(BenchmarkName, name) \ - if (Catch::Benchmark::Benchmark BenchmarkName{name}) \ - BenchmarkName = [&] - -// end catch_benchmark.hpp -// start catch_constructor.hpp - -// Constructor and destructor helpers - -#include - -namespace Catch { - namespace Benchmark { - namespace Detail { - template - struct ObjectStorage { - using TStorage = typename std::aligned_storage::value>::type; - - ObjectStorage() - : data() { - } - - ObjectStorage(const ObjectStorage &other) { new (&data) T(other.stored_object()); } - - ObjectStorage(ObjectStorage &&other) { new (&data) T(std::move(other.stored_object())); } - - ~ObjectStorage() { destruct_on_exit(); } - - template - void construct(Args &&...args) { - new (&data) T(std::forward(args)...); - } - - template - typename std::enable_if::type destruct() { - stored_object().~T(); - } - - private: - // If this is a constructor benchmark, destruct the underlying object - template - void destruct_on_exit(typename std::enable_if::type * = 0) { - destruct(); - } - // Otherwise, don't - template - void destruct_on_exit(typename std::enable_if::type * = 0) { - } - - T &stored_object() { return *static_cast(static_cast(&data)); } - - T const &stored_object() const { return *static_cast(static_cast(&data)); } - - TStorage data; - }; - } // namespace Detail - - template - using storage_for = Detail::ObjectStorage; - - template - using destructable_object = Detail::ObjectStorage; - } // namespace Benchmark -} // namespace Catch - -// end catch_constructor.hpp -// end catch_benchmarking_all.hpp -#endif - -#endif // ! CATCH_CONFIG_IMPL_ONLY - -#ifdef CATCH_IMPL -// start catch_impl.hpp - -#ifdef __clang__ -#pragma clang diagnostic push -#pragma clang diagnostic ignored "-Wweak-vtables" -#endif - -// Keep these here for external reporters -// start catch_test_case_tracker.h - -#include -#include -#include - -namespace Catch { - namespace TestCaseTracking { - struct NameAndLocation { - std::string name; - SourceLineInfo location; - - NameAndLocation(std::string const &_name, SourceLineInfo const &_location); - friend bool operator==(NameAndLocation const &lhs, NameAndLocation const &rhs) { - return lhs.name == rhs.name && lhs.location == rhs.location; - } - }; - - class ITracker; - - using ITrackerPtr = std::shared_ptr; - - class ITracker { - NameAndLocation m_nameAndLocation; - - public: - ITracker(NameAndLocation const &nameAndLoc) - : m_nameAndLocation(nameAndLoc) { - } - - // static queries - NameAndLocation const &nameAndLocation() const { return m_nameAndLocation; } - - virtual ~ITracker(); - - // dynamic queries - virtual bool isComplete() const = 0; // Successfully completed or failed - virtual bool isSuccessfullyCompleted() const = 0; - virtual bool isOpen() const = 0; // Started but not complete - virtual bool hasChildren() const = 0; - virtual bool hasStarted() const = 0; - - virtual ITracker &parent() = 0; - - // actions - virtual void close() = 0; // Successfully complete - virtual void fail() = 0; - virtual void markAsNeedingAnotherRun() = 0; - - virtual void addChild(ITrackerPtr const &child) = 0; - virtual ITrackerPtr findChild(NameAndLocation const &nameAndLocation) = 0; - virtual void openChild() = 0; - - // Debug/ checking - virtual bool isSectionTracker() const = 0; - virtual bool isGeneratorTracker() const = 0; - }; - - class TrackerContext { - enum RunState { - NotStarted, - Executing, - CompletedCycle - }; - - ITrackerPtr m_rootTracker; - ITracker *m_currentTracker = nullptr; - RunState m_runState = NotStarted; - - public: - ITracker &startRun(); - void endRun(); - - void startCycle(); - void completeCycle(); - - bool completedCycle() const; - ITracker ¤tTracker(); - void setCurrentTracker(ITracker *tracker); - }; - - class TrackerBase : public ITracker { - protected: - enum CycleState { - NotStarted, - Executing, - ExecutingChildren, - NeedsAnotherRun, - CompletedSuccessfully, - Failed - }; - - using Children = std::vector; - TrackerContext &m_ctx; - ITracker *m_parent; - Children m_children; - CycleState m_runState = NotStarted; - - public: - TrackerBase(NameAndLocation const &nameAndLocation, TrackerContext &ctx, ITracker *parent); - - bool isComplete() const override; - bool isSuccessfullyCompleted() const override; - bool isOpen() const override; - bool hasChildren() const override; - bool hasStarted() const override { return m_runState != NotStarted; } - - void addChild(ITrackerPtr const &child) override; - - ITrackerPtr findChild(NameAndLocation const &nameAndLocation) override; - ITracker &parent() override; - - void openChild() override; - - bool isSectionTracker() const override; - bool isGeneratorTracker() const override; - - void open(); - - void close() override; - void fail() override; - void markAsNeedingAnotherRun() override; - - private: - void moveToParent(); - void moveToThis(); - }; - - class SectionTracker : public TrackerBase { - std::vector m_filters; - std::string m_trimmed_name; - - public: - SectionTracker(NameAndLocation const &nameAndLocation, TrackerContext &ctx, ITracker *parent); - - bool isSectionTracker() const override; - - bool isComplete() const override; - - static SectionTracker &acquire(TrackerContext &ctx, NameAndLocation const &nameAndLocation); - - void tryOpen(); - - void addInitialFilters(std::vector const &filters); - void addNextFilters(std::vector const &filters); - //! Returns filters active in this tracker - std::vector const &getFilters() const; - //! Returns whitespace-trimmed name of the tracked section - std::string const &trimmedName() const; - }; - - } // namespace TestCaseTracking - - using TestCaseTracking::ITracker; - using TestCaseTracking::SectionTracker; - using TestCaseTracking::TrackerContext; - -} // namespace Catch - -// end catch_test_case_tracker.h - -// start catch_leak_detector.h - -namespace Catch { - struct LeakDetector { - LeakDetector(); - ~LeakDetector(); - }; - -} // namespace Catch -// end catch_leak_detector.h -// Cpp files will be included in the single-header file here -// start catch_stats.cpp - -// Statistical analysis tools - -#if defined(CATCH_CONFIG_ENABLE_BENCHMARKING) - -#include -#include - -#if defined(CATCH_CONFIG_USE_ASYNC) -#include -#endif - -namespace { - double erf_inv(double x) { - // Code accompanying the article "Approximating the erfinv function" in GPU Computing Gems, Volume 2 - double w, p; - - w = -log((1.0 - x) * (1.0 + x)); - - if (w < 6.250000) { - w = w - 3.125000; - p = -3.6444120640178196996e-21; - p = -1.685059138182016589e-19 + p * w; - p = 1.2858480715256400167e-18 + p * w; - p = 1.115787767802518096e-17 + p * w; - p = -1.333171662854620906e-16 + p * w; - p = 2.0972767875968561637e-17 + p * w; - p = 6.6376381343583238325e-15 + p * w; - p = -4.0545662729752068639e-14 + p * w; - p = -8.1519341976054721522e-14 + p * w; - p = 2.6335093153082322977e-12 + p * w; - p = -1.2975133253453532498e-11 + p * w; - p = -5.4154120542946279317e-11 + p * w; - p = 1.051212273321532285e-09 + p * w; - p = -4.1126339803469836976e-09 + p * w; - p = -2.9070369957882005086e-08 + p * w; - p = 4.2347877827932403518e-07 + p * w; - p = -1.3654692000834678645e-06 + p * w; - p = -1.3882523362786468719e-05 + p * w; - p = 0.0001867342080340571352 + p * w; - p = -0.00074070253416626697512 + p * w; - p = -0.0060336708714301490533 + p * w; - p = 0.24015818242558961693 + p * w; - p = 1.6536545626831027356 + p * w; - } else if (w < 16.000000) { - w = sqrt(w) - 3.250000; - p = 2.2137376921775787049e-09; - p = 9.0756561938885390979e-08 + p * w; - p = -2.7517406297064545428e-07 + p * w; - p = 1.8239629214389227755e-08 + p * w; - p = 1.5027403968909827627e-06 + p * w; - p = -4.013867526981545969e-06 + p * w; - p = 2.9234449089955446044e-06 + p * w; - p = 1.2475304481671778723e-05 + p * w; - p = -4.7318229009055733981e-05 + p * w; - p = 6.8284851459573175448e-05 + p * w; - p = 2.4031110387097893999e-05 + p * w; - p = -0.0003550375203628474796 + p * w; - p = 0.00095328937973738049703 + p * w; - p = -0.0016882755560235047313 + p * w; - p = 0.0024914420961078508066 + p * w; - p = -0.0037512085075692412107 + p * w; - p = 0.005370914553590063617 + p * w; - p = 1.0052589676941592334 + p * w; - p = 3.0838856104922207635 + p * w; - } else { - w = sqrt(w) - 5.000000; - p = -2.7109920616438573243e-11; - p = -2.5556418169965252055e-10 + p * w; - p = 1.5076572693500548083e-09 + p * w; - p = -3.7894654401267369937e-09 + p * w; - p = 7.6157012080783393804e-09 + p * w; - p = -1.4960026627149240478e-08 + p * w; - p = 2.9147953450901080826e-08 + p * w; - p = -6.7711997758452339498e-08 + p * w; - p = 2.2900482228026654717e-07 + p * w; - p = -9.9298272942317002539e-07 + p * w; - p = 4.5260625972231537039e-06 + p * w; - p = -1.9681778105531670567e-05 + p * w; - p = 7.5995277030017761139e-05 + p * w; - p = -0.00021503011930044477347 + p * w; - p = -0.00013871931833623122026 + p * w; - p = 1.0103004648645343977 + p * w; - p = 4.8499064014085844221 + p * w; - } - return p * x; - } - - double standard_deviation(std::vector::iterator first, std::vector::iterator last) { - auto m = Catch::Benchmark::Detail::mean(first, last); - double variance = std::accumulate(first, - last, - 0., - [m](double a, double b) { - double diff = b - m; - return a + diff * diff; - }) - / (last - first); - return std::sqrt(variance); - } - -} // namespace - -namespace Catch { - namespace Benchmark { - namespace Detail { - double weighted_average_quantile(int k, int q, std::vector::iterator first, std::vector::iterator last) { - auto count = last - first; - double idx = (count - 1) * k / static_cast(q); - int j = static_cast(idx); - double g = idx - j; - std::nth_element(first, first + j, last); - auto xj = first[j]; - if (g == 0) - return xj; - - auto xj1 = *std::min_element(first + (j + 1), last); - return xj + g * (xj1 - xj); - } - - double erfc_inv(double x) { - return erf_inv(1.0 - x); - } - - double normal_quantile(double p) { - static const double ROOT_TWO = std::sqrt(2.0); - - double result = 0.0; - assert(p >= 0 && p <= 1); - if (p < 0 || p > 1) { - return result; - } - - result = -erfc_inv(2.0 * p); - // result *= normal distribution standard deviation (1.0) * sqrt(2) - result *= /*sd * */ ROOT_TWO; - // result += normal disttribution mean (0) - return result; - } - - double outlier_variance(Estimate mean, Estimate stddev, int n) { - double sb = stddev.point; - double mn = mean.point / n; - double mg_min = mn / 2.; - double sg = (std::min)(mg_min / 4., sb / std::sqrt(n)); - double sg2 = sg * sg; - double sb2 = sb * sb; - - auto c_max = [n, mn, sb2, sg2](double x) -> double { - double k = mn - x; - double d = k * k; - double nd = n * d; - double k0 = -n * nd; - double k1 = sb2 - n * sg2 + nd; - double det = k1 * k1 - 4 * sg2 * k0; - return (int)(-2. * k0 / (k1 + std::sqrt(det))); - }; - - auto var_out = [n, sb2, sg2](double c) { - double nc = n - c; - return (nc / n) * (sb2 - nc * sg2); - }; - - return (std::min)(var_out(1), var_out((std::min)(c_max(0.), c_max(mg_min)))) / sb2; - } - - bootstrap_analysis - analyse_samples(double confidence_level, int n_resamples, std::vector::iterator first, std::vector::iterator last) { - CATCH_INTERNAL_START_WARNINGS_SUPPRESSION - CATCH_INTERNAL_SUPPRESS_GLOBALS_WARNINGS - static std::random_device entropy; - CATCH_INTERNAL_STOP_WARNINGS_SUPPRESSION - - auto n = static_cast(last - first); // seriously, one can't use integral types without hell in C++ - - auto mean = &Detail::mean::iterator>; - auto stddev = &standard_deviation; - -#if defined(CATCH_CONFIG_USE_ASYNC) - auto Estimate = [=](double (*f)(std::vector::iterator, std::vector::iterator)) { - auto seed = entropy(); - return std::async(std::launch::async, [=] { - std::mt19937 rng(seed); - auto resampled = resample(rng, n_resamples, first, last, f); - return bootstrap(confidence_level, first, last, resampled, f); - }); - }; - - auto mean_future = Estimate(mean); - auto stddev_future = Estimate(stddev); - - auto mean_estimate = mean_future.get(); - auto stddev_estimate = stddev_future.get(); -#else - auto Estimate = [=](double (*f)(std::vector::iterator, std::vector::iterator)) { - auto seed = entropy(); - std::mt19937 rng(seed); - auto resampled = resample(rng, n_resamples, first, last, f); - return bootstrap(confidence_level, first, last, resampled, f); - }; - - auto mean_estimate = Estimate(mean); - auto stddev_estimate = Estimate(stddev); -#endif // CATCH_USE_ASYNC - - double outlier_variance = Detail::outlier_variance(mean_estimate, stddev_estimate, n); - - return {mean_estimate, stddev_estimate, outlier_variance}; - } - } // namespace Detail - } // namespace Benchmark -} // namespace Catch - -#endif // CATCH_CONFIG_ENABLE_BENCHMARKING \ - // end catch_stats.cpp -// start catch_approx.cpp - -#include -#include - -namespace { - // Performs equivalent check of std::fabs(lhs - rhs) <= margin - // But without the subtraction to allow for INFINITY in comparison - bool marginComparison(double lhs, double rhs, double margin) { - return (lhs + margin >= rhs) && (rhs + margin >= lhs); - } - -} // namespace - -namespace Catch { - namespace Detail { - Approx::Approx(double value) - : m_epsilon(std::numeric_limits::epsilon() * 100) - , m_margin(0.0) - , m_scale(0.0) - , m_value(value) { - } - - Approx Approx::custom() { - return Approx(0); - } - - Approx Approx::operator-() const { - auto temp(*this); - temp.m_value = -temp.m_value; - return temp; - } - - std::string Approx::toString() const { - ReusableStringStream rss; - rss << "Approx( " << ::Catch::Detail::stringify(m_value) << " )"; - return rss.str(); - } - - bool Approx::equalityComparisonImpl(const double other) const { - // First try with fixed margin, then compute margin based on epsilon, scale and Approx's value - // Thanks to Richard Harris for his help refining the scaled margin value - return marginComparison(m_value, other, m_margin) - || marginComparison(m_value, other, m_epsilon * (m_scale + std::fabs(std::isinf(m_value) ? 0 : m_value))); - } - - void Approx::setMargin(double newMargin) { - CATCH_ENFORCE(newMargin >= 0, "Invalid Approx::margin: " << newMargin << '.' << " Approx::Margin has to be non-negative."); - m_margin = newMargin; - } - - void Approx::setEpsilon(double newEpsilon) { - CATCH_ENFORCE(newEpsilon >= 0 && newEpsilon <= 1.0, - "Invalid Approx::epsilon: " << newEpsilon << '.' << " Approx::epsilon has to be in [0, 1]"); - m_epsilon = newEpsilon; - } - - } // end namespace Detail - - namespace literals { - Detail::Approx operator"" _a(long double val) { - return Detail::Approx(val); - } - Detail::Approx operator"" _a(unsigned long long val) { - return Detail::Approx(val); - } - } // end namespace literals - - std::string StringMaker::convert(Catch::Detail::Approx const &value) { - return value.toString(); - } - -} // end namespace Catch -// end catch_approx.cpp -// start catch_assertionhandler.cpp - -// start catch_debugger.h - -namespace Catch { - bool isDebuggerActive(); -} - -#ifdef CATCH_PLATFORM_MAC - -#if defined(__i386__) || defined(__x86_64__) -#define CATCH_TRAP() __asm__("int $3\n" \ - : \ - :) /* NOLINT */ -#elif defined(__aarch64__) -#define CATCH_TRAP() __asm__(".inst 0xd4200000") -#endif - -#elif defined(CATCH_PLATFORM_IPHONE) - -// use inline assembler -#if defined(__i386__) || defined(__x86_64__) -#define CATCH_TRAP() __asm__("int $3") -#elif defined(__aarch64__) -#define CATCH_TRAP() __asm__(".inst 0xd4200000") -#elif defined(__arm__) && !defined(__thumb__) -#define CATCH_TRAP() __asm__(".inst 0xe7f001f0") -#elif defined(__arm__) && defined(__thumb__) -#define CATCH_TRAP() __asm__(".inst 0xde01") -#endif - -#elif defined(CATCH_PLATFORM_LINUX) -// If we can use inline assembler, do it because this allows us to break -// directly at the location of the failing check instead of breaking inside -// raise() called from it, i.e. one stack frame below. -#if defined(__GNUC__) && (defined(__i386) || defined(__x86_64)) -#define CATCH_TRAP() asm volatile("int $3") /* NOLINT */ -#else // Fall back to the generic way. -#include - -#define CATCH_TRAP() raise(SIGTRAP) -#endif -#elif defined(_MSC_VER) -#define CATCH_TRAP() __debugbreak() -#elif defined(__MINGW32__) -extern "C" __declspec(dllimport) void __stdcall DebugBreak(); -#define CATCH_TRAP() DebugBreak() -#endif - -#ifndef CATCH_BREAK_INTO_DEBUGGER -#ifdef CATCH_TRAP -#define CATCH_BREAK_INTO_DEBUGGER() \ - [] { \ - if (Catch::isDebuggerActive()) { \ - CATCH_TRAP(); \ - } \ - }() -#else -#define CATCH_BREAK_INTO_DEBUGGER() [] {}() -#endif -#endif - -// end catch_debugger.h -// start catch_run_context.h - -// start catch_fatal_condition.h - -#include - -namespace Catch { - // Wrapper for platform-specific fatal error (signals/SEH) handlers - // - // Tries to be cooperative with other handlers, and not step over - // other handlers. This means that unknown structured exceptions - // are passed on, previous signal handlers are called, and so on. - // - // Can only be instantiated once, and assumes that once a signal - // is caught, the binary will end up terminating. Thus, there - class FatalConditionHandler { - bool m_started = false; - - // Install/disengage implementation for specific platform. - // Should be if-defed to work on current platform, can assume - // engage-disengage 1:1 pairing. - void engage_platform(); - void disengage_platform(); - - public: - // Should also have platform-specific implementations as needed - FatalConditionHandler(); - ~FatalConditionHandler(); - - void engage() { - assert(!m_started && "Handler cannot be installed twice."); - m_started = true; - engage_platform(); - } - - void disengage() { - assert(m_started && "Handler cannot be uninstalled without being installed first"); - m_started = false; - disengage_platform(); - } - }; - - //! Simple RAII guard for (dis)engaging the FatalConditionHandler - class FatalConditionHandlerGuard { - FatalConditionHandler *m_handler; - - public: - FatalConditionHandlerGuard(FatalConditionHandler *handler) - : m_handler(handler) { - m_handler->engage(); - } - ~FatalConditionHandlerGuard() { m_handler->disengage(); } - }; - -} // end namespace Catch - -// end catch_fatal_condition.h -#include - -namespace Catch { - struct IMutableContext; - - /////////////////////////////////////////////////////////////////////////// - - class RunContext : public IResultCapture, public IRunner { - public: - RunContext(RunContext const &) = delete; - RunContext &operator=(RunContext const &) = delete; - - explicit RunContext(IConfigPtr const &_config, IStreamingReporterPtr &&reporter); - - ~RunContext() override; - - void testGroupStarting(std::string const &testSpec, std::size_t groupIndex, std::size_t groupsCount); - void testGroupEnded(std::string const &testSpec, Totals const &totals, std::size_t groupIndex, std::size_t groupsCount); - - Totals runTest(TestCase const &testCase); - - IConfigPtr config() const; - IStreamingReporter &reporter() const; - - public: // IResultCapture - // Assertion handlers - void handleExpr(AssertionInfo const &info, ITransientExpression const &expr, AssertionReaction &reaction) override; - void handleMessage(AssertionInfo const &info, ResultWas::OfType resultType, StringRef const &message, AssertionReaction &reaction) override; - void handleUnexpectedExceptionNotThrown(AssertionInfo const &info, AssertionReaction &reaction) override; - void handleUnexpectedInflightException(AssertionInfo const &info, std::string const &message, AssertionReaction &reaction) override; - void handleIncomplete(AssertionInfo const &info) override; - void handleNonExpr(AssertionInfo const &info, ResultWas::OfType resultType, AssertionReaction &reaction) override; - - bool sectionStarted(SectionInfo const §ionInfo, Counts &assertions) override; - - void sectionEnded(SectionEndInfo const &endInfo) override; - void sectionEndedEarly(SectionEndInfo const &endInfo) override; - - auto acquireGeneratorTracker(StringRef generatorName, SourceLineInfo const &lineInfo) -> IGeneratorTracker & override; - -#if defined(CATCH_CONFIG_ENABLE_BENCHMARKING) - void benchmarkPreparing(std::string const &name) override; - void benchmarkStarting(BenchmarkInfo const &info) override; - void benchmarkEnded(BenchmarkStats<> const &stats) override; - void benchmarkFailed(std::string const &error) override; -#endif // CATCH_CONFIG_ENABLE_BENCHMARKING - - void pushScopedMessage(MessageInfo const &message) override; - void popScopedMessage(MessageInfo const &message) override; - - void emplaceUnscopedMessage(MessageBuilder const &builder) override; - - std::string getCurrentTestName() const override; - - const AssertionResult *getLastResult() const override; - - void exceptionEarlyReported() override; - - void handleFatalErrorCondition(StringRef message) override; - - bool lastAssertionPassed() override; - - void assertionPassed() override; - - public: - // !TBD We need to do this another way! - bool aborting() const final; - - private: - void runCurrentTest(std::string &redirectedCout, std::string &redirectedCerr); - void invokeActiveTestCase(); - - void resetAssertionInfo(); - bool testForMissingAssertions(Counts &assertions); - - void assertionEnded(AssertionResult const &result); - void reportExpr(AssertionInfo const &info, ResultWas::OfType resultType, ITransientExpression const *expr, bool negated); - - void populateReaction(AssertionReaction &reaction); - - private: - void handleUnfinishedSections(); - - TestRunInfo m_runInfo; - IMutableContext &m_context; - TestCase const *m_activeTestCase = nullptr; - ITracker *m_testCaseTracker = nullptr; - Option m_lastResult; - - IConfigPtr m_config; - Totals m_totals; - IStreamingReporterPtr m_reporter; - std::vector m_messages; - std::vector m_messageScopes; /* Keeps owners of so-called unscoped messages. */ - AssertionInfo m_lastAssertionInfo; - std::vector m_unfinishedSections; - std::vector m_activeSections; - TrackerContext m_trackerContext; - FatalConditionHandler m_fatalConditionhandler; - bool m_lastAssertionPassed = false; - bool m_shouldReportUnexpected = true; - bool m_includeSuccessfulResults; - }; - - void seedRng(IConfig const &config); - unsigned int rngSeed(); -} // end namespace Catch - -// end catch_run_context.h -namespace Catch { - namespace { - auto operator<<(std::ostream &os, ITransientExpression const &expr) -> std::ostream & { - expr.streamReconstructedExpression(os); - return os; - } - } // namespace - - LazyExpression::LazyExpression(bool isNegated) - : m_isNegated(isNegated) { - } - - LazyExpression::LazyExpression(LazyExpression const &other) - : m_isNegated(other.m_isNegated) { - } - - LazyExpression::operator bool() const { - return m_transientExpression != nullptr; - } - - auto operator<<(std::ostream &os, LazyExpression const &lazyExpr) -> std::ostream & { - if (lazyExpr.m_isNegated) - os << "!"; - - if (lazyExpr) { - if (lazyExpr.m_isNegated && lazyExpr.m_transientExpression->isBinaryExpression()) - os << "(" << *lazyExpr.m_transientExpression << ")"; - else - os << *lazyExpr.m_transientExpression; - } else { - os << "{** error - unchecked empty expression requested **}"; - } - return os; - } - - AssertionHandler::AssertionHandler(StringRef const ¯oName, - SourceLineInfo const &lineInfo, - StringRef capturedExpression, - ResultDisposition::Flags resultDisposition) - : m_assertionInfo{macroName, lineInfo, capturedExpression, resultDisposition} - , m_resultCapture(getResultCapture()) { - } - - void AssertionHandler::handleExpr(ITransientExpression const &expr) { - m_resultCapture.handleExpr(m_assertionInfo, expr, m_reaction); - } - void AssertionHandler::handleMessage(ResultWas::OfType resultType, StringRef const &message) { - m_resultCapture.handleMessage(m_assertionInfo, resultType, message, m_reaction); - } - - auto AssertionHandler::allowThrows() const -> bool { - return getCurrentContext().getConfig()->allowThrows(); - } - - void AssertionHandler::complete() { - setCompleted(); - if (m_reaction.shouldDebugBreak) { - // If you find your debugger stopping you here then go one level up on the - // call-stack for the code that caused it (typically a failed assertion) - - // (To go back to the test and change execution, jump over the throw, next) - CATCH_BREAK_INTO_DEBUGGER(); - } - if (m_reaction.shouldThrow) { -#if !defined(CATCH_CONFIG_DISABLE_EXCEPTIONS) - throw Catch::TestFailureException(); -#else - CATCH_ERROR("Test failure requires aborting test!"); -#endif - } - } - void AssertionHandler::setCompleted() { - m_completed = true; - } - - void AssertionHandler::handleUnexpectedInflightException() { - m_resultCapture.handleUnexpectedInflightException(m_assertionInfo, Catch::translateActiveException(), m_reaction); - } - - void AssertionHandler::handleExceptionThrownAsExpected() { - m_resultCapture.handleNonExpr(m_assertionInfo, ResultWas::Ok, m_reaction); - } - void AssertionHandler::handleExceptionNotThrownAsExpected() { - m_resultCapture.handleNonExpr(m_assertionInfo, ResultWas::Ok, m_reaction); - } - - void AssertionHandler::handleUnexpectedExceptionNotThrown() { - m_resultCapture.handleUnexpectedExceptionNotThrown(m_assertionInfo, m_reaction); - } - - void AssertionHandler::handleThrowingCallSkipped() { - m_resultCapture.handleNonExpr(m_assertionInfo, ResultWas::Ok, m_reaction); - } - - // This is the overload that takes a string and infers the Equals matcher from it - // The more general overload, that takes any string matcher, is in catch_capture_matchers.cpp - void handleExceptionMatchExpr(AssertionHandler &handler, std::string const &str, StringRef const &matcherString) { - handleExceptionMatchExpr(handler, Matchers::Equals(str), matcherString); - } - -} // namespace Catch -// end catch_assertionhandler.cpp -// start catch_assertionresult.cpp - -namespace Catch { - AssertionResultData::AssertionResultData(ResultWas::OfType _resultType, LazyExpression const &_lazyExpression) - : lazyExpression(_lazyExpression) - , resultType(_resultType) { - } - - std::string AssertionResultData::reconstructExpression() const { - if (reconstructedExpression.empty()) { - if (lazyExpression) { - ReusableStringStream rss; - rss << lazyExpression; - reconstructedExpression = rss.str(); - } - } - return reconstructedExpression; - } - - AssertionResult::AssertionResult(AssertionInfo const &info, AssertionResultData const &data) - : m_info(info) - , m_resultData(data) { - } - - // Result was a success - bool AssertionResult::succeeded() const { - return Catch::isOk(m_resultData.resultType); - } - - // Result was a success, or failure is suppressed - bool AssertionResult::isOk() const { - return Catch::isOk(m_resultData.resultType) || shouldSuppressFailure(m_info.resultDisposition); - } - - ResultWas::OfType AssertionResult::getResultType() const { - return m_resultData.resultType; - } - - bool AssertionResult::hasExpression() const { - return !m_info.capturedExpression.empty(); - } - - bool AssertionResult::hasMessage() const { - return !m_resultData.message.empty(); - } - - std::string AssertionResult::getExpression() const { - // Possibly overallocating by 3 characters should be basically free - std::string expr; - expr.reserve(m_info.capturedExpression.size() + 3); - if (isFalseTest(m_info.resultDisposition)) { - expr += "!("; - } - expr += m_info.capturedExpression; - if (isFalseTest(m_info.resultDisposition)) { - expr += ')'; - } - return expr; - } - - std::string AssertionResult::getExpressionInMacro() const { - std::string expr; - if (m_info.macroName.empty()) - expr = static_cast(m_info.capturedExpression); - else { - expr.reserve(m_info.macroName.size() + m_info.capturedExpression.size() + 4); - expr += m_info.macroName; - expr += "( "; - expr += m_info.capturedExpression; - expr += " )"; - } - return expr; - } - - bool AssertionResult::hasExpandedExpression() const { - return hasExpression() && getExpandedExpression() != getExpression(); - } - - std::string AssertionResult::getExpandedExpression() const { - std::string expr = m_resultData.reconstructExpression(); - return expr.empty() ? getExpression() : expr; - } - - std::string AssertionResult::getMessage() const { - return m_resultData.message; - } - SourceLineInfo AssertionResult::getSourceInfo() const { - return m_info.lineInfo; - } - - StringRef AssertionResult::getTestMacroName() const { - return m_info.macroName; - } - -} // end namespace Catch -// end catch_assertionresult.cpp -// start catch_capture_matchers.cpp - -namespace Catch { - using StringMatcher = Matchers::Impl::MatcherBase; - - // This is the general overload that takes a any string matcher - // There is another overload, in catch_assertionhandler.h/.cpp, that only takes a string and infers - // the Equals matcher (so the header does not mention matchers) - void handleExceptionMatchExpr(AssertionHandler &handler, StringMatcher const &matcher, StringRef const &matcherString) { - std::string exceptionMessage = Catch::translateActiveException(); - MatchExpr expr(exceptionMessage, matcher, matcherString); - handler.handleExpr(expr); - } - -} // namespace Catch -// end catch_capture_matchers.cpp -// start catch_commandline.cpp - -// start catch_commandline.h - -// start catch_clara.h - -// Use Catch's value for console width (store Clara's off to the side, if present) -#ifdef CLARA_CONFIG_CONSOLE_WIDTH -#define CATCH_TEMP_CLARA_CONFIG_CONSOLE_WIDTH CATCH_CLARA_TEXTFLOW_CONFIG_CONSOLE_WIDTH -#undef CATCH_CLARA_TEXTFLOW_CONFIG_CONSOLE_WIDTH -#endif -#define CATCH_CLARA_TEXTFLOW_CONFIG_CONSOLE_WIDTH CATCH_CONFIG_CONSOLE_WIDTH - 1 - -#ifdef __clang__ -#pragma clang diagnostic push -#pragma clang diagnostic ignored "-Wweak-vtables" -#pragma clang diagnostic ignored "-Wexit-time-destructors" -#pragma clang diagnostic ignored "-Wshadow" -#endif - -// start clara.hpp -// Copyright 2017 Two Blue Cubes Ltd. All rights reserved. -// -// Distributed under the Boost Software License, Version 1.0. (See accompanying -// file LICENSE_1_0.txt or copy at http://www.boost.org/LICENSE_1_0.txt) -// -// See https://github.com/philsquared/Clara for more details - -// Clara v1.1.5 - -#ifndef CATCH_CLARA_CONFIG_CONSOLE_WIDTH -#define CATCH_CLARA_CONFIG_CONSOLE_WIDTH 80 -#endif - -#ifndef CATCH_CLARA_TEXTFLOW_CONFIG_CONSOLE_WIDTH -#define CATCH_CLARA_TEXTFLOW_CONFIG_CONSOLE_WIDTH CATCH_CLARA_CONFIG_CONSOLE_WIDTH -#endif - -#ifndef CLARA_CONFIG_OPTIONAL_TYPE -#ifdef __has_include -#if __has_include() && __cplusplus >= 201703L -#include -#define CLARA_CONFIG_OPTIONAL_TYPE std::optional -#endif -#endif -#endif - -// ----------- #included from clara_textflow.hpp ----------- - -// TextFlowCpp -// -// A single-header library for wrapping and laying out basic text, by Phil Nash -// -// Distributed under the Boost Software License, Version 1.0. (See accompanying -// file LICENSE.txt or copy at http://www.boost.org/LICENSE_1_0.txt) -// -// This project is hosted at https://github.com/philsquared/textflowcpp - -#include -#include -#include -#include - -#ifndef CATCH_CLARA_TEXTFLOW_CONFIG_CONSOLE_WIDTH -#define CATCH_CLARA_TEXTFLOW_CONFIG_CONSOLE_WIDTH 80 -#endif - -namespace Catch { - namespace clara { - namespace TextFlow { - inline auto isWhitespace(char c) -> bool { - static std::string chars = " \t\n\r"; - return chars.find(c) != std::string::npos; - } - inline auto isBreakableBefore(char c) -> bool { - static std::string chars = "[({<|"; - return chars.find(c) != std::string::npos; - } - inline auto isBreakableAfter(char c) -> bool { - static std::string chars = "])}>.,:;*+-=&/\\"; - return chars.find(c) != std::string::npos; - } - - class Columns; - - class Column { - std::vector m_strings; - size_t m_width = CATCH_CLARA_TEXTFLOW_CONFIG_CONSOLE_WIDTH; - size_t m_indent = 0; - size_t m_initialIndent = std::string::npos; - - public: - class iterator { - friend Column; - - Column const &m_column; - size_t m_stringIndex = 0; - size_t m_pos = 0; - - size_t m_len = 0; - size_t m_end = 0; - bool m_suffix = false; - - iterator(Column const &column, size_t stringIndex) - : m_column(column) - , m_stringIndex(stringIndex) { - } - - auto line() const -> std::string const & { return m_column.m_strings[m_stringIndex]; } - - auto isBoundary(size_t at) const -> bool { - assert(at > 0); - assert(at <= line().size()); - - return at == line().size() || (isWhitespace(line()[at]) && !isWhitespace(line()[at - 1])) || isBreakableBefore(line()[at]) - || isBreakableAfter(line()[at - 1]); - } - - void calcLength() { - assert(m_stringIndex < m_column.m_strings.size()); - - m_suffix = false; - auto width = m_column.m_width - indent(); - m_end = m_pos; - if (line()[m_pos] == '\n') { - ++m_end; - } - while (m_end < line().size() && line()[m_end] != '\n') - ++m_end; - - if (m_end < m_pos + width) { - m_len = m_end - m_pos; - } else { - size_t len = width; - while (len > 0 && !isBoundary(m_pos + len)) - --len; - while (len > 0 && isWhitespace(line()[m_pos + len - 1])) - --len; - - if (len > 0) { - m_len = len; - } else { - m_suffix = true; - m_len = width - 1; - } - } - } - - auto indent() const -> size_t { - auto initial = m_pos == 0 && m_stringIndex == 0 ? m_column.m_initialIndent : std::string::npos; - return initial == std::string::npos ? m_column.m_indent : initial; - } - - auto addIndentAndSuffix(std::string const &plain) const -> std::string { - return std::string(indent(), ' ') + (m_suffix ? plain + "-" : plain); - } - - public: - using difference_type = std::ptrdiff_t; - using value_type = std::string; - using pointer = value_type *; - using reference = value_type &; - using iterator_category = std::forward_iterator_tag; - - explicit iterator(Column const &column) - : m_column(column) { - assert(m_column.m_width > m_column.m_indent); - assert(m_column.m_initialIndent == std::string::npos || m_column.m_width > m_column.m_initialIndent); - calcLength(); - if (m_len == 0) - m_stringIndex++; // Empty string - } - - auto operator*() const -> std::string { - assert(m_stringIndex < m_column.m_strings.size()); - assert(m_pos <= m_end); - return addIndentAndSuffix(line().substr(m_pos, m_len)); - } - - auto operator++() -> iterator & { - m_pos += m_len; - if (m_pos < line().size() && line()[m_pos] == '\n') - m_pos += 1; - else - while (m_pos < line().size() && isWhitespace(line()[m_pos])) - ++m_pos; - - if (m_pos == line().size()) { - m_pos = 0; - ++m_stringIndex; - } - if (m_stringIndex < m_column.m_strings.size()) - calcLength(); - return *this; - } - auto operator++(int) -> iterator { - iterator prev(*this); - operator++(); - return prev; - } - - auto operator==(iterator const &other) const -> bool { - return m_pos == other.m_pos && m_stringIndex == other.m_stringIndex && &m_column == &other.m_column; - } - auto operator!=(iterator const &other) const -> bool { return !operator==(other); } - }; - using const_iterator = iterator; - - explicit Column(std::string const &text) { m_strings.push_back(text); } - - auto width(size_t newWidth) -> Column & { - assert(newWidth > 0); - m_width = newWidth; - return *this; - } - auto indent(size_t newIndent) -> Column & { - m_indent = newIndent; - return *this; - } - auto initialIndent(size_t newIndent) -> Column & { - m_initialIndent = newIndent; - return *this; - } - - auto width() const -> size_t { return m_width; } - auto begin() const -> iterator { return iterator(*this); } - auto end() const -> iterator { return {*this, m_strings.size()}; } - - inline friend std::ostream &operator<<(std::ostream &os, Column const &col) { - bool first = true; - for (auto line : col) { - if (first) - first = false; - else - os << "\n"; - os << line; - } - return os; - } - - auto operator+(Column const &other) -> Columns; - - auto toString() const -> std::string { - std::ostringstream oss; - oss << *this; - return oss.str(); - } - }; - - class Spacer : public Column { - public: - explicit Spacer(size_t spaceWidth) - : Column("") { - width(spaceWidth); - } - }; - - class Columns { - std::vector m_columns; - - public: - class iterator { - friend Columns; - struct EndTag { - }; - - std::vector const &m_columns; - std::vector m_iterators; - size_t m_activeIterators; - - iterator(Columns const &columns, EndTag) - : m_columns(columns.m_columns) - , m_activeIterators(0) { - m_iterators.reserve(m_columns.size()); - - for (auto const &col : m_columns) - m_iterators.push_back(col.end()); - } - - public: - using difference_type = std::ptrdiff_t; - using value_type = std::string; - using pointer = value_type *; - using reference = value_type &; - using iterator_category = std::forward_iterator_tag; - - explicit iterator(Columns const &columns) - : m_columns(columns.m_columns) - , m_activeIterators(m_columns.size()) { - m_iterators.reserve(m_columns.size()); - - for (auto const &col : m_columns) - m_iterators.push_back(col.begin()); - } - - auto operator==(iterator const &other) const -> bool { return m_iterators == other.m_iterators; } - auto operator!=(iterator const &other) const -> bool { return m_iterators != other.m_iterators; } - auto operator*() const -> std::string { - std::string row, padding; - - for (size_t i = 0; i < m_columns.size(); ++i) { - auto width = m_columns[i].width(); - if (m_iterators[i] != m_columns[i].end()) { - std::string col = *m_iterators[i]; - row += padding + col; - if (col.size() < width) - padding = std::string(width - col.size(), ' '); - else - padding = ""; - } else { - padding += std::string(width, ' '); - } - } - return row; - } - auto operator++() -> iterator & { - for (size_t i = 0; i < m_columns.size(); ++i) { - if (m_iterators[i] != m_columns[i].end()) - ++m_iterators[i]; - } - return *this; - } - auto operator++(int) -> iterator { - iterator prev(*this); - operator++(); - return prev; - } - }; - using const_iterator = iterator; - - auto begin() const -> iterator { return iterator(*this); } - auto end() const -> iterator { return {*this, iterator::EndTag()}; } - - auto operator+=(Column const &col) -> Columns & { - m_columns.push_back(col); - return *this; - } - auto operator+(Column const &col) -> Columns { - Columns combined = *this; - combined += col; - return combined; - } - - inline friend std::ostream &operator<<(std::ostream &os, Columns const &cols) { - bool first = true; - for (auto line : cols) { - if (first) - first = false; - else - os << "\n"; - os << line; - } - return os; - } - - auto toString() const -> std::string { - std::ostringstream oss; - oss << *this; - return oss.str(); - } - }; - - inline auto Column::operator+(Column const &other) -> Columns { - Columns cols; - cols += *this; - cols += other; - return cols; - } - } // namespace TextFlow - } // namespace clara -} // namespace Catch - -// ----------- end of #include from clara_textflow.hpp ----------- -// ........... back in clara.hpp - -#include -#include -#include -#include -#include - -#if !defined(CATCH_PLATFORM_WINDOWS) && (defined(WIN32) || defined(__WIN32__) || defined(_WIN32) || defined(_MSC_VER)) -#define CATCH_PLATFORM_WINDOWS -#endif - -namespace Catch { - namespace clara { - namespace detail { - // Traits for extracting arg and return type of lambdas (for single argument lambdas) - template - struct UnaryLambdaTraits : UnaryLambdaTraits { - }; - - template - struct UnaryLambdaTraits { - static const bool isValid = false; - }; - - template - struct UnaryLambdaTraits { - static const bool isValid = true; - using ArgType = typename std::remove_const::type>::type; - using ReturnType = ReturnT; - }; - - class TokenStream; - - // Transport for raw args (copied from main args, or supplied via init list for testing) - class Args { - friend TokenStream; - std::string m_exeName; - std::vector m_args; - - public: - Args(int argc, char const *const *argv) - : m_exeName(argv[0]) - , m_args(argv + 1, argv + argc) { - } - - Args(std::initializer_list args) - : m_exeName(*args.begin()) - , m_args(args.begin() + 1, args.end()) { - } - - auto exeName() const -> std::string { return m_exeName; } - }; - - // Wraps a token coming from a token stream. These may not directly correspond to strings as a single string - // may encode an option + its argument if the : or = form is used - enum class TokenType { - Option, - Argument - }; - struct Token { - TokenType type; - std::string token; - }; - - inline auto isOptPrefix(char c) -> bool { - return c == '-' -#ifdef CATCH_PLATFORM_WINDOWS - || c == '/' -#endif - ; - } - - // Abstracts iterators into args as a stream of tokens, with option arguments uniformly handled - class TokenStream { - using Iterator = std::vector::const_iterator; - Iterator it; - Iterator itEnd; - std::vector m_tokenBuffer; - - void loadBuffer() { - m_tokenBuffer.resize(0); - - // Skip any empty strings - while (it != itEnd && it->empty()) - ++it; - - if (it != itEnd) { - auto const &next = *it; - if (isOptPrefix(next[0])) { - auto delimiterPos = next.find_first_of(" :="); - if (delimiterPos != std::string::npos) { - m_tokenBuffer.push_back({TokenType::Option, next.substr(0, delimiterPos)}); - m_tokenBuffer.push_back({TokenType::Argument, next.substr(delimiterPos + 1)}); - } else { - if (next[1] != '-' && next.size() > 2) { - std::string opt = "- "; - for (size_t i = 1; i < next.size(); ++i) { - opt[1] = next[i]; - m_tokenBuffer.push_back({TokenType::Option, opt}); - } - } else { - m_tokenBuffer.push_back({TokenType::Option, next}); - } - } - } else { - m_tokenBuffer.push_back({TokenType::Argument, next}); - } - } - } - - public: - explicit TokenStream(Args const &args) - : TokenStream(args.m_args.begin(), args.m_args.end()) { - } - - TokenStream(Iterator it, Iterator itEnd) - : it(it) - , itEnd(itEnd) { - loadBuffer(); - } - - explicit operator bool() const { return !m_tokenBuffer.empty() || it != itEnd; } - - auto count() const -> size_t { return m_tokenBuffer.size() + (itEnd - it); } - - auto operator*() const -> Token { - assert(!m_tokenBuffer.empty()); - return m_tokenBuffer.front(); - } - - auto operator->() const -> Token const * { - assert(!m_tokenBuffer.empty()); - return &m_tokenBuffer.front(); - } - - auto operator++() -> TokenStream & { - if (m_tokenBuffer.size() >= 2) { - m_tokenBuffer.erase(m_tokenBuffer.begin()); - } else { - if (it != itEnd) - ++it; - loadBuffer(); - } - return *this; - } - }; - - class ResultBase { - public: - enum Type { - Ok, - LogicError, - RuntimeError - }; - - protected: - ResultBase(Type type) - : m_type(type) { - } - virtual ~ResultBase() = default; - - virtual void enforceOk() const = 0; - - Type m_type; - }; - - template - class ResultValueBase : public ResultBase { - public: - auto value() const -> T const & { - enforceOk(); - return m_value; - } - - protected: - ResultValueBase(Type type) - : ResultBase(type) { - } - - ResultValueBase(ResultValueBase const &other) - : ResultBase(other) { - if (m_type == ResultBase::Ok) - new (&m_value) T(other.m_value); - } - - ResultValueBase(Type, T const &value) - : ResultBase(Ok) { - new (&m_value) T(value); - } - - auto operator=(ResultValueBase const &other) -> ResultValueBase & { - if (m_type == ResultBase::Ok) - m_value.~T(); - ResultBase::operator=(other); - if (m_type == ResultBase::Ok) - new (&m_value) T(other.m_value); - return *this; - } - - ~ResultValueBase() override { - if (m_type == Ok) - m_value.~T(); - } - - union { - T m_value; - }; - }; - - template<> - class ResultValueBase : public ResultBase { - protected: - using ResultBase::ResultBase; - }; - - template - class BasicResult : public ResultValueBase { - public: - template - explicit BasicResult(BasicResult const &other) - : ResultValueBase(other.type()) - , m_errorMessage(other.errorMessage()) { - assert(type() != ResultBase::Ok); - } - - template - static auto ok(U const &value) -> BasicResult { - return {ResultBase::Ok, value}; - } - static auto ok() -> BasicResult { return {ResultBase::Ok}; } - static auto logicError(std::string const &message) -> BasicResult { return {ResultBase::LogicError, message}; } - static auto runtimeError(std::string const &message) -> BasicResult { return {ResultBase::RuntimeError, message}; } - - explicit operator bool() const { return m_type == ResultBase::Ok; } - auto type() const -> ResultBase::Type { return m_type; } - auto errorMessage() const -> std::string { return m_errorMessage; } - - protected: - void enforceOk() const override { - // Errors shouldn't reach this point, but if they do - // the actual error message will be in m_errorMessage - assert(m_type != ResultBase::LogicError); - assert(m_type != ResultBase::RuntimeError); - if (m_type != ResultBase::Ok) - std::abort(); - } - - std::string m_errorMessage; // Only populated if resultType is an error - - BasicResult(ResultBase::Type type, std::string const &message) - : ResultValueBase(type) - , m_errorMessage(message) { - assert(m_type != ResultBase::Ok); - } - - using ResultValueBase::ResultValueBase; - using ResultBase::m_type; - }; - - enum class ParseResultType { - Matched, - NoMatch, - ShortCircuitAll, - ShortCircuitSame - }; - - class ParseState { - public: - ParseState(ParseResultType type, TokenStream const &remainingTokens) - : m_type(type) - , m_remainingTokens(remainingTokens) { - } - - auto type() const -> ParseResultType { return m_type; } - auto remainingTokens() const -> TokenStream { return m_remainingTokens; } - - private: - ParseResultType m_type; - TokenStream m_remainingTokens; - }; - - using Result = BasicResult; - using ParserResult = BasicResult; - using InternalParseResult = BasicResult; - - struct HelpColumns { - std::string left; - std::string right; - }; - - template - inline auto convertInto(std::string const &source, T &target) -> ParserResult { - std::stringstream ss; - ss << source; - ss >> target; - if (ss.fail()) - return ParserResult::runtimeError("Unable to convert '" + source + "' to destination type"); - else - return ParserResult::ok(ParseResultType::Matched); - } - inline auto convertInto(std::string const &source, std::string &target) -> ParserResult { - target = source; - return ParserResult::ok(ParseResultType::Matched); - } - inline auto convertInto(std::string const &source, bool &target) -> ParserResult { - std::string srcLC = source; - std::transform(srcLC.begin(), srcLC.end(), srcLC.begin(), [](unsigned char c) { return static_cast(std::tolower(c)); }); - if (srcLC == "y" || srcLC == "1" || srcLC == "true" || srcLC == "yes" || srcLC == "on") - target = true; - else if (srcLC == "n" || srcLC == "0" || srcLC == "false" || srcLC == "no" || srcLC == "off") - target = false; - else - return ParserResult::runtimeError("Expected a boolean value but did not recognise: '" + source + "'"); - return ParserResult::ok(ParseResultType::Matched); - } -#ifdef CLARA_CONFIG_OPTIONAL_TYPE - template - inline auto convertInto(std::string const &source, CLARA_CONFIG_OPTIONAL_TYPE &target) -> ParserResult { - T temp; - auto result = convertInto(source, temp); - if (result) - target = std::move(temp); - return result; - } -#endif // CLARA_CONFIG_OPTIONAL_TYPE - - struct NonCopyable { - NonCopyable() = default; - NonCopyable(NonCopyable const &) = delete; - NonCopyable(NonCopyable &&) = delete; - NonCopyable &operator=(NonCopyable const &) = delete; - NonCopyable &operator=(NonCopyable &&) = delete; - }; - - struct BoundRef : NonCopyable { - virtual ~BoundRef() = default; - virtual auto isContainer() const -> bool { return false; } - virtual auto isFlag() const -> bool { return false; } - }; - struct BoundValueRefBase : BoundRef { - virtual auto setValue(std::string const &arg) -> ParserResult = 0; - }; - struct BoundFlagRefBase : BoundRef { - virtual auto setFlag(bool flag) -> ParserResult = 0; - virtual auto isFlag() const -> bool { return true; } - }; - - template - struct BoundValueRef : BoundValueRefBase { - T &m_ref; - - explicit BoundValueRef(T &ref) - : m_ref(ref) { - } - - auto setValue(std::string const &arg) -> ParserResult override { return convertInto(arg, m_ref); } - }; - - template - struct BoundValueRef> : BoundValueRefBase { - std::vector &m_ref; - - explicit BoundValueRef(std::vector &ref) - : m_ref(ref) { - } - - auto isContainer() const -> bool override { return true; } - - auto setValue(std::string const &arg) -> ParserResult override { - T temp; - auto result = convertInto(arg, temp); - if (result) - m_ref.push_back(temp); - return result; - } - }; - - struct BoundFlagRef : BoundFlagRefBase { - bool &m_ref; - - explicit BoundFlagRef(bool &ref) - : m_ref(ref) { - } - - auto setFlag(bool flag) -> ParserResult override { - m_ref = flag; - return ParserResult::ok(ParseResultType::Matched); - } - }; - - template - struct LambdaInvoker { - static_assert(std::is_same::value, "Lambda must return void or clara::ParserResult"); - - template - static auto invoke(L const &lambda, ArgType const &arg) -> ParserResult { - return lambda(arg); - } - }; - - template<> - struct LambdaInvoker { - template - static auto invoke(L const &lambda, ArgType const &arg) -> ParserResult { - lambda(arg); - return ParserResult::ok(ParseResultType::Matched); - } - }; - - template - inline auto invokeLambda(L const &lambda, std::string const &arg) -> ParserResult { - ArgType temp{}; - auto result = convertInto(arg, temp); - return !result ? result : LambdaInvoker::ReturnType>::invoke(lambda, temp); - } - - template - struct BoundLambda : BoundValueRefBase { - L m_lambda; - - static_assert(UnaryLambdaTraits::isValid, "Supplied lambda must take exactly one argument"); - explicit BoundLambda(L const &lambda) - : m_lambda(lambda) { - } - - auto setValue(std::string const &arg) -> ParserResult override { - return invokeLambda::ArgType>(m_lambda, arg); - } - }; - - template - struct BoundFlagLambda : BoundFlagRefBase { - L m_lambda; - - static_assert(UnaryLambdaTraits::isValid, "Supplied lambda must take exactly one argument"); - static_assert(std::is_same::ArgType, bool>::value, "flags must be boolean"); - - explicit BoundFlagLambda(L const &lambda) - : m_lambda(lambda) { - } - - auto setFlag(bool flag) -> ParserResult override { - return LambdaInvoker::ReturnType>::invoke(m_lambda, flag); - } - }; - - enum class Optionality { - Optional, - Required - }; - - struct Parser; - - class ParserBase { - public: - virtual ~ParserBase() = default; - virtual auto validate() const -> Result { return Result::ok(); } - virtual auto parse(std::string const &exeName, TokenStream const &tokens) const -> InternalParseResult = 0; - virtual auto cardinality() const -> size_t { return 1; } - - auto parse(Args const &args) const -> InternalParseResult { return parse(args.exeName(), TokenStream(args)); } - }; - - template - class ComposableParserImpl : public ParserBase { - public: - template - auto operator|(T const &other) const -> Parser; - - template - auto operator+(T const &other) const -> Parser; - }; - - // Common code and state for Args and Opts - template - class ParserRefImpl : public ComposableParserImpl { - protected: - Optionality m_optionality = Optionality::Optional; - std::shared_ptr m_ref; - std::string m_hint; - std::string m_description; - - explicit ParserRefImpl(std::shared_ptr const &ref) - : m_ref(ref) { - } - - public: - template - ParserRefImpl(T &ref, std::string const &hint) - : m_ref(std::make_shared>(ref)) - , m_hint(hint) { - } - - template - ParserRefImpl(LambdaT const &ref, std::string const &hint) - : m_ref(std::make_shared>(ref)) - , m_hint(hint) { - } - - auto operator()(std::string const &description) -> DerivedT & { - m_description = description; - return static_cast(*this); - } - - auto optional() -> DerivedT & { - m_optionality = Optionality::Optional; - return static_cast(*this); - }; - - auto required() -> DerivedT & { - m_optionality = Optionality::Required; - return static_cast(*this); - }; - - auto isOptional() const -> bool { return m_optionality == Optionality::Optional; } - - auto cardinality() const -> size_t override { - if (m_ref->isContainer()) - return 0; - else - return 1; - } - - auto hint() const -> std::string { return m_hint; } - }; - - class ExeName : public ComposableParserImpl { - std::shared_ptr m_name; - std::shared_ptr m_ref; - - template - static auto makeRef(LambdaT const &lambda) -> std::shared_ptr { - return std::make_shared>(lambda); - } - - public: - ExeName() - : m_name(std::make_shared("")) { - } - - explicit ExeName(std::string &ref) - : ExeName() { - m_ref = std::make_shared>(ref); - } - - template - explicit ExeName(LambdaT const &lambda) - : ExeName() { - m_ref = std::make_shared>(lambda); - } - - // The exe name is not parsed out of the normal tokens, but is handled specially - auto parse(std::string const &, TokenStream const &tokens) const -> InternalParseResult override { - return InternalParseResult::ok(ParseState(ParseResultType::NoMatch, tokens)); - } - - auto name() const -> std::string { return *m_name; } - auto set(std::string const &newName) -> ParserResult { - auto lastSlash = newName.find_last_of("\\/"); - auto filename = (lastSlash == std::string::npos) ? newName : newName.substr(lastSlash + 1); - - *m_name = filename; - if (m_ref) - return m_ref->setValue(filename); - else - return ParserResult::ok(ParseResultType::Matched); - } - }; - - class Arg : public ParserRefImpl { - public: - using ParserRefImpl::ParserRefImpl; - - auto parse(std::string const &, TokenStream const &tokens) const -> InternalParseResult override { - auto validationResult = validate(); - if (!validationResult) - return InternalParseResult(validationResult); - - auto remainingTokens = tokens; - auto const &token = *remainingTokens; - if (token.type != TokenType::Argument) - return InternalParseResult::ok(ParseState(ParseResultType::NoMatch, remainingTokens)); - - assert(!m_ref->isFlag()); - auto valueRef = static_cast(m_ref.get()); - - auto result = valueRef->setValue(remainingTokens->token); - if (!result) - return InternalParseResult(result); - else - return InternalParseResult::ok(ParseState(ParseResultType::Matched, ++remainingTokens)); - } - }; - - inline auto normaliseOpt(std::string const &optName) -> std::string { -#ifdef CATCH_PLATFORM_WINDOWS - if (optName[0] == '/') - return "-" + optName.substr(1); - else -#endif - return optName; - } - - class Opt : public ParserRefImpl { - protected: - std::vector m_optNames; - - public: - template - explicit Opt(LambdaT const &ref) - : ParserRefImpl(std::make_shared>(ref)) { - } - - explicit Opt(bool &ref) - : ParserRefImpl(std::make_shared(ref)) { - } - - template - Opt(LambdaT const &ref, std::string const &hint) - : ParserRefImpl(ref, hint) { - } - - template - Opt(T &ref, std::string const &hint) - : ParserRefImpl(ref, hint) { - } - - auto operator[](std::string const &optName) -> Opt & { - m_optNames.push_back(optName); - return *this; - } - - auto getHelpColumns() const -> std::vector { - std::ostringstream oss; - bool first = true; - for (auto const &opt : m_optNames) { - if (first) - first = false; - else - oss << ", "; - oss << opt; - } - if (!m_hint.empty()) - oss << " <" << m_hint << ">"; - return {{oss.str(), m_description}}; - } - - auto isMatch(std::string const &optToken) const -> bool { - auto normalisedToken = normaliseOpt(optToken); - for (auto const &name : m_optNames) { - if (normaliseOpt(name) == normalisedToken) - return true; - } - return false; - } - - using ParserBase::parse; - - auto parse(std::string const &, TokenStream const &tokens) const -> InternalParseResult override { - auto validationResult = validate(); - if (!validationResult) - return InternalParseResult(validationResult); - - auto remainingTokens = tokens; - if (remainingTokens && remainingTokens->type == TokenType::Option) { - auto const &token = *remainingTokens; - if (isMatch(token.token)) { - if (m_ref->isFlag()) { - auto flagRef = static_cast(m_ref.get()); - auto result = flagRef->setFlag(true); - if (!result) - return InternalParseResult(result); - if (result.value() == ParseResultType::ShortCircuitAll) - return InternalParseResult::ok(ParseState(result.value(), remainingTokens)); - } else { - auto valueRef = static_cast(m_ref.get()); - ++remainingTokens; - if (!remainingTokens) - return InternalParseResult::runtimeError("Expected argument following " + token.token); - auto const &argToken = *remainingTokens; - if (argToken.type != TokenType::Argument) - return InternalParseResult::runtimeError("Expected argument following " + token.token); - auto result = valueRef->setValue(argToken.token); - if (!result) - return InternalParseResult(result); - if (result.value() == ParseResultType::ShortCircuitAll) - return InternalParseResult::ok(ParseState(result.value(), remainingTokens)); - } - return InternalParseResult::ok(ParseState(ParseResultType::Matched, ++remainingTokens)); - } - } - return InternalParseResult::ok(ParseState(ParseResultType::NoMatch, remainingTokens)); - } - - auto validate() const -> Result override { - if (m_optNames.empty()) - return Result::logicError("No options supplied to Opt"); - for (auto const &name : m_optNames) { - if (name.empty()) - return Result::logicError("Option name cannot be empty"); -#ifdef CATCH_PLATFORM_WINDOWS - if (name[0] != '-' && name[0] != '/') - return Result::logicError("Option name must begin with '-' or '/'"); -#else - if (name[0] != '-') - return Result::logicError("Option name must begin with '-'"); -#endif - } - return ParserRefImpl::validate(); - } - }; - - struct Help : Opt { - Help(bool &showHelpFlag) - : Opt([&](bool flag) { - showHelpFlag = flag; - return ParserResult::ok(ParseResultType::ShortCircuitAll); - }) { - static_cast (*this)("display usage information")["-?"]["-h"]["--help"].optional(); - } - }; - - struct Parser : ParserBase { - mutable ExeName m_exeName; - std::vector m_options; - std::vector m_args; - - auto operator|=(ExeName const &exeName) -> Parser & { - m_exeName = exeName; - return *this; - } - - auto operator|=(Arg const &arg) -> Parser & { - m_args.push_back(arg); - return *this; - } - - auto operator|=(Opt const &opt) -> Parser & { - m_options.push_back(opt); - return *this; - } - - auto operator|=(Parser const &other) -> Parser & { - m_options.insert(m_options.end(), other.m_options.begin(), other.m_options.end()); - m_args.insert(m_args.end(), other.m_args.begin(), other.m_args.end()); - return *this; - } - - template - auto operator|(T const &other) const -> Parser { - return Parser(*this) |= other; - } - - // Forward deprecated interface with '+' instead of '|' - template - auto operator+=(T const &other) -> Parser & { - return operator|=(other); - } - template - auto operator+(T const &other) const -> Parser { - return operator|(other); - } - - auto getHelpColumns() const -> std::vector { - std::vector cols; - for (auto const &o : m_options) { - auto childCols = o.getHelpColumns(); - cols.insert(cols.end(), childCols.begin(), childCols.end()); - } - return cols; - } - - void writeToStream(std::ostream &os) const { - if (!m_exeName.name().empty()) { - os << "usage:\n" - << " " << m_exeName.name() << " "; - bool required = true, first = true; - for (auto const &arg : m_args) { - if (first) - first = false; - else - os << " "; - if (arg.isOptional() && required) { - os << "["; - required = false; - } - os << "<" << arg.hint() << ">"; - if (arg.cardinality() == 0) - os << " ... "; - } - if (!required) - os << "]"; - if (!m_options.empty()) - os << " options"; - os << "\n\nwhere options are:" << std::endl; - } - - auto rows = getHelpColumns(); - size_t consoleWidth = CATCH_CLARA_CONFIG_CONSOLE_WIDTH; - size_t optWidth = 0; - for (auto const &cols : rows) - optWidth = (std::max)(optWidth, cols.left.size() + 2); - - optWidth = (std::min)(optWidth, consoleWidth / 2); - - for (auto const &cols : rows) { - auto row = TextFlow::Column(cols.left).width(optWidth).indent(2) + TextFlow::Spacer(4) - + TextFlow::Column(cols.right).width(consoleWidth - 7 - optWidth); - os << row << std::endl; - } - } - - friend auto operator<<(std::ostream &os, Parser const &parser) -> std::ostream & { - parser.writeToStream(os); - return os; - } - - auto validate() const -> Result override { - for (auto const &opt : m_options) { - auto result = opt.validate(); - if (!result) - return result; - } - for (auto const &arg : m_args) { - auto result = arg.validate(); - if (!result) - return result; - } - return Result::ok(); - } - - using ParserBase::parse; - - auto parse(std::string const &exeName, TokenStream const &tokens) const -> InternalParseResult override { - struct ParserInfo { - ParserBase const *parser = nullptr; - size_t count = 0; - }; - const size_t totalParsers = m_options.size() + m_args.size(); - assert(totalParsers < 512); - // ParserInfo parseInfos[totalParsers]; // <-- this is what we really want to do - ParserInfo parseInfos[512]; - - { - size_t i = 0; - for (auto const &opt : m_options) - parseInfos[i++].parser = &opt; - for (auto const &arg : m_args) - parseInfos[i++].parser = &arg; - } - - m_exeName.set(exeName); - - auto result = InternalParseResult::ok(ParseState(ParseResultType::NoMatch, tokens)); - while (result.value().remainingTokens()) { - bool tokenParsed = false; - - for (size_t i = 0; i < totalParsers; ++i) { - auto &parseInfo = parseInfos[i]; - if (parseInfo.parser->cardinality() == 0 || parseInfo.count < parseInfo.parser->cardinality()) { - result = parseInfo.parser->parse(exeName, result.value().remainingTokens()); - if (!result) - return result; - if (result.value().type() != ParseResultType::NoMatch) { - tokenParsed = true; - ++parseInfo.count; - break; - } - } - } - - if (result.value().type() == ParseResultType::ShortCircuitAll) - return result; - if (!tokenParsed) - return InternalParseResult::runtimeError("Unrecognised token: " + result.value().remainingTokens()->token); - } - // !TBD Check missing required options - return result; - } - }; - - template - template - auto ComposableParserImpl::operator|(T const &other) const -> Parser { - return Parser() | static_cast(*this) | other; - } - } // namespace detail - - // A Combined parser - using detail::Parser; - - // A parser for options - using detail::Opt; - - // A parser for arguments - using detail::Arg; - - // Wrapper for argc, argv from main() - using detail::Args; - - // Specifies the name of the executable - using detail::ExeName; - - // Convenience wrapper for option parser that specifies the help option - using detail::Help; - - // enum of result types from a parse - using detail::ParseResultType; - - // Result type for parser operation - using detail::ParserResult; - - } // namespace clara -} // namespace Catch - -// end clara.hpp -#ifdef __clang__ -#pragma clang diagnostic pop -#endif - -// Restore Clara's value for console width, if present -#ifdef CATCH_TEMP_CLARA_CONFIG_CONSOLE_WIDTH -#define CATCH_CLARA_TEXTFLOW_CONFIG_CONSOLE_WIDTH CATCH_TEMP_CLARA_CONFIG_CONSOLE_WIDTH -#undef CATCH_TEMP_CLARA_CONFIG_CONSOLE_WIDTH -#endif - -// end catch_clara.h -namespace Catch { - clara::Parser makeCommandLineParser(ConfigData &config); - -} // end namespace Catch - -// end catch_commandline.h -#include -#include - -namespace Catch { - clara::Parser makeCommandLineParser(ConfigData &config) { - using namespace clara; - - auto const setWarning = [&](std::string const &warning) { - auto warningSet = [&]() { - if (warning == "NoAssertions") - return WarnAbout::NoAssertions; - - if (warning == "NoTests") - return WarnAbout::NoTests; - - return WarnAbout::Nothing; - }(); - - if (warningSet == WarnAbout::Nothing) - return ParserResult::runtimeError("Unrecognised warning: '" + warning + "'"); - config.warnings = static_cast(config.warnings | warningSet); - return ParserResult::ok(ParseResultType::Matched); - }; - auto const loadTestNamesFromFile = [&](std::string const &filename) { - std::ifstream f(filename.c_str()); - if (!f.is_open()) - return ParserResult::runtimeError("Unable to load input file: '" + filename + "'"); - - std::string line; - while (std::getline(f, line)) { - line = trim(line); - if (!line.empty() && !startsWith(line, '#')) { - if (!startsWith(line, '"')) - line = '"' + line + '"'; - config.testsOrTags.push_back(line); - config.testsOrTags.emplace_back(","); - } - } - // Remove comma in the end - if (!config.testsOrTags.empty()) - config.testsOrTags.erase(config.testsOrTags.end() - 1); - - return ParserResult::ok(ParseResultType::Matched); - }; - auto const setTestOrder = [&](std::string const &order) { - if (startsWith("declared", order)) - config.runOrder = RunTests::InDeclarationOrder; - else if (startsWith("lexical", order)) - config.runOrder = RunTests::InLexicographicalOrder; - else if (startsWith("random", order)) - config.runOrder = RunTests::InRandomOrder; - else - return clara::ParserResult::runtimeError("Unrecognised ordering: '" + order + "'"); - return ParserResult::ok(ParseResultType::Matched); - }; - auto const setRngSeed = [&](std::string const &seed) { - if (seed != "time") - return clara::detail::convertInto(seed, config.rngSeed); - config.rngSeed = static_cast(std::time(nullptr)); - return ParserResult::ok(ParseResultType::Matched); - }; - auto const setColourUsage = [&](std::string const &useColour) { - auto mode = toLower(useColour); - - if (mode == "yes") - config.useColour = UseColour::Yes; - else if (mode == "no") - config.useColour = UseColour::No; - else if (mode == "auto") - config.useColour = UseColour::Auto; - else - return ParserResult::runtimeError("colour mode must be one of: auto, yes or no. '" + useColour + "' not recognised"); - return ParserResult::ok(ParseResultType::Matched); - }; - auto const setWaitForKeypress = [&](std::string const &keypress) { - auto keypressLc = toLower(keypress); - if (keypressLc == "never") - config.waitForKeypress = WaitForKeypress::Never; - else if (keypressLc == "start") - config.waitForKeypress = WaitForKeypress::BeforeStart; - else if (keypressLc == "exit") - config.waitForKeypress = WaitForKeypress::BeforeExit; - else if (keypressLc == "both") - config.waitForKeypress = WaitForKeypress::BeforeStartAndExit; - else - return ParserResult::runtimeError("keypress argument must be one of: never, start, exit or both. '" + keypress + "' not recognised"); - return ParserResult::ok(ParseResultType::Matched); - }; - auto const setVerbosity = [&](std::string const &verbosity) { - auto lcVerbosity = toLower(verbosity); - if (lcVerbosity == "quiet") - config.verbosity = Verbosity::Quiet; - else if (lcVerbosity == "normal") - config.verbosity = Verbosity::Normal; - else if (lcVerbosity == "high") - config.verbosity = Verbosity::High; - else - return ParserResult::runtimeError("Unrecognised verbosity, '" + verbosity + "'"); - return ParserResult::ok(ParseResultType::Matched); - }; - auto const setReporter = [&](std::string const &reporter) { - IReporterRegistry::FactoryMap const &factories = getRegistryHub().getReporterRegistry().getFactories(); - - auto lcReporter = toLower(reporter); - auto result = factories.find(lcReporter); - - if (factories.end() != result) - config.reporterName = lcReporter; - else - return ParserResult::runtimeError("Unrecognized reporter, '" + reporter + "'. Check available with --list-reporters"); - return ParserResult::ok(ParseResultType::Matched); - }; - - auto cli = ExeName(config.processName) | Help(config.showHelp) - | Opt(config.listTests)["-l"]["--list-tests"]("list all/matching test cases") - | Opt(config.listTags)["-t"]["--list-tags"]("list all/matching tags") - | Opt(config.showSuccessfulTests)["-s"]["--success"]("include successful tests in output") - | Opt(config.shouldDebugBreak)["-b"]["--break"]("break into debugger on failure") - | Opt(config.noThrow)["-e"]["--nothrow"]("skip exception tests") - | Opt(config.showInvisibles)["-i"]["--invisibles"]("show invisibles (tabs, newlines)") - | Opt(config.outputFilename, "filename")["-o"]["--out"]("output filename") - | Opt(setReporter, "name")["-r"]["--reporter"]("reporter to use (defaults to console)") - | Opt(config.name, "name")["-n"]["--name"]("suite name") - | Opt([&](bool) { config.abortAfter = 1; })["-a"]["--abort"]("abort at first failure") - | Opt([&](int x) { config.abortAfter = x; }, "no. failures")["-x"]["--abortx"]("abort after x failures") - | Opt(setWarning, "warning name")["-w"]["--warn"]("enable warnings") - | Opt([&](bool flag) { config.showDurations = flag ? ShowDurations::Always : ShowDurations::Never; }, - "yes|no")["-d"]["--durations"]("show test durations") - | Opt(config.minDuration, - "seconds")["-D"]["--min-duration"]("show test durations for tests taking at least the given number of seconds") - | Opt(loadTestNamesFromFile, "filename")["-f"]["--input-file"]("load test names to run from a file") - | Opt(config.filenamesAsTags)["-#"]["--filenames-as-tags"]("adds a tag for the filename") - | Opt(config.sectionsToRun, "section name")["-c"]["--section"]("specify section to run") - | Opt(setVerbosity, "quiet|normal|high")["-v"]["--verbosity"]("set output verbosity") - | Opt(config.listTestNamesOnly)["--list-test-names-only"]("list all/matching test cases names only") - | Opt(config.listReporters)["--list-reporters"]("list all reporters") - | Opt(setTestOrder, "decl|lex|rand")["--order"]("test case order (defaults to decl)") - | Opt(setRngSeed, "'time'|number")["--rng-seed"]("set a specific seed for random numbers") - | Opt(setColourUsage, "yes|no")["--use-colour"]("should output be colourised") - | Opt(config.libIdentify)["--libidentify"]("report name and version according to libidentify standard") - | Opt(setWaitForKeypress, "never|start|exit|both")["--wait-for-keypress"]("waits for a keypress before exiting") - | Opt(config.benchmarkSamples, "samples")["--benchmark-samples"]("number of samples to collect (default: 100)") - | Opt(config.benchmarkResamples, "resamples")["--benchmark-resamples"]("number of resamples for the bootstrap (default: 100000)") - | Opt(config.benchmarkConfidenceInterval, "confidence interval")["--benchmark-confidence-interval"]( - "confidence interval for the bootstrap (between 0 and 1, default: 0.95)") - | Opt(config.benchmarkNoAnalysis)["--benchmark-no-analysis"]("perform only measurements; do not perform any analysis") - | Opt(config.benchmarkWarmupTime, "benchmarkWarmupTime")["--benchmark-warmup-time"]( - "amount of time in milliseconds spent on warming up each test (default: 100)") - | Arg(config.testsOrTags, "test name|pattern|tags")("which test or tests to use"); - - return cli; - } - -} // end namespace Catch -// end catch_commandline.cpp -// start catch_common.cpp - -#include -#include - -namespace Catch { - bool SourceLineInfo::operator==(SourceLineInfo const &other) const noexcept { - return line == other.line && (file == other.file || std::strcmp(file, other.file) == 0); - } - bool SourceLineInfo::operator<(SourceLineInfo const &other) const noexcept { - // We can assume that the same file will usually have the same pointer. - // Thus, if the pointers are the same, there is no point in calling the strcmp - return line < other.line || (line == other.line && file != other.file && (std::strcmp(file, other.file) < 0)); - } - - std::ostream &operator<<(std::ostream &os, SourceLineInfo const &info) { -#ifndef __GNUG__ - os << info.file << '(' << info.line << ')'; -#else - os << info.file << ':' << info.line; -#endif - return os; - } - - std::string StreamEndStop::operator+() const { - return std::string(); - } - - NonCopyable::NonCopyable() = default; - NonCopyable::~NonCopyable() = default; - -} // namespace Catch -// end catch_common.cpp -// start catch_config.cpp - -namespace Catch { - Config::Config(ConfigData const &data) - : m_data(data) - , m_stream(openStream()) { - // We need to trim filter specs to avoid trouble with superfluous - // whitespace (esp. important for bdd macros, as those are manually - // aligned with whitespace). - - for (auto &elem : m_data.testsOrTags) { - elem = trim(elem); - } - for (auto &elem : m_data.sectionsToRun) { - elem = trim(elem); - } - - TestSpecParser parser(ITagAliasRegistry::get()); - if (!m_data.testsOrTags.empty()) { - m_hasTestFilters = true; - for (auto const &testOrTags : m_data.testsOrTags) { - parser.parse(testOrTags); - } - } - m_testSpec = parser.testSpec(); - } - - std::string const &Config::getFilename() const { - return m_data.outputFilename; - } - - bool Config::listTests() const { - return m_data.listTests; - } - bool Config::listTestNamesOnly() const { - return m_data.listTestNamesOnly; - } - bool Config::listTags() const { - return m_data.listTags; - } - bool Config::listReporters() const { - return m_data.listReporters; - } - - std::string Config::getProcessName() const { - return m_data.processName; - } - std::string const &Config::getReporterName() const { - return m_data.reporterName; - } - - std::vector const &Config::getTestsOrTags() const { - return m_data.testsOrTags; - } - std::vector const &Config::getSectionsToRun() const { - return m_data.sectionsToRun; - } - - TestSpec const &Config::testSpec() const { - return m_testSpec; - } - bool Config::hasTestFilters() const { - return m_hasTestFilters; - } - - bool Config::showHelp() const { - return m_data.showHelp; - } - - // IConfig interface - bool Config::allowThrows() const { - return !m_data.noThrow; - } - std::ostream &Config::stream() const { - return m_stream->stream(); - } - std::string Config::name() const { - return m_data.name.empty() ? m_data.processName : m_data.name; - } - bool Config::includeSuccessfulResults() const { - return m_data.showSuccessfulTests; - } - bool Config::warnAboutMissingAssertions() const { - return !!(m_data.warnings & WarnAbout::NoAssertions); - } - bool Config::warnAboutNoTests() const { - return !!(m_data.warnings & WarnAbout::NoTests); - } - ShowDurations::OrNot Config::showDurations() const { - return m_data.showDurations; - } - double Config::minDuration() const { - return m_data.minDuration; - } - RunTests::InWhatOrder Config::runOrder() const { - return m_data.runOrder; - } - unsigned int Config::rngSeed() const { - return m_data.rngSeed; - } - UseColour::YesOrNo Config::useColour() const { - return m_data.useColour; - } - bool Config::shouldDebugBreak() const { - return m_data.shouldDebugBreak; - } - int Config::abortAfter() const { - return m_data.abortAfter; - } - bool Config::showInvisibles() const { - return m_data.showInvisibles; - } - Verbosity Config::verbosity() const { - return m_data.verbosity; - } - - bool Config::benchmarkNoAnalysis() const { - return m_data.benchmarkNoAnalysis; - } - int Config::benchmarkSamples() const { - return m_data.benchmarkSamples; - } - double Config::benchmarkConfidenceInterval() const { - return m_data.benchmarkConfidenceInterval; - } - unsigned int Config::benchmarkResamples() const { - return m_data.benchmarkResamples; - } - std::chrono::milliseconds Config::benchmarkWarmupTime() const { - return std::chrono::milliseconds(m_data.benchmarkWarmupTime); - } - - IStream const *Config::openStream() { - return Catch::makeStream(m_data.outputFilename); - } - -} // end namespace Catch -// end catch_config.cpp -// start catch_console_colour.cpp - -#if defined(__clang__) -#pragma clang diagnostic push -#pragma clang diagnostic ignored "-Wexit-time-destructors" -#endif - -// start catch_errno_guard.h - -namespace Catch { - class ErrnoGuard { - public: - ErrnoGuard(); - ~ErrnoGuard(); - - private: - int m_oldErrno; - }; - -} // namespace Catch - -// end catch_errno_guard.h -// start catch_windows_h_proxy.h - -#if defined(CATCH_PLATFORM_WINDOWS) - -#if !defined(NOMINMAX) && !defined(CATCH_CONFIG_NO_NOMINMAX) -#define CATCH_DEFINED_NOMINMAX -#define NOMINMAX -#endif -#if !defined(WIN32_LEAN_AND_MEAN) && !defined(CATCH_CONFIG_NO_WIN32_LEAN_AND_MEAN) -#define CATCH_DEFINED_WIN32_LEAN_AND_MEAN -#define WIN32_LEAN_AND_MEAN -#endif - -#ifdef __AFXDLL -#include -#else -#include -#endif - -#ifdef CATCH_DEFINED_NOMINMAX -#undef NOMINMAX -#endif -#ifdef CATCH_DEFINED_WIN32_LEAN_AND_MEAN -#undef WIN32_LEAN_AND_MEAN -#endif - -#endif // defined(CATCH_PLATFORM_WINDOWS) - -// end catch_windows_h_proxy.h -#include - -namespace Catch { - namespace { - struct IColourImpl { - virtual ~IColourImpl() = default; - virtual void use(Colour::Code _colourCode) = 0; - }; - - struct NoColourImpl : IColourImpl { - void use(Colour::Code) override {} - - static IColourImpl *instance() { - static NoColourImpl s_instance; - return &s_instance; - } - }; - - } // namespace -} // namespace Catch - -#if !defined(CATCH_CONFIG_COLOUR_NONE) && !defined(CATCH_CONFIG_COLOUR_WINDOWS) && !defined(CATCH_CONFIG_COLOUR_ANSI) -#ifdef CATCH_PLATFORM_WINDOWS -#define CATCH_CONFIG_COLOUR_WINDOWS -#else -#define CATCH_CONFIG_COLOUR_ANSI -#endif -#endif - -#if defined(CATCH_CONFIG_COLOUR_WINDOWS) ///////////////////////////////////////// - -namespace Catch { - namespace { - class Win32ColourImpl : public IColourImpl { - public: - Win32ColourImpl() - : stdoutHandle(GetStdHandle(STD_OUTPUT_HANDLE)) { - CONSOLE_SCREEN_BUFFER_INFO csbiInfo; - GetConsoleScreenBufferInfo(stdoutHandle, &csbiInfo); - originalForegroundAttributes = csbiInfo.wAttributes & ~(BACKGROUND_GREEN | BACKGROUND_RED | BACKGROUND_BLUE | BACKGROUND_INTENSITY); - originalBackgroundAttributes = csbiInfo.wAttributes & ~(FOREGROUND_GREEN | FOREGROUND_RED | FOREGROUND_BLUE | FOREGROUND_INTENSITY); - } - - void use(Colour::Code _colourCode) override { - switch (_colourCode) { - case Colour::None: - return setTextAttribute(originalForegroundAttributes); - case Colour::White: - return setTextAttribute(FOREGROUND_GREEN | FOREGROUND_RED | FOREGROUND_BLUE); - case Colour::Red: - return setTextAttribute(FOREGROUND_RED); - case Colour::Green: - return setTextAttribute(FOREGROUND_GREEN); - case Colour::Blue: - return setTextAttribute(FOREGROUND_BLUE); - case Colour::Cyan: - return setTextAttribute(FOREGROUND_BLUE | FOREGROUND_GREEN); - case Colour::Yellow: - return setTextAttribute(FOREGROUND_RED | FOREGROUND_GREEN); - case Colour::Grey: - return setTextAttribute(0); - - case Colour::LightGrey: - return setTextAttribute(FOREGROUND_INTENSITY); - case Colour::BrightRed: - return setTextAttribute(FOREGROUND_INTENSITY | FOREGROUND_RED); - case Colour::BrightGreen: - return setTextAttribute(FOREGROUND_INTENSITY | FOREGROUND_GREEN); - case Colour::BrightWhite: - return setTextAttribute(FOREGROUND_INTENSITY | FOREGROUND_GREEN | FOREGROUND_RED | FOREGROUND_BLUE); - case Colour::BrightYellow: - return setTextAttribute(FOREGROUND_INTENSITY | FOREGROUND_RED | FOREGROUND_GREEN); - - case Colour::Bright: - CATCH_INTERNAL_ERROR("not a colour"); - - default: - CATCH_ERROR("Unknown colour requested"); - } - } - - private: - void setTextAttribute(WORD _textAttribute) { SetConsoleTextAttribute(stdoutHandle, _textAttribute | originalBackgroundAttributes); } - HANDLE stdoutHandle; - WORD originalForegroundAttributes; - WORD originalBackgroundAttributes; - }; - - IColourImpl *platformColourInstance() { - static Win32ColourImpl s_instance; - - IConfigPtr config = getCurrentContext().getConfig(); - UseColour::YesOrNo colourMode = config ? config->useColour() : UseColour::Auto; - if (colourMode == UseColour::Auto) - colourMode = UseColour::Yes; - return colourMode == UseColour::Yes ? &s_instance : NoColourImpl::instance(); - } - - } // namespace -} // namespace Catch - -#elif defined(CATCH_CONFIG_COLOUR_ANSI) ////////////////////////////////////// - -#include - -namespace Catch { - namespace { - // use POSIX/ ANSI console terminal codes - // Thanks to Adam Strzelecki for original contribution - // (http://github.com/nanoant) - // https://github.com/philsquared/Catch/pull/131 - class PosixColourImpl : public IColourImpl { - public: - void use(Colour::Code _colourCode) override { - switch (_colourCode) { - case Colour::None: - case Colour::White: - return setColour("[0m"); - case Colour::Red: - return setColour("[0;31m"); - case Colour::Green: - return setColour("[0;32m"); - case Colour::Blue: - return setColour("[0;34m"); - case Colour::Cyan: - return setColour("[0;36m"); - case Colour::Yellow: - return setColour("[0;33m"); - case Colour::Grey: - return setColour("[1;30m"); - - case Colour::LightGrey: - return setColour("[0;37m"); - case Colour::BrightRed: - return setColour("[1;31m"); - case Colour::BrightGreen: - return setColour("[1;32m"); - case Colour::BrightWhite: - return setColour("[1;37m"); - case Colour::BrightYellow: - return setColour("[1;33m"); - - case Colour::Bright: - CATCH_INTERNAL_ERROR("not a colour"); - default: - CATCH_INTERNAL_ERROR("Unknown colour requested"); - } - } - static IColourImpl *instance() { - static PosixColourImpl s_instance; - return &s_instance; - } - - private: - void setColour(const char *_escapeCode) { getCurrentContext().getConfig()->stream() << '\033' << _escapeCode; } - }; - - bool useColourOnPlatform() { - return -#if defined(CATCH_PLATFORM_MAC) || defined(CATCH_PLATFORM_IPHONE) - !isDebuggerActive() && -#endif -#if !(defined(__DJGPP__) && defined(__STRICT_ANSI__)) - isatty(STDOUT_FILENO) -#else - false -#endif - ; - } - IColourImpl *platformColourInstance() { - ErrnoGuard guard; - IConfigPtr config = getCurrentContext().getConfig(); - UseColour::YesOrNo colourMode = config ? config->useColour() : UseColour::Auto; - if (colourMode == UseColour::Auto) - colourMode = useColourOnPlatform() ? UseColour::Yes : UseColour::No; - return colourMode == UseColour::Yes ? PosixColourImpl::instance() : NoColourImpl::instance(); - } - - } // namespace -} // namespace Catch - -#else // not Windows or ANSI /////////////////////////////////////////////// - -namespace Catch { - static IColourImpl *platformColourInstance() { - return NoColourImpl::instance(); - } - -} // end namespace Catch - -#endif // Windows/ ANSI/ None - -namespace Catch { - Colour::Colour(Code _colourCode) { - use(_colourCode); - } - Colour::Colour(Colour &&other) noexcept { - m_moved = other.m_moved; - other.m_moved = true; - } - Colour &Colour::operator=(Colour &&other) noexcept { - m_moved = other.m_moved; - other.m_moved = true; - return *this; - } - - Colour::~Colour() { - if (!m_moved) - use(None); - } - - void Colour::use(Code _colourCode) { - static IColourImpl *impl = platformColourInstance(); - // Strictly speaking, this cannot possibly happen. - // However, under some conditions it does happen (see #1626), - // and this change is small enough that we can let practicality - // triumph over purity in this case. - if (impl != nullptr) { - impl->use(_colourCode); - } - } - - std::ostream &operator<<(std::ostream &os, Colour const &) { - return os; - } - -} // end namespace Catch - -#if defined(__clang__) -#pragma clang diagnostic pop -#endif - -// end catch_console_colour.cpp -// start catch_context.cpp - -namespace Catch { - class Context : public IMutableContext, NonCopyable { - public: // IContext - IResultCapture *getResultCapture() override { return m_resultCapture; } - IRunner *getRunner() override { return m_runner; } - - IConfigPtr const &getConfig() const override { return m_config; } - - ~Context() override; - - public: // IMutableContext - void setResultCapture(IResultCapture *resultCapture) override { m_resultCapture = resultCapture; } - void setRunner(IRunner *runner) override { m_runner = runner; } - void setConfig(IConfigPtr const &config) override { m_config = config; } - - friend IMutableContext &getCurrentMutableContext(); - - private: - IConfigPtr m_config; - IRunner *m_runner = nullptr; - IResultCapture *m_resultCapture = nullptr; - }; - - IMutableContext *IMutableContext::currentContext = nullptr; - - void IMutableContext::createContext() { - currentContext = new Context(); - } - - void cleanUpContext() { - delete IMutableContext::currentContext; - IMutableContext::currentContext = nullptr; - } - IContext::~IContext() = default; - IMutableContext::~IMutableContext() = default; - Context::~Context() = default; - - SimplePcg32 &rng() { - static SimplePcg32 s_rng; - return s_rng; - } - -} // namespace Catch -// end catch_context.cpp -// start catch_debug_console.cpp - -// start catch_debug_console.h - -#include - -namespace Catch { - void writeToDebugConsole(std::string const &text); -} - -// end catch_debug_console.h -#if defined(CATCH_CONFIG_ANDROID_LOGWRITE) -#include - -namespace Catch { - void writeToDebugConsole(std::string const &text) { - __android_log_write(ANDROID_LOG_DEBUG, "Catch", text.c_str()); - } -} // namespace Catch - -#elif defined(CATCH_PLATFORM_WINDOWS) - -namespace Catch { - void writeToDebugConsole(std::string const &text) { - ::OutputDebugStringA(text.c_str()); - } -} // namespace Catch - -#else - -namespace Catch { - void writeToDebugConsole(std::string const &text) { - // !TBD: Need a version for Mac/ XCode and other IDEs - Catch::cout() << text; - } -} // namespace Catch - -#endif // Platform \ - // end catch_debug_console.cpp -// start catch_debugger.cpp - -#if defined(CATCH_PLATFORM_MAC) || defined(CATCH_PLATFORM_IPHONE) - -#include -#include -#include -#include -#include - -#ifdef __apple_build_version__ -// These headers will only compile with AppleClang (XCode) -// For other compilers (Clang, GCC, ... ) we need to exclude them -#include -#endif - -namespace Catch { -#ifdef __apple_build_version__ - // 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]; - struct kinfo_proc info; - std::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, sizeof(mib) / sizeof(*mib), &info, &size, nullptr, 0) != 0) { - Catch::cerr() << "\n** Call to sysctl failed - unable to determine if debugger is active **\n" - << std::endl; - return false; - } - - // We're being debugged if the P_TRACED flag is set. - - return ((info.kp_proc.p_flag & P_TRACED) != 0); - } -#else - bool isDebuggerActive() { - // We need to find another way to determine this for non-appleclang compilers on macOS - return false; - } -#endif -} // namespace Catch - -#elif defined(CATCH_PLATFORM_LINUX) -#include -#include - -namespace Catch { - // The standard POSIX way of detecting a debugger is to attempt to - // ptrace() the process, but this needs to be done from a child and not - // this process itself to still allow attaching to this process later - // if wanted, so is rather heavy. Under Linux we have the PID of the - // "debugger" (which doesn't need to be gdb, of course, it could also - // be strace, for example) in /proc/$PID/status, so just get it from - // there instead. - bool isDebuggerActive() { - // Libstdc++ has a bug, where std::ifstream sets errno to 0 - // This way our users can properly assert over errno values - 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) { - // We're traced if the PID is not 0 and no other PID starts - // with 0 digit, so it's enough to check for just a single - // character. - return line.length() > PREFIX_LEN && line[PREFIX_LEN] != '0'; - } - } - - return false; - } -} // namespace Catch -#elif defined(_MSC_VER) -extern "C" __declspec(dllimport) int __stdcall IsDebuggerPresent(); -namespace Catch { - bool isDebuggerActive() { - return IsDebuggerPresent() != 0; - } -} // namespace Catch -#elif defined(__MINGW32__) -extern "C" __declspec(dllimport) int __stdcall IsDebuggerPresent(); -namespace Catch { - bool isDebuggerActive() { - return IsDebuggerPresent() != 0; - } -} // namespace Catch -#else -namespace Catch { - bool isDebuggerActive() { - return false; - } -} // namespace Catch -#endif // Platform -// end catch_debugger.cpp -// start catch_decomposer.cpp - -namespace Catch { - ITransientExpression::~ITransientExpression() = default; - - void formatReconstructedExpression(std::ostream &os, std::string const &lhs, StringRef op, std::string const &rhs) { - if (lhs.size() + rhs.size() < 40 && lhs.find('\n') == std::string::npos && rhs.find('\n') == std::string::npos) - os << lhs << " " << op << " " << rhs; - else - os << lhs << "\n" - << op << "\n" - << rhs; - } -} // namespace Catch -// end catch_decomposer.cpp -// start catch_enforce.cpp - -#include - -namespace Catch { -#if defined(CATCH_CONFIG_DISABLE_EXCEPTIONS) && !defined(CATCH_CONFIG_DISABLE_EXCEPTIONS_CUSTOM_HANDLER) - [[noreturn]] void throw_exception(std::exception const &e) { - Catch::cerr() << "Catch will terminate because it needed to throw an exception.\n" - << "The message was: " << e.what() << '\n'; - std::terminate(); - } -#endif - - [[noreturn]] void throw_logic_error(std::string const &msg) { - throw_exception(std::logic_error(msg)); - } - - [[noreturn]] void throw_domain_error(std::string const &msg) { - throw_exception(std::domain_error(msg)); - } - - [[noreturn]] void throw_runtime_error(std::string const &msg) { - throw_exception(std::runtime_error(msg)); - } - -} // namespace Catch -// end catch_enforce.cpp -// start catch_enum_values_registry.cpp -// start catch_enum_values_registry.h - -#include -#include - -namespace Catch { - namespace Detail { - std::unique_ptr makeEnumInfo(StringRef enumName, StringRef allValueNames, std::vector const &values); - - class EnumValuesRegistry : public IMutableEnumValuesRegistry { - std::vector> m_enumInfos; - - EnumInfo const ®isterEnum(StringRef enumName, StringRef allEnums, std::vector const &values) override; - }; - - std::vector parseEnums(StringRef enums); - - } // namespace Detail -} // namespace Catch - -// end catch_enum_values_registry.h - -#include -#include - -namespace Catch { - IMutableEnumValuesRegistry::~IMutableEnumValuesRegistry() {} - - namespace Detail { - namespace { - // Extracts the actual name part of an enum instance - // In other words, it returns the Blue part of Bikeshed::Colour::Blue - StringRef extractInstanceName(StringRef enumInstance) { - // Find last occurrence of ":" - size_t name_start = enumInstance.size(); - while (name_start > 0 && enumInstance[name_start - 1] != ':') { - --name_start; - } - return enumInstance.substr(name_start, enumInstance.size() - name_start); - } - } // namespace - - std::vector parseEnums(StringRef enums) { - auto enumValues = splitStringRef(enums, ','); - std::vector parsed; - parsed.reserve(enumValues.size()); - for (auto const &enumValue : enumValues) { - parsed.push_back(trim(extractInstanceName(enumValue))); - } - return parsed; - } - - EnumInfo::~EnumInfo() {} - - StringRef EnumInfo::lookup(int value) const { - for (auto const &valueToName : m_values) { - if (valueToName.first == value) - return valueToName.second; - } - return "{** unexpected enum value **}"_sr; - } - - std::unique_ptr makeEnumInfo(StringRef enumName, StringRef allValueNames, std::vector const &values) { - std::unique_ptr enumInfo(new EnumInfo); - enumInfo->m_name = enumName; - enumInfo->m_values.reserve(values.size()); - - const auto valueNames = Catch::Detail::parseEnums(allValueNames); - assert(valueNames.size() == values.size()); - std::size_t i = 0; - for (auto value : values) - enumInfo->m_values.emplace_back(value, valueNames[i++]); - - return enumInfo; - } - - EnumInfo const &EnumValuesRegistry::registerEnum(StringRef enumName, StringRef allValueNames, std::vector const &values) { - m_enumInfos.push_back(makeEnumInfo(enumName, allValueNames, values)); - return *m_enumInfos.back(); - } - - } // namespace Detail -} // namespace Catch - -// end catch_enum_values_registry.cpp -// start catch_errno_guard.cpp - -#include - -namespace Catch { - ErrnoGuard::ErrnoGuard() - : m_oldErrno(errno) { - } - ErrnoGuard::~ErrnoGuard() { - errno = m_oldErrno; - } -} // namespace Catch -// end catch_errno_guard.cpp -// start catch_exception_translator_registry.cpp - -// start catch_exception_translator_registry.h - -#include -#include -#include - -namespace Catch { - class ExceptionTranslatorRegistry : public IExceptionTranslatorRegistry { - public: - ~ExceptionTranslatorRegistry(); - virtual void registerTranslator(const IExceptionTranslator *translator); - std::string translateActiveException() const override; - std::string tryTranslators() const; - - private: - std::vector> m_translators; - }; -} // namespace Catch - -// end catch_exception_translator_registry.h -#ifdef __OBJC__ -#import "Foundation/Foundation.h" -#endif - -namespace Catch { - ExceptionTranslatorRegistry::~ExceptionTranslatorRegistry() {} - - void ExceptionTranslatorRegistry::registerTranslator(const IExceptionTranslator *translator) { - m_translators.push_back(std::unique_ptr(translator)); - } - -#if !defined(CATCH_CONFIG_DISABLE_EXCEPTIONS) - std::string ExceptionTranslatorRegistry::translateActiveException() const { - try { -#ifdef __OBJC__ - // In Objective-C try objective-c exceptions first - @try { - return tryTranslators(); - } @catch (NSException *exception) { - return Catch::Detail::stringify([exception description]); - } -#else - // Compiling a mixed mode project with MSVC means that CLR - // exceptions will be caught in (...) as well. However, these - // do not fill-in std::current_exception and thus lead to crash - // when attempting rethrow. - // /EHa switch also causes structured exceptions to be caught - // here, but they fill-in current_exception properly, so - // at worst the output should be a little weird, instead of - // causing a crash. - if (std::current_exception() == nullptr) { - return "Non C++ exception. Possibly a CLR exception."; - } - return tryTranslators(); -#endif - } catch (TestFailureException &) { - std::rethrow_exception(std::current_exception()); - } catch (std::exception &ex) { - return ex.what(); - } catch (std::string &msg) { - return msg; - } catch (const char *msg) { - return msg; - } catch (...) { - return "Unknown exception"; - } - } - - std::string ExceptionTranslatorRegistry::tryTranslators() const { - if (m_translators.empty()) { - std::rethrow_exception(std::current_exception()); - } else { - return m_translators[0]->translate(m_translators.begin() + 1, m_translators.end()); - } - } - -#else // ^^ Exceptions are enabled // Exceptions are disabled vv - std::string ExceptionTranslatorRegistry::translateActiveException() const { - CATCH_INTERNAL_ERROR("Attempted to translate active exception under CATCH_CONFIG_DISABLE_EXCEPTIONS!"); - } - - std::string ExceptionTranslatorRegistry::tryTranslators() const { - CATCH_INTERNAL_ERROR("Attempted to use exception translators under CATCH_CONFIG_DISABLE_EXCEPTIONS!"); - } -#endif - -} // namespace Catch -// end catch_exception_translator_registry.cpp -// start catch_fatal_condition.cpp - -#include - -#if !defined(CATCH_CONFIG_WINDOWS_SEH) && !defined(CATCH_CONFIG_POSIX_SIGNALS) - -namespace Catch { - // If neither SEH nor signal handling is required, the handler impls - // do not have to do anything, and can be empty. - void FatalConditionHandler::engage_platform() {} - void FatalConditionHandler::disengage_platform() {} - FatalConditionHandler::FatalConditionHandler() = default; - FatalConditionHandler::~FatalConditionHandler() = default; - -} // end namespace Catch - -#endif // !CATCH_CONFIG_WINDOWS_SEH && !CATCH_CONFIG_POSIX_SIGNALS - -#if defined(CATCH_CONFIG_WINDOWS_SEH) && defined(CATCH_CONFIG_POSIX_SIGNALS) -#error "Inconsistent configuration: Windows' SEH handling and POSIX signals cannot be enabled at the same time" -#endif // CATCH_CONFIG_WINDOWS_SEH && CATCH_CONFIG_POSIX_SIGNALS - -#if defined(CATCH_CONFIG_WINDOWS_SEH) || defined(CATCH_CONFIG_POSIX_SIGNALS) - -namespace { - //! Signals fatal error message to the run context - void reportFatal(char const *const message) { - Catch::getCurrentContext().getResultCapture()->handleFatalErrorCondition(message); - } - - //! Minimal size Catch2 needs for its own fatal error handling. - //! Picked anecdotally, so it might not be sufficient on all - //! platforms, and for all configurations. - constexpr std::size_t minStackSizeForErrors = 32 * 1024; -} // end unnamed namespace - -#endif // CATCH_CONFIG_WINDOWS_SEH || CATCH_CONFIG_POSIX_SIGNALS - -#if defined(CATCH_CONFIG_WINDOWS_SEH) - -namespace Catch { - 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. - static 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"}, - }; - - static LONG CALLBACK handleVectoredException(PEXCEPTION_POINTERS ExceptionInfo) { - for (auto const &def : signalDefs) { - if (ExceptionInfo->ExceptionRecord->ExceptionCode == def.id) { - reportFatal(def.name); - } - } - // If its not an exception we care about, pass it along. - // This stops us from eating debugger breaks etc. - return EXCEPTION_CONTINUE_SEARCH; - } - - // Since we do not support multiple instantiations, we put these - // into global variables and rely on cleaning them up in outlined - // constructors/destructors - static PVOID exceptionHandlerHandle = nullptr; - - // For MSVC, we reserve part of the stack memory for handling - // memory overflow structured exception. - FatalConditionHandler::FatalConditionHandler() { - ULONG guaranteeSize = static_cast(minStackSizeForErrors); - if (!SetThreadStackGuarantee(&guaranteeSize)) { - // We do not want to fully error out, because needing - // the stack reserve should be rare enough anyway. - Catch::cerr() << "Failed to reserve piece of stack." - << " Stack overflows will not be reported successfully."; - } - } - - // We do not attempt to unset the stack guarantee, because - // Windows does not support lowering the stack size guarantee. - FatalConditionHandler::~FatalConditionHandler() = default; - - void FatalConditionHandler::engage_platform() { - // Register as first handler in current chain - exceptionHandlerHandle = AddVectoredExceptionHandler(1, handleVectoredException); - if (!exceptionHandlerHandle) { - CATCH_RUNTIME_ERROR("Could not register vectored exception handler"); - } - } - - void FatalConditionHandler::disengage_platform() { - if (!RemoveVectoredExceptionHandler(exceptionHandlerHandle)) { - CATCH_RUNTIME_ERROR("Could not unregister vectored exception handler"); - } - exceptionHandlerHandle = nullptr; - } - -} // end namespace Catch - -#endif // CATCH_CONFIG_WINDOWS_SEH - -#if defined(CATCH_CONFIG_POSIX_SIGNALS) - -#include - -namespace Catch { - struct SignalDefs { - int id; - const char *name; - }; - - static 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"}}; - -// Older GCCs trigger -Wmissing-field-initializers for T foo = {} -// which is zero initialization, but not explicit. We want to avoid -// that. -#if defined(__GNUC__) -#pragma GCC diagnostic push -#pragma GCC diagnostic ignored "-Wmissing-field-initializers" -#endif - - static char *altStackMem = nullptr; - static std::size_t altStackSize = 0; - static stack_t oldSigStack{}; - static struct sigaction oldSigActions[sizeof(signalDefs) / sizeof(SignalDefs)]{}; - - static void restorePreviousSignalHandlers() { - // We set signal handlers back to the previous ones. Hopefully - // nobody overwrote them in the meantime, and doesn't expect - // their signal handlers to live past ours given that they - // installed them after ours.. - for (std::size_t i = 0; i < sizeof(signalDefs) / sizeof(SignalDefs); ++i) { - sigaction(signalDefs[i].id, &oldSigActions[i], nullptr); - } - // Return the old stack - sigaltstack(&oldSigStack, nullptr); - } - - static void handleSignal(int sig) { - char const *name = ""; - for (auto const &def : signalDefs) { - if (sig == def.id) { - name = def.name; - break; - } - } - // We need to restore previous signal handlers and let them do - // their thing, so that the users can have the debugger break - // when a signal is raised, and so on. - restorePreviousSignalHandlers(); - reportFatal(name); - raise(sig); - } - - FatalConditionHandler::FatalConditionHandler() { - assert(!altStackMem && "Cannot initialize POSIX signal handler when one already exists"); - if (altStackSize == 0) { - altStackSize = std::max(static_cast(SIGSTKSZ), minStackSizeForErrors); - } - altStackMem = new char[altStackSize](); - } - - FatalConditionHandler::~FatalConditionHandler() { - delete[] altStackMem; - // We signal that another instance can be constructed by zeroing - // out the pointer. - altStackMem = nullptr; - } - - void FatalConditionHandler::engage_platform() { - 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 < sizeof(signalDefs) / sizeof(SignalDefs); ++i) { - sigaction(signalDefs[i].id, &sa, &oldSigActions[i]); - } - } - -#if defined(__GNUC__) -#pragma GCC diagnostic pop -#endif - - void FatalConditionHandler::disengage_platform() { - restorePreviousSignalHandlers(); - } - -} // end namespace Catch - -#endif // CATCH_CONFIG_POSIX_SIGNALS \ - // end catch_fatal_condition.cpp -// start catch_generators.cpp - -#include -#include - -namespace Catch { - IGeneratorTracker::~IGeneratorTracker() {} - - const char *GeneratorException::what() const noexcept { - return m_msg; - } - - namespace Generators { - GeneratorUntypedBase::~GeneratorUntypedBase() {} - - auto acquireGeneratorTracker(StringRef generatorName, SourceLineInfo const &lineInfo) -> IGeneratorTracker & { - return getResultCapture().acquireGeneratorTracker(generatorName, lineInfo); - } - - } // namespace Generators -} // namespace Catch -// end catch_generators.cpp -// start catch_interfaces_capture.cpp - -namespace Catch { - IResultCapture::~IResultCapture() = default; -} -// end catch_interfaces_capture.cpp -// start catch_interfaces_config.cpp - -namespace Catch { - IConfig::~IConfig() = default; -} -// end catch_interfaces_config.cpp -// start catch_interfaces_exception.cpp - -namespace Catch { - IExceptionTranslator::~IExceptionTranslator() = default; - IExceptionTranslatorRegistry::~IExceptionTranslatorRegistry() = default; -} // namespace Catch -// end catch_interfaces_exception.cpp -// start catch_interfaces_registry_hub.cpp - -namespace Catch { - IRegistryHub::~IRegistryHub() = default; - IMutableRegistryHub::~IMutableRegistryHub() = default; -} // namespace Catch -// end catch_interfaces_registry_hub.cpp -// start catch_interfaces_reporter.cpp - -// start catch_reporter_listening.h - -namespace Catch { - class ListeningReporter : public IStreamingReporter { - using Reporters = std::vector; - Reporters m_listeners; - IStreamingReporterPtr m_reporter = nullptr; - ReporterPreferences m_preferences; - - public: - ListeningReporter(); - - void addListener(IStreamingReporterPtr &&listener); - void addReporter(IStreamingReporterPtr &&reporter); - - public: // IStreamingReporter - ReporterPreferences getPreferences() const override; - - void noMatchingTestCases(std::string const &spec) override; - - void reportInvalidArguments(std::string const &arg) override; - - static std::set getSupportedVerbosities(); - -#if defined(CATCH_CONFIG_ENABLE_BENCHMARKING) - void benchmarkPreparing(std::string const &name) override; - void benchmarkStarting(BenchmarkInfo const &benchmarkInfo) override; - void benchmarkEnded(BenchmarkStats<> const &benchmarkStats) override; - void benchmarkFailed(std::string const &) override; -#endif // CATCH_CONFIG_ENABLE_BENCHMARKING - - void testRunStarting(TestRunInfo const &testRunInfo) override; - void testGroupStarting(GroupInfo const &groupInfo) override; - void testCaseStarting(TestCaseInfo const &testInfo) override; - void sectionStarting(SectionInfo const §ionInfo) override; - void assertionStarting(AssertionInfo const &assertionInfo) override; - - // The return value indicates if the messages buffer should be cleared: - bool assertionEnded(AssertionStats const &assertionStats) override; - void sectionEnded(SectionStats const §ionStats) override; - void testCaseEnded(TestCaseStats const &testCaseStats) override; - void testGroupEnded(TestGroupStats const &testGroupStats) override; - void testRunEnded(TestRunStats const &testRunStats) override; - - void skipTest(TestCaseInfo const &testInfo) override; - bool isMulti() const override; - }; - -} // end namespace Catch - -// end catch_reporter_listening.h -namespace Catch { - ReporterConfig::ReporterConfig(IConfigPtr const &_fullConfig) - : m_stream(&_fullConfig->stream()) - , m_fullConfig(_fullConfig) { - } - - ReporterConfig::ReporterConfig(IConfigPtr const &_fullConfig, std::ostream &_stream) - : m_stream(&_stream) - , m_fullConfig(_fullConfig) { - } - - std::ostream &ReporterConfig::stream() const { - return *m_stream; - } - IConfigPtr ReporterConfig::fullConfig() const { - return m_fullConfig; - } - - TestRunInfo::TestRunInfo(std::string const &_name) - : name(_name) { - } - - GroupInfo::GroupInfo(std::string const &_name, std::size_t _groupIndex, std::size_t _groupsCount) - : name(_name) - , groupIndex(_groupIndex) - , groupsCounts(_groupsCount) { - } - - AssertionStats::AssertionStats(AssertionResult const &_assertionResult, std::vector const &_infoMessages, Totals const &_totals) - : assertionResult(_assertionResult) - , infoMessages(_infoMessages) - , totals(_totals) { - assertionResult.m_resultData.lazyExpression.m_transientExpression = _assertionResult.m_resultData.lazyExpression.m_transientExpression; - - if (assertionResult.hasMessage()) { - // Copy message into messages list. - // !TBD This should have been done earlier, somewhere - MessageBuilder builder(assertionResult.getTestMacroName(), assertionResult.getSourceInfo(), assertionResult.getResultType()); - builder << assertionResult.getMessage(); - builder.m_info.message = builder.m_stream.str(); - - infoMessages.push_back(builder.m_info); - } - } - - AssertionStats::~AssertionStats() = default; - - SectionStats::SectionStats(SectionInfo const &_sectionInfo, Counts const &_assertions, double _durationInSeconds, bool _missingAssertions) - : sectionInfo(_sectionInfo) - , assertions(_assertions) - , durationInSeconds(_durationInSeconds) - , missingAssertions(_missingAssertions) { - } - - SectionStats::~SectionStats() = default; - - TestCaseStats::TestCaseStats(TestCaseInfo const &_testInfo, Totals const &_totals, std::string const &_stdOut, std::string const &_stdErr, bool _aborting) - : testInfo(_testInfo) - , totals(_totals) - , stdOut(_stdOut) - , stdErr(_stdErr) - , aborting(_aborting) { - } - - TestCaseStats::~TestCaseStats() = default; - - TestGroupStats::TestGroupStats(GroupInfo const &_groupInfo, Totals const &_totals, bool _aborting) - : groupInfo(_groupInfo) - , totals(_totals) - , aborting(_aborting) { - } - - TestGroupStats::TestGroupStats(GroupInfo const &_groupInfo) - : groupInfo(_groupInfo) - , aborting(false) { - } - - TestGroupStats::~TestGroupStats() = default; - - TestRunStats::TestRunStats(TestRunInfo const &_runInfo, Totals const &_totals, bool _aborting) - : runInfo(_runInfo) - , totals(_totals) - , aborting(_aborting) { - } - - TestRunStats::~TestRunStats() = default; - - void IStreamingReporter::fatalErrorEncountered(StringRef) {} - bool IStreamingReporter::isMulti() const { - return false; - } - - IReporterFactory::~IReporterFactory() = default; - IReporterRegistry::~IReporterRegistry() = default; - -} // end namespace Catch -// end catch_interfaces_reporter.cpp -// start catch_interfaces_runner.cpp - -namespace Catch { - IRunner::~IRunner() = default; -} -// end catch_interfaces_runner.cpp -// start catch_interfaces_testcase.cpp - -namespace Catch { - ITestInvoker::~ITestInvoker() = default; - ITestCaseRegistry::~ITestCaseRegistry() = default; -} // namespace Catch -// end catch_interfaces_testcase.cpp -// start catch_leak_detector.cpp - -#ifdef CATCH_CONFIG_WINDOWS_CRTDBG -#include - -namespace Catch { - LeakDetector::LeakDetector() { - int flag = _CrtSetDbgFlag(_CRTDBG_REPORT_FLAG); - flag |= _CRTDBG_LEAK_CHECK_DF; - flag |= _CRTDBG_ALLOC_MEM_DF; - _CrtSetDbgFlag(flag); - _CrtSetReportMode(_CRT_WARN, _CRTDBG_MODE_FILE | _CRTDBG_MODE_DEBUG); - _CrtSetReportFile(_CRT_WARN, _CRTDBG_FILE_STDERR); - // Change this to leaking allocation's number to break there - _CrtSetBreakAlloc(-1); - } -} // namespace Catch - -#else - -Catch::LeakDetector::LeakDetector() { -} - -#endif - -Catch::LeakDetector::~LeakDetector() { - Catch::cleanUp(); -} -// end catch_leak_detector.cpp -// start catch_list.cpp - -// start catch_list.h - -#include - -namespace Catch { - std::size_t listTests(Config const &config); - - std::size_t listTestsNamesOnly(Config const &config); - - struct TagInfo { - void add(std::string const &spelling); - std::string all() const; - - std::set spellings; - std::size_t count = 0; - }; - - std::size_t listTags(Config const &config); - - std::size_t listReporters(); - - Option list(std::shared_ptr const &config); - -} // end namespace Catch - -// end catch_list.h -// start catch_text.h - -namespace Catch { - using namespace clara::TextFlow; -} - -// end catch_text.h -#include -#include -#include - -namespace Catch { - std::size_t listTests(Config const &config) { - TestSpec const &testSpec = config.testSpec(); - if (config.hasTestFilters()) - Catch::cout() << "Matching test cases:\n"; - else { - Catch::cout() << "All available test cases:\n"; - } - - auto matchedTestCases = filterTests(getAllTestCasesSorted(config), testSpec, config); - for (auto const &testCaseInfo : matchedTestCases) { - Colour::Code colour = testCaseInfo.isHidden() ? Colour::SecondaryText : Colour::None; - Colour colourGuard(colour); - - Catch::cout() << Column(testCaseInfo.name).initialIndent(2).indent(4) << "\n"; - if (config.verbosity() >= Verbosity::High) { - Catch::cout() << Column(Catch::Detail::stringify(testCaseInfo.lineInfo)).indent(4) << std::endl; - std::string description = testCaseInfo.description; - if (description.empty()) - description = "(NO DESCRIPTION)"; - Catch::cout() << Column(description).indent(4) << std::endl; - } - if (!testCaseInfo.tags.empty()) - Catch::cout() << Column(testCaseInfo.tagsAsString()).indent(6) << "\n"; - } - - if (!config.hasTestFilters()) - Catch::cout() << pluralise(matchedTestCases.size(), "test case") << '\n' - << std::endl; - else - Catch::cout() << pluralise(matchedTestCases.size(), "matching test case") << '\n' - << std::endl; - return matchedTestCases.size(); - } - - std::size_t listTestsNamesOnly(Config const &config) { - TestSpec const &testSpec = config.testSpec(); - std::size_t matchedTests = 0; - std::vector matchedTestCases = filterTests(getAllTestCasesSorted(config), testSpec, config); - for (auto const &testCaseInfo : matchedTestCases) { - matchedTests++; - if (startsWith(testCaseInfo.name, '#')) - Catch::cout() << '"' << testCaseInfo.name << '"'; - else - Catch::cout() << testCaseInfo.name; - if (config.verbosity() >= Verbosity::High) - Catch::cout() << "\t@" << testCaseInfo.lineInfo; - Catch::cout() << std::endl; - } - return matchedTests; - } - - void TagInfo::add(std::string const &spelling) { - ++count; - spellings.insert(spelling); - } - - std::string TagInfo::all() const { - size_t size = 0; - for (auto const &spelling : spellings) { - // Add 2 for the brackes - size += spelling.size() + 2; - } - - std::string out; - out.reserve(size); - for (auto const &spelling : spellings) { - out += '['; - out += spelling; - out += ']'; - } - return out; - } - - std::size_t listTags(Config const &config) { - TestSpec const &testSpec = config.testSpec(); - if (config.hasTestFilters()) - Catch::cout() << "Tags for matching test cases:\n"; - else { - Catch::cout() << "All available tags:\n"; - } - - std::map tagCounts; - - std::vector matchedTestCases = filterTests(getAllTestCasesSorted(config), testSpec, config); - for (auto const &testCase : matchedTestCases) { - for (auto const &tagName : testCase.getTestCaseInfo().tags) { - std::string lcaseTagName = toLower(tagName); - auto countIt = tagCounts.find(lcaseTagName); - if (countIt == tagCounts.end()) - countIt = tagCounts.insert(std::make_pair(lcaseTagName, TagInfo())).first; - countIt->second.add(tagName); - } - } - - for (auto const &tagCount : tagCounts) { - ReusableStringStream rss; - rss << " " << std::setw(2) << tagCount.second.count << " "; - auto str = rss.str(); - auto wrapper = Column(tagCount.second.all()).initialIndent(0).indent(str.size()).width(CATCH_CONFIG_CONSOLE_WIDTH - 10); - Catch::cout() << str << wrapper << '\n'; - } - Catch::cout() << pluralise(tagCounts.size(), "tag") << '\n' - << std::endl; - return tagCounts.size(); - } - - std::size_t listReporters() { - Catch::cout() << "Available reporters:\n"; - IReporterRegistry::FactoryMap const &factories = getRegistryHub().getReporterRegistry().getFactories(); - std::size_t maxNameLen = 0; - for (auto const &factoryKvp : factories) - maxNameLen = (std::max)(maxNameLen, factoryKvp.first.size()); - - for (auto const &factoryKvp : factories) { - Catch::cout() << Column(factoryKvp.first + ":").indent(2).width(5 + maxNameLen) - + Column(factoryKvp.second->getDescription()).initialIndent(0).indent(2).width(CATCH_CONFIG_CONSOLE_WIDTH - maxNameLen - 8) - << "\n"; - } - Catch::cout() << std::endl; - return factories.size(); - } - - Option list(std::shared_ptr const &config) { - Option listedCount; - getCurrentMutableContext().setConfig(config); - if (config->listTests()) - listedCount = listedCount.valueOr(0) + listTests(*config); - if (config->listTestNamesOnly()) - listedCount = listedCount.valueOr(0) + listTestsNamesOnly(*config); - if (config->listTags()) - listedCount = listedCount.valueOr(0) + listTags(*config); - if (config->listReporters()) - listedCount = listedCount.valueOr(0) + listReporters(); - return listedCount; - } - -} // end namespace Catch -// end catch_list.cpp -// start catch_matchers.cpp - -namespace Catch { - namespace Matchers { - namespace Impl { - std::string MatcherUntypedBase::toString() const { - if (m_cachedToString.empty()) - m_cachedToString = describe(); - return m_cachedToString; - } - - MatcherUntypedBase::~MatcherUntypedBase() = default; - - } // namespace Impl - } // namespace Matchers - - using namespace Matchers; - using Matchers::Impl::MatcherBase; - -} // namespace Catch -// end catch_matchers.cpp -// start catch_matchers_exception.cpp - -namespace Catch { - namespace Matchers { - namespace Exception { - bool ExceptionMessageMatcher::match(std::exception const &ex) const { - return ex.what() == m_message; - } - - std::string ExceptionMessageMatcher::describe() const { - return "exception message matches \"" + m_message + "\""; - } - - } // namespace Exception - Exception::ExceptionMessageMatcher Message(std::string const &message) { - return Exception::ExceptionMessageMatcher(message); - } - - // namespace Exception - } // namespace Matchers -} // namespace Catch -// end catch_matchers_exception.cpp -// start catch_matchers_floating.cpp - -// start catch_polyfills.hpp - -namespace Catch { - bool isnan(float f); - bool isnan(double d); -} // namespace Catch - -// end catch_polyfills.hpp -// start catch_to_string.hpp - -#include - -namespace Catch { - template - std::string to_string(T const &t) { -#if defined(CATCH_CONFIG_CPP11_TO_STRING) - return std::to_string(t); -#else - ReusableStringStream rss; - rss << t; - return rss.str(); -#endif - } -} // end namespace Catch - -// end catch_to_string.hpp -#include -#include -#include -#include -#include -#include -#include -#include -#include - -namespace Catch { - namespace { - int32_t convert(float f) { - static_assert(sizeof(float) == sizeof(int32_t), "Important ULP matcher assumption violated"); - int32_t i; - std::memcpy(&i, &f, sizeof(f)); - return i; - } - - int64_t convert(double d) { - static_assert(sizeof(double) == sizeof(int64_t), "Important ULP matcher assumption violated"); - int64_t i; - std::memcpy(&i, &d, sizeof(d)); - return i; - } - - template - bool almostEqualUlps(FP lhs, FP rhs, uint64_t maxUlpDiff) { - // Comparison with NaN should always be false. - // This way we can rule it out before getting into the ugly details - if (Catch::isnan(lhs) || Catch::isnan(rhs)) { - return false; - } - - auto lc = convert(lhs); - auto rc = convert(rhs); - - if ((lc < 0) != (rc < 0)) { - // Potentially we can have +0 and -0 - return lhs == rhs; - } - - // static cast as a workaround for IBM XLC - auto ulpDiff = std::abs(static_cast(lc - rc)); - return static_cast(ulpDiff) <= maxUlpDiff; - } - -#if defined(CATCH_CONFIG_GLOBAL_NEXTAFTER) - - float nextafter(float x, float y) { - return ::nextafterf(x, y); - } - - double nextafter(double x, double y) { - return ::nextafter(x, y); - } - -#endif // ^^^ CATCH_CONFIG_GLOBAL_NEXTAFTER ^^^ - - template - FP step(FP start, FP direction, uint64_t steps) { - for (uint64_t i = 0; i < steps; ++i) { -#if defined(CATCH_CONFIG_GLOBAL_NEXTAFTER) - start = Catch::nextafter(start, direction); -#else - start = std::nextafter(start, direction); -#endif - } - return start; - } - - // Performs equivalent check of std::fabs(lhs - rhs) <= margin - // But without the subtraction to allow for INFINITY in comparison - bool marginComparison(double lhs, double rhs, double margin) { - return (lhs + margin >= rhs) && (rhs + margin >= lhs); - } - - template - void write(std::ostream &out, FloatingPoint num) { - out << std::scientific << std::setprecision(std::numeric_limits::max_digits10 - 1) << num; - } - - } // end anonymous namespace - - namespace Matchers { - namespace Floating { - enum class FloatingPointKind : uint8_t { - Float, - Double - }; - - WithinAbsMatcher::WithinAbsMatcher(double target, double margin) - : m_target{target} - , m_margin{margin} { - CATCH_ENFORCE(margin >= 0, "Invalid margin: " << margin << '.' << " Margin has to be non-negative."); - } - - // Performs equivalent check of std::fabs(lhs - rhs) <= margin - // But without the subtraction to allow for INFINITY in comparison - bool WithinAbsMatcher::match(double const &matchee) const { - return (matchee + m_margin >= m_target) && (m_target + m_margin >= matchee); - } - - std::string WithinAbsMatcher::describe() const { - return "is within " + ::Catch::Detail::stringify(m_margin) + " of " + ::Catch::Detail::stringify(m_target); - } - - WithinUlpsMatcher::WithinUlpsMatcher(double target, uint64_t ulps, FloatingPointKind baseType) - : m_target{target} - , m_ulps{ulps} - , m_type{baseType} { - CATCH_ENFORCE(m_type == FloatingPointKind::Double || m_ulps < (std::numeric_limits::max)(), - "Provided ULP is impossibly large for a float comparison."); - } - -#if defined(__clang__) -#pragma clang diagnostic push -// Clang <3.5 reports on the default branch in the switch below -#pragma clang diagnostic ignored "-Wunreachable-code" -#endif - - bool WithinUlpsMatcher::match(double const &matchee) const { - switch (m_type) { - case FloatingPointKind::Float: - return almostEqualUlps(static_cast(matchee), static_cast(m_target), m_ulps); - case FloatingPointKind::Double: - return almostEqualUlps(matchee, m_target, m_ulps); - default: - CATCH_INTERNAL_ERROR("Unknown FloatingPointKind value"); - } - } - -#if defined(__clang__) -#pragma clang diagnostic pop -#endif - - std::string WithinUlpsMatcher::describe() const { - std::stringstream ret; - - ret << "is within " << m_ulps << " ULPs of "; - - if (m_type == FloatingPointKind::Float) { - write(ret, static_cast(m_target)); - ret << 'f'; - } else { - write(ret, m_target); - } - - ret << " (["; - if (m_type == FloatingPointKind::Double) { - write(ret, step(m_target, static_cast(-INFINITY), m_ulps)); - ret << ", "; - write(ret, step(m_target, static_cast(INFINITY), m_ulps)); - } else { - // We have to cast INFINITY to float because of MinGW, see #1782 - write(ret, step(static_cast(m_target), static_cast(-INFINITY), m_ulps)); - ret << ", "; - write(ret, step(static_cast(m_target), static_cast(INFINITY), m_ulps)); - } - ret << "])"; - - return ret.str(); - } - - WithinRelMatcher::WithinRelMatcher(double target, double epsilon) - : m_target(target) - , m_epsilon(epsilon) { - CATCH_ENFORCE(m_epsilon >= 0., "Relative comparison with epsilon < 0 does not make sense."); - CATCH_ENFORCE(m_epsilon < 1., "Relative comparison with epsilon >= 1 does not make sense."); - } - - bool WithinRelMatcher::match(double const &matchee) const { - const auto relMargin = m_epsilon * (std::max)(std::fabs(matchee), std::fabs(m_target)); - return marginComparison(matchee, m_target, std::isinf(relMargin) ? 0 : relMargin); - } - - std::string WithinRelMatcher::describe() const { - Catch::ReusableStringStream sstr; - sstr << "and " << m_target << " are within " << m_epsilon * 100. << "% of each other"; - return sstr.str(); - } - - } // namespace Floating - - Floating::WithinUlpsMatcher WithinULP(double target, uint64_t maxUlpDiff) { - return Floating::WithinUlpsMatcher(target, maxUlpDiff, Floating::FloatingPointKind::Double); - } - - Floating::WithinUlpsMatcher WithinULP(float target, uint64_t maxUlpDiff) { - return Floating::WithinUlpsMatcher(target, maxUlpDiff, Floating::FloatingPointKind::Float); - } - - Floating::WithinAbsMatcher WithinAbs(double target, double margin) { - return Floating::WithinAbsMatcher(target, margin); - } - - Floating::WithinRelMatcher WithinRel(double target, double eps) { - return Floating::WithinRelMatcher(target, eps); - } - - Floating::WithinRelMatcher WithinRel(double target) { - return Floating::WithinRelMatcher(target, std::numeric_limits::epsilon() * 100); - } - - Floating::WithinRelMatcher WithinRel(float target, float eps) { - return Floating::WithinRelMatcher(target, eps); - } - - Floating::WithinRelMatcher WithinRel(float target) { - return Floating::WithinRelMatcher(target, std::numeric_limits::epsilon() * 100); - } - - } // namespace Matchers -} // namespace Catch -// end catch_matchers_floating.cpp -// start catch_matchers_generic.cpp - -std::string Catch::Matchers::Generic::Detail::finalizeDescription(const std::string &desc) { - if (desc.empty()) { - return "matches undescribed predicate"; - } else { - return "matches predicate: \"" + desc + '"'; - } -} -// end catch_matchers_generic.cpp -// start catch_matchers_string.cpp - -#include - -namespace Catch { - namespace Matchers { - namespace StdString { - CasedString::CasedString(std::string const &str, CaseSensitive::Choice caseSensitivity) - : m_caseSensitivity(caseSensitivity) - , m_str(adjustString(str)) { - } - std::string CasedString::adjustString(std::string const &str) const { - return m_caseSensitivity == CaseSensitive::No ? toLower(str) : str; - } - std::string CasedString::caseSensitivitySuffix() const { - return m_caseSensitivity == CaseSensitive::No ? " (case insensitive)" : std::string(); - } - - StringMatcherBase::StringMatcherBase(std::string const &operation, CasedString const &comparator) - : m_comparator(comparator) - , m_operation(operation) { - } - - std::string StringMatcherBase::describe() const { - std::string description; - description.reserve(5 + m_operation.size() + m_comparator.m_str.size() + m_comparator.caseSensitivitySuffix().size()); - description += m_operation; - description += ": \""; - description += m_comparator.m_str; - description += "\""; - description += m_comparator.caseSensitivitySuffix(); - return description; - } - - EqualsMatcher::EqualsMatcher(CasedString const &comparator) - : StringMatcherBase("equals", comparator) { - } - - bool EqualsMatcher::match(std::string const &source) const { - return m_comparator.adjustString(source) == m_comparator.m_str; - } - - ContainsMatcher::ContainsMatcher(CasedString const &comparator) - : StringMatcherBase("contains", comparator) { - } - - bool ContainsMatcher::match(std::string const &source) const { - return contains(m_comparator.adjustString(source), m_comparator.m_str); - } - - StartsWithMatcher::StartsWithMatcher(CasedString const &comparator) - : StringMatcherBase("starts with", comparator) { - } - - bool StartsWithMatcher::match(std::string const &source) const { - return startsWith(m_comparator.adjustString(source), m_comparator.m_str); - } - - EndsWithMatcher::EndsWithMatcher(CasedString const &comparator) - : StringMatcherBase("ends with", comparator) { - } - - bool EndsWithMatcher::match(std::string const &source) const { - return endsWith(m_comparator.adjustString(source), m_comparator.m_str); - } - - RegexMatcher::RegexMatcher(std::string regex, CaseSensitive::Choice caseSensitivity) - : m_regex(std::move(regex)) - , m_caseSensitivity(caseSensitivity) { - } - - bool RegexMatcher::match(std::string const &matchee) const { - auto flags = std::regex::ECMAScript; // ECMAScript is the default syntax option anyway - if (m_caseSensitivity == CaseSensitive::Choice::No) { - flags |= std::regex::icase; - } - auto reg = std::regex(m_regex, flags); - return std::regex_match(matchee, reg); - } - - std::string RegexMatcher::describe() const { - return "matches " + ::Catch::Detail::stringify(m_regex) - + ((m_caseSensitivity == CaseSensitive::Choice::Yes) ? " case sensitively" : " case insensitively"); - } - - } // namespace StdString - - StdString::EqualsMatcher Equals(std::string const &str, CaseSensitive::Choice caseSensitivity) { - return StdString::EqualsMatcher(StdString::CasedString(str, caseSensitivity)); - } - StdString::ContainsMatcher Contains(std::string const &str, CaseSensitive::Choice caseSensitivity) { - return StdString::ContainsMatcher(StdString::CasedString(str, caseSensitivity)); - } - StdString::EndsWithMatcher EndsWith(std::string const &str, CaseSensitive::Choice caseSensitivity) { - return StdString::EndsWithMatcher(StdString::CasedString(str, caseSensitivity)); - } - StdString::StartsWithMatcher StartsWith(std::string const &str, CaseSensitive::Choice caseSensitivity) { - return StdString::StartsWithMatcher(StdString::CasedString(str, caseSensitivity)); - } - - StdString::RegexMatcher Matches(std::string const ®ex, CaseSensitive::Choice caseSensitivity) { - return StdString::RegexMatcher(regex, caseSensitivity); - } - - } // namespace Matchers -} // namespace Catch -// end catch_matchers_string.cpp -// start catch_message.cpp - -// start catch_uncaught_exceptions.h - -namespace Catch { - bool uncaught_exceptions(); -} // end namespace Catch - -// end catch_uncaught_exceptions.h -#include -#include - -namespace Catch { - MessageInfo::MessageInfo(StringRef const &_macroName, SourceLineInfo const &_lineInfo, ResultWas::OfType _type) - : macroName(_macroName) - , lineInfo(_lineInfo) - , type(_type) - , sequence(++globalCount) { - } - - bool MessageInfo::operator==(MessageInfo const &other) const { - return sequence == other.sequence; - } - - bool MessageInfo::operator<(MessageInfo const &other) const { - return sequence < other.sequence; - } - - // This may need protecting if threading support is added - unsigned int MessageInfo::globalCount = 0; - - //////////////////////////////////////////////////////////////////////////// - - Catch::MessageBuilder::MessageBuilder(StringRef const ¯oName, SourceLineInfo const &lineInfo, ResultWas::OfType type) - : m_info(macroName, lineInfo, type) { - } - - //////////////////////////////////////////////////////////////////////////// - - ScopedMessage::ScopedMessage(MessageBuilder const &builder) - : m_info(builder.m_info) - , m_moved() { - m_info.message = builder.m_stream.str(); - getResultCapture().pushScopedMessage(m_info); - } - - ScopedMessage::ScopedMessage(ScopedMessage &&old) - : m_info(old.m_info) - , m_moved() { - old.m_moved = true; - } - - ScopedMessage::~ScopedMessage() { - if (!uncaught_exceptions() && !m_moved) { - getResultCapture().popScopedMessage(m_info); - } - } - - Capturer::Capturer(StringRef macroName, SourceLineInfo const &lineInfo, ResultWas::OfType resultType, StringRef names) { - auto trimmed = [&](size_t start, size_t end) { - while (names[start] == ',' || isspace(static_cast(names[start]))) { - ++start; - } - while (names[end] == ',' || isspace(static_cast(names[end]))) { - --end; - } - return names.substr(start, end - start + 1); - }; - auto skipq = [&](size_t start, char quote) { - for (auto i = start + 1; i < names.size(); ++i) { - if (names[i] == quote) - return i; - if (names[i] == '\\') - ++i; - } - CATCH_INTERNAL_ERROR("CAPTURE parsing encountered unmatched quote"); - }; - - size_t start = 0; - std::stack openings; - for (size_t pos = 0; pos < names.size(); ++pos) { - char c = names[pos]; - switch (c) { - case '[': - case '{': - case '(': - // It is basically impossible to disambiguate between - // comparison and start of template args in this context - // case '<': - openings.push(c); - break; - case ']': - case '}': - case ')': - // case '>': - openings.pop(); - break; - case '"': - case '\'': - pos = skipq(pos, c); - break; - case ',': - if (start != pos && openings.empty()) { - m_messages.emplace_back(macroName, lineInfo, resultType); - m_messages.back().message = static_cast(trimmed(start, pos)); - m_messages.back().message += " := "; - start = pos; - } - } - } - assert(openings.empty() && "Mismatched openings"); - m_messages.emplace_back(macroName, lineInfo, resultType); - m_messages.back().message = static_cast(trimmed(start, names.size() - 1)); - m_messages.back().message += " := "; - } - Capturer::~Capturer() { - if (!uncaught_exceptions()) { - assert(m_captured == m_messages.size()); - for (size_t i = 0; i < m_captured; ++i) - m_resultCapture.popScopedMessage(m_messages[i]); - } - } - - void Capturer::captureValue(size_t index, std::string const &value) { - assert(index < m_messages.size()); - m_messages[index].message += value; - m_resultCapture.pushScopedMessage(m_messages[index]); - m_captured++; - } - -} // end namespace Catch -// end catch_message.cpp -// start catch_output_redirect.cpp - -// start catch_output_redirect.h -#ifndef TWOBLUECUBES_CATCH_OUTPUT_REDIRECT_H -#define TWOBLUECUBES_CATCH_OUTPUT_REDIRECT_H - -#include -#include -#include - -namespace Catch { - class RedirectedStream { - std::ostream &m_originalStream; - std::ostream &m_redirectionStream; - std::streambuf *m_prevBuf; - - public: - RedirectedStream(std::ostream &originalStream, std::ostream &redirectionStream); - ~RedirectedStream(); - }; - - class RedirectedStdOut { - ReusableStringStream m_rss; - RedirectedStream m_cout; - - public: - RedirectedStdOut(); - auto str() const -> std::string; - }; - - // StdErr has two constituent streams in C++, std::cerr and std::clog - // This means that we need to redirect 2 streams into 1 to keep proper - // order of writes - class RedirectedStdErr { - ReusableStringStream m_rss; - RedirectedStream m_cerr; - RedirectedStream m_clog; - - public: - RedirectedStdErr(); - auto str() const -> std::string; - }; - - class RedirectedStreams { - public: - RedirectedStreams(RedirectedStreams const &) = delete; - RedirectedStreams &operator=(RedirectedStreams const &) = delete; - RedirectedStreams(RedirectedStreams &&) = delete; - RedirectedStreams &operator=(RedirectedStreams &&) = delete; - - RedirectedStreams(std::string &redirectedCout, std::string &redirectedCerr); - ~RedirectedStreams(); - - private: - std::string &m_redirectedCout; - std::string &m_redirectedCerr; - RedirectedStdOut m_redirectedStdOut; - RedirectedStdErr m_redirectedStdErr; - }; - -#if defined(CATCH_CONFIG_NEW_CAPTURE) - - // Windows's implementation of std::tmpfile is terrible (it tries - // to create a file inside system folder, thus requiring elevated - // privileges for the binary), so we have to use tmpnam(_s) and - // create the file ourselves there. - class TempFile { - public: - TempFile(TempFile const &) = delete; - TempFile &operator=(TempFile const &) = delete; - TempFile(TempFile &&) = delete; - TempFile &operator=(TempFile &&) = delete; - - TempFile(); - ~TempFile(); - - std::FILE *getFile(); - std::string getContents(); - - private: - std::FILE *m_file = nullptr; -#if defined(_MSC_VER) - char m_buffer[L_tmpnam] = {0}; -#endif - }; - - class OutputRedirect { - public: - OutputRedirect(OutputRedirect const &) = delete; - OutputRedirect &operator=(OutputRedirect const &) = delete; - OutputRedirect(OutputRedirect &&) = delete; - OutputRedirect &operator=(OutputRedirect &&) = delete; - - OutputRedirect(std::string &stdout_dest, std::string &stderr_dest); - ~OutputRedirect(); - - private: - int m_originalStdout = -1; - int m_originalStderr = -1; - TempFile m_stdoutFile; - TempFile m_stderrFile; - std::string &m_stdoutDest; - std::string &m_stderrDest; - }; - -#endif - -} // end namespace Catch - -#endif // TWOBLUECUBES_CATCH_OUTPUT_REDIRECT_H \ - // end catch_output_redirect.h -#include -#include -#include -#include -#include - -#if defined(CATCH_CONFIG_NEW_CAPTURE) -#if defined(_MSC_VER) -#include //_dup and _dup2 -#define dup _dup -#define dup2 _dup2 -#define fileno _fileno -#else -#include // dup and dup2 -#endif -#endif - -namespace Catch { - RedirectedStream::RedirectedStream(std::ostream &originalStream, std::ostream &redirectionStream) - : m_originalStream(originalStream) - , m_redirectionStream(redirectionStream) - , m_prevBuf(m_originalStream.rdbuf()) { - m_originalStream.rdbuf(m_redirectionStream.rdbuf()); - } - - RedirectedStream::~RedirectedStream() { - m_originalStream.rdbuf(m_prevBuf); - } - - RedirectedStdOut::RedirectedStdOut() - : m_cout(Catch::cout(), m_rss.get()) { - } - auto RedirectedStdOut::str() const -> std::string { - return m_rss.str(); - } - - RedirectedStdErr::RedirectedStdErr() - : m_cerr(Catch::cerr(), m_rss.get()) - , m_clog(Catch::clog(), m_rss.get()) { - } - auto RedirectedStdErr::str() const -> std::string { - return m_rss.str(); - } - - RedirectedStreams::RedirectedStreams(std::string &redirectedCout, std::string &redirectedCerr) - : m_redirectedCout(redirectedCout) - , m_redirectedCerr(redirectedCerr) { - } - - RedirectedStreams::~RedirectedStreams() { - m_redirectedCout += m_redirectedStdOut.str(); - m_redirectedCerr += m_redirectedStdErr.str(); - } - -#if defined(CATCH_CONFIG_NEW_CAPTURE) - -#if defined(_MSC_VER) - TempFile::TempFile() { - if (tmpnam_s(m_buffer)) { - CATCH_RUNTIME_ERROR("Could not get a temp filename"); - } - if (fopen_s(&m_file, m_buffer, "w+")) { - char buffer[100]; - if (strerror_s(buffer, errno)) { - CATCH_RUNTIME_ERROR("Could not translate errno to a string"); - } - CATCH_RUNTIME_ERROR("Could not open the temp file: '" << m_buffer << "' because: " << buffer); - } - } -#else - TempFile::TempFile() { - m_file = std::tmpfile(); - if (!m_file) { - CATCH_RUNTIME_ERROR("Could not create a temp file."); - } - } - -#endif - - TempFile::~TempFile() { - // TBD: What to do about errors here? - std::fclose(m_file); -// We manually create the file on Windows only, on Linux -// it will be autodeleted -#if defined(_MSC_VER) - std::remove(m_buffer); -#endif - } - - FILE *TempFile::getFile() { - return m_file; - } - - std::string TempFile::getContents() { - std::stringstream sstr; - char buffer[100] = {}; - std::rewind(m_file); - while (std::fgets(buffer, sizeof(buffer), m_file)) { - sstr << buffer; - } - return sstr.str(); - } - - OutputRedirect::OutputRedirect(std::string &stdout_dest, std::string &stderr_dest) - : m_originalStdout(dup(1)) - , m_originalStderr(dup(2)) - , m_stdoutDest(stdout_dest) - , m_stderrDest(stderr_dest) { - dup2(fileno(m_stdoutFile.getFile()), 1); - dup2(fileno(m_stderrFile.getFile()), 2); - } - - OutputRedirect::~OutputRedirect() { - Catch::cout() << std::flush; - fflush(stdout); - // Since we support overriding these streams, we flush cerr - // even though std::cerr is unbuffered - Catch::cerr() << std::flush; - Catch::clog() << std::flush; - fflush(stderr); - - dup2(m_originalStdout, 1); - dup2(m_originalStderr, 2); - - m_stdoutDest += m_stdoutFile.getContents(); - m_stderrDest += m_stderrFile.getContents(); - } - -#endif // CATCH_CONFIG_NEW_CAPTURE - -} // namespace Catch - -#if defined(CATCH_CONFIG_NEW_CAPTURE) -#if defined(_MSC_VER) -#undef dup -#undef dup2 -#undef fileno -#endif -#endif -// end catch_output_redirect.cpp -// start catch_polyfills.cpp - -#include - -namespace Catch { -#if !defined(CATCH_CONFIG_POLYFILL_ISNAN) - bool isnan(float f) { - return std::isnan(f); - } - bool isnan(double d) { - return std::isnan(d); - } -#else - // For now we only use this for embarcadero - bool isnan(float f) { - return std::_isnan(f); - } - bool isnan(double d) { - return std::_isnan(d); - } -#endif - -} // end namespace Catch -// end catch_polyfills.cpp -// start catch_random_number_generator.cpp - -namespace Catch { - namespace { -#if defined(_MSC_VER) -#pragma warning(push) -#pragma warning(disable : 4146) // we negate uint32 during the rotate -#endif - // Safe rotr implementation thanks to John Regehr - uint32_t rotate_right(uint32_t val, uint32_t count) { - const uint32_t mask = 31; - count &= mask; - return (val >> count) | (val << (-count & mask)); - } - -#if defined(_MSC_VER) -#pragma warning(pop) -#endif - - } // namespace - - SimplePcg32::SimplePcg32(result_type seed_) { - seed(seed_); - } - - void SimplePcg32::seed(result_type seed_) { - m_state = 0; - (*this)(); - m_state += seed_; - (*this)(); - } - - void SimplePcg32::discard(uint64_t skip) { - // We could implement this to run in O(log n) steps, but this - // should suffice for our use case. - for (uint64_t s = 0; s < skip; ++s) { - static_cast((*this)()); - } - } - - SimplePcg32::result_type SimplePcg32::operator()() { - // prepare the output value - const uint32_t xorshifted = static_cast(((m_state >> 18u) ^ m_state) >> 27u); - const auto output = rotate_right(xorshifted, m_state >> 59u); - - // advance state - m_state = m_state * 6364136223846793005ULL + s_inc; - - return output; - } - - bool operator==(SimplePcg32 const &lhs, SimplePcg32 const &rhs) { - return lhs.m_state == rhs.m_state; - } - - bool operator!=(SimplePcg32 const &lhs, SimplePcg32 const &rhs) { - return lhs.m_state != rhs.m_state; - } -} // namespace Catch -// end catch_random_number_generator.cpp -// start catch_registry_hub.cpp - -// start catch_test_case_registry_impl.h - -#include -#include -#include -#include - -namespace Catch { - class TestCase; - struct IConfig; - - std::vector sortTests(IConfig const &config, std::vector const &unsortedTestCases); - - bool isThrowSafe(TestCase const &testCase, IConfig const &config); - bool matchTest(TestCase const &testCase, TestSpec const &testSpec, IConfig const &config); - - void enforceNoDuplicateTestCases(std::vector const &functions); - - std::vector filterTests(std::vector const &testCases, TestSpec const &testSpec, IConfig const &config); - std::vector const &getAllTestCasesSorted(IConfig const &config); - - class TestRegistry : public ITestCaseRegistry { - public: - virtual ~TestRegistry() = default; - - virtual void registerTest(TestCase const &testCase); - - std::vector const &getAllTests() const override; - std::vector const &getAllTestsSorted(IConfig const &config) const override; - - private: - std::vector m_functions; - mutable RunTests::InWhatOrder m_currentSortOrder = RunTests::InDeclarationOrder; - mutable std::vector m_sortedFunctions; - std::size_t m_unnamedCount = 0; - std::ios_base::Init m_ostreamInit; // Forces cout/ cerr to be initialised - }; - - /////////////////////////////////////////////////////////////////////////// - - class TestInvokerAsFunction : public ITestInvoker { - void (*m_testAsFunction)(); - - public: - TestInvokerAsFunction(void (*testAsFunction)()) noexcept; - - void invoke() const override; - }; - - std::string extractClassName(StringRef const &classOrQualifiedMethodName); - - /////////////////////////////////////////////////////////////////////////// - -} // end namespace Catch - -// end catch_test_case_registry_impl.h -// start catch_reporter_registry.h - -#include - -namespace Catch { - class ReporterRegistry : public IReporterRegistry { - public: - ~ReporterRegistry() override; - - IStreamingReporterPtr create(std::string const &name, IConfigPtr const &config) const override; - - void registerReporter(std::string const &name, IReporterFactoryPtr const &factory); - void registerListener(IReporterFactoryPtr const &factory); - - FactoryMap const &getFactories() const override; - Listeners const &getListeners() const override; - - private: - FactoryMap m_factories; - Listeners m_listeners; - }; -} // namespace Catch - -// end catch_reporter_registry.h -// start catch_tag_alias_registry.h - -// start catch_tag_alias.h - -#include - -namespace Catch { - struct TagAlias { - TagAlias(std::string const &_tag, SourceLineInfo _lineInfo); - - std::string tag; - SourceLineInfo lineInfo; - }; - -} // end namespace Catch - -// end catch_tag_alias.h -#include - -namespace Catch { - class TagAliasRegistry : public ITagAliasRegistry { - public: - ~TagAliasRegistry() override; - TagAlias const *find(std::string const &alias) const override; - std::string expandAliases(std::string const &unexpandedTestSpec) const override; - void add(std::string const &alias, std::string const &tag, SourceLineInfo const &lineInfo); - - private: - std::map m_registry; - }; - -} // end namespace Catch - -// end catch_tag_alias_registry.h -// start catch_startup_exception_registry.h - -#include -#include - -namespace Catch { - class StartupExceptionRegistry { -#if !defined(CATCH_CONFIG_DISABLE_EXCEPTIONS) - public: - void add(std::exception_ptr const &exception) noexcept; - std::vector const &getExceptions() const noexcept; - - private: - std::vector m_exceptions; -#endif - }; - -} // end namespace Catch - -// end catch_startup_exception_registry.h -// start catch_singletons.hpp - -namespace Catch { - struct ISingleton { - virtual ~ISingleton(); - }; - - void addSingleton(ISingleton *singleton); - void cleanupSingletons(); - - template - class Singleton : SingletonImplT, public ISingleton { - static auto getInternal() -> Singleton * { - static Singleton *s_instance = nullptr; - if (!s_instance) { - s_instance = new Singleton; - addSingleton(s_instance); - } - return s_instance; - } - - public: - static auto get() -> InterfaceT const & { return *getInternal(); } - static auto getMutable() -> MutableInterfaceT & { return *getInternal(); } - }; - -} // namespace Catch - -// end catch_singletons.hpp -namespace Catch { - namespace { - class RegistryHub : public IRegistryHub, public IMutableRegistryHub, private NonCopyable { - public: // IRegistryHub - RegistryHub() = default; - IReporterRegistry const &getReporterRegistry() const override { return m_reporterRegistry; } - ITestCaseRegistry const &getTestCaseRegistry() const override { return m_testCaseRegistry; } - IExceptionTranslatorRegistry const &getExceptionTranslatorRegistry() const override { return m_exceptionTranslatorRegistry; } - ITagAliasRegistry const &getTagAliasRegistry() const override { return m_tagAliasRegistry; } - StartupExceptionRegistry const &getStartupExceptionRegistry() const override { return m_exceptionRegistry; } - - public: // IMutableRegistryHub - void registerReporter(std::string const &name, IReporterFactoryPtr const &factory) override { - m_reporterRegistry.registerReporter(name, factory); - } - void registerListener(IReporterFactoryPtr const &factory) override { m_reporterRegistry.registerListener(factory); } - void registerTest(TestCase const &testInfo) override { m_testCaseRegistry.registerTest(testInfo); } - void registerTranslator(const IExceptionTranslator *translator) override { - m_exceptionTranslatorRegistry.registerTranslator(translator); - } - void registerTagAlias(std::string const &alias, std::string const &tag, SourceLineInfo const &lineInfo) override { - m_tagAliasRegistry.add(alias, tag, lineInfo); - } - void registerStartupException() noexcept override { -#if !defined(CATCH_CONFIG_DISABLE_EXCEPTIONS) - m_exceptionRegistry.add(std::current_exception()); -#else - CATCH_INTERNAL_ERROR("Attempted to register active exception under CATCH_CONFIG_DISABLE_EXCEPTIONS!"); -#endif - } - IMutableEnumValuesRegistry &getMutableEnumValuesRegistry() override { return m_enumValuesRegistry; } - - private: - TestRegistry m_testCaseRegistry; - ReporterRegistry m_reporterRegistry; - ExceptionTranslatorRegistry m_exceptionTranslatorRegistry; - TagAliasRegistry m_tagAliasRegistry; - StartupExceptionRegistry m_exceptionRegistry; - Detail::EnumValuesRegistry m_enumValuesRegistry; - }; - } // namespace - - using RegistryHubSingleton = Singleton; - - IRegistryHub const &getRegistryHub() { - return RegistryHubSingleton::get(); - } - IMutableRegistryHub &getMutableRegistryHub() { - return RegistryHubSingleton::getMutable(); - } - void cleanUp() { - cleanupSingletons(); - cleanUpContext(); - } - std::string translateActiveException() { - return getRegistryHub().getExceptionTranslatorRegistry().translateActiveException(); - } - -} // end namespace Catch -// end catch_registry_hub.cpp -// start catch_reporter_registry.cpp - -namespace Catch { - ReporterRegistry::~ReporterRegistry() = default; - - IStreamingReporterPtr ReporterRegistry::create(std::string const &name, IConfigPtr const &config) const { - auto it = m_factories.find(name); - if (it == m_factories.end()) - return nullptr; - return it->second->create(ReporterConfig(config)); - } - - void ReporterRegistry::registerReporter(std::string const &name, IReporterFactoryPtr const &factory) { - m_factories.emplace(name, factory); - } - void ReporterRegistry::registerListener(IReporterFactoryPtr const &factory) { - m_listeners.push_back(factory); - } - - IReporterRegistry::FactoryMap const &ReporterRegistry::getFactories() const { - return m_factories; - } - IReporterRegistry::Listeners const &ReporterRegistry::getListeners() const { - return m_listeners; - } - -} // namespace Catch -// end catch_reporter_registry.cpp -// start catch_result_type.cpp - -namespace Catch { - bool isOk(ResultWas::OfType resultType) { - return (resultType & ResultWas::FailureBit) == 0; - } - bool isJustInfo(int flags) { - return flags == ResultWas::Info; - } - - ResultDisposition::Flags operator|(ResultDisposition::Flags lhs, ResultDisposition::Flags rhs) { - return static_cast(static_cast(lhs) | static_cast(rhs)); - } - - bool shouldContinueOnFailure(int flags) { - return (flags & ResultDisposition::ContinueOnFailure) != 0; - } - bool shouldSuppressFailure(int flags) { - return (flags & ResultDisposition::SuppressFail) != 0; - } - -} // end namespace Catch -// end catch_result_type.cpp -// start catch_run_context.cpp - -#include -#include -#include - -namespace Catch { - namespace Generators { - struct GeneratorTracker : TestCaseTracking::TrackerBase, IGeneratorTracker { - GeneratorBasePtr m_generator; - - GeneratorTracker(TestCaseTracking::NameAndLocation const &nameAndLocation, TrackerContext &ctx, ITracker *parent) - : TrackerBase(nameAndLocation, ctx, parent) { - } - ~GeneratorTracker(); - - static GeneratorTracker &acquire(TrackerContext &ctx, TestCaseTracking::NameAndLocation const &nameAndLocation) { - std::shared_ptr tracker; - - ITracker ¤tTracker = ctx.currentTracker(); - // Under specific circumstances, the generator we want - // to acquire is also the current tracker. If this is - // the case, we have to avoid looking through current - // tracker's children, and instead return the current - // tracker. - // A case where this check is important is e.g. - // for (int i = 0; i < 5; ++i) { - // int n = GENERATE(1, 2); - // } - // - // without it, the code above creates 5 nested generators. - if (currentTracker.nameAndLocation() == nameAndLocation) { - auto thisTracker = currentTracker.parent().findChild(nameAndLocation); - assert(thisTracker); - assert(thisTracker->isGeneratorTracker()); - tracker = std::static_pointer_cast(thisTracker); - } else if (TestCaseTracking::ITrackerPtr childTracker = currentTracker.findChild(nameAndLocation)) { - assert(childTracker); - assert(childTracker->isGeneratorTracker()); - tracker = std::static_pointer_cast(childTracker); - } else { - tracker = std::make_shared(nameAndLocation, ctx, ¤tTracker); - currentTracker.addChild(tracker); - } - - if (!tracker->isComplete()) { - tracker->open(); - } - - return *tracker; - } - - // TrackerBase interface - bool isGeneratorTracker() const override { return true; } - auto hasGenerator() const -> bool override { return !!m_generator; } - void close() override { - TrackerBase::close(); - // If a generator has a child (it is followed by a section) - // and none of its children have started, then we must wait - // until later to start consuming its values. - // This catches cases where `GENERATE` is placed between two - // `SECTION`s. - // **The check for m_children.empty cannot be removed**. - // doing so would break `GENERATE` _not_ followed by `SECTION`s. - const bool should_wait_for_child = [&]() { - // No children -> nobody to wait for - if (m_children.empty()) { - return false; - } - // If at least one child started executing, don't wait - if (std::find_if(m_children.begin(), - m_children.end(), - [](TestCaseTracking::ITrackerPtr tracker) { return tracker->hasStarted(); }) - != m_children.end()) { - return false; - } - - // No children have started. We need to check if they _can_ - // start, and thus we should wait for them, or they cannot - // start (due to filters), and we shouldn't wait for them - auto *parent = m_parent; - // This is safe: there is always at least one section - // tracker in a test case tracking tree - while (!parent->isSectionTracker()) { - parent = &(parent->parent()); - } - assert(parent && "Missing root (test case) level section"); - - auto const &parentSection = static_cast(*parent); - auto const &filters = parentSection.getFilters(); - // No filters -> no restrictions on running sections - if (filters.empty()) { - return true; - } - - for (auto const &child : m_children) { - if (child->isSectionTracker() - && std::find(filters.begin(), filters.end(), static_cast(*child).trimmedName()) != filters.end()) { - return true; - } - } - return false; - }(); - - // This check is a bit tricky, because m_generator->next() - // has a side-effect, where it consumes generator's current - // value, but we do not want to invoke the side-effect if - // this generator is still waiting for any child to start. - if (should_wait_for_child || (m_runState == CompletedSuccessfully && m_generator->next())) { - m_children.clear(); - m_runState = Executing; - } - } - - // IGeneratorTracker interface - auto getGenerator() const -> GeneratorBasePtr const & override { return m_generator; } - void setGenerator(GeneratorBasePtr &&generator) override { m_generator = std::move(generator); } - }; - GeneratorTracker::~GeneratorTracker() {} - } // namespace Generators - - RunContext::RunContext(IConfigPtr const &_config, IStreamingReporterPtr &&reporter) - : m_runInfo(_config->name()) - , m_context(getCurrentMutableContext()) - , m_config(_config) - , m_reporter(std::move(reporter)) - , m_lastAssertionInfo{StringRef(), SourceLineInfo("", 0), StringRef(), ResultDisposition::Normal} - , m_includeSuccessfulResults(m_config->includeSuccessfulResults() || m_reporter->getPreferences().shouldReportAllAssertions) { - m_context.setRunner(this); - m_context.setConfig(m_config); - m_context.setResultCapture(this); - m_reporter->testRunStarting(m_runInfo); - } - - RunContext::~RunContext() { - m_reporter->testRunEnded(TestRunStats(m_runInfo, m_totals, aborting())); - } - - void RunContext::testGroupStarting(std::string const &testSpec, std::size_t groupIndex, std::size_t groupsCount) { - m_reporter->testGroupStarting(GroupInfo(testSpec, groupIndex, groupsCount)); - } - - void RunContext::testGroupEnded(std::string const &testSpec, Totals const &totals, std::size_t groupIndex, std::size_t groupsCount) { - m_reporter->testGroupEnded(TestGroupStats(GroupInfo(testSpec, groupIndex, groupsCount), totals, aborting())); - } - - Totals RunContext::runTest(TestCase const &testCase) { - Totals prevTotals = m_totals; - - std::string redirectedCout; - std::string redirectedCerr; - - auto const &testInfo = testCase.getTestCaseInfo(); - - m_reporter->testCaseStarting(testInfo); - - m_activeTestCase = &testCase; - - ITracker &rootTracker = m_trackerContext.startRun(); - assert(rootTracker.isSectionTracker()); - static_cast(rootTracker).addInitialFilters(m_config->getSectionsToRun()); - do { - m_trackerContext.startCycle(); - m_testCaseTracker = &SectionTracker::acquire(m_trackerContext, TestCaseTracking::NameAndLocation(testInfo.name, testInfo.lineInfo)); - runCurrentTest(redirectedCout, redirectedCerr); - } while (!m_testCaseTracker->isSuccessfullyCompleted() && !aborting()); - - Totals deltaTotals = m_totals.delta(prevTotals); - if (testInfo.expectedToFail() && deltaTotals.testCases.passed > 0) { - deltaTotals.assertions.failed++; - deltaTotals.testCases.passed--; - deltaTotals.testCases.failed++; - } - m_totals.testCases += deltaTotals.testCases; - m_reporter->testCaseEnded(TestCaseStats(testInfo, deltaTotals, redirectedCout, redirectedCerr, aborting())); - - m_activeTestCase = nullptr; - m_testCaseTracker = nullptr; - - return deltaTotals; - } - - IConfigPtr RunContext::config() const { - return m_config; - } - - IStreamingReporter &RunContext::reporter() const { - return *m_reporter; - } - - void RunContext::assertionEnded(AssertionResult const &result) { - if (result.getResultType() == ResultWas::Ok) { - m_totals.assertions.passed++; - m_lastAssertionPassed = true; - } else if (!result.isOk()) { - m_lastAssertionPassed = false; - if (m_activeTestCase->getTestCaseInfo().okToFail()) - m_totals.assertions.failedButOk++; - else - m_totals.assertions.failed++; - } else { - m_lastAssertionPassed = true; - } - - // We have no use for the return value (whether messages should be cleared), because messages were made scoped - // and should be let to clear themselves out. - static_cast(m_reporter->assertionEnded(AssertionStats(result, m_messages, m_totals))); - - if (result.getResultType() != ResultWas::Warning) - m_messageScopes.clear(); - - // Reset working state - resetAssertionInfo(); - m_lastResult = result; - } - void RunContext::resetAssertionInfo() { - m_lastAssertionInfo.macroName = StringRef(); - m_lastAssertionInfo.capturedExpression = "{Unknown expression after the reported line}"_sr; - } - - bool RunContext::sectionStarted(SectionInfo const §ionInfo, Counts &assertions) { - ITracker §ionTracker - = SectionTracker::acquire(m_trackerContext, TestCaseTracking::NameAndLocation(sectionInfo.name, sectionInfo.lineInfo)); - if (!sectionTracker.isOpen()) - return false; - m_activeSections.push_back(§ionTracker); - - m_lastAssertionInfo.lineInfo = sectionInfo.lineInfo; - - m_reporter->sectionStarting(sectionInfo); - - assertions = m_totals.assertions; - - return true; - } - auto RunContext::acquireGeneratorTracker(StringRef generatorName, SourceLineInfo const &lineInfo) -> IGeneratorTracker & { - using namespace Generators; - GeneratorTracker &tracker - = GeneratorTracker::acquire(m_trackerContext, TestCaseTracking::NameAndLocation(static_cast(generatorName), lineInfo)); - m_lastAssertionInfo.lineInfo = lineInfo; - return tracker; - } - - bool RunContext::testForMissingAssertions(Counts &assertions) { - if (assertions.total() != 0) - return false; - if (!m_config->warnAboutMissingAssertions()) - return false; - if (m_trackerContext.currentTracker().hasChildren()) - return false; - m_totals.assertions.failed++; - assertions.failed++; - return true; - } - - void RunContext::sectionEnded(SectionEndInfo const &endInfo) { - Counts assertions = m_totals.assertions - endInfo.prevAssertions; - bool missingAssertions = testForMissingAssertions(assertions); - - if (!m_activeSections.empty()) { - m_activeSections.back()->close(); - m_activeSections.pop_back(); - } - - m_reporter->sectionEnded(SectionStats(endInfo.sectionInfo, assertions, endInfo.durationInSeconds, missingAssertions)); - m_messages.clear(); - m_messageScopes.clear(); - } - - void RunContext::sectionEndedEarly(SectionEndInfo const &endInfo) { - if (m_unfinishedSections.empty()) - m_activeSections.back()->fail(); - else - m_activeSections.back()->close(); - m_activeSections.pop_back(); - - m_unfinishedSections.push_back(endInfo); - } - -#if defined(CATCH_CONFIG_ENABLE_BENCHMARKING) - void RunContext::benchmarkPreparing(std::string const &name) { - m_reporter->benchmarkPreparing(name); - } - void RunContext::benchmarkStarting(BenchmarkInfo const &info) { - m_reporter->benchmarkStarting(info); - } - void RunContext::benchmarkEnded(BenchmarkStats<> const &stats) { - m_reporter->benchmarkEnded(stats); - } - void RunContext::benchmarkFailed(std::string const &error) { - m_reporter->benchmarkFailed(error); - } -#endif // CATCH_CONFIG_ENABLE_BENCHMARKING - - void RunContext::pushScopedMessage(MessageInfo const &message) { - m_messages.push_back(message); - } - - void RunContext::popScopedMessage(MessageInfo const &message) { - m_messages.erase(std::remove(m_messages.begin(), m_messages.end(), message), m_messages.end()); - } - - void RunContext::emplaceUnscopedMessage(MessageBuilder const &builder) { - m_messageScopes.emplace_back(builder); - } - - std::string RunContext::getCurrentTestName() const { - return m_activeTestCase ? m_activeTestCase->getTestCaseInfo().name : std::string(); - } - - const AssertionResult *RunContext::getLastResult() const { - return &(*m_lastResult); - } - - void RunContext::exceptionEarlyReported() { - m_shouldReportUnexpected = false; - } - - void RunContext::handleFatalErrorCondition(StringRef message) { - // First notify reporter that bad things happened - m_reporter->fatalErrorEncountered(message); - - // Don't rebuild the result -- the stringification itself can cause more fatal errors - // Instead, fake a result data. - AssertionResultData tempResult(ResultWas::FatalErrorCondition, {false}); - tempResult.message = static_cast(message); - AssertionResult result(m_lastAssertionInfo, tempResult); - - assertionEnded(result); - - handleUnfinishedSections(); - - // Recreate section for test case (as we will lose the one that was in scope) - auto const &testCaseInfo = m_activeTestCase->getTestCaseInfo(); - SectionInfo testCaseSection(testCaseInfo.lineInfo, testCaseInfo.name); - - Counts assertions; - assertions.failed = 1; - SectionStats testCaseSectionStats(testCaseSection, assertions, 0, false); - m_reporter->sectionEnded(testCaseSectionStats); - - auto const &testInfo = m_activeTestCase->getTestCaseInfo(); - - Totals deltaTotals; - deltaTotals.testCases.failed = 1; - deltaTotals.assertions.failed = 1; - m_reporter->testCaseEnded(TestCaseStats(testInfo, deltaTotals, std::string(), std::string(), false)); - m_totals.testCases.failed++; - testGroupEnded(std::string(), m_totals, 1, 1); - m_reporter->testRunEnded(TestRunStats(m_runInfo, m_totals, false)); - } - - bool RunContext::lastAssertionPassed() { - return m_lastAssertionPassed; - } - - void RunContext::assertionPassed() { - m_lastAssertionPassed = true; - ++m_totals.assertions.passed; - resetAssertionInfo(); - m_messageScopes.clear(); - } - - bool RunContext::aborting() const { - return m_totals.assertions.failed >= static_cast(m_config->abortAfter()); - } - - void RunContext::runCurrentTest(std::string &redirectedCout, std::string &redirectedCerr) { - auto const &testCaseInfo = m_activeTestCase->getTestCaseInfo(); - SectionInfo testCaseSection(testCaseInfo.lineInfo, testCaseInfo.name); - m_reporter->sectionStarting(testCaseSection); - Counts prevAssertions = m_totals.assertions; - double duration = 0; - m_shouldReportUnexpected = true; - m_lastAssertionInfo = {"TEST_CASE"_sr, testCaseInfo.lineInfo, StringRef(), ResultDisposition::Normal}; - - seedRng(*m_config); - - Timer timer; - CATCH_TRY { - if (m_reporter->getPreferences().shouldRedirectStdOut) { -#if !defined(CATCH_CONFIG_EXPERIMENTAL_REDIRECT) - RedirectedStreams redirectedStreams(redirectedCout, redirectedCerr); - - timer.start(); - invokeActiveTestCase(); -#else - OutputRedirect r(redirectedCout, redirectedCerr); - timer.start(); - invokeActiveTestCase(); -#endif - } else { - timer.start(); - invokeActiveTestCase(); - } - duration = timer.getElapsedSeconds(); - } - CATCH_CATCH_ANON(TestFailureException &) { - // This just means the test was aborted due to failure - } - CATCH_CATCH_ALL { - // Under CATCH_CONFIG_FAST_COMPILE, unexpected exceptions under REQUIRE assertions - // are reported without translation at the point of origin. - if (m_shouldReportUnexpected) { - AssertionReaction dummyReaction; - handleUnexpectedInflightException(m_lastAssertionInfo, translateActiveException(), dummyReaction); - } - } - Counts assertions = m_totals.assertions - prevAssertions; - bool missingAssertions = testForMissingAssertions(assertions); - - m_testCaseTracker->close(); - handleUnfinishedSections(); - m_messages.clear(); - m_messageScopes.clear(); - - SectionStats testCaseSectionStats(testCaseSection, assertions, duration, missingAssertions); - m_reporter->sectionEnded(testCaseSectionStats); - } - - void RunContext::invokeActiveTestCase() { - FatalConditionHandlerGuard _(&m_fatalConditionhandler); - m_activeTestCase->invoke(); - } - - void RunContext::handleUnfinishedSections() { - // If sections ended prematurely due to an exception we stored their - // infos here so we can tear them down outside the unwind process. - for (auto it = m_unfinishedSections.rbegin(), itEnd = m_unfinishedSections.rend(); it != itEnd; ++it) - sectionEnded(*it); - m_unfinishedSections.clear(); - } - - void RunContext::handleExpr(AssertionInfo const &info, ITransientExpression const &expr, AssertionReaction &reaction) { - m_reporter->assertionStarting(info); - - bool negated = isFalseTest(info.resultDisposition); - bool result = expr.getResult() != negated; - - if (result) { - if (!m_includeSuccessfulResults) { - assertionPassed(); - } else { - reportExpr(info, ResultWas::Ok, &expr, negated); - } - } else { - reportExpr(info, ResultWas::ExpressionFailed, &expr, negated); - populateReaction(reaction); - } - } - void RunContext::reportExpr(AssertionInfo const &info, ResultWas::OfType resultType, ITransientExpression const *expr, bool negated) { - m_lastAssertionInfo = info; - AssertionResultData data(resultType, LazyExpression(negated)); - - AssertionResult assertionResult{info, data}; - assertionResult.m_resultData.lazyExpression.m_transientExpression = expr; - - assertionEnded(assertionResult); - } - - void RunContext::handleMessage(AssertionInfo const &info, ResultWas::OfType resultType, StringRef const &message, AssertionReaction &reaction) { - m_reporter->assertionStarting(info); - - m_lastAssertionInfo = info; - - AssertionResultData data(resultType, LazyExpression(false)); - data.message = static_cast(message); - AssertionResult assertionResult{m_lastAssertionInfo, data}; - assertionEnded(assertionResult); - if (!assertionResult.isOk()) - populateReaction(reaction); - } - void RunContext::handleUnexpectedExceptionNotThrown(AssertionInfo const &info, AssertionReaction &reaction) { - handleNonExpr(info, Catch::ResultWas::DidntThrowException, reaction); - } - - void RunContext::handleUnexpectedInflightException(AssertionInfo const &info, std::string const &message, AssertionReaction &reaction) { - m_lastAssertionInfo = info; - - AssertionResultData data(ResultWas::ThrewException, LazyExpression(false)); - data.message = message; - AssertionResult assertionResult{info, data}; - assertionEnded(assertionResult); - populateReaction(reaction); - } - - void RunContext::populateReaction(AssertionReaction &reaction) { - reaction.shouldDebugBreak = m_config->shouldDebugBreak(); - reaction.shouldThrow = aborting() || (m_lastAssertionInfo.resultDisposition & ResultDisposition::Normal); - } - - void RunContext::handleIncomplete(AssertionInfo const &info) { - m_lastAssertionInfo = info; - - AssertionResultData data(ResultWas::ThrewException, LazyExpression(false)); - data.message = "Exception translation was disabled by CATCH_CONFIG_FAST_COMPILE"; - AssertionResult assertionResult{info, data}; - assertionEnded(assertionResult); - } - void RunContext::handleNonExpr(AssertionInfo const &info, ResultWas::OfType resultType, AssertionReaction &reaction) { - m_lastAssertionInfo = info; - - AssertionResultData data(resultType, LazyExpression(false)); - AssertionResult assertionResult{info, data}; - assertionEnded(assertionResult); - - if (!assertionResult.isOk()) - populateReaction(reaction); - } - - IResultCapture &getResultCapture() { - if (auto *capture = getCurrentContext().getResultCapture()) - return *capture; - else - CATCH_INTERNAL_ERROR("No result capture instance"); - } - - void seedRng(IConfig const &config) { - if (config.rngSeed() != 0) { - std::srand(config.rngSeed()); - rng().seed(config.rngSeed()); - } - } - - unsigned int rngSeed() { - return getCurrentContext().getConfig()->rngSeed(); - } - -} // namespace Catch -// end catch_run_context.cpp -// start catch_section.cpp - -namespace Catch { - Section::Section(SectionInfo const &info) - : m_info(info) - , m_sectionIncluded(getResultCapture().sectionStarted(m_info, m_assertions)) { - m_timer.start(); - } - - Section::~Section() { - if (m_sectionIncluded) { - SectionEndInfo endInfo{m_info, m_assertions, m_timer.getElapsedSeconds()}; - if (uncaught_exceptions()) - getResultCapture().sectionEndedEarly(endInfo); - else - getResultCapture().sectionEnded(endInfo); - } - } - - // This indicates whether the section should be executed or not - Section::operator bool() const { - return m_sectionIncluded; - } - -} // end namespace Catch -// end catch_section.cpp -// start catch_section_info.cpp - -namespace Catch { - SectionInfo::SectionInfo(SourceLineInfo const &_lineInfo, std::string const &_name) - : name(_name) - , lineInfo(_lineInfo) { - } - -} // end namespace Catch -// end catch_section_info.cpp -// start catch_session.cpp - -// start catch_session.h - -#include - -namespace Catch { - class Session : NonCopyable { - public: - Session(); - ~Session() override; - - void showHelp() const; - void libIdentify(); - - int applyCommandLine(int argc, char const *const *argv); -#if defined(CATCH_CONFIG_WCHAR) && defined(_WIN32) && defined(UNICODE) - int applyCommandLine(int argc, wchar_t const *const *argv); -#endif - - void useConfigData(ConfigData const &configData); - - template - int run(int argc, CharT const *const argv[]) { - if (m_startupExceptions) - return 1; - int returnCode = applyCommandLine(argc, argv); - if (returnCode == 0) - returnCode = run(); - return returnCode; - } - - int run(); - - clara::Parser const &cli() const; - void cli(clara::Parser const &newParser); - ConfigData &configData(); - Config &config(); - - private: - int runInternal(); - - clara::Parser m_cli; - ConfigData m_configData; - std::shared_ptr m_config; - bool m_startupExceptions = false; - }; - -} // end namespace Catch - -// end catch_session.h -// start catch_version.h - -#include - -namespace Catch { - // Versioning information - struct Version { - Version(Version const &) = delete; - Version &operator=(Version const &) = delete; - Version(unsigned int _majorVersion, unsigned int _minorVersion, unsigned int _patchNumber, char const *const _branchName, unsigned int _buildNumber); - - unsigned int const majorVersion; - unsigned int const minorVersion; - unsigned int const patchNumber; - - // buildNumber is only used if branchName is not null - char const *const branchName; - unsigned int const buildNumber; - - friend std::ostream &operator<<(std::ostream &os, Version const &version); - }; - - Version const &libraryVersion(); -} // namespace Catch - -// end catch_version.h -#include -#include -#include -#include - -namespace Catch { - namespace { - const int MaxExitCode = 255; - - IStreamingReporterPtr createReporter(std::string const &reporterName, IConfigPtr const &config) { - auto reporter = Catch::getRegistryHub().getReporterRegistry().create(reporterName, config); - CATCH_ENFORCE(reporter, "No reporter registered with name: '" << reporterName << "'"); - - return reporter; - } - - IStreamingReporterPtr makeReporter(std::shared_ptr const &config) { - if (Catch::getRegistryHub().getReporterRegistry().getListeners().empty()) { - return createReporter(config->getReporterName(), config); - } - - // On older platforms, returning std::unique_ptr - // when the return type is std::unique_ptr - // doesn't compile without a std::move call. However, this causes - // a warning on newer platforms. Thus, we have to work around - // it a bit and downcast the pointer manually. - auto ret = std::unique_ptr(new ListeningReporter); - auto &multi = static_cast(*ret); - auto const &listeners = Catch::getRegistryHub().getReporterRegistry().getListeners(); - for (auto const &listener : listeners) { - multi.addListener(listener->create(Catch::ReporterConfig(config))); - } - multi.addReporter(createReporter(config->getReporterName(), config)); - return ret; - } - - class TestGroup { - public: - explicit TestGroup(std::shared_ptr const &config) - : m_config{config} - , m_context{config, makeReporter(config)} { - auto const &allTestCases = getAllTestCasesSorted(*m_config); - m_matches = m_config->testSpec().matchesByFilter(allTestCases, *m_config); - auto const &invalidArgs = m_config->testSpec().getInvalidArgs(); - - if (m_matches.empty() && invalidArgs.empty()) { - for (auto const &test : allTestCases) - if (!test.isHidden()) - m_tests.emplace(&test); - } else { - for (auto const &match : m_matches) - m_tests.insert(match.tests.begin(), match.tests.end()); - } - } - - Totals execute() { - auto const &invalidArgs = m_config->testSpec().getInvalidArgs(); - Totals totals; - m_context.testGroupStarting(m_config->name(), 1, 1); - for (auto const &testCase : m_tests) { - if (!m_context.aborting()) - totals += m_context.runTest(*testCase); - else - m_context.reporter().skipTest(*testCase); - } - - for (auto const &match : m_matches) { - if (match.tests.empty()) { - m_context.reporter().noMatchingTestCases(match.name); - totals.error = -1; - } - } - - if (!invalidArgs.empty()) { - for (auto const &invalidArg : invalidArgs) - m_context.reporter().reportInvalidArguments(invalidArg); - } - - m_context.testGroupEnded(m_config->name(), totals, 1, 1); - return totals; - } - - private: - using Tests = std::set; - - std::shared_ptr m_config; - RunContext m_context; - Tests m_tests; - TestSpec::Matches m_matches; - }; - - void applyFilenamesAsTags(Catch::IConfig const &config) { - auto &tests = const_cast &>(getAllTestCasesSorted(config)); - for (auto &testCase : tests) { - auto tags = testCase.tags; - - std::string filename = testCase.lineInfo.file; - auto lastSlash = filename.find_last_of("\\/"); - if (lastSlash != std::string::npos) { - filename.erase(0, lastSlash); - filename[0] = '#'; - } - - auto lastDot = filename.find_last_of('.'); - if (lastDot != std::string::npos) { - filename.erase(lastDot); - } - - tags.push_back(std::move(filename)); - setTags(testCase, tags); - } - } - - } // namespace - - Session::Session() { - static bool alreadyInstantiated = false; - if (alreadyInstantiated) { - CATCH_TRY { CATCH_INTERNAL_ERROR("Only one instance of Catch::Session can ever be used"); } - CATCH_CATCH_ALL { getMutableRegistryHub().registerStartupException(); } - } - -// There cannot be exceptions at startup in no-exception mode. -#if !defined(CATCH_CONFIG_DISABLE_EXCEPTIONS) - const auto &exceptions = getRegistryHub().getStartupExceptionRegistry().getExceptions(); - if (!exceptions.empty()) { - config(); - getCurrentMutableContext().setConfig(m_config); - - m_startupExceptions = true; - Colour colourGuard(Colour::Red); - Catch::cerr() << "Errors occurred during startup!" << '\n'; - // iterate over all exceptions and notify user - for (const auto &ex_ptr : exceptions) { - try { - std::rethrow_exception(ex_ptr); - } catch (std::exception const &ex) { - Catch::cerr() << Column(ex.what()).indent(2) << '\n'; - } - } - } -#endif - - alreadyInstantiated = true; - m_cli = makeCommandLineParser(m_configData); - } - Session::~Session() { - Catch::cleanUp(); - } - - void Session::showHelp() const { - Catch::cout() << "\nCatch v" << libraryVersion() << "\n" - << m_cli << std::endl - << "For more detailed usage please see the project docs\n" - << std::endl; - } - void Session::libIdentify() { - Catch::cout() << std::left << std::setw(16) << "description: " - << "A Catch2 test executable\n" - << std::left << std::setw(16) << "category: " - << "testframework\n" - << std::left << std::setw(16) << "framework: " - << "Catch Test\n" - << std::left << std::setw(16) << "version: " << libraryVersion() << std::endl; - } - - int Session::applyCommandLine(int argc, char const *const *argv) { - if (m_startupExceptions) - return 1; - - auto result = m_cli.parse(clara::Args(argc, argv)); - if (!result) { - config(); - getCurrentMutableContext().setConfig(m_config); - Catch::cerr() << Colour(Colour::Red) << "\nError(s) in input:\n" - << Column(result.errorMessage()).indent(2) << "\n\n"; - Catch::cerr() << "Run with -? for usage\n" - << std::endl; - return MaxExitCode; - } - - if (m_configData.showHelp) - showHelp(); - if (m_configData.libIdentify) - libIdentify(); - m_config.reset(); - return 0; - } - -#if defined(CATCH_CONFIG_WCHAR) && defined(_WIN32) && defined(UNICODE) - int Session::applyCommandLine(int argc, wchar_t const *const *argv) { - char **utf8Argv = new char *[argc]; - - for (int i = 0; i < argc; ++i) { - int bufSize = WideCharToMultiByte(CP_UTF8, 0, argv[i], -1, nullptr, 0, nullptr, nullptr); - - utf8Argv[i] = new char[bufSize]; - - WideCharToMultiByte(CP_UTF8, 0, argv[i], -1, utf8Argv[i], bufSize, nullptr, nullptr); - } - - int returnCode = applyCommandLine(argc, utf8Argv); - - for (int i = 0; i < argc; ++i) - delete[] utf8Argv[i]; - - delete[] utf8Argv; - - return returnCode; - } -#endif - - void Session::useConfigData(ConfigData const &configData) { - m_configData = configData; - m_config.reset(); - } - - int Session::run() { - if ((m_configData.waitForKeypress & WaitForKeypress::BeforeStart) != 0) { - Catch::cout() << "...waiting for enter/ return before starting" << std::endl; - static_cast(std::getchar()); - } - int exitCode = runInternal(); - if ((m_configData.waitForKeypress & WaitForKeypress::BeforeExit) != 0) { - Catch::cout() << "...waiting for enter/ return before exiting, with code: " << exitCode << std::endl; - static_cast(std::getchar()); - } - return exitCode; - } - - clara::Parser const &Session::cli() const { - return m_cli; - } - void Session::cli(clara::Parser const &newParser) { - m_cli = newParser; - } - ConfigData &Session::configData() { - return m_configData; - } - Config &Session::config() { - if (!m_config) - m_config = std::make_shared(m_configData); - return *m_config; - } - - int Session::runInternal() { - if (m_startupExceptions) - return 1; - - if (m_configData.showHelp || m_configData.libIdentify) { - return 0; - } - - CATCH_TRY { - config(); // Force config to be constructed - - seedRng(*m_config); - - if (m_configData.filenamesAsTags) - applyFilenamesAsTags(*m_config); - - // Handle list request - if (Option listed = list(m_config)) - return static_cast(*listed); - - TestGroup tests{m_config}; - auto const totals = tests.execute(); - - if (m_config->warnAboutNoTests() && totals.error == -1) - return 2; - - // Note that on unices only the lower 8 bits are usually used, clamping - // the return value to 255 prevents false negative when some multiple - // of 256 tests has failed - return (std::min)(MaxExitCode, (std::max)(totals.error, static_cast(totals.assertions.failed))); - } -#if !defined(CATCH_CONFIG_DISABLE_EXCEPTIONS) - catch (std::exception &ex) { - Catch::cerr() << ex.what() << std::endl; - return MaxExitCode; - } -#endif - } - -} // end namespace Catch -// end catch_session.cpp -// start catch_singletons.cpp - -#include - -namespace Catch { - namespace { - static auto getSingletons() -> std::vector *& { - static std::vector *g_singletons = nullptr; - if (!g_singletons) - g_singletons = new std::vector(); - return g_singletons; - } - } // namespace - - ISingleton::~ISingleton() {} - - void addSingleton(ISingleton *singleton) { - getSingletons()->push_back(singleton); - } - void cleanupSingletons() { - auto &singletons = getSingletons(); - for (auto singleton : *singletons) - delete singleton; - delete singletons; - singletons = nullptr; - } - -} // namespace Catch -// end catch_singletons.cpp -// start catch_startup_exception_registry.cpp - -#if !defined(CATCH_CONFIG_DISABLE_EXCEPTIONS) -namespace Catch { - void StartupExceptionRegistry::add(std::exception_ptr const &exception) noexcept { - CATCH_TRY { m_exceptions.push_back(exception); } - CATCH_CATCH_ALL { - // If we run out of memory during start-up there's really not a lot more we can do about it - std::terminate(); - } - } - - std::vector const &StartupExceptionRegistry::getExceptions() const noexcept { - return m_exceptions; - } - -} // end namespace Catch -#endif -// end catch_startup_exception_registry.cpp -// start catch_stream.cpp - -#include -#include -#include -#include -#include -#include - -namespace Catch { - Catch::IStream::~IStream() = default; - - namespace Detail { - namespace { - template - class StreamBufImpl : public std::streambuf { - char data[bufferSize]; - WriterF m_writer; - - public: - StreamBufImpl() { setp(data, data + sizeof(data)); } - - ~StreamBufImpl() noexcept { StreamBufImpl::sync(); } - - private: - int overflow(int c) override { - sync(); - - if (c != EOF) { - if (pbase() == epptr()) - m_writer(std::string(1, static_cast(c))); - else - sputc(static_cast(c)); - } - return 0; - } - - int sync() override { - if (pbase() != pptr()) { - m_writer(std::string(pbase(), static_cast(pptr() - pbase()))); - setp(pbase(), epptr()); - } - return 0; - } - }; - - /////////////////////////////////////////////////////////////////////////// - - struct OutputDebugWriter { - void operator()(std::string const &str) { writeToDebugConsole(str); } - }; - - /////////////////////////////////////////////////////////////////////////// - - class FileStream : public IStream { - mutable std::ofstream m_ofs; - - public: - FileStream(StringRef filename) { - m_ofs.open(filename.c_str()); - CATCH_ENFORCE(!m_ofs.fail(), "Unable to open file: '" << filename << "'"); - } - ~FileStream() override = default; - - public: // IStream - std::ostream &stream() const override { return m_ofs; } - }; - - /////////////////////////////////////////////////////////////////////////// - - class CoutStream : public IStream { - mutable std::ostream m_os; - - public: - // Store the streambuf from cout up-front because - // cout may get redirected when running tests - CoutStream() - : m_os(Catch::cout().rdbuf()) { - } - ~CoutStream() override = default; - - public: // IStream - std::ostream &stream() const override { return m_os; } - }; - - /////////////////////////////////////////////////////////////////////////// - - class DebugOutStream : public IStream { - std::unique_ptr> m_streamBuf; - mutable std::ostream m_os; - - public: - DebugOutStream() - : m_streamBuf(new StreamBufImpl()) - , m_os(m_streamBuf.get()) { - } - - ~DebugOutStream() override = default; - - public: // IStream - std::ostream &stream() const override { return m_os; } - }; - - } // namespace - } // namespace Detail - - /////////////////////////////////////////////////////////////////////////// - - auto makeStream(StringRef const &filename) -> IStream const * { - if (filename.empty()) - return new Detail::CoutStream(); - else if (filename[0] == '%') { - if (filename == "%debug") - return new Detail::DebugOutStream(); - else - CATCH_ERROR("Unrecognised stream: '" << filename << "'"); - } else - return new Detail::FileStream(filename); - } - - // This class encapsulates the idea of a pool of ostringstreams that can be reused. - struct StringStreams { - std::vector> m_streams; - std::vector m_unused; - std::ostringstream m_referenceStream; // Used for copy state/ flags from - - auto add() -> std::size_t { - if (m_unused.empty()) { - m_streams.push_back(std::unique_ptr(new std::ostringstream)); - return m_streams.size() - 1; - } else { - auto index = m_unused.back(); - m_unused.pop_back(); - return index; - } - } - - void release(std::size_t index) { - m_streams[index]->copyfmt(m_referenceStream); // Restore initial flags and other state - m_unused.push_back(index); - } - }; - - ReusableStringStream::ReusableStringStream() - : m_index(Singleton::getMutable().add()) - , m_oss(Singleton::getMutable().m_streams[m_index].get()) { - } - - ReusableStringStream::~ReusableStringStream() { - static_cast(m_oss)->str(""); - m_oss->clear(); - Singleton::getMutable().release(m_index); - } - - auto ReusableStringStream::str() const -> std::string { - return static_cast(m_oss)->str(); - } - - /////////////////////////////////////////////////////////////////////////// - -#ifndef CATCH_CONFIG_NOSTDOUT // If you #define this you must implement these functions - std::ostream &cout() { - return std::cout; - } - std::ostream &cerr() { - return std::cerr; - } - std::ostream &clog() { - return std::clog; - } -#endif -} // namespace Catch -// end catch_stream.cpp -// start catch_string_manip.cpp - -#include -#include -#include -#include -#include - -namespace Catch { - namespace { - char toLowerCh(char c) { - return static_cast(std::tolower(static_cast(c))); - } - } // namespace - - bool startsWith(std::string const &s, std::string const &prefix) { - return s.size() >= prefix.size() && std::equal(prefix.begin(), prefix.end(), s.begin()); - } - bool startsWith(std::string const &s, char prefix) { - return !s.empty() && s[0] == prefix; - } - bool endsWith(std::string const &s, std::string const &suffix) { - return s.size() >= suffix.size() && std::equal(suffix.rbegin(), suffix.rend(), s.rbegin()); - } - bool endsWith(std::string const &s, char suffix) { - return !s.empty() && s[s.size() - 1] == suffix; - } - bool contains(std::string const &s, std::string const &infix) { - return s.find(infix) != std::string::npos; - } - void toLowerInPlace(std::string &s) { - std::transform(s.begin(), s.end(), s.begin(), toLowerCh); - } - std::string toLower(std::string const &s) { - std::string lc = s; - toLowerInPlace(lc); - return lc; - } - std::string trim(std::string const &str) { - static char const *whitespaceChars = "\n\r\t "; - std::string::size_type start = str.find_first_not_of(whitespaceChars); - std::string::size_type end = str.find_last_not_of(whitespaceChars); - - return start != std::string::npos ? str.substr(start, 1 + end - start) : std::string(); - } - - StringRef trim(StringRef ref) { - const auto is_ws = [](char c) { return c == ' ' || c == '\t' || c == '\n' || c == '\r'; }; - size_t real_begin = 0; - while (real_begin < ref.size() && is_ws(ref[real_begin])) { - ++real_begin; - } - size_t real_end = ref.size(); - while (real_end > real_begin && is_ws(ref[real_end - 1])) { - --real_end; - } - - return ref.substr(real_begin, real_end - real_begin); - } - - bool replaceInPlace(std::string &str, std::string const &replaceThis, std::string const &withThis) { - bool replaced = false; - std::size_t i = str.find(replaceThis); - while (i != std::string::npos) { - replaced = true; - str = str.substr(0, i) + withThis + str.substr(i + replaceThis.size()); - if (i < str.size() - withThis.size()) - i = str.find(replaceThis, i + withThis.size()); - else - i = std::string::npos; - } - return replaced; - } - - std::vector splitStringRef(StringRef str, char delimiter) { - std::vector subStrings; - std::size_t start = 0; - for (std::size_t pos = 0; pos < str.size(); ++pos) { - if (str[pos] == delimiter) { - if (pos - start > 1) - subStrings.push_back(str.substr(start, pos - start)); - start = pos + 1; - } - } - if (start < str.size()) - subStrings.push_back(str.substr(start, str.size() - start)); - return subStrings; - } - - pluralise::pluralise(std::size_t count, std::string const &label) - : m_count(count) - , m_label(label) { - } - - std::ostream &operator<<(std::ostream &os, pluralise const &pluraliser) { - os << pluraliser.m_count << ' ' << pluraliser.m_label; - if (pluraliser.m_count != 1) - os << 's'; - return os; - } - -} // namespace Catch -// end catch_string_manip.cpp -// start catch_stringref.cpp - -#include -#include -#include -#include - -namespace Catch { - StringRef::StringRef(char const *rawChars) noexcept - : StringRef(rawChars, static_cast(std::strlen(rawChars))) { - } - - auto StringRef::c_str() const -> char const * { - CATCH_ENFORCE(isNullTerminated(), "Called StringRef::c_str() on a non-null-terminated instance"); - return m_start; - } - auto StringRef::data() const noexcept -> char const * { - return m_start; - } - - auto StringRef::substr(size_type start, size_type size) const noexcept -> StringRef { - if (start < m_size) { - return StringRef(m_start + start, (std::min)(m_size - start, size)); - } else { - return StringRef(); - } - } - auto StringRef::operator==(StringRef const &other) const noexcept -> bool { - return m_size == other.m_size && (std::memcmp(m_start, other.m_start, m_size) == 0); - } - - auto operator<<(std::ostream &os, StringRef const &str) -> std::ostream & { - return os.write(str.data(), str.size()); - } - - auto operator+=(std::string &lhs, StringRef const &rhs) -> std::string & { - lhs.append(rhs.data(), rhs.size()); - return lhs; - } - -} // namespace Catch -// end catch_stringref.cpp -// start catch_tag_alias.cpp - -namespace Catch { - TagAlias::TagAlias(std::string const &_tag, SourceLineInfo _lineInfo) - : tag(_tag) - , lineInfo(_lineInfo) { - } -} // namespace Catch -// end catch_tag_alias.cpp -// start catch_tag_alias_autoregistrar.cpp - -namespace Catch { - RegistrarForTagAliases::RegistrarForTagAliases(char const *alias, char const *tag, SourceLineInfo const &lineInfo) { - CATCH_TRY { getMutableRegistryHub().registerTagAlias(alias, tag, lineInfo); } - CATCH_CATCH_ALL { - // Do not throw when constructing global objects, instead register the exception to be processed later - getMutableRegistryHub().registerStartupException(); - } - } - -} // namespace Catch -// end catch_tag_alias_autoregistrar.cpp -// start catch_tag_alias_registry.cpp - -#include - -namespace Catch { - TagAliasRegistry::~TagAliasRegistry() {} - - TagAlias const *TagAliasRegistry::find(std::string const &alias) const { - auto it = m_registry.find(alias); - if (it != m_registry.end()) - return &(it->second); - else - return nullptr; - } - - std::string TagAliasRegistry::expandAliases(std::string const &unexpandedTestSpec) const { - std::string expandedTestSpec = unexpandedTestSpec; - for (auto const ®istryKvp : m_registry) { - std::size_t pos = expandedTestSpec.find(registryKvp.first); - if (pos != std::string::npos) { - expandedTestSpec - = expandedTestSpec.substr(0, pos) + registryKvp.second.tag + expandedTestSpec.substr(pos + registryKvp.first.size()); - } - } - return expandedTestSpec; - } - - void TagAliasRegistry::add(std::string const &alias, std::string const &tag, SourceLineInfo const &lineInfo) { - CATCH_ENFORCE(startsWith(alias, "[@") && endsWith(alias, ']'), - "error: tag alias, '" << alias << "' is not of the form [@alias name].\n" - << lineInfo); - - CATCH_ENFORCE(m_registry.insert(std::make_pair(alias, TagAlias(tag, lineInfo))).second, - "error: tag alias, '" << alias << "' already registered.\n" - << "\tFirst seen at: " << find(alias)->lineInfo << "\n" - << "\tRedefined at: " << lineInfo); - } - - ITagAliasRegistry::~ITagAliasRegistry() {} - - ITagAliasRegistry const &ITagAliasRegistry::get() { - return getRegistryHub().getTagAliasRegistry(); - } - -} // end namespace Catch -// end catch_tag_alias_registry.cpp -// start catch_test_case_info.cpp - -#include -#include -#include -#include - -namespace Catch { - namespace { - TestCaseInfo::SpecialProperties parseSpecialTag(std::string const &tag) { - if (startsWith(tag, '.') || tag == "!hide") - return TestCaseInfo::IsHidden; - else if (tag == "!throws") - return TestCaseInfo::Throws; - else if (tag == "!shouldfail") - return TestCaseInfo::ShouldFail; - else if (tag == "!mayfail") - return TestCaseInfo::MayFail; - else if (tag == "!nonportable") - return TestCaseInfo::NonPortable; - else if (tag == "!benchmark") - return static_cast(TestCaseInfo::Benchmark | TestCaseInfo::IsHidden); - else - return TestCaseInfo::None; - } - bool isReservedTag(std::string const &tag) { - return parseSpecialTag(tag) == TestCaseInfo::None && tag.size() > 0 && !std::isalnum(static_cast(tag[0])); - } - void enforceNotReservedTag(std::string const &tag, SourceLineInfo const &_lineInfo) { - CATCH_ENFORCE(!isReservedTag(tag), - "Tag name: [" << tag << "] is not allowed.\n" - << "Tag names starting with non alphanumeric characters are reserved\n" - << _lineInfo); - } - } // namespace - - TestCase makeTestCase(ITestInvoker *_testCase, std::string const &_className, NameAndTags const &nameAndTags, SourceLineInfo const &_lineInfo) { - bool isHidden = false; - - // Parse out tags - std::vector tags; - std::string desc, tag; - bool inTag = false; - for (char c : nameAndTags.tags) { - if (!inTag) { - if (c == '[') - inTag = true; - else - desc += c; - } else { - if (c == ']') { - TestCaseInfo::SpecialProperties prop = parseSpecialTag(tag); - if ((prop & TestCaseInfo::IsHidden) != 0) - isHidden = true; - else if (prop == TestCaseInfo::None) - enforceNotReservedTag(tag, _lineInfo); - - // Merged hide tags like `[.approvals]` should be added as - // `[.][approvals]`. The `[.]` is added at later point, so - // we only strip the prefix - if (startsWith(tag, '.') && tag.size() > 1) { - tag.erase(0, 1); - } - tags.push_back(tag); - tag.clear(); - inTag = false; - } else - tag += c; - } - } - if (isHidden) { - // Add all "hidden" tags to make them behave identically - tags.insert(tags.end(), {".", "!hide"}); - } - - TestCaseInfo info(static_cast(nameAndTags.name), _className, desc, tags, _lineInfo); - return TestCase(_testCase, std::move(info)); - } - - void setTags(TestCaseInfo &testCaseInfo, std::vector tags) { - std::sort(begin(tags), end(tags)); - tags.erase(std::unique(begin(tags), end(tags)), end(tags)); - testCaseInfo.lcaseTags.clear(); - - for (auto const &tag : tags) { - std::string lcaseTag = toLower(tag); - testCaseInfo.properties = static_cast(testCaseInfo.properties | parseSpecialTag(lcaseTag)); - testCaseInfo.lcaseTags.push_back(lcaseTag); - } - testCaseInfo.tags = std::move(tags); - } - - TestCaseInfo::TestCaseInfo(std::string const &_name, - std::string const &_className, - std::string const &_description, - std::vector const &_tags, - SourceLineInfo const &_lineInfo) - : name(_name) - , className(_className) - , description(_description) - , lineInfo(_lineInfo) - , properties(None) { - setTags(*this, _tags); - } - - bool TestCaseInfo::isHidden() const { - return (properties & IsHidden) != 0; - } - bool TestCaseInfo::throws() const { - return (properties & Throws) != 0; - } - bool TestCaseInfo::okToFail() const { - return (properties & (ShouldFail | MayFail)) != 0; - } - bool TestCaseInfo::expectedToFail() const { - return (properties & (ShouldFail)) != 0; - } - - std::string TestCaseInfo::tagsAsString() const { - std::string ret; - // '[' and ']' per tag - std::size_t full_size = 2 * tags.size(); - for (const auto &tag : tags) { - full_size += tag.size(); - } - ret.reserve(full_size); - for (const auto &tag : tags) { - ret.push_back('['); - ret.append(tag); - ret.push_back(']'); - } - - return ret; - } - - TestCase::TestCase(ITestInvoker *testCase, TestCaseInfo &&info) - : TestCaseInfo(std::move(info)) - , test(testCase) { - } - - TestCase TestCase::withName(std::string const &_newName) const { - TestCase other(*this); - other.name = _newName; - return other; - } - - void TestCase::invoke() const { - test->invoke(); - } - - bool TestCase::operator==(TestCase const &other) const { - return test.get() == other.test.get() && name == other.name && className == other.className; - } - - bool TestCase::operator<(TestCase const &other) const { - return name < other.name; - } - - TestCaseInfo const &TestCase::getTestCaseInfo() const { - return *this; - } - -} // end namespace Catch -// end catch_test_case_info.cpp -// start catch_test_case_registry_impl.cpp - -#include -#include - -namespace Catch { - namespace { - struct TestHasher { - using hash_t = uint64_t; - - explicit TestHasher(hash_t hashSuffix) - : m_hashSuffix{hashSuffix} { - } - - uint32_t operator()(TestCase const &t) const { - // FNV-1a hash with multiplication fold. - const hash_t prime = 1099511628211u; - hash_t hash = 14695981039346656037u; - for (const char c : t.name) { - hash ^= c; - hash *= prime; - } - hash ^= m_hashSuffix; - hash *= prime; - const uint32_t low{static_cast(hash)}; - const uint32_t high{static_cast(hash >> 32)}; - return low * high; - } - - private: - hash_t m_hashSuffix; - }; - } // end unnamed namespace - - std::vector sortTests(IConfig const &config, std::vector const &unsortedTestCases) { - switch (config.runOrder()) { - case RunTests::InDeclarationOrder: - // already in declaration order - break; - - case RunTests::InLexicographicalOrder: { - std::vector sorted = unsortedTestCases; - std::sort(sorted.begin(), sorted.end()); - return sorted; - } - - case RunTests::InRandomOrder: { - seedRng(config); - TestHasher h{config.rngSeed()}; - - using hashedTest = std::pair; - std::vector indexed_tests; - indexed_tests.reserve(unsortedTestCases.size()); - - for (auto const &testCase : unsortedTestCases) { - indexed_tests.emplace_back(h(testCase), &testCase); - } - - std::sort(indexed_tests.begin(), indexed_tests.end(), [](hashedTest const &lhs, hashedTest const &rhs) { - if (lhs.first == rhs.first) { - return lhs.second->name < rhs.second->name; - } - return lhs.first < rhs.first; - }); - - std::vector sorted; - sorted.reserve(indexed_tests.size()); - - for (auto const &hashed : indexed_tests) { - sorted.emplace_back(*hashed.second); - } - - return sorted; - } - } - return unsortedTestCases; - } - - bool isThrowSafe(TestCase const &testCase, IConfig const &config) { - return !testCase.throws() || config.allowThrows(); - } - - bool matchTest(TestCase const &testCase, TestSpec const &testSpec, IConfig const &config) { - return testSpec.matches(testCase) && isThrowSafe(testCase, config); - } - - void enforceNoDuplicateTestCases(std::vector const &functions) { - std::set seenFunctions; - for (auto const &function : functions) { - auto prev = seenFunctions.insert(function); - CATCH_ENFORCE(prev.second, - "error: TEST_CASE( \"" << function.name << "\" ) already defined.\n" - << "\tFirst seen at " << prev.first->getTestCaseInfo().lineInfo << "\n" - << "\tRedefined at " << function.getTestCaseInfo().lineInfo); - } - } - - std::vector filterTests(std::vector const &testCases, TestSpec const &testSpec, IConfig const &config) { - std::vector filtered; - filtered.reserve(testCases.size()); - for (auto const &testCase : testCases) { - if ((!testSpec.hasFilters() && !testCase.isHidden()) || (testSpec.hasFilters() && matchTest(testCase, testSpec, config))) { - filtered.push_back(testCase); - } - } - return filtered; - } - std::vector const &getAllTestCasesSorted(IConfig const &config) { - return getRegistryHub().getTestCaseRegistry().getAllTestsSorted(config); - } - - void TestRegistry::registerTest(TestCase const &testCase) { - std::string name = testCase.getTestCaseInfo().name; - if (name.empty()) { - ReusableStringStream rss; - rss << "Anonymous test case " << ++m_unnamedCount; - return registerTest(testCase.withName(rss.str())); - } - m_functions.push_back(testCase); - } - - std::vector const &TestRegistry::getAllTests() const { - return m_functions; - } - std::vector const &TestRegistry::getAllTestsSorted(IConfig const &config) const { - if (m_sortedFunctions.empty()) - enforceNoDuplicateTestCases(m_functions); - - if (m_currentSortOrder != config.runOrder() || m_sortedFunctions.empty()) { - m_sortedFunctions = sortTests(config, m_functions); - m_currentSortOrder = config.runOrder(); - } - return m_sortedFunctions; - } - - /////////////////////////////////////////////////////////////////////////// - TestInvokerAsFunction::TestInvokerAsFunction(void (*testAsFunction)()) noexcept - : m_testAsFunction(testAsFunction) { - } - - void TestInvokerAsFunction::invoke() const { - m_testAsFunction(); - } - - std::string extractClassName(StringRef const &classOrQualifiedMethodName) { - std::string className(classOrQualifiedMethodName); - if (startsWith(className, '&')) { - std::size_t lastColons = className.rfind("::"); - std::size_t penultimateColons = className.rfind("::", lastColons - 1); - if (penultimateColons == std::string::npos) - penultimateColons = 1; - className = className.substr(penultimateColons, lastColons - penultimateColons); - } - return className; - } - -} // end namespace Catch -// end catch_test_case_registry_impl.cpp -// start catch_test_case_tracker.cpp - -#include -#include -#include -#include -#include - -#if defined(__clang__) -#pragma clang diagnostic push -#pragma clang diagnostic ignored "-Wexit-time-destructors" -#endif - -namespace Catch { - namespace TestCaseTracking { - NameAndLocation::NameAndLocation(std::string const &_name, SourceLineInfo const &_location) - : name(_name) - , location(_location) { - } - - ITracker::~ITracker() = default; - - ITracker &TrackerContext::startRun() { - m_rootTracker = std::make_shared(NameAndLocation("{root}", CATCH_INTERNAL_LINEINFO), *this, nullptr); - m_currentTracker = nullptr; - m_runState = Executing; - return *m_rootTracker; - } - - void TrackerContext::endRun() { - m_rootTracker.reset(); - m_currentTracker = nullptr; - m_runState = NotStarted; - } - - void TrackerContext::startCycle() { - m_currentTracker = m_rootTracker.get(); - m_runState = Executing; - } - void TrackerContext::completeCycle() { - m_runState = CompletedCycle; - } - - bool TrackerContext::completedCycle() const { - return m_runState == CompletedCycle; - } - ITracker &TrackerContext::currentTracker() { - return *m_currentTracker; - } - void TrackerContext::setCurrentTracker(ITracker *tracker) { - m_currentTracker = tracker; - } - - TrackerBase::TrackerBase(NameAndLocation const &nameAndLocation, TrackerContext &ctx, ITracker *parent) - : ITracker(nameAndLocation) - , m_ctx(ctx) - , m_parent(parent) { - } - - bool TrackerBase::isComplete() const { - return m_runState == CompletedSuccessfully || m_runState == Failed; - } - bool TrackerBase::isSuccessfullyCompleted() const { - return m_runState == CompletedSuccessfully; - } - bool TrackerBase::isOpen() const { - return m_runState != NotStarted && !isComplete(); - } - bool TrackerBase::hasChildren() const { - return !m_children.empty(); - } - - void TrackerBase::addChild(ITrackerPtr const &child) { - m_children.push_back(child); - } - - ITrackerPtr TrackerBase::findChild(NameAndLocation const &nameAndLocation) { - auto it = std::find_if(m_children.begin(), m_children.end(), [&nameAndLocation](ITrackerPtr const &tracker) { - return tracker->nameAndLocation().location == nameAndLocation.location && tracker->nameAndLocation().name == nameAndLocation.name; - }); - return (it != m_children.end()) ? *it : nullptr; - } - ITracker &TrackerBase::parent() { - assert(m_parent); // Should always be non-null except for root - return *m_parent; - } - - void TrackerBase::openChild() { - if (m_runState != ExecutingChildren) { - m_runState = ExecutingChildren; - if (m_parent) - m_parent->openChild(); - } - } - - bool TrackerBase::isSectionTracker() const { - return false; - } - bool TrackerBase::isGeneratorTracker() const { - return false; - } - - void TrackerBase::open() { - m_runState = Executing; - moveToThis(); - if (m_parent) - m_parent->openChild(); - } - - void TrackerBase::close() { - // Close any still open children (e.g. generators) - while (&m_ctx.currentTracker() != this) - m_ctx.currentTracker().close(); - - switch (m_runState) { - case NeedsAnotherRun: - break; - - case Executing: - m_runState = CompletedSuccessfully; - break; - case ExecutingChildren: - if (std::all_of(m_children.begin(), m_children.end(), [](ITrackerPtr const &t) { return t->isComplete(); })) - m_runState = CompletedSuccessfully; - break; - - case NotStarted: - case CompletedSuccessfully: - case Failed: - CATCH_INTERNAL_ERROR("Illogical state: " << m_runState); - - default: - CATCH_INTERNAL_ERROR("Unknown state: " << m_runState); - } - moveToParent(); - m_ctx.completeCycle(); - } - void TrackerBase::fail() { - m_runState = Failed; - if (m_parent) - m_parent->markAsNeedingAnotherRun(); - moveToParent(); - m_ctx.completeCycle(); - } - void TrackerBase::markAsNeedingAnotherRun() { - m_runState = NeedsAnotherRun; - } - - void TrackerBase::moveToParent() { - assert(m_parent); - m_ctx.setCurrentTracker(m_parent); - } - void TrackerBase::moveToThis() { - m_ctx.setCurrentTracker(this); - } - - SectionTracker::SectionTracker(NameAndLocation const &nameAndLocation, TrackerContext &ctx, ITracker *parent) - : TrackerBase(nameAndLocation, ctx, parent) - , m_trimmed_name(trim(nameAndLocation.name)) { - if (parent) { - while (!parent->isSectionTracker()) - parent = &parent->parent(); - - SectionTracker &parentSection = static_cast(*parent); - addNextFilters(parentSection.m_filters); - } - } - - bool SectionTracker::isComplete() const { - bool complete = true; - - if (m_filters.empty() || m_filters[0] == "" || std::find(m_filters.begin(), m_filters.end(), m_trimmed_name) != m_filters.end()) { - complete = TrackerBase::isComplete(); - } - return complete; - } - - bool SectionTracker::isSectionTracker() const { - return true; - } - - SectionTracker &SectionTracker::acquire(TrackerContext &ctx, NameAndLocation const &nameAndLocation) { - std::shared_ptr section; - - ITracker ¤tTracker = ctx.currentTracker(); - if (ITrackerPtr childTracker = currentTracker.findChild(nameAndLocation)) { - assert(childTracker); - assert(childTracker->isSectionTracker()); - section = std::static_pointer_cast(childTracker); - } else { - section = std::make_shared(nameAndLocation, ctx, ¤tTracker); - currentTracker.addChild(section); - } - if (!ctx.completedCycle()) - section->tryOpen(); - return *section; - } - - void SectionTracker::tryOpen() { - if (!isComplete()) - open(); - } - - void SectionTracker::addInitialFilters(std::vector const &filters) { - if (!filters.empty()) { - m_filters.reserve(m_filters.size() + filters.size() + 2); - m_filters.emplace_back(""); // Root - should never be consulted - m_filters.emplace_back(""); // Test Case - not a section filter - m_filters.insert(m_filters.end(), filters.begin(), filters.end()); - } - } - void SectionTracker::addNextFilters(std::vector const &filters) { - if (filters.size() > 1) - m_filters.insert(m_filters.end(), filters.begin() + 1, filters.end()); - } - - std::vector const &SectionTracker::getFilters() const { - return m_filters; - } - - std::string const &SectionTracker::trimmedName() const { - return m_trimmed_name; - } - - } // namespace TestCaseTracking - - using TestCaseTracking::ITracker; - using TestCaseTracking::SectionTracker; - using TestCaseTracking::TrackerContext; - -} // namespace Catch - -#if defined(__clang__) -#pragma clang diagnostic pop -#endif -// end catch_test_case_tracker.cpp -// start catch_test_registry.cpp - -namespace Catch { - auto makeTestInvoker(void (*testAsFunction)()) noexcept -> ITestInvoker * { - return new (std::nothrow) TestInvokerAsFunction(testAsFunction); - } - - NameAndTags::NameAndTags(StringRef const &name_, StringRef const &tags_) noexcept - : name(name_) - , tags(tags_) { - } - - AutoReg::AutoReg(ITestInvoker *invoker, SourceLineInfo const &lineInfo, StringRef const &classOrMethod, NameAndTags const &nameAndTags) noexcept { - CATCH_TRY { getMutableRegistryHub().registerTest(makeTestCase(invoker, extractClassName(classOrMethod), nameAndTags, lineInfo)); } - CATCH_CATCH_ALL { - // Do not throw when constructing global objects, instead register the exception to be processed later - getMutableRegistryHub().registerStartupException(); - } - } - - AutoReg::~AutoReg() = default; -} // namespace Catch -// end catch_test_registry.cpp -// start catch_test_spec.cpp - -#include -#include -#include -#include - -namespace Catch { - TestSpec::Pattern::Pattern(std::string const &name) - : m_name(name) { - } - - TestSpec::Pattern::~Pattern() = default; - - std::string const &TestSpec::Pattern::name() const { - return m_name; - } - - TestSpec::NamePattern::NamePattern(std::string const &name, std::string const &filterString) - : Pattern(filterString) - , m_wildcardPattern(toLower(name), CaseSensitive::No) { - } - - bool TestSpec::NamePattern::matches(TestCaseInfo const &testCase) const { - return m_wildcardPattern.matches(testCase.name); - } - - TestSpec::TagPattern::TagPattern(std::string const &tag, std::string const &filterString) - : Pattern(filterString) - , m_tag(toLower(tag)) { - } - - bool TestSpec::TagPattern::matches(TestCaseInfo const &testCase) const { - return std::find(begin(testCase.lcaseTags), end(testCase.lcaseTags), m_tag) != end(testCase.lcaseTags); - } - - TestSpec::ExcludedPattern::ExcludedPattern(PatternPtr const &underlyingPattern) - : Pattern(underlyingPattern->name()) - , m_underlyingPattern(underlyingPattern) { - } - - bool TestSpec::ExcludedPattern::matches(TestCaseInfo const &testCase) const { - return !m_underlyingPattern->matches(testCase); - } - - bool TestSpec::Filter::matches(TestCaseInfo const &testCase) const { - return std::all_of(m_patterns.begin(), m_patterns.end(), [&](PatternPtr const &p) { return p->matches(testCase); }); - } - - std::string TestSpec::Filter::name() const { - std::string name; - for (auto const &p : m_patterns) - name += p->name(); - return name; - } - - bool TestSpec::hasFilters() const { - return !m_filters.empty(); - } - - bool TestSpec::matches(TestCaseInfo const &testCase) const { - return std::any_of(m_filters.begin(), m_filters.end(), [&](Filter const &f) { return f.matches(testCase); }); - } - - TestSpec::Matches TestSpec::matchesByFilter(std::vector const &testCases, IConfig const &config) const { - Matches matches(m_filters.size()); - std::transform(m_filters.begin(), m_filters.end(), matches.begin(), [&](Filter const &filter) { - std::vector currentMatches; - for (auto const &test : testCases) - if (isThrowSafe(test, config) && filter.matches(test)) - currentMatches.emplace_back(&test); - return FilterMatch{filter.name(), currentMatches}; - }); - return matches; - } - - const TestSpec::vectorStrings &TestSpec::getInvalidArgs() const { - return (m_invalidArgs); - } - -} // namespace Catch -// end catch_test_spec.cpp -// start catch_test_spec_parser.cpp - -namespace Catch { - TestSpecParser::TestSpecParser(ITagAliasRegistry const &tagAliases) - : m_tagAliases(&tagAliases) { - } - - TestSpecParser &TestSpecParser::parse(std::string const &arg) { - m_mode = None; - m_exclusion = false; - m_arg = m_tagAliases->expandAliases(arg); - m_escapeChars.clear(); - m_substring.reserve(m_arg.size()); - m_patternName.reserve(m_arg.size()); - m_realPatternPos = 0; - - for (m_pos = 0; m_pos < m_arg.size(); ++m_pos) - // if visitChar fails - if (!visitChar(m_arg[m_pos])) { - m_testSpec.m_invalidArgs.push_back(arg); - break; - } - endMode(); - return *this; - } - TestSpec TestSpecParser::testSpec() { - addFilter(); - return m_testSpec; - } - bool TestSpecParser::visitChar(char c) { - if ((m_mode != EscapedName) && (c == '\\')) { - escape(); - addCharToPattern(c); - return true; - } else if ((m_mode != EscapedName) && (c == ',')) { - return separate(); - } - - switch (m_mode) { - case None: - if (processNoneChar(c)) - return true; - break; - case Name: - processNameChar(c); - break; - case EscapedName: - endMode(); - addCharToPattern(c); - return true; - default: - case Tag: - case QuotedName: - if (processOtherChar(c)) - return true; - break; - } - - m_substring += c; - if (!isControlChar(c)) { - m_patternName += c; - m_realPatternPos++; - } - return true; - } - // Two of the processing methods return true to signal the caller to return - // without adding the given character to the current pattern strings - bool TestSpecParser::processNoneChar(char c) { - switch (c) { - case ' ': - return true; - case '~': - m_exclusion = true; - return false; - case '[': - startNewMode(Tag); - return false; - case '"': - startNewMode(QuotedName); - return false; - default: - startNewMode(Name); - return false; - } - } - void TestSpecParser::processNameChar(char c) { - if (c == '[') { - if (m_substring == "exclude:") - m_exclusion = true; - else - endMode(); - startNewMode(Tag); - } - } - bool TestSpecParser::processOtherChar(char c) { - if (!isControlChar(c)) - return false; - m_substring += c; - endMode(); - return true; - } - void TestSpecParser::startNewMode(Mode mode) { - m_mode = mode; - } - void TestSpecParser::endMode() { - switch (m_mode) { - case Name: - case QuotedName: - return addNamePattern(); - case Tag: - return addTagPattern(); - case EscapedName: - revertBackToLastMode(); - return; - case None: - default: - return startNewMode(None); - } - } - void TestSpecParser::escape() { - saveLastMode(); - m_mode = EscapedName; - m_escapeChars.push_back(m_realPatternPos); - } - bool TestSpecParser::isControlChar(char c) const { - switch (m_mode) { - default: - return false; - case None: - return c == '~'; - case Name: - return c == '['; - case EscapedName: - return true; - case QuotedName: - return c == '"'; - case Tag: - return c == '[' || c == ']'; - } - } - - void TestSpecParser::addFilter() { - if (!m_currentFilter.m_patterns.empty()) { - m_testSpec.m_filters.push_back(m_currentFilter); - m_currentFilter = TestSpec::Filter(); - } - } - - void TestSpecParser::saveLastMode() { - lastMode = m_mode; - } - - void TestSpecParser::revertBackToLastMode() { - m_mode = lastMode; - } - - bool TestSpecParser::separate() { - if ((m_mode == QuotedName) || (m_mode == Tag)) { - // invalid argument, signal failure to previous scope. - m_mode = None; - m_pos = m_arg.size(); - m_substring.clear(); - m_patternName.clear(); - m_realPatternPos = 0; - return false; - } - endMode(); - addFilter(); - return true; // success - } - - std::string TestSpecParser::preprocessPattern() { - std::string token = m_patternName; - for (std::size_t i = 0; i < m_escapeChars.size(); ++i) - token = token.substr(0, m_escapeChars[i] - i) + token.substr(m_escapeChars[i] - i + 1); - m_escapeChars.clear(); - if (startsWith(token, "exclude:")) { - m_exclusion = true; - token = token.substr(8); - } - - m_patternName.clear(); - m_realPatternPos = 0; - - return token; - } - - void TestSpecParser::addNamePattern() { - auto token = preprocessPattern(); - - if (!token.empty()) { - TestSpec::PatternPtr pattern = std::make_shared(token, m_substring); - if (m_exclusion) - pattern = std::make_shared(pattern); - m_currentFilter.m_patterns.push_back(pattern); - } - m_substring.clear(); - m_exclusion = false; - m_mode = None; - } - - void TestSpecParser::addTagPattern() { - auto token = preprocessPattern(); - - if (!token.empty()) { - // If the tag pattern is the "hide and tag" shorthand (e.g. [.foo]) - // we have to create a separate hide tag and shorten the real one - if (token.size() > 1 && token[0] == '.') { - token.erase(token.begin()); - TestSpec::PatternPtr pattern = std::make_shared(".", m_substring); - if (m_exclusion) { - pattern = std::make_shared(pattern); - } - m_currentFilter.m_patterns.push_back(pattern); - } - - TestSpec::PatternPtr pattern = std::make_shared(token, m_substring); - - if (m_exclusion) { - pattern = std::make_shared(pattern); - } - m_currentFilter.m_patterns.push_back(pattern); - } - m_substring.clear(); - m_exclusion = false; - m_mode = None; - } - - TestSpec parseTestSpec(std::string const &arg) { - return TestSpecParser(ITagAliasRegistry::get()).parse(arg).testSpec(); - } - -} // namespace Catch -// end catch_test_spec_parser.cpp -// start catch_timer.cpp - -#include - -static const uint64_t nanosecondsInSecond = 1000000000; - -namespace Catch { - auto getCurrentNanosecondsSinceEpoch() -> uint64_t { - return std::chrono::duration_cast(std::chrono::high_resolution_clock::now().time_since_epoch()).count(); - } - - namespace { - auto estimateClockResolution() -> uint64_t { - uint64_t sum = 0; - static const uint64_t iterations = 1000000; - - auto startTime = getCurrentNanosecondsSinceEpoch(); - - for (std::size_t i = 0; i < iterations; ++i) { - uint64_t ticks; - uint64_t baseTicks = getCurrentNanosecondsSinceEpoch(); - do { - ticks = getCurrentNanosecondsSinceEpoch(); - } while (ticks == baseTicks); - - auto delta = ticks - baseTicks; - sum += delta; - - // If we have been calibrating for over 3 seconds -- the clock - // is terrible and we should move on. - // TBD: How to signal that the measured resolution is probably wrong? - if (ticks > startTime + 3 * nanosecondsInSecond) { - return sum / (i + 1u); - } - } - - // We're just taking the mean, here. To do better we could take the std. dev and exclude outliers - // - and potentially do more iterations if there's a high variance. - return sum / iterations; - } - } // namespace - auto getEstimatedClockResolution() -> uint64_t { - static auto s_resolution = estimateClockResolution(); - return s_resolution; - } - - void Timer::start() { - m_nanoseconds = getCurrentNanosecondsSinceEpoch(); - } - auto Timer::getElapsedNanoseconds() const -> uint64_t { - return getCurrentNanosecondsSinceEpoch() - m_nanoseconds; - } - auto Timer::getElapsedMicroseconds() const -> uint64_t { - return getElapsedNanoseconds() / 1000; - } - auto Timer::getElapsedMilliseconds() const -> unsigned int { - return static_cast(getElapsedMicroseconds() / 1000); - } - auto Timer::getElapsedSeconds() const -> double { - return getElapsedMicroseconds() / 1000000.0; - } - -} // namespace Catch -// end catch_timer.cpp -// start catch_tostring.cpp - -#if defined(__clang__) -#pragma clang diagnostic push -#pragma clang diagnostic ignored "-Wexit-time-destructors" -#pragma clang diagnostic ignored "-Wglobal-constructors" -#endif - -// Enable specific decls locally -#if !defined(CATCH_CONFIG_ENABLE_CHRONO_STRINGMAKER) -#define CATCH_CONFIG_ENABLE_CHRONO_STRINGMAKER -#endif - -#include -#include - -namespace Catch { - namespace Detail { - const std::string unprintableString = "{?}"; - - namespace { - const int hexThreshold = 255; - - struct Endianness { - enum Arch { - Big, - Little - }; - - static Arch which() { - int one = 1; - // If the lowest byte we read is non-zero, we can assume - // that little endian format is used. - auto value = *reinterpret_cast(&one); - return value ? Little : Big; - } - }; - } // namespace - - std::string rawMemoryToString(const void *object, std::size_t size) { - // Reverse order for little endian architectures - int i = 0, end = static_cast(size), inc = 1; - if (Endianness::which() == Endianness::Little) { - i = end - 1; - end = inc = -1; - } - - unsigned char const *bytes = static_cast(object); - ReusableStringStream rss; - rss << "0x" << std::setfill('0') << std::hex; - for (; i != end; i += inc) - rss << std::setw(2) << static_cast(bytes[i]); - return rss.str(); - } - } // namespace Detail - - template - std::string fpToString(T value, int precision) { - if (Catch::isnan(value)) { - return "nan"; - } - - ReusableStringStream rss; - rss << std::setprecision(precision) << std::fixed << value; - std::string d = rss.str(); - std::size_t i = d.find_last_not_of('0'); - if (i != std::string::npos && i != d.size() - 1) { - if (d[i] == '.') - i++; - d = d.substr(0, i + 1); - } - return d; - } - - //// ======================================================= //// - // - // Out-of-line defs for full specialization of StringMaker - // - //// ======================================================= //// - - std::string StringMaker::convert(const std::string &str) { - if (!getCurrentContext().getConfig()->showInvisibles()) { - return '"' + str + '"'; - } - - std::string s("\""); - for (char c : str) { - switch (c) { - case '\n': - s.append("\\n"); - break; - case '\t': - s.append("\\t"); - break; - default: - s.push_back(c); - break; - } - } - s.append("\""); - return s; - } - -#ifdef CATCH_CONFIG_CPP17_STRING_VIEW - std::string StringMaker::convert(std::string_view str) { - return ::Catch::Detail::stringify(std::string{str}); - } -#endif - - std::string StringMaker::convert(char const *str) { - if (str) { - return ::Catch::Detail::stringify(std::string{str}); - } else { - return {"{null string}"}; - } - } - std::string StringMaker::convert(char *str) { - if (str) { - return ::Catch::Detail::stringify(std::string{str}); - } else { - return {"{null string}"}; - } - } - -#ifdef CATCH_CONFIG_WCHAR - std::string StringMaker::convert(const std::wstring &wstr) { - std::string s; - s.reserve(wstr.size()); - for (auto c : wstr) { - s += (c <= 0xff) ? static_cast(c) : '?'; - } - return ::Catch::Detail::stringify(s); - } - -#ifdef CATCH_CONFIG_CPP17_STRING_VIEW - std::string StringMaker::convert(std::wstring_view str) { - return StringMaker::convert(std::wstring(str)); - } -#endif - - std::string StringMaker::convert(wchar_t const *str) { - if (str) { - return ::Catch::Detail::stringify(std::wstring{str}); - } else { - return {"{null string}"}; - } - } - std::string StringMaker::convert(wchar_t *str) { - if (str) { - return ::Catch::Detail::stringify(std::wstring{str}); - } else { - return {"{null string}"}; - } - } -#endif - -#if defined(CATCH_CONFIG_CPP17_BYTE) -#include - std::string StringMaker::convert(std::byte value) { - return ::Catch::Detail::stringify(std::to_integer(value)); - } -#endif // defined(CATCH_CONFIG_CPP17_BYTE) - - std::string StringMaker::convert(int value) { - return ::Catch::Detail::stringify(static_cast(value)); - } - std::string StringMaker::convert(long value) { - return ::Catch::Detail::stringify(static_cast(value)); - } - std::string StringMaker::convert(long long value) { - ReusableStringStream rss; - rss << value; - if (value > Detail::hexThreshold) { - rss << " (0x" << std::hex << value << ')'; - } - return rss.str(); - } - - std::string StringMaker::convert(unsigned int value) { - return ::Catch::Detail::stringify(static_cast(value)); - } - std::string StringMaker::convert(unsigned long value) { - return ::Catch::Detail::stringify(static_cast(value)); - } - std::string StringMaker::convert(unsigned long long value) { - ReusableStringStream rss; - rss << value; - if (value > Detail::hexThreshold) { - rss << " (0x" << std::hex << value << ')'; - } - return rss.str(); - } - - std::string StringMaker::convert(bool b) { - return b ? "true" : "false"; - } - - std::string StringMaker::convert(signed char value) { - if (value == '\r') { - return "'\\r'"; - } else if (value == '\f') { - return "'\\f'"; - } else if (value == '\n') { - return "'\\n'"; - } else if (value == '\t') { - return "'\\t'"; - } else if ('\0' <= value && value < ' ') { - return ::Catch::Detail::stringify(static_cast(value)); - } else { - char chstr[] = "' '"; - chstr[1] = value; - return chstr; - } - } - std::string StringMaker::convert(char c) { - return ::Catch::Detail::stringify(static_cast(c)); - } - std::string StringMaker::convert(unsigned char c) { - return ::Catch::Detail::stringify(static_cast(c)); - } - - std::string StringMaker::convert(std::nullptr_t) { - return "nullptr"; - } - - int StringMaker::precision = 5; - - std::string StringMaker::convert(float value) { - return fpToString(value, precision) + 'f'; - } - - int StringMaker::precision = 10; - - std::string StringMaker::convert(double value) { - return fpToString(value, precision); - } - - std::string ratio_string::symbol() { - return "a"; - } - std::string ratio_string::symbol() { - return "f"; - } - std::string ratio_string::symbol() { - return "p"; - } - std::string ratio_string::symbol() { - return "n"; - } - std::string ratio_string::symbol() { - return "u"; - } - std::string ratio_string::symbol() { - return "m"; - } - -} // end namespace Catch - -#if defined(__clang__) -#pragma clang diagnostic pop -#endif - -// end catch_tostring.cpp -// start catch_totals.cpp - -namespace Catch { - Counts Counts::operator-(Counts const &other) const { - Counts diff; - diff.passed = passed - other.passed; - diff.failed = failed - other.failed; - diff.failedButOk = failedButOk - other.failedButOk; - return diff; - } - - Counts &Counts::operator+=(Counts const &other) { - passed += other.passed; - failed += other.failed; - failedButOk += other.failedButOk; - return *this; - } - - std::size_t Counts::total() const { - return passed + failed + failedButOk; - } - bool Counts::allPassed() const { - return failed == 0 && failedButOk == 0; - } - bool Counts::allOk() const { - return failed == 0; - } - - Totals Totals::operator-(Totals const &other) const { - Totals diff; - diff.assertions = assertions - other.assertions; - diff.testCases = testCases - other.testCases; - return diff; - } - - Totals &Totals::operator+=(Totals const &other) { - assertions += other.assertions; - testCases += other.testCases; - return *this; - } - - Totals Totals::delta(Totals const &prevTotals) const { - Totals diff = *this - prevTotals; - if (diff.assertions.failed > 0) - ++diff.testCases.failed; - else if (diff.assertions.failedButOk > 0) - ++diff.testCases.failedButOk; - else - ++diff.testCases.passed; - return diff; - } - -} // namespace Catch -// end catch_totals.cpp -// start catch_uncaught_exceptions.cpp - -// start catch_config_uncaught_exceptions.hpp - -// Copyright Catch2 Authors -// Distributed under the Boost Software License, Version 1.0. -// (See accompanying file LICENSE_1_0.txt or copy at -// https://www.boost.org/LICENSE_1_0.txt) - -// SPDX-License-Identifier: BSL-1.0 - -#ifndef CATCH_CONFIG_UNCAUGHT_EXCEPTIONS_HPP -#define CATCH_CONFIG_UNCAUGHT_EXCEPTIONS_HPP - -#if defined(_MSC_VER) -#if _MSC_VER >= 1900 // Visual Studio 2015 or newer -#define CATCH_INTERNAL_CONFIG_CPP17_UNCAUGHT_EXCEPTIONS -#endif -#endif - -#include - -#if defined(__cpp_lib_uncaught_exceptions) && !defined(CATCH_INTERNAL_CONFIG_CPP17_UNCAUGHT_EXCEPTIONS) - -#define CATCH_INTERNAL_CONFIG_CPP17_UNCAUGHT_EXCEPTIONS -#endif // __cpp_lib_uncaught_exceptions - -#if defined(CATCH_INTERNAL_CONFIG_CPP17_UNCAUGHT_EXCEPTIONS) && !defined(CATCH_CONFIG_NO_CPP17_UNCAUGHT_EXCEPTIONS) \ - && !defined(CATCH_CONFIG_CPP17_UNCAUGHT_EXCEPTIONS) - -#define CATCH_CONFIG_CPP17_UNCAUGHT_EXCEPTIONS -#endif - -#endif // CATCH_CONFIG_UNCAUGHT_EXCEPTIONS_HPP \ - // end catch_config_uncaught_exceptions.hpp -#include - -namespace Catch { - bool uncaught_exceptions() { -#if defined(CATCH_CONFIG_DISABLE_EXCEPTIONS) - return false; -#elif defined(CATCH_CONFIG_CPP17_UNCAUGHT_EXCEPTIONS) - return std::uncaught_exceptions() > 0; -#else - return std::uncaught_exception(); -#endif - } -} // end namespace Catch -// end catch_uncaught_exceptions.cpp -// start catch_version.cpp - -#include - -namespace Catch { - Version::Version(unsigned int _majorVersion, unsigned int _minorVersion, unsigned int _patchNumber, char const *const _branchName, unsigned int _buildNumber) - : majorVersion(_majorVersion) - , minorVersion(_minorVersion) - , patchNumber(_patchNumber) - , branchName(_branchName) - , buildNumber(_buildNumber) { - } - - std::ostream &operator<<(std::ostream &os, Version const &version) { - os << version.majorVersion << '.' << version.minorVersion << '.' << version.patchNumber; - // branchName is never null -> 0th char is \0 if it is empty - if (version.branchName[0]) { - os << '-' << version.branchName << '.' << version.buildNumber; - } - return os; - } - - Version const &libraryVersion() { - static Version version(2, 13, 6, "", 0); - return version; - } - -} // namespace Catch -// end catch_version.cpp -// start catch_wildcard_pattern.cpp - -namespace Catch { - WildcardPattern::WildcardPattern(std::string const &pattern, CaseSensitive::Choice caseSensitivity) - : m_caseSensitivity(caseSensitivity) - , m_pattern(normaliseString(pattern)) { - if (startsWith(m_pattern, '*')) { - m_pattern = m_pattern.substr(1); - m_wildcard = WildcardAtStart; - } - if (endsWith(m_pattern, '*')) { - m_pattern = m_pattern.substr(0, m_pattern.size() - 1); - m_wildcard = static_cast(m_wildcard | WildcardAtEnd); - } - } - - bool WildcardPattern::matches(std::string const &str) const { - switch (m_wildcard) { - case NoWildcard: - return m_pattern == normaliseString(str); - case WildcardAtStart: - return endsWith(normaliseString(str), m_pattern); - case WildcardAtEnd: - return startsWith(normaliseString(str), m_pattern); - case WildcardAtBothEnds: - return contains(normaliseString(str), m_pattern); - default: - CATCH_INTERNAL_ERROR("Unknown enum"); - } - } - - std::string WildcardPattern::normaliseString(std::string const &str) const { - return trim(m_caseSensitivity == CaseSensitive::No ? toLower(str) : str); - } -} // namespace Catch -// end catch_wildcard_pattern.cpp -// start catch_xmlwriter.cpp - -#include -#include - -namespace Catch { - 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; - } - CATCH_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; - } - CATCH_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); - } - - bool shouldNewline(XmlFormatting fmt) { - return !!(static_cast::type>(fmt & XmlFormatting::Newline)); - } - - bool shouldIndent(XmlFormatting fmt) { - return !!(static_cast::type>(fmt & XmlFormatting::Indent)); - } - - } // anonymous namespace - - XmlFormatting operator|(XmlFormatting lhs, XmlFormatting rhs) { - return static_cast(static_cast::type>(lhs) - | static_cast::type>(rhs)); - } - - XmlFormatting operator&(XmlFormatting lhs, XmlFormatting rhs) { - return static_cast(static_cast::type>(lhs) - & static_cast::type>(rhs)); - } - - 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: http://www.w3.org/TR/xml/#syntax) - - for (std::size_t idx = 0; idx < m_str.size(); ++idx) { - unsigned char c = m_str[idx]; - switch (c) { - case '<': - os << "<"; - break; - case '&': - os << "&"; - break; - - case '>': - // See: http://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 http://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) { - unsigned char 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) || (0x80 <= value && value < 0x800 && encBytes > 2) || (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, XmlFormatting fmt) - : m_writer(writer) - , m_fmt(fmt) { - } - - XmlWriter::ScopedElement::ScopedElement(ScopedElement &&other) noexcept - : m_writer(other.m_writer) - , m_fmt(other.m_fmt) { - other.m_writer = nullptr; - other.m_fmt = XmlFormatting::None; - } - XmlWriter::ScopedElement &XmlWriter::ScopedElement::operator=(ScopedElement &&other) noexcept { - if (m_writer) { - m_writer->endElement(); - } - m_writer = other.m_writer; - other.m_writer = nullptr; - m_fmt = other.m_fmt; - other.m_fmt = XmlFormatting::None; - return *this; - } - - XmlWriter::ScopedElement::~ScopedElement() { - if (m_writer) { - m_writer->endElement(m_fmt); - } - } - - XmlWriter::ScopedElement &XmlWriter::ScopedElement::writeText(std::string const &text, XmlFormatting fmt) { - m_writer->writeText(text, fmt); - return *this; - } - - XmlWriter::XmlWriter(std::ostream &os) - : m_os(os) { - writeDeclaration(); - } - - XmlWriter::~XmlWriter() { - while (!m_tags.empty()) { - endElement(); - } - newlineIfNecessary(); - } - - XmlWriter &XmlWriter::startElement(std::string const &name, XmlFormatting fmt) { - ensureTagClosed(); - newlineIfNecessary(); - if (shouldIndent(fmt)) { - m_os << m_indent; - m_indent += " "; - } - m_os << '<' << name; - m_tags.push_back(name); - m_tagIsOpen = true; - applyFormatting(fmt); - return *this; - } - - XmlWriter::ScopedElement XmlWriter::scopedElement(std::string const &name, XmlFormatting fmt) { - ScopedElement scoped(this, fmt); - startElement(name, fmt); - return scoped; - } - - XmlWriter &XmlWriter::endElement(XmlFormatting fmt) { - m_indent = m_indent.substr(0, m_indent.size() - 2); - - if (m_tagIsOpen) { - m_os << "/>"; - m_tagIsOpen = false; - } else { - newlineIfNecessary(); - if (shouldIndent(fmt)) { - m_os << m_indent; - } - m_os << ""; - } - m_os << std::flush; - applyFormatting(fmt); - 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, bool attribute) { - m_os << ' ' << name << "=\"" << (attribute ? "true" : "false") << '"'; - return *this; - } - - XmlWriter &XmlWriter::writeText(std::string const &text, XmlFormatting fmt) { - if (!text.empty()) { - bool tagWasOpen = m_tagIsOpen; - ensureTagClosed(); - if (tagWasOpen && shouldIndent(fmt)) { - m_os << m_indent; - } - m_os << XmlEncode(text); - applyFormatting(fmt); - } - return *this; - } - - XmlWriter &XmlWriter::writeComment(std::string const &text, XmlFormatting fmt) { - ensureTagClosed(); - if (shouldIndent(fmt)) { - m_os << m_indent; - } - m_os << ""; - applyFormatting(fmt); - 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::flush; - newlineIfNecessary(); - m_tagIsOpen = false; - } - } - - void XmlWriter::applyFormatting(XmlFormatting fmt) { - m_needsNewline = shouldNewline(fmt); - } - - void XmlWriter::writeDeclaration() { - m_os << "\n"; - } - - void XmlWriter::newlineIfNecessary() { - if (m_needsNewline) { - m_os << std::endl; - m_needsNewline = false; - } - } -} // namespace Catch -// end catch_xmlwriter.cpp -// start catch_reporter_bases.cpp - -#include -#include -#include -#include -#include - -namespace Catch { - void prepareExpandedExpression(AssertionResult &result) { - result.getExpandedExpression(); - } - - // Because formatting using c++ streams is stateful, drop down to C is required - // Alternatively we could use stringstream, but its performance is... not good. - std::string getFormattedDuration(double duration) { - // Max exponent + 1 is required to represent the whole part - // + 1 for decimal point - // + 3 for the 3 decimal places - // + 1 for null terminator - const std::size_t maxDoubleSize = DBL_MAX_10_EXP + 1 + 1 + 3 + 1; - char buffer[maxDoubleSize]; - - // Save previous errno, to prevent sprintf from overwriting it - ErrnoGuard guard; -#ifdef _MSC_VER - sprintf_s(buffer, "%.3f", duration); -#else - std::sprintf(buffer, "%.3f", duration); -#endif - return std::string(buffer); - } - - bool shouldShowDuration(IConfig const &config, double duration) { - if (config.showDurations() == ShowDurations::Always) { - return true; - } - if (config.showDurations() == ShowDurations::Never) { - return false; - } - const double min = config.minDuration(); - return min >= 0 && duration >= min; - } - - std::string serializeFilters(std::vector const &container) { - ReusableStringStream oss; - bool first = true; - for (auto &&filter : container) { - if (!first) - oss << ' '; - else - first = false; - - oss << filter; - } - return oss.str(); - } - - TestEventListenerBase::TestEventListenerBase(ReporterConfig const &_config) - : StreamingReporterBase(_config) { - } - - std::set TestEventListenerBase::getSupportedVerbosities() { - return {Verbosity::Quiet, Verbosity::Normal, Verbosity::High}; - } - - void TestEventListenerBase::assertionStarting(AssertionInfo const &) {} - - bool TestEventListenerBase::assertionEnded(AssertionStats const &) { - return false; - } - -} // end namespace Catch -// end catch_reporter_bases.cpp -// start catch_reporter_compact.cpp - -namespace { -#ifdef CATCH_PLATFORM_MAC - const char *failedString() { - return "FAILED"; - } - const char *passedString() { - return "PASSED"; - } -#else - const char *failedString() { - return "failed"; - } - const char *passedString() { - return "passed"; - } -#endif - - // Colour::LightGrey - Catch::Colour::Code dimColour() { - return Catch::Colour::FileName; - } - - std::string bothOrAll(std::size_t count) { - return count == 1 ? std::string() : count == 2 ? "both " - : "all "; - } - -} // namespace - -namespace Catch { - namespace { - // Colour, message variants: - // - white: No tests ran. - // - red: Failed [both/all] N test cases, failed [both/all] M assertions. - // - white: Passed [both/all] N test cases (no assertions). - // - red: Failed N tests cases, failed M assertions. - // - green: Passed [both/all] N tests cases with M assertions. - void printTotals(std::ostream &out, const Totals &totals) { - if (totals.testCases.total() == 0) { - out << "No tests ran."; - } else if (totals.testCases.failed == totals.testCases.total()) { - Colour colour(Colour::ResultError); - const std::string qualify_assertions_failed - = totals.assertions.failed == totals.assertions.total() ? bothOrAll(totals.assertions.failed) : std::string(); - out << "Failed " << bothOrAll(totals.testCases.failed) << pluralise(totals.testCases.failed, "test case") - << ", " - "failed " - << qualify_assertions_failed << pluralise(totals.assertions.failed, "assertion") << '.'; - } else if (totals.assertions.total() == 0) { - out << "Passed " << bothOrAll(totals.testCases.total()) << pluralise(totals.testCases.total(), "test case") << " (no assertions)."; - } else if (totals.assertions.failed) { - Colour colour(Colour::ResultError); - out << "Failed " << pluralise(totals.testCases.failed, "test case") - << ", " - "failed " - << pluralise(totals.assertions.failed, "assertion") << '.'; - } else { - Colour colour(Colour::ResultSuccess); - out << "Passed " << bothOrAll(totals.testCases.passed) << pluralise(totals.testCases.passed, "test case") << " with " - << pluralise(totals.assertions.passed, "assertion") << '.'; - } - } - - // Implementation of CompactReporter formatting - class AssertionPrinter { - public: - AssertionPrinter &operator=(AssertionPrinter const &) = delete; - AssertionPrinter(AssertionPrinter const &) = delete; - AssertionPrinter(std::ostream &_stream, AssertionStats const &_stats, bool _printInfoMessages) - : stream(_stream) - , result(_stats.assertionResult) - , messages(_stats.infoMessages) - , itMessage(_stats.infoMessages.begin()) - , printInfoMessages(_printInfoMessages) { - } - - void print() { - printSourceInfo(); - - itMessage = messages.begin(); - - switch (result.getResultType()) { - case ResultWas::Ok: - printResultType(Colour::ResultSuccess, passedString()); - printOriginalExpression(); - printReconstructedExpression(); - if (!result.hasExpression()) - printRemainingMessages(Colour::None); - else - printRemainingMessages(); - break; - case ResultWas::ExpressionFailed: - if (result.isOk()) - printResultType(Colour::ResultSuccess, failedString() + std::string(" - but was ok")); - else - printResultType(Colour::Error, failedString()); - printOriginalExpression(); - printReconstructedExpression(); - printRemainingMessages(); - break; - case ResultWas::ThrewException: - printResultType(Colour::Error, failedString()); - printIssue("unexpected exception with message:"); - printMessage(); - printExpressionWas(); - printRemainingMessages(); - break; - case ResultWas::FatalErrorCondition: - printResultType(Colour::Error, failedString()); - printIssue("fatal error condition with message:"); - printMessage(); - printExpressionWas(); - printRemainingMessages(); - break; - case ResultWas::DidntThrowException: - printResultType(Colour::Error, failedString()); - printIssue("expected exception, got none"); - printExpressionWas(); - printRemainingMessages(); - break; - case ResultWas::Info: - printResultType(Colour::None, "info"); - printMessage(); - printRemainingMessages(); - break; - case ResultWas::Warning: - printResultType(Colour::None, "warning"); - printMessage(); - printRemainingMessages(); - break; - case ResultWas::ExplicitFailure: - printResultType(Colour::Error, failedString()); - printIssue("explicitly"); - printRemainingMessages(Colour::None); - break; - // These cases are here to prevent compiler warnings - case ResultWas::Unknown: - case ResultWas::FailureBit: - case ResultWas::Exception: - printResultType(Colour::Error, "** internal error **"); - break; - } - } - - private: - void printSourceInfo() const { - Colour colourGuard(Colour::FileName); - stream << result.getSourceInfo() << ':'; - } - - void printResultType(Colour::Code colour, std::string const &passOrFail) const { - if (!passOrFail.empty()) { - { - Colour colourGuard(colour); - stream << ' ' << passOrFail; - } - stream << ':'; - } - } - - void printIssue(std::string const &issue) const { stream << ' ' << issue; } - - void printExpressionWas() { - if (result.hasExpression()) { - stream << ';'; - { - Colour colour(dimColour()); - stream << " expression was:"; - } - printOriginalExpression(); - } - } - - void printOriginalExpression() const { - if (result.hasExpression()) { - stream << ' ' << result.getExpression(); - } - } - - void printReconstructedExpression() const { - if (result.hasExpandedExpression()) { - { - Colour colour(dimColour()); - stream << " for: "; - } - stream << result.getExpandedExpression(); - } - } - - void printMessage() { - if (itMessage != messages.end()) { - stream << " '" << itMessage->message << '\''; - ++itMessage; - } - } - - void printRemainingMessages(Colour::Code colour = dimColour()) { - if (itMessage == messages.end()) - return; - - const auto itEnd = messages.cend(); - const auto N = static_cast(std::distance(itMessage, itEnd)); - - { - Colour colourGuard(colour); - stream << " with " << pluralise(N, "message") << ':'; - } - - while (itMessage != itEnd) { - // If this assertion is a warning ignore any INFO messages - if (printInfoMessages || itMessage->type != ResultWas::Info) { - printMessage(); - if (itMessage != itEnd) { - Colour colourGuard(dimColour()); - stream << " and"; - } - continue; - } - ++itMessage; - } - } - - private: - std::ostream &stream; - AssertionResult const &result; - std::vector messages; - std::vector::const_iterator itMessage; - bool printInfoMessages; - }; - - } // namespace - - std::string CompactReporter::getDescription() { - return "Reports test results on a single line, suitable for IDEs"; - } - - void CompactReporter::noMatchingTestCases(std::string const &spec) { - stream << "No test cases matched '" << spec << '\'' << std::endl; - } - - void CompactReporter::assertionStarting(AssertionInfo const &) {} - - bool CompactReporter::assertionEnded(AssertionStats const &_assertionStats) { - AssertionResult const &result = _assertionStats.assertionResult; - - bool printInfoMessages = true; - - // Drop out if result was successful and we're not printing those - if (!m_config->includeSuccessfulResults() && result.isOk()) { - if (result.getResultType() != ResultWas::Warning) - return false; - printInfoMessages = false; - } - - AssertionPrinter printer(stream, _assertionStats, printInfoMessages); - printer.print(); - - stream << std::endl; - return true; - } - - void CompactReporter::sectionEnded(SectionStats const &_sectionStats) { - double dur = _sectionStats.durationInSeconds; - if (shouldShowDuration(*m_config, dur)) { - stream << getFormattedDuration(dur) << " s: " << _sectionStats.sectionInfo.name << std::endl; - } - } - - void CompactReporter::testRunEnded(TestRunStats const &_testRunStats) { - printTotals(stream, _testRunStats.totals); - stream << '\n' - << std::endl; - StreamingReporterBase::testRunEnded(_testRunStats); - } - - CompactReporter::~CompactReporter() {} - - CATCH_REGISTER_REPORTER("compact", CompactReporter) - -} // end namespace Catch -// end catch_reporter_compact.cpp -// start catch_reporter_console.cpp - -#include -#include - -#if defined(_MSC_VER) -#pragma warning(push) -#pragma warning(disable : 4061) // Not all labels are EXPLICITLY handled in switch \ - // Note that 4062 (not all labels are handled and default is missing) is enabled -#endif - -#if defined(__clang__) -#pragma clang diagnostic push -// For simplicity, benchmarking-only helpers are always enabled -#pragma clang diagnostic ignored "-Wunused-function" -#endif - -namespace Catch { - namespace { - // Formatter impl for ConsoleReporter - class ConsoleAssertionPrinter { - public: - ConsoleAssertionPrinter &operator=(ConsoleAssertionPrinter const &) = delete; - ConsoleAssertionPrinter(ConsoleAssertionPrinter const &) = delete; - ConsoleAssertionPrinter(std::ostream &_stream, AssertionStats const &_stats, bool _printInfoMessages) - : stream(_stream) - , stats(_stats) - , result(_stats.assertionResult) - , colour(Colour::None) - , message(result.getMessage()) - , messages(_stats.infoMessages) - , printInfoMessages(_printInfoMessages) { - switch (result.getResultType()) { - case ResultWas::Ok: - colour = Colour::Success; - passOrFail = "PASSED"; - // if( result.hasMessage() ) - if (_stats.infoMessages.size() == 1) - messageLabel = "with message"; - if (_stats.infoMessages.size() > 1) - messageLabel = "with messages"; - break; - case ResultWas::ExpressionFailed: - if (result.isOk()) { - colour = Colour::Success; - passOrFail = "FAILED - but was ok"; - } else { - colour = Colour::Error; - passOrFail = "FAILED"; - } - if (_stats.infoMessages.size() == 1) - messageLabel = "with message"; - if (_stats.infoMessages.size() > 1) - messageLabel = "with messages"; - break; - case ResultWas::ThrewException: - colour = Colour::Error; - passOrFail = "FAILED"; - messageLabel = "due to unexpected exception with "; - if (_stats.infoMessages.size() == 1) - messageLabel += "message"; - if (_stats.infoMessages.size() > 1) - messageLabel += "messages"; - break; - case ResultWas::FatalErrorCondition: - colour = Colour::Error; - passOrFail = "FAILED"; - messageLabel = "due to a fatal error condition"; - break; - case ResultWas::DidntThrowException: - colour = Colour::Error; - passOrFail = "FAILED"; - messageLabel = "because no exception was thrown where one was expected"; - break; - case ResultWas::Info: - messageLabel = "info"; - break; - case ResultWas::Warning: - messageLabel = "warning"; - break; - case ResultWas::ExplicitFailure: - passOrFail = "FAILED"; - colour = Colour::Error; - if (_stats.infoMessages.size() == 1) - messageLabel = "explicitly with message"; - if (_stats.infoMessages.size() > 1) - messageLabel = "explicitly with messages"; - break; - // These cases are here to prevent compiler warnings - case ResultWas::Unknown: - case ResultWas::FailureBit: - case ResultWas::Exception: - passOrFail = "** internal error **"; - colour = Colour::Error; - break; - } - } - - void print() const { - printSourceInfo(); - if (stats.totals.assertions.total() > 0) { - printResultType(); - printOriginalExpression(); - printReconstructedExpression(); - } else { - stream << '\n'; - } - printMessage(); - } - - private: - void printResultType() const { - if (!passOrFail.empty()) { - Colour colourGuard(colour); - stream << passOrFail << ":\n"; - } - } - void printOriginalExpression() const { - if (result.hasExpression()) { - Colour colourGuard(Colour::OriginalExpression); - stream << " "; - stream << result.getExpressionInMacro(); - stream << '\n'; - } - } - void printReconstructedExpression() const { - if (result.hasExpandedExpression()) { - stream << "with expansion:\n"; - Colour colourGuard(Colour::ReconstructedExpression); - stream << Column(result.getExpandedExpression()).indent(2) << '\n'; - } - } - void printMessage() const { - if (!messageLabel.empty()) - stream << messageLabel << ':' << '\n'; - for (auto const &msg : messages) { - // If this assertion is a warning ignore any INFO messages - if (printInfoMessages || msg.type != ResultWas::Info) - stream << Column(msg.message).indent(2) << '\n'; - } - } - void printSourceInfo() const { - Colour colourGuard(Colour::FileName); - stream << result.getSourceInfo() << ": "; - } - - std::ostream &stream; - AssertionStats const &stats; - AssertionResult const &result; - Colour::Code colour; - std::string passOrFail; - std::string messageLabel; - std::string message; - std::vector messages; - bool printInfoMessages; - }; - - std::size_t makeRatio(std::size_t number, std::size_t total) { - std::size_t ratio = total > 0 ? CATCH_CONFIG_CONSOLE_WIDTH * number / total : 0; - return (ratio == 0 && number > 0) ? 1 : ratio; - } - - std::size_t &findMax(std::size_t &i, std::size_t &j, std::size_t &k) { - if (i > j && i > k) - return i; - else if (j > k) - return j; - else - return k; - } - - struct ColumnInfo { - enum Justification { - Left, - Right - }; - std::string name; - int width; - Justification justification; - }; - struct ColumnBreak { - }; - struct RowBreak { - }; - - class Duration { - enum class Unit { - Auto, - Nanoseconds, - Microseconds, - Milliseconds, - Seconds, - Minutes - }; - static const uint64_t s_nanosecondsInAMicrosecond = 1000; - static const uint64_t s_nanosecondsInAMillisecond = 1000 * s_nanosecondsInAMicrosecond; - static const uint64_t s_nanosecondsInASecond = 1000 * s_nanosecondsInAMillisecond; - static const uint64_t s_nanosecondsInAMinute = 60 * s_nanosecondsInASecond; - - double m_inNanoseconds; - Unit m_units; - - public: - explicit Duration(double inNanoseconds, Unit units = Unit::Auto) - : m_inNanoseconds(inNanoseconds) - , m_units(units) { - if (m_units == Unit::Auto) { - if (m_inNanoseconds < s_nanosecondsInAMicrosecond) - m_units = Unit::Nanoseconds; - else if (m_inNanoseconds < s_nanosecondsInAMillisecond) - m_units = Unit::Microseconds; - else if (m_inNanoseconds < s_nanosecondsInASecond) - m_units = Unit::Milliseconds; - else if (m_inNanoseconds < s_nanosecondsInAMinute) - m_units = Unit::Seconds; - else - m_units = Unit::Minutes; - } - } - - auto value() const -> double { - switch (m_units) { - case Unit::Microseconds: - return m_inNanoseconds / static_cast(s_nanosecondsInAMicrosecond); - case Unit::Milliseconds: - return m_inNanoseconds / static_cast(s_nanosecondsInAMillisecond); - case Unit::Seconds: - return m_inNanoseconds / static_cast(s_nanosecondsInASecond); - case Unit::Minutes: - return m_inNanoseconds / static_cast(s_nanosecondsInAMinute); - default: - return m_inNanoseconds; - } - } - auto unitsAsString() const -> std::string { - switch (m_units) { - case Unit::Nanoseconds: - return "ns"; - case Unit::Microseconds: - return "us"; - case Unit::Milliseconds: - return "ms"; - case Unit::Seconds: - return "s"; - case Unit::Minutes: - return "m"; - default: - return "** internal error **"; - } - } - friend auto operator<<(std::ostream &os, Duration const &duration) -> std::ostream & { - return os << duration.value() << ' ' << duration.unitsAsString(); - } - }; - } // namespace - - class TablePrinter { - std::ostream &m_os; - std::vector m_columnInfos; - std::ostringstream m_oss; - int m_currentColumn = -1; - bool m_isOpen = false; - - public: - TablePrinter(std::ostream &os, std::vector columnInfos) - : m_os(os) - , m_columnInfos(std::move(columnInfos)) { - } - - auto columnInfos() const -> std::vector const & { return m_columnInfos; } - - void open() { - if (!m_isOpen) { - m_isOpen = true; - *this << RowBreak(); - - Columns headerCols; - Spacer spacer(2); - for (auto const &info : m_columnInfos) { - headerCols += Column(info.name).width(static_cast(info.width - 2)); - headerCols += spacer; - } - m_os << headerCols << '\n'; - - m_os << Catch::getLineOfChars<'-'>() << '\n'; - } - } - void close() { - if (m_isOpen) { - *this << RowBreak(); - m_os << std::endl; - m_isOpen = false; - } - } - - template - friend TablePrinter &operator<<(TablePrinter &tp, T const &value) { - tp.m_oss << value; - return tp; - } - - friend TablePrinter &operator<<(TablePrinter &tp, ColumnBreak) { - auto colStr = tp.m_oss.str(); - const auto strSize = colStr.size(); - tp.m_oss.str(""); - tp.open(); - if (tp.m_currentColumn == static_cast(tp.m_columnInfos.size() - 1)) { - tp.m_currentColumn = -1; - tp.m_os << '\n'; - } - tp.m_currentColumn++; - - auto colInfo = tp.m_columnInfos[tp.m_currentColumn]; - auto padding - = (strSize + 1 < static_cast(colInfo.width)) ? std::string(colInfo.width - (strSize + 1), ' ') : std::string(); - if (colInfo.justification == ColumnInfo::Left) - tp.m_os << colStr << padding << ' '; - else - tp.m_os << padding << colStr << ' '; - return tp; - } - - friend TablePrinter &operator<<(TablePrinter &tp, RowBreak) { - if (tp.m_currentColumn > 0) { - tp.m_os << '\n'; - tp.m_currentColumn = -1; - } - return tp; - } - }; - - ConsoleReporter::ConsoleReporter(ReporterConfig const &config) - : StreamingReporterBase(config) - , m_tablePrinter(new TablePrinter(config.stream(), [&config]() -> std::vector { - if (config.fullConfig()->benchmarkNoAnalysis()) { - return {{"benchmark name", CATCH_CONFIG_CONSOLE_WIDTH - 43, ColumnInfo::Left}, - {" samples", 14, ColumnInfo::Right}, - {" iterations", 14, ColumnInfo::Right}, - {" mean", 14, ColumnInfo::Right}}; - } else { - return {{"benchmark name", CATCH_CONFIG_CONSOLE_WIDTH - 43, ColumnInfo::Left}, - {"samples mean std dev", 14, ColumnInfo::Right}, - {"iterations low mean low std dev", 14, ColumnInfo::Right}, - {"estimated high mean high std dev", 14, ColumnInfo::Right}}; - } - }())) { - } - ConsoleReporter::~ConsoleReporter() = default; - - std::string ConsoleReporter::getDescription() { - return "Reports test results as plain lines of text"; - } - - void ConsoleReporter::noMatchingTestCases(std::string const &spec) { - stream << "No test cases matched '" << spec << '\'' << std::endl; - } - - void ConsoleReporter::reportInvalidArguments(std::string const &arg) { - stream << "Invalid Filter: " << arg << std::endl; - } - - void ConsoleReporter::assertionStarting(AssertionInfo const &) {} - - bool ConsoleReporter::assertionEnded(AssertionStats const &_assertionStats) { - AssertionResult const &result = _assertionStats.assertionResult; - - bool includeResults = m_config->includeSuccessfulResults() || !result.isOk(); - - // Drop out if result was successful but we're not printing them. - if (!includeResults && result.getResultType() != ResultWas::Warning) - return false; - - lazyPrint(); - - ConsoleAssertionPrinter printer(stream, _assertionStats, includeResults); - printer.print(); - stream << std::endl; - return true; - } - - void ConsoleReporter::sectionStarting(SectionInfo const &_sectionInfo) { - m_tablePrinter->close(); - m_headerPrinted = false; - StreamingReporterBase::sectionStarting(_sectionInfo); - } - void ConsoleReporter::sectionEnded(SectionStats const &_sectionStats) { - m_tablePrinter->close(); - if (_sectionStats.missingAssertions) { - lazyPrint(); - Colour colour(Colour::ResultError); - if (m_sectionStack.size() > 1) - stream << "\nNo assertions in section"; - else - stream << "\nNo assertions in test case"; - stream << " '" << _sectionStats.sectionInfo.name << "'\n" - << std::endl; - } - double dur = _sectionStats.durationInSeconds; - if (shouldShowDuration(*m_config, dur)) { - stream << getFormattedDuration(dur) << " s: " << _sectionStats.sectionInfo.name << std::endl; - } - if (m_headerPrinted) { - m_headerPrinted = false; - } - StreamingReporterBase::sectionEnded(_sectionStats); - } - -#if defined(CATCH_CONFIG_ENABLE_BENCHMARKING) - void ConsoleReporter::benchmarkPreparing(std::string const &name) { - lazyPrintWithoutClosingBenchmarkTable(); - - auto nameCol = Column(name).width(static_cast(m_tablePrinter->columnInfos()[0].width - 2)); - - bool firstLine = true; - for (auto line : nameCol) { - if (!firstLine) - (*m_tablePrinter) << ColumnBreak() << ColumnBreak() << ColumnBreak(); - else - firstLine = false; - - (*m_tablePrinter) << line << ColumnBreak(); - } - } - - void ConsoleReporter::benchmarkStarting(BenchmarkInfo const &info) { - (*m_tablePrinter) << info.samples << ColumnBreak() << info.iterations << ColumnBreak(); - if (!m_config->benchmarkNoAnalysis()) - (*m_tablePrinter) << Duration(info.estimatedDuration) << ColumnBreak(); - } - void ConsoleReporter::benchmarkEnded(BenchmarkStats<> const &stats) { - if (m_config->benchmarkNoAnalysis()) { - (*m_tablePrinter) << Duration(stats.mean.point.count()) << ColumnBreak(); - } else { - (*m_tablePrinter) << ColumnBreak() << Duration(stats.mean.point.count()) << ColumnBreak() - << Duration(stats.mean.lower_bound.count()) << ColumnBreak() << Duration(stats.mean.upper_bound.count()) - << ColumnBreak() << ColumnBreak() << Duration(stats.standardDeviation.point.count()) << ColumnBreak() - << Duration(stats.standardDeviation.lower_bound.count()) << ColumnBreak() - << Duration(stats.standardDeviation.upper_bound.count()) << ColumnBreak() << ColumnBreak() << ColumnBreak() - << ColumnBreak() << ColumnBreak(); - } - } - - void ConsoleReporter::benchmarkFailed(std::string const &error) { - Colour colour(Colour::Red); - (*m_tablePrinter) << "Benchmark failed (" << error << ')' << ColumnBreak() << RowBreak(); - } -#endif // CATCH_CONFIG_ENABLE_BENCHMARKING - - void ConsoleReporter::testCaseEnded(TestCaseStats const &_testCaseStats) { - m_tablePrinter->close(); - StreamingReporterBase::testCaseEnded(_testCaseStats); - m_headerPrinted = false; - } - void ConsoleReporter::testGroupEnded(TestGroupStats const &_testGroupStats) { - if (currentGroupInfo.used) { - printSummaryDivider(); - stream << "Summary for group '" << _testGroupStats.groupInfo.name << "':\n"; - printTotals(_testGroupStats.totals); - stream << '\n' - << std::endl; - } - StreamingReporterBase::testGroupEnded(_testGroupStats); - } - void ConsoleReporter::testRunEnded(TestRunStats const &_testRunStats) { - printTotalsDivider(_testRunStats.totals); - printTotals(_testRunStats.totals); - stream << std::endl; - StreamingReporterBase::testRunEnded(_testRunStats); - } - void ConsoleReporter::testRunStarting(TestRunInfo const &_testInfo) { - StreamingReporterBase::testRunStarting(_testInfo); - printTestFilters(); - } - - void ConsoleReporter::lazyPrint() { - m_tablePrinter->close(); - lazyPrintWithoutClosingBenchmarkTable(); - } - - void ConsoleReporter::lazyPrintWithoutClosingBenchmarkTable() { - if (!currentTestRunInfo.used) - lazyPrintRunInfo(); - if (!currentGroupInfo.used) - lazyPrintGroupInfo(); - - if (!m_headerPrinted) { - printTestCaseAndSectionHeader(); - m_headerPrinted = true; - } - } - void ConsoleReporter::lazyPrintRunInfo() { - stream << '\n' - << getLineOfChars<'~'>() << '\n'; - Colour colour(Colour::SecondaryText); - stream << currentTestRunInfo->name << " is a Catch v" << libraryVersion() << " host application.\n" - << "Run with -? for options\n\n"; - - if (m_config->rngSeed() != 0) - stream << "Randomness seeded to: " << m_config->rngSeed() << "\n\n"; - - currentTestRunInfo.used = true; - } - void ConsoleReporter::lazyPrintGroupInfo() { - if (!currentGroupInfo->name.empty() && currentGroupInfo->groupsCounts > 1) { - printClosedHeader("Group: " + currentGroupInfo->name); - currentGroupInfo.used = true; - } - } - void ConsoleReporter::printTestCaseAndSectionHeader() { - assert(!m_sectionStack.empty()); - printOpenHeader(currentTestCaseInfo->name); - - if (m_sectionStack.size() > 1) { - Colour colourGuard(Colour::Headers); - - auto it = m_sectionStack.begin() + 1, // Skip first section (test case) - itEnd = m_sectionStack.end(); - for (; it != itEnd; ++it) - printHeaderString(it->name, 2); - } - - SourceLineInfo lineInfo = m_sectionStack.back().lineInfo; - - stream << getLineOfChars<'-'>() << '\n'; - Colour colourGuard(Colour::FileName); - stream << lineInfo << '\n'; - stream << getLineOfChars<'.'>() << '\n' - << std::endl; - } - - void ConsoleReporter::printClosedHeader(std::string const &_name) { - printOpenHeader(_name); - stream << getLineOfChars<'.'>() << '\n'; - } - void ConsoleReporter::printOpenHeader(std::string const &_name) { - stream << getLineOfChars<'-'>() << '\n'; - { - Colour colourGuard(Colour::Headers); - printHeaderString(_name); - } - } - - // if string has a : in first line will set indent to follow it on - // subsequent lines - void ConsoleReporter::printHeaderString(std::string const &_string, std::size_t indent) { - std::size_t i = _string.find(": "); - if (i != std::string::npos) - i += 2; - else - i = 0; - stream << Column(_string).indent(indent + i).initialIndent(indent) << '\n'; - } - - struct SummaryColumn { - SummaryColumn(std::string _label, Colour::Code _colour) - : label(std::move(_label)) - , colour(_colour) { - } - SummaryColumn addRow(std::size_t count) { - ReusableStringStream rss; - rss << count; - std::string row = rss.str(); - for (auto &oldRow : rows) { - while (oldRow.size() < row.size()) - oldRow = ' ' + oldRow; - while (oldRow.size() > row.size()) - row = ' ' + row; - } - rows.push_back(row); - return *this; - } - - std::string label; - Colour::Code colour; - std::vector rows; - }; - - void ConsoleReporter::printTotals(Totals const &totals) { - if (totals.testCases.total() == 0) { - stream << Colour(Colour::Warning) << "No tests ran\n"; - } else if (totals.assertions.total() > 0 && totals.testCases.allPassed()) { - stream << Colour(Colour::ResultSuccess) << "All tests passed"; - stream << " (" << pluralise(totals.assertions.passed, "assertion") << " in " << pluralise(totals.testCases.passed, "test case") - << ')' << '\n'; - } else { - std::vector columns; - columns.push_back(SummaryColumn("", Colour::None).addRow(totals.testCases.total()).addRow(totals.assertions.total())); - columns.push_back(SummaryColumn("passed", Colour::Success).addRow(totals.testCases.passed).addRow(totals.assertions.passed)); - columns.push_back(SummaryColumn("failed", Colour::ResultError).addRow(totals.testCases.failed).addRow(totals.assertions.failed)); - columns.push_back(SummaryColumn("failed as expected", Colour::ResultExpectedFailure) - .addRow(totals.testCases.failedButOk) - .addRow(totals.assertions.failedButOk)); - - printSummaryRow("test cases", columns, 0); - printSummaryRow("assertions", columns, 1); - } - } - void ConsoleReporter::printSummaryRow(std::string const &label, std::vector const &cols, std::size_t row) { - for (auto col : cols) { - std::string value = col.rows[row]; - if (col.label.empty()) { - stream << label << ": "; - if (value != "0") - stream << value; - else - stream << Colour(Colour::Warning) << "- none -"; - } else if (value != "0") { - stream << Colour(Colour::LightGrey) << " | "; - stream << Colour(col.colour) << value << ' ' << col.label; - } - } - stream << '\n'; - } - - void ConsoleReporter::printTotalsDivider(Totals const &totals) { - if (totals.testCases.total() > 0) { - std::size_t failedRatio = makeRatio(totals.testCases.failed, totals.testCases.total()); - std::size_t failedButOkRatio = makeRatio(totals.testCases.failedButOk, totals.testCases.total()); - std::size_t passedRatio = makeRatio(totals.testCases.passed, totals.testCases.total()); - while (failedRatio + failedButOkRatio + passedRatio < CATCH_CONFIG_CONSOLE_WIDTH - 1) - findMax(failedRatio, failedButOkRatio, passedRatio)++; - while (failedRatio + failedButOkRatio + passedRatio > CATCH_CONFIG_CONSOLE_WIDTH - 1) - findMax(failedRatio, failedButOkRatio, passedRatio)--; - - stream << Colour(Colour::Error) << std::string(failedRatio, '='); - stream << Colour(Colour::ResultExpectedFailure) << std::string(failedButOkRatio, '='); - if (totals.testCases.allPassed()) - stream << Colour(Colour::ResultSuccess) << std::string(passedRatio, '='); - else - stream << Colour(Colour::Success) << std::string(passedRatio, '='); - } else { - stream << Colour(Colour::Warning) << std::string(CATCH_CONFIG_CONSOLE_WIDTH - 1, '='); - } - stream << '\n'; - } - void ConsoleReporter::printSummaryDivider() { - stream << getLineOfChars<'-'>() << '\n'; - } - - void ConsoleReporter::printTestFilters() { - if (m_config->testSpec().hasFilters()) { - Colour guard(Colour::BrightYellow); - stream << "Filters: " << serializeFilters(m_config->getTestsOrTags()) << '\n'; - } - } - - CATCH_REGISTER_REPORTER("console", ConsoleReporter) - -} // end namespace Catch - -#if defined(_MSC_VER) -#pragma warning(pop) -#endif - -#if defined(__clang__) -#pragma clang diagnostic pop -#endif -// end catch_reporter_console.cpp -// start catch_reporter_junit.cpp - -#include -#include -#include -#include - -namespace Catch { - namespace { - 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"); - -#ifdef _MSC_VER - std::tm timeInfo = {}; - gmtime_s(&timeInfo, &rawtime); -#else - std::tm *timeInfo; - timeInfo = std::gmtime(&rawtime); -#endif - - char timeStamp[timeStampSize]; - const char *const fmt = "%Y-%m-%dT%H:%M:%SZ"; - -#ifdef _MSC_VER - std::strftime(timeStamp, timeStampSize, fmt, &timeInfo); -#else - std::strftime(timeStamp, timeStampSize, fmt, timeInfo); -#endif - return std::string(timeStamp); - } - - std::string fileNameTag(const std::vector &tags) { - auto it = std::find_if(begin(tags), end(tags), [](std::string const &tag) { return tag.front() == '#'; }); - if (it != tags.end()) - return it->substr(1); - return std::string(); - } - } // anonymous namespace - - JunitReporter::JunitReporter(ReporterConfig const &_config) - : CumulativeReporterBase(_config) - , xml(_config.stream()) { - m_reporterPrefs.shouldRedirectStdOut = true; - m_reporterPrefs.shouldReportAllAssertions = true; - } - - JunitReporter::~JunitReporter() {} - - std::string JunitReporter::getDescription() { - return "Reports test results in an XML format that looks like Ant's junitreport target"; - } - - void JunitReporter::noMatchingTestCases(std::string const & /*spec*/) {} - - void JunitReporter::testRunStarting(TestRunInfo const &runInfo) { - CumulativeReporterBase::testRunStarting(runInfo); - xml.startElement("testsuites"); - } - - void JunitReporter::testGroupStarting(GroupInfo const &groupInfo) { - suiteTimer.start(); - stdOutForSuite.clear(); - stdErrForSuite.clear(); - unexpectedExceptions = 0; - CumulativeReporterBase::testGroupStarting(groupInfo); - } - - void JunitReporter::testCaseStarting(TestCaseInfo const &testCaseInfo) { - m_okToFail = testCaseInfo.okToFail(); - } - - bool JunitReporter::assertionEnded(AssertionStats const &assertionStats) { - if (assertionStats.assertionResult.getResultType() == ResultWas::ThrewException && !m_okToFail) - unexpectedExceptions++; - return CumulativeReporterBase::assertionEnded(assertionStats); - } - - void JunitReporter::testCaseEnded(TestCaseStats const &testCaseStats) { - stdOutForSuite += testCaseStats.stdOut; - stdErrForSuite += testCaseStats.stdErr; - CumulativeReporterBase::testCaseEnded(testCaseStats); - } - - void JunitReporter::testGroupEnded(TestGroupStats const &testGroupStats) { - double suiteTime = suiteTimer.getElapsedSeconds(); - CumulativeReporterBase::testGroupEnded(testGroupStats); - writeGroup(*m_testGroups.back(), suiteTime); - } - - void JunitReporter::testRunEndedCumulative() { - xml.endElement(); - } - - void JunitReporter::writeGroup(TestGroupNode const &groupNode, double suiteTime) { - XmlWriter::ScopedElement e = xml.scopedElement("testsuite"); - - TestGroupStats const &stats = groupNode.value; - xml.writeAttribute("name", stats.groupInfo.name); - xml.writeAttribute("errors", unexpectedExceptions); - xml.writeAttribute("failures", stats.totals.assertions.failed - unexpectedExceptions); - xml.writeAttribute("tests", stats.totals.assertions.total()); - xml.writeAttribute("hostname", "tbd"); // !TBD - if (m_config->showDurations() == ShowDurations::Never) - xml.writeAttribute("time", ""); - else - xml.writeAttribute("time", suiteTime); - xml.writeAttribute("timestamp", getCurrentTimestamp()); - - // Write properties if there are any - if (m_config->hasTestFilters() || m_config->rngSeed() != 0) { - auto properties = xml.scopedElement("properties"); - if (m_config->hasTestFilters()) { - xml.scopedElement("property").writeAttribute("name", "filters").writeAttribute("value", serializeFilters(m_config->getTestsOrTags())); - } - if (m_config->rngSeed() != 0) { - xml.scopedElement("property").writeAttribute("name", "random-seed").writeAttribute("value", m_config->rngSeed()); - } - } - - // Write test cases - for (auto const &child : groupNode.children) - writeTestCase(*child); - - xml.scopedElement("system-out").writeText(trim(stdOutForSuite), XmlFormatting::Newline); - xml.scopedElement("system-err").writeText(trim(stdErrForSuite), XmlFormatting::Newline); - } - - void JunitReporter::writeTestCase(TestCaseNode const &testCaseNode) { - TestCaseStats const &stats = testCaseNode.value; - - // All test cases have exactly one section - which represents the - // test case itself. That section may have 0-n nested sections - assert(testCaseNode.children.size() == 1); - SectionNode const &rootSection = *testCaseNode.children.front(); - - std::string className = stats.testInfo.className; - - if (className.empty()) { - className = fileNameTag(stats.testInfo.tags); - if (className.empty()) - className = "global"; - } - - if (!m_config->name().empty()) - className = m_config->name() + "." + className; - - writeSection(className, "", rootSection); - } - - void JunitReporter::writeSection(std::string const &className, std::string const &rootName, SectionNode const §ionNode) { - std::string name = trim(sectionNode.stats.sectionInfo.name); - if (!rootName.empty()) - name = rootName + '/' + name; - - if (!sectionNode.assertions.empty() || !sectionNode.stdOut.empty() || !sectionNode.stdErr.empty()) { - XmlWriter::ScopedElement e = xml.scopedElement("testcase"); - if (className.empty()) { - xml.writeAttribute("classname", name); - xml.writeAttribute("name", "root"); - } else { - xml.writeAttribute("classname", className); - xml.writeAttribute("name", name); - } - xml.writeAttribute("time", ::Catch::Detail::stringify(sectionNode.stats.durationInSeconds)); - // This is not ideal, but it should be enough to mimic gtest's - // junit output. - // Ideally the JUnit reporter would also handle `skipTest` - // events and write those out appropriately. - xml.writeAttribute("status", "run"); - - writeAssertions(sectionNode); - - if (!sectionNode.stdOut.empty()) - xml.scopedElement("system-out").writeText(trim(sectionNode.stdOut), XmlFormatting::Newline); - if (!sectionNode.stdErr.empty()) - xml.scopedElement("system-err").writeText(trim(sectionNode.stdErr), XmlFormatting::Newline); - } - for (auto const &childNode : sectionNode.childSections) - if (className.empty()) - writeSection(name, "", *childNode); - else - writeSection(className, name, *childNode); - } - - void JunitReporter::writeAssertions(SectionNode const §ionNode) { - for (auto const &assertion : sectionNode.assertions) - writeAssertion(assertion); - } - - void JunitReporter::writeAssertion(AssertionStats const &stats) { - AssertionResult const &result = stats.assertionResult; - if (!result.isOk()) { - std::string elementName; - switch (result.getResultType()) { - case ResultWas::ThrewException: - case ResultWas::FatalErrorCondition: - elementName = "error"; - break; - case ResultWas::ExplicitFailure: - case ResultWas::ExpressionFailed: - case ResultWas::DidntThrowException: - elementName = "failure"; - break; - - // We should never see these here: - case ResultWas::Info: - case ResultWas::Warning: - case ResultWas::Ok: - case ResultWas::Unknown: - case ResultWas::FailureBit: - case ResultWas::Exception: - elementName = "internalError"; - break; - } - - XmlWriter::ScopedElement e = xml.scopedElement(elementName); - - xml.writeAttribute("message", result.getExpression()); - xml.writeAttribute("type", result.getTestMacroName()); - - ReusableStringStream rss; - if (stats.totals.assertions.total() > 0) { - rss << "FAILED" - << ":\n"; - if (result.hasExpression()) { - rss << " "; - rss << result.getExpressionInMacro(); - rss << '\n'; - } - if (result.hasExpandedExpression()) { - rss << "with expansion:\n"; - rss << Column(result.getExpandedExpression()).indent(2) << '\n'; - } - } else { - rss << '\n'; - } - - if (!result.getMessage().empty()) - rss << result.getMessage() << '\n'; - for (auto const &msg : stats.infoMessages) - if (msg.type == ResultWas::Info) - rss << msg.message << '\n'; - - rss << "at " << result.getSourceInfo(); - xml.writeText(rss.str(), XmlFormatting::Newline); - } - } - - CATCH_REGISTER_REPORTER("junit", JunitReporter) - -} // end namespace Catch -// end catch_reporter_junit.cpp -// start catch_reporter_listening.cpp - -#include - -namespace Catch { - ListeningReporter::ListeningReporter() { - // We will assume that listeners will always want all assertions - m_preferences.shouldReportAllAssertions = true; - } - - void ListeningReporter::addListener(IStreamingReporterPtr &&listener) { - m_listeners.push_back(std::move(listener)); - } - - void ListeningReporter::addReporter(IStreamingReporterPtr &&reporter) { - assert(!m_reporter && "Listening reporter can wrap only 1 real reporter"); - m_reporter = std::move(reporter); - m_preferences.shouldRedirectStdOut = m_reporter->getPreferences().shouldRedirectStdOut; - } - - ReporterPreferences ListeningReporter::getPreferences() const { - return m_preferences; - } - - std::set ListeningReporter::getSupportedVerbosities() { - return std::set{}; - } - - void ListeningReporter::noMatchingTestCases(std::string const &spec) { - for (auto const &listener : m_listeners) { - listener->noMatchingTestCases(spec); - } - m_reporter->noMatchingTestCases(spec); - } - - void ListeningReporter::reportInvalidArguments(std::string const &arg) { - for (auto const &listener : m_listeners) { - listener->reportInvalidArguments(arg); - } - m_reporter->reportInvalidArguments(arg); - } - -#if defined(CATCH_CONFIG_ENABLE_BENCHMARKING) - void ListeningReporter::benchmarkPreparing(std::string const &name) { - for (auto const &listener : m_listeners) { - listener->benchmarkPreparing(name); - } - m_reporter->benchmarkPreparing(name); - } - void ListeningReporter::benchmarkStarting(BenchmarkInfo const &benchmarkInfo) { - for (auto const &listener : m_listeners) { - listener->benchmarkStarting(benchmarkInfo); - } - m_reporter->benchmarkStarting(benchmarkInfo); - } - void ListeningReporter::benchmarkEnded(BenchmarkStats<> const &benchmarkStats) { - for (auto const &listener : m_listeners) { - listener->benchmarkEnded(benchmarkStats); - } - m_reporter->benchmarkEnded(benchmarkStats); - } - - void ListeningReporter::benchmarkFailed(std::string const &error) { - for (auto const &listener : m_listeners) { - listener->benchmarkFailed(error); - } - m_reporter->benchmarkFailed(error); - } -#endif // CATCH_CONFIG_ENABLE_BENCHMARKING - - void ListeningReporter::testRunStarting(TestRunInfo const &testRunInfo) { - for (auto const &listener : m_listeners) { - listener->testRunStarting(testRunInfo); - } - m_reporter->testRunStarting(testRunInfo); - } - - void ListeningReporter::testGroupStarting(GroupInfo const &groupInfo) { - for (auto const &listener : m_listeners) { - listener->testGroupStarting(groupInfo); - } - m_reporter->testGroupStarting(groupInfo); - } - - void ListeningReporter::testCaseStarting(TestCaseInfo const &testInfo) { - for (auto const &listener : m_listeners) { - listener->testCaseStarting(testInfo); - } - m_reporter->testCaseStarting(testInfo); - } - - void ListeningReporter::sectionStarting(SectionInfo const §ionInfo) { - for (auto const &listener : m_listeners) { - listener->sectionStarting(sectionInfo); - } - m_reporter->sectionStarting(sectionInfo); - } - - void ListeningReporter::assertionStarting(AssertionInfo const &assertionInfo) { - for (auto const &listener : m_listeners) { - listener->assertionStarting(assertionInfo); - } - m_reporter->assertionStarting(assertionInfo); - } - - // The return value indicates if the messages buffer should be cleared: - bool ListeningReporter::assertionEnded(AssertionStats const &assertionStats) { - for (auto const &listener : m_listeners) { - static_cast(listener->assertionEnded(assertionStats)); - } - return m_reporter->assertionEnded(assertionStats); - } - - void ListeningReporter::sectionEnded(SectionStats const §ionStats) { - for (auto const &listener : m_listeners) { - listener->sectionEnded(sectionStats); - } - m_reporter->sectionEnded(sectionStats); - } - - void ListeningReporter::testCaseEnded(TestCaseStats const &testCaseStats) { - for (auto const &listener : m_listeners) { - listener->testCaseEnded(testCaseStats); - } - m_reporter->testCaseEnded(testCaseStats); - } - - void ListeningReporter::testGroupEnded(TestGroupStats const &testGroupStats) { - for (auto const &listener : m_listeners) { - listener->testGroupEnded(testGroupStats); - } - m_reporter->testGroupEnded(testGroupStats); - } - - void ListeningReporter::testRunEnded(TestRunStats const &testRunStats) { - for (auto const &listener : m_listeners) { - listener->testRunEnded(testRunStats); - } - m_reporter->testRunEnded(testRunStats); - } - - void ListeningReporter::skipTest(TestCaseInfo const &testInfo) { - for (auto const &listener : m_listeners) { - listener->skipTest(testInfo); - } - m_reporter->skipTest(testInfo); - } - - bool ListeningReporter::isMulti() const { - return true; - } - -} // end namespace Catch -// end catch_reporter_listening.cpp -// start catch_reporter_xml.cpp - -#if defined(_MSC_VER) -#pragma warning(push) -#pragma warning(disable : 4061) // Not all labels are EXPLICITLY handled in switch \ - // Note that 4062 (not all labels are handled \ - // and default is missing) is enabled -#endif - -namespace Catch { - XmlReporter::XmlReporter(ReporterConfig const &_config) - : StreamingReporterBase(_config) - , m_xml(_config.stream()) { - m_reporterPrefs.shouldRedirectStdOut = true; - m_reporterPrefs.shouldReportAllAssertions = true; - } - - XmlReporter::~XmlReporter() = default; - - std::string XmlReporter::getDescription() { - return "Reports test results as an XML document"; - } - - std::string XmlReporter::getStylesheetRef() const { - return std::string(); - } - - void XmlReporter::writeSourceInfo(SourceLineInfo const &sourceInfo) { - m_xml.writeAttribute("filename", sourceInfo.file).writeAttribute("line", sourceInfo.line); - } - - void XmlReporter::noMatchingTestCases(std::string const &s) { - StreamingReporterBase::noMatchingTestCases(s); - } - - void XmlReporter::testRunStarting(TestRunInfo const &testInfo) { - StreamingReporterBase::testRunStarting(testInfo); - std::string stylesheetRef = getStylesheetRef(); - if (!stylesheetRef.empty()) - m_xml.writeStylesheetRef(stylesheetRef); - m_xml.startElement("Catch"); - if (!m_config->name().empty()) - m_xml.writeAttribute("name", m_config->name()); - if (m_config->testSpec().hasFilters()) - m_xml.writeAttribute("filters", serializeFilters(m_config->getTestsOrTags())); - if (m_config->rngSeed() != 0) - m_xml.scopedElement("Randomness").writeAttribute("seed", m_config->rngSeed()); - } - - void XmlReporter::testGroupStarting(GroupInfo const &groupInfo) { - StreamingReporterBase::testGroupStarting(groupInfo); - m_xml.startElement("Group").writeAttribute("name", groupInfo.name); - } - - void XmlReporter::testCaseStarting(TestCaseInfo const &testInfo) { - StreamingReporterBase::testCaseStarting(testInfo); - m_xml.startElement("TestCase") - .writeAttribute("name", trim(testInfo.name)) - .writeAttribute("description", testInfo.description) - .writeAttribute("tags", testInfo.tagsAsString()); - - writeSourceInfo(testInfo.lineInfo); - - if (m_config->showDurations() == ShowDurations::Always) - m_testCaseTimer.start(); - m_xml.ensureTagClosed(); - } - - void XmlReporter::sectionStarting(SectionInfo const §ionInfo) { - StreamingReporterBase::sectionStarting(sectionInfo); - if (m_sectionDepth++ > 0) { - m_xml.startElement("Section").writeAttribute("name", trim(sectionInfo.name)); - writeSourceInfo(sectionInfo.lineInfo); - m_xml.ensureTagClosed(); - } - } - - void XmlReporter::assertionStarting(AssertionInfo const &) {} - - bool XmlReporter::assertionEnded(AssertionStats const &assertionStats) { - AssertionResult const &result = assertionStats.assertionResult; - - bool includeResults = m_config->includeSuccessfulResults() || !result.isOk(); - - if (includeResults || result.getResultType() == ResultWas::Warning) { - // Print any info messages in tags. - for (auto const &msg : assertionStats.infoMessages) { - if (msg.type == ResultWas::Info && includeResults) { - m_xml.scopedElement("Info").writeText(msg.message); - } else if (msg.type == ResultWas::Warning) { - m_xml.scopedElement("Warning").writeText(msg.message); - } - } - } - - // Drop out if result was successful but we're not printing them. - if (!includeResults && result.getResultType() != ResultWas::Warning) - return true; - - // Print the expression if there is one. - if (result.hasExpression()) { - m_xml.startElement("Expression").writeAttribute("success", result.succeeded()).writeAttribute("type", result.getTestMacroName()); - - writeSourceInfo(result.getSourceInfo()); - - m_xml.scopedElement("Original").writeText(result.getExpression()); - m_xml.scopedElement("Expanded").writeText(result.getExpandedExpression()); - } - - // And... Print a result applicable to each result type. - switch (result.getResultType()) { - case ResultWas::ThrewException: - m_xml.startElement("Exception"); - writeSourceInfo(result.getSourceInfo()); - m_xml.writeText(result.getMessage()); - m_xml.endElement(); - break; - case ResultWas::FatalErrorCondition: - m_xml.startElement("FatalErrorCondition"); - writeSourceInfo(result.getSourceInfo()); - m_xml.writeText(result.getMessage()); - m_xml.endElement(); - break; - case ResultWas::Info: - m_xml.scopedElement("Info").writeText(result.getMessage()); - break; - case ResultWas::Warning: - // Warning will already have been written - break; - case ResultWas::ExplicitFailure: - m_xml.startElement("Failure"); - writeSourceInfo(result.getSourceInfo()); - m_xml.writeText(result.getMessage()); - m_xml.endElement(); - break; - default: - break; - } - - if (result.hasExpression()) - m_xml.endElement(); - - return true; - } - - void XmlReporter::sectionEnded(SectionStats const §ionStats) { - StreamingReporterBase::sectionEnded(sectionStats); - if (--m_sectionDepth > 0) { - XmlWriter::ScopedElement e = m_xml.scopedElement("OverallResults"); - e.writeAttribute("successes", sectionStats.assertions.passed); - e.writeAttribute("failures", sectionStats.assertions.failed); - e.writeAttribute("expectedFailures", sectionStats.assertions.failedButOk); - - if (m_config->showDurations() == ShowDurations::Always) - e.writeAttribute("durationInSeconds", sectionStats.durationInSeconds); - - m_xml.endElement(); - } - } - - void XmlReporter::testCaseEnded(TestCaseStats const &testCaseStats) { - StreamingReporterBase::testCaseEnded(testCaseStats); - XmlWriter::ScopedElement e = m_xml.scopedElement("OverallResult"); - e.writeAttribute("success", testCaseStats.totals.assertions.allOk()); - - if (m_config->showDurations() == ShowDurations::Always) - e.writeAttribute("durationInSeconds", m_testCaseTimer.getElapsedSeconds()); - - if (!testCaseStats.stdOut.empty()) - m_xml.scopedElement("StdOut").writeText(trim(testCaseStats.stdOut), XmlFormatting::Newline); - if (!testCaseStats.stdErr.empty()) - m_xml.scopedElement("StdErr").writeText(trim(testCaseStats.stdErr), XmlFormatting::Newline); - - m_xml.endElement(); - } - - void XmlReporter::testGroupEnded(TestGroupStats const &testGroupStats) { - StreamingReporterBase::testGroupEnded(testGroupStats); - // TODO: Check testGroupStats.aborting and act accordingly. - m_xml.scopedElement("OverallResults") - .writeAttribute("successes", testGroupStats.totals.assertions.passed) - .writeAttribute("failures", testGroupStats.totals.assertions.failed) - .writeAttribute("expectedFailures", testGroupStats.totals.assertions.failedButOk); - m_xml.scopedElement("OverallResultsCases") - .writeAttribute("successes", testGroupStats.totals.testCases.passed) - .writeAttribute("failures", testGroupStats.totals.testCases.failed) - .writeAttribute("expectedFailures", testGroupStats.totals.testCases.failedButOk); - m_xml.endElement(); - } - - void XmlReporter::testRunEnded(TestRunStats const &testRunStats) { - StreamingReporterBase::testRunEnded(testRunStats); - m_xml.scopedElement("OverallResults") - .writeAttribute("successes", testRunStats.totals.assertions.passed) - .writeAttribute("failures", testRunStats.totals.assertions.failed) - .writeAttribute("expectedFailures", testRunStats.totals.assertions.failedButOk); - m_xml.scopedElement("OverallResultsCases") - .writeAttribute("successes", testRunStats.totals.testCases.passed) - .writeAttribute("failures", testRunStats.totals.testCases.failed) - .writeAttribute("expectedFailures", testRunStats.totals.testCases.failedButOk); - m_xml.endElement(); - } - -#if defined(CATCH_CONFIG_ENABLE_BENCHMARKING) - void XmlReporter::benchmarkPreparing(std::string const &name) { - m_xml.startElement("BenchmarkResults").writeAttribute("name", name); - } - - void XmlReporter::benchmarkStarting(BenchmarkInfo const &info) { - m_xml.writeAttribute("samples", info.samples) - .writeAttribute("resamples", info.resamples) - .writeAttribute("iterations", info.iterations) - .writeAttribute("clockResolution", info.clockResolution) - .writeAttribute("estimatedDuration", info.estimatedDuration) - .writeComment("All values in nano seconds"); - } - - void XmlReporter::benchmarkEnded(BenchmarkStats<> const &benchmarkStats) { - m_xml.startElement("mean") - .writeAttribute("value", benchmarkStats.mean.point.count()) - .writeAttribute("lowerBound", benchmarkStats.mean.lower_bound.count()) - .writeAttribute("upperBound", benchmarkStats.mean.upper_bound.count()) - .writeAttribute("ci", benchmarkStats.mean.confidence_interval); - m_xml.endElement(); - m_xml.startElement("standardDeviation") - .writeAttribute("value", benchmarkStats.standardDeviation.point.count()) - .writeAttribute("lowerBound", benchmarkStats.standardDeviation.lower_bound.count()) - .writeAttribute("upperBound", benchmarkStats.standardDeviation.upper_bound.count()) - .writeAttribute("ci", benchmarkStats.standardDeviation.confidence_interval); - m_xml.endElement(); - m_xml.startElement("outliers") - .writeAttribute("variance", benchmarkStats.outlierVariance) - .writeAttribute("lowMild", benchmarkStats.outliers.low_mild) - .writeAttribute("lowSevere", benchmarkStats.outliers.low_severe) - .writeAttribute("highMild", benchmarkStats.outliers.high_mild) - .writeAttribute("highSevere", benchmarkStats.outliers.high_severe); - m_xml.endElement(); - m_xml.endElement(); - } - - void XmlReporter::benchmarkFailed(std::string const &error) { - m_xml.scopedElement("failed").writeAttribute("message", error); - m_xml.endElement(); - } -#endif // CATCH_CONFIG_ENABLE_BENCHMARKING - - CATCH_REGISTER_REPORTER("xml", XmlReporter) - -} // end namespace Catch - -#if defined(_MSC_VER) -#pragma warning(pop) -#endif -// end catch_reporter_xml.cpp - -namespace Catch { - LeakDetector leakDetector; -} - -#ifdef __clang__ -#pragma clang diagnostic pop -#endif - -// end catch_impl.hpp -#endif - -#ifdef CATCH_CONFIG_MAIN -// start catch_default_main.hpp - -#ifndef __OBJC__ - -#if defined(CATCH_CONFIG_WCHAR) && defined(CATCH_PLATFORM_WINDOWS) && defined(_UNICODE) && !defined(DO_NOT_USE_WMAIN) -// Standard C/C++ Win32 Unicode wmain entry point -extern "C" int wmain(int argc, wchar_t *argv[], wchar_t *[]) { -#else -// Standard C/C++ main entry point -int main(int argc, char *argv[]) { -#endif - - return Catch::Session().run(argc, argv); -} - -#else // __OBJC__ - -// Objective-C entry point -int main(int argc, char *const argv[]) { -#if !CATCH_ARC_ENABLED - NSAutoreleasePool *pool = [[NSAutoreleasePool alloc] init]; -#endif - - Catch::registerTestMethods(); - int result = Catch::Session().run(argc, (char **)argv); - -#if !CATCH_ARC_ENABLED - [pool drain]; -#endif - - return result; -} - -#endif // __OBJC__ - -// end catch_default_main.hpp -#endif - -#if !defined(CATCH_CONFIG_IMPL_ONLY) - -#ifdef CLARA_CONFIG_MAIN_NOT_DEFINED -#undef CLARA_CONFIG_MAIN -#endif - -#if !defined(CATCH_CONFIG_DISABLE) -////// -// If this config identifier is defined then all CATCH macros are prefixed with CATCH_ -#ifdef CATCH_CONFIG_PREFIX_ALL - -#define CATCH_REQUIRE(...) INTERNAL_CATCH_TEST("CATCH_REQUIRE", Catch::ResultDisposition::Normal, __VA_ARGS__) -#define CATCH_REQUIRE_FALSE(...) \ - INTERNAL_CATCH_TEST("CATCH_REQUIRE_FALSE", Catch::ResultDisposition::Normal | Catch::ResultDisposition::FalseTest, __VA_ARGS__) - -#define CATCH_REQUIRE_THROWS(...) INTERNAL_CATCH_THROWS("CATCH_REQUIRE_THROWS", Catch::ResultDisposition::Normal, __VA_ARGS__) -#define CATCH_REQUIRE_THROWS_AS(expr, exceptionType) \ - INTERNAL_CATCH_THROWS_AS("CATCH_REQUIRE_THROWS_AS", exceptionType, Catch::ResultDisposition::Normal, expr) -#define CATCH_REQUIRE_THROWS_WITH(expr, matcher) \ - INTERNAL_CATCH_THROWS_STR_MATCHES("CATCH_REQUIRE_THROWS_WITH", Catch::ResultDisposition::Normal, matcher, expr) -#if !defined(CATCH_CONFIG_DISABLE_MATCHERS) -#define CATCH_REQUIRE_THROWS_MATCHES(expr, exceptionType, matcher) \ - INTERNAL_CATCH_THROWS_MATCHES("CATCH_REQUIRE_THROWS_MATCHES", exceptionType, Catch::ResultDisposition::Normal, matcher, expr) -#endif // CATCH_CONFIG_DISABLE_MATCHERS -#define CATCH_REQUIRE_NOTHROW(...) \ - INTERNAL_CATCH_NO_THROW("CATCH_REQUIRE_NOTHROW", Catch::ResultDisposition::Normal, __VA_ARGS__) - -#define CATCH_CHECK(...) INTERNAL_CATCH_TEST("CATCH_CHECK", Catch::ResultDisposition::ContinueOnFailure, __VA_ARGS__) -#define CATCH_CHECK_FALSE(...) \ - INTERNAL_CATCH_TEST("CATCH_CHECK_FALSE", \ - Catch::ResultDisposition::ContinueOnFailure | Catch::ResultDisposition::FalseTest, \ - __VA_ARGS__) -#define CATCH_CHECKED_IF(...) INTERNAL_CATCH_IF("CATCH_CHECKED_IF", Catch::ResultDisposition::ContinueOnFailure, __VA_ARGS__) -#define CATCH_CHECKED_ELSE(...) \ - INTERNAL_CATCH_ELSE("CATCH_CHECKED_ELSE", Catch::ResultDisposition::ContinueOnFailure, __VA_ARGS__) -#define CATCH_CHECK_NOFAIL(...) \ - INTERNAL_CATCH_TEST("CATCH_CHECK_NOFAIL", \ - Catch::ResultDisposition::ContinueOnFailure | Catch::ResultDisposition::SuppressFail, \ - __VA_ARGS__) - -#define CATCH_CHECK_THROWS(...) \ - INTERNAL_CATCH_THROWS("CATCH_CHECK_THROWS", Catch::ResultDisposition::ContinueOnFailure, __VA_ARGS__) -#define CATCH_CHECK_THROWS_AS(expr, exceptionType) \ - INTERNAL_CATCH_THROWS_AS("CATCH_CHECK_THROWS_AS", exceptionType, Catch::ResultDisposition::ContinueOnFailure, expr) -#define CATCH_CHECK_THROWS_WITH(expr, matcher) \ - INTERNAL_CATCH_THROWS_STR_MATCHES("CATCH_CHECK_THROWS_WITH", Catch::ResultDisposition::ContinueOnFailure, matcher, expr) -#if !defined(CATCH_CONFIG_DISABLE_MATCHERS) -#define CATCH_CHECK_THROWS_MATCHES(expr, exceptionType, matcher) \ - INTERNAL_CATCH_THROWS_MATCHES("CATCH_CHECK_THROWS_MATCHES", \ - exceptionType, \ - Catch::ResultDisposition::ContinueOnFailure, \ - matcher, \ - expr) -#endif // CATCH_CONFIG_DISABLE_MATCHERS -#define CATCH_CHECK_NOTHROW(...) \ - INTERNAL_CATCH_NO_THROW("CATCH_CHECK_NOTHROW", Catch::ResultDisposition::ContinueOnFailure, __VA_ARGS__) - -#if !defined(CATCH_CONFIG_DISABLE_MATCHERS) -#define CATCH_CHECK_THAT(arg, matcher) \ - INTERNAL_CHECK_THAT("CATCH_CHECK_THAT", matcher, Catch::ResultDisposition::ContinueOnFailure, arg) - -#define CATCH_REQUIRE_THAT(arg, matcher) \ - INTERNAL_CHECK_THAT("CATCH_REQUIRE_THAT", matcher, Catch::ResultDisposition::Normal, arg) -#endif // CATCH_CONFIG_DISABLE_MATCHERS - -#define CATCH_INFO(msg) INTERNAL_CATCH_INFO("CATCH_INFO", msg) -#define CATCH_UNSCOPED_INFO(msg) INTERNAL_CATCH_UNSCOPED_INFO("CATCH_UNSCOPED_INFO", msg) -#define CATCH_WARN(msg) \ - INTERNAL_CATCH_MSG("CATCH_WARN", Catch::ResultWas::Warning, Catch::ResultDisposition::ContinueOnFailure, msg) -#define CATCH_CAPTURE(...) INTERNAL_CATCH_CAPTURE(INTERNAL_CATCH_UNIQUE_NAME(capturer), "CATCH_CAPTURE", __VA_ARGS__) - -#define CATCH_TEST_CASE(...) INTERNAL_CATCH_TESTCASE(__VA_ARGS__) -#define CATCH_TEST_CASE_METHOD(className, ...) INTERNAL_CATCH_TEST_CASE_METHOD(className, __VA_ARGS__) -#define CATCH_METHOD_AS_TEST_CASE(method, ...) INTERNAL_CATCH_METHOD_AS_TEST_CASE(method, __VA_ARGS__) -#define CATCH_REGISTER_TEST_CASE(Function, ...) INTERNAL_CATCH_REGISTER_TESTCASE(Function, __VA_ARGS__) -#define CATCH_SECTION(...) INTERNAL_CATCH_SECTION(__VA_ARGS__) -#define CATCH_DYNAMIC_SECTION(...) INTERNAL_CATCH_DYNAMIC_SECTION(__VA_ARGS__) -#define CATCH_FAIL(...) \ - INTERNAL_CATCH_MSG("CATCH_FAIL", Catch::ResultWas::ExplicitFailure, Catch::ResultDisposition::Normal, __VA_ARGS__) -#define CATCH_FAIL_CHECK(...) \ - INTERNAL_CATCH_MSG("CATCH_FAIL_CHECK", Catch::ResultWas::ExplicitFailure, Catch::ResultDisposition::ContinueOnFailure, __VA_ARGS__) -#define CATCH_SUCCEED(...) \ - INTERNAL_CATCH_MSG("CATCH_SUCCEED", Catch::ResultWas::Ok, Catch::ResultDisposition::ContinueOnFailure, __VA_ARGS__) - -#define CATCH_ANON_TEST_CASE() INTERNAL_CATCH_TESTCASE() - -#ifndef CATCH_CONFIG_TRADITIONAL_MSVC_PREPROCESSOR -#define CATCH_TEMPLATE_TEST_CASE(...) INTERNAL_CATCH_TEMPLATE_TEST_CASE(__VA_ARGS__) -#define CATCH_TEMPLATE_TEST_CASE_SIG(...) INTERNAL_CATCH_TEMPLATE_TEST_CASE_SIG(__VA_ARGS__) -#define CATCH_TEMPLATE_TEST_CASE_METHOD(className, ...) INTERNAL_CATCH_TEMPLATE_TEST_CASE_METHOD(className, __VA_ARGS__) -#define CATCH_TEMPLATE_TEST_CASE_METHOD_SIG(className, ...) \ - INTERNAL_CATCH_TEMPLATE_TEST_CASE_METHOD_SIG(className, __VA_ARGS__) -#define CATCH_TEMPLATE_PRODUCT_TEST_CASE(...) INTERNAL_CATCH_TEMPLATE_PRODUCT_TEST_CASE(__VA_ARGS__) -#define CATCH_TEMPLATE_PRODUCT_TEST_CASE_SIG(...) INTERNAL_CATCH_TEMPLATE_PRODUCT_TEST_CASE_SIG(__VA_ARGS__) -#define CATCH_TEMPLATE_PRODUCT_TEST_CASE_METHOD(className, ...) \ - INTERNAL_CATCH_TEMPLATE_PRODUCT_TEST_CASE_METHOD(className, __VA_ARGS__) -#define CATCH_TEMPLATE_PRODUCT_TEST_CASE_METHOD_SIG(className, ...) \ - INTERNAL_CATCH_TEMPLATE_PRODUCT_TEST_CASE_METHOD_SIG(className, __VA_ARGS__) -#else -#define CATCH_TEMPLATE_TEST_CASE(...) INTERNAL_CATCH_EXPAND_VARGS(INTERNAL_CATCH_TEMPLATE_TEST_CASE(__VA_ARGS__)) -#define CATCH_TEMPLATE_TEST_CASE_SIG(...) INTERNAL_CATCH_EXPAND_VARGS(INTERNAL_CATCH_TEMPLATE_TEST_CASE_SIG(__VA_ARGS__)) -#define CATCH_TEMPLATE_TEST_CASE_METHOD(className, ...) \ - INTERNAL_CATCH_EXPAND_VARGS(INTERNAL_CATCH_TEMPLATE_TEST_CASE_METHOD(className, __VA_ARGS__)) -#define CATCH_TEMPLATE_TEST_CASE_METHOD_SIG(className, ...) \ - INTERNAL_CATCH_EXPAND_VARGS(INTERNAL_CATCH_TEMPLATE_TEST_CASE_METHOD_SIG(className, __VA_ARGS__)) -#define CATCH_TEMPLATE_PRODUCT_TEST_CASE(...) \ - INTERNAL_CATCH_EXPAND_VARGS(INTERNAL_CATCH_TEMPLATE_PRODUCT_TEST_CASE(__VA_ARGS__)) -#define CATCH_TEMPLATE_PRODUCT_TEST_CASE_SIG(...) \ - INTERNAL_CATCH_EXPAND_VARGS(INTERNAL_CATCH_TEMPLATE_PRODUCT_TEST_CASE_SIG(__VA_ARGS__)) -#define CATCH_TEMPLATE_PRODUCT_TEST_CASE_METHOD(className, ...) \ - INTERNAL_CATCH_EXPAND_VARGS(INTERNAL_CATCH_TEMPLATE_PRODUCT_TEST_CASE_METHOD(className, __VA_ARGS__)) -#define CATCH_TEMPLATE_PRODUCT_TEST_CASE_METHOD_SIG(className, ...) \ - INTERNAL_CATCH_EXPAND_VARGS(INTERNAL_CATCH_TEMPLATE_PRODUCT_TEST_CASE_METHOD_SIG(className, __VA_ARGS__)) -#endif - -#if !defined(CATCH_CONFIG_RUNTIME_STATIC_REQUIRE) -#define CATCH_STATIC_REQUIRE(...) \ - static_assert(__VA_ARGS__, #__VA_ARGS__); \ - CATCH_SUCCEED(#__VA_ARGS__) -#define CATCH_STATIC_REQUIRE_FALSE(...) \ - static_assert(!(__VA_ARGS__), "!(" #__VA_ARGS__ ")"); \ - CATCH_SUCCEED(#__VA_ARGS__) -#else -#define CATCH_STATIC_REQUIRE(...) CATCH_REQUIRE(__VA_ARGS__) -#define CATCH_STATIC_REQUIRE_FALSE(...) CATCH_REQUIRE_FALSE(__VA_ARGS__) -#endif - -// "BDD-style" convenience wrappers -#define CATCH_SCENARIO(...) CATCH_TEST_CASE("Scenario: " __VA_ARGS__) -#define CATCH_SCENARIO_METHOD(className, ...) INTERNAL_CATCH_TEST_CASE_METHOD(className, "Scenario: " __VA_ARGS__) -#define CATCH_GIVEN(desc) INTERNAL_CATCH_DYNAMIC_SECTION(" Given: " << desc) -#define CATCH_AND_GIVEN(desc) INTERNAL_CATCH_DYNAMIC_SECTION("And given: " << desc) -#define CATCH_WHEN(desc) INTERNAL_CATCH_DYNAMIC_SECTION(" When: " << desc) -#define CATCH_AND_WHEN(desc) INTERNAL_CATCH_DYNAMIC_SECTION(" And when: " << desc) -#define CATCH_THEN(desc) INTERNAL_CATCH_DYNAMIC_SECTION(" Then: " << desc) -#define CATCH_AND_THEN(desc) INTERNAL_CATCH_DYNAMIC_SECTION(" And: " << desc) - -#if defined(CATCH_CONFIG_ENABLE_BENCHMARKING) -#define CATCH_BENCHMARK(...) \ - INTERNAL_CATCH_BENCHMARK(INTERNAL_CATCH_UNIQUE_NAME(____C_A_T_C_H____B_E_N_C_H____), \ - INTERNAL_CATCH_GET_1_ARG(__VA_ARGS__, , ), \ - INTERNAL_CATCH_GET_2_ARG(__VA_ARGS__, , )) -#define CATCH_BENCHMARK_ADVANCED(name) \ - INTERNAL_CATCH_BENCHMARK_ADVANCED(INTERNAL_CATCH_UNIQUE_NAME(____C_A_T_C_H____B_E_N_C_H____), name) -#endif // CATCH_CONFIG_ENABLE_BENCHMARKING - -// If CATCH_CONFIG_PREFIX_ALL is not defined then the CATCH_ prefix is not required -#else - -#define REQUIRE(...) INTERNAL_CATCH_TEST("REQUIRE", Catch::ResultDisposition::Normal, __VA_ARGS__) -#define REQUIRE_FALSE(...) \ - INTERNAL_CATCH_TEST("REQUIRE_FALSE", Catch::ResultDisposition::Normal | Catch::ResultDisposition::FalseTest, __VA_ARGS__) - -#define REQUIRE_THROWS(...) INTERNAL_CATCH_THROWS("REQUIRE_THROWS", Catch::ResultDisposition::Normal, __VA_ARGS__) -#define REQUIRE_THROWS_AS(expr, exceptionType) \ - INTERNAL_CATCH_THROWS_AS("REQUIRE_THROWS_AS", exceptionType, Catch::ResultDisposition::Normal, expr) -#define REQUIRE_THROWS_WITH(expr, matcher) \ - INTERNAL_CATCH_THROWS_STR_MATCHES("REQUIRE_THROWS_WITH", Catch::ResultDisposition::Normal, matcher, expr) -#if !defined(CATCH_CONFIG_DISABLE_MATCHERS) -#define REQUIRE_THROWS_MATCHES(expr, exceptionType, matcher) \ - INTERNAL_CATCH_THROWS_MATCHES("REQUIRE_THROWS_MATCHES", exceptionType, Catch::ResultDisposition::Normal, matcher, expr) -#endif // CATCH_CONFIG_DISABLE_MATCHERS -#define REQUIRE_NOTHROW(...) INTERNAL_CATCH_NO_THROW("REQUIRE_NOTHROW", Catch::ResultDisposition::Normal, __VA_ARGS__) - -#define CHECK(...) INTERNAL_CATCH_TEST("CHECK", Catch::ResultDisposition::ContinueOnFailure, __VA_ARGS__) -#define CHECK_FALSE(...) \ - INTERNAL_CATCH_TEST("CHECK_FALSE", Catch::ResultDisposition::ContinueOnFailure | Catch::ResultDisposition::FalseTest, __VA_ARGS__) -#define CHECKED_IF(...) INTERNAL_CATCH_IF("CHECKED_IF", Catch::ResultDisposition::ContinueOnFailure, __VA_ARGS__) -#define CHECKED_ELSE(...) INTERNAL_CATCH_ELSE("CHECKED_ELSE", Catch::ResultDisposition::ContinueOnFailure, __VA_ARGS__) -#define CHECK_NOFAIL(...) \ - INTERNAL_CATCH_TEST("CHECK_NOFAIL", Catch::ResultDisposition::ContinueOnFailure | Catch::ResultDisposition::SuppressFail, __VA_ARGS__) - -#define CHECK_THROWS(...) INTERNAL_CATCH_THROWS("CHECK_THROWS", Catch::ResultDisposition::ContinueOnFailure, __VA_ARGS__) -#define CHECK_THROWS_AS(expr, exceptionType) \ - INTERNAL_CATCH_THROWS_AS("CHECK_THROWS_AS", exceptionType, Catch::ResultDisposition::ContinueOnFailure, expr) -#define CHECK_THROWS_WITH(expr, matcher) \ - INTERNAL_CATCH_THROWS_STR_MATCHES("CHECK_THROWS_WITH", Catch::ResultDisposition::ContinueOnFailure, matcher, expr) -#if !defined(CATCH_CONFIG_DISABLE_MATCHERS) -#define CHECK_THROWS_MATCHES(expr, exceptionType, matcher) \ - INTERNAL_CATCH_THROWS_MATCHES("CHECK_THROWS_MATCHES", exceptionType, Catch::ResultDisposition::ContinueOnFailure, matcher, expr) -#endif // CATCH_CONFIG_DISABLE_MATCHERS -#define CHECK_NOTHROW(...) INTERNAL_CATCH_NO_THROW("CHECK_NOTHROW", Catch::ResultDisposition::ContinueOnFailure, __VA_ARGS__) - -#if !defined(CATCH_CONFIG_DISABLE_MATCHERS) -#define CHECK_THAT(arg, matcher) \ - INTERNAL_CHECK_THAT("CHECK_THAT", matcher, Catch::ResultDisposition::ContinueOnFailure, arg) - -#define REQUIRE_THAT(arg, matcher) INTERNAL_CHECK_THAT("REQUIRE_THAT", matcher, Catch::ResultDisposition::Normal, arg) -#endif // CATCH_CONFIG_DISABLE_MATCHERS - -#define INFO(msg) INTERNAL_CATCH_INFO("INFO", msg) -#define UNSCOPED_INFO(msg) INTERNAL_CATCH_UNSCOPED_INFO("UNSCOPED_INFO", msg) -#define WARN(msg) INTERNAL_CATCH_MSG("WARN", Catch::ResultWas::Warning, Catch::ResultDisposition::ContinueOnFailure, msg) -#define CAPTURE(...) INTERNAL_CATCH_CAPTURE(INTERNAL_CATCH_UNIQUE_NAME(capturer), "CAPTURE", __VA_ARGS__) - -#define TEST_CASE(...) INTERNAL_CATCH_TESTCASE(__VA_ARGS__) -#define TEST_CASE_METHOD(className, ...) INTERNAL_CATCH_TEST_CASE_METHOD(className, __VA_ARGS__) -#define METHOD_AS_TEST_CASE(method, ...) INTERNAL_CATCH_METHOD_AS_TEST_CASE(method, __VA_ARGS__) -#define REGISTER_TEST_CASE(Function, ...) INTERNAL_CATCH_REGISTER_TESTCASE(Function, __VA_ARGS__) -#define SECTION(...) INTERNAL_CATCH_SECTION(__VA_ARGS__) -#define DYNAMIC_SECTION(...) INTERNAL_CATCH_DYNAMIC_SECTION(__VA_ARGS__) -#define FAIL(...) INTERNAL_CATCH_MSG("FAIL", Catch::ResultWas::ExplicitFailure, Catch::ResultDisposition::Normal, __VA_ARGS__) -#define FAIL_CHECK(...) \ - INTERNAL_CATCH_MSG("FAIL_CHECK", Catch::ResultWas::ExplicitFailure, Catch::ResultDisposition::ContinueOnFailure, __VA_ARGS__) -#define SUCCEED(...) \ - INTERNAL_CATCH_MSG("SUCCEED", Catch::ResultWas::Ok, Catch::ResultDisposition::ContinueOnFailure, __VA_ARGS__) -#define ANON_TEST_CASE() INTERNAL_CATCH_TESTCASE() - -#ifndef CATCH_CONFIG_TRADITIONAL_MSVC_PREPROCESSOR -#define TEMPLATE_TEST_CASE(...) INTERNAL_CATCH_TEMPLATE_TEST_CASE(__VA_ARGS__) -#define TEMPLATE_TEST_CASE_SIG(...) INTERNAL_CATCH_TEMPLATE_TEST_CASE_SIG(__VA_ARGS__) -#define TEMPLATE_TEST_CASE_METHOD(className, ...) INTERNAL_CATCH_TEMPLATE_TEST_CASE_METHOD(className, __VA_ARGS__) -#define TEMPLATE_TEST_CASE_METHOD_SIG(className, ...) INTERNAL_CATCH_TEMPLATE_TEST_CASE_METHOD_SIG(className, __VA_ARGS__) -#define TEMPLATE_PRODUCT_TEST_CASE(...) INTERNAL_CATCH_TEMPLATE_PRODUCT_TEST_CASE(__VA_ARGS__) -#define TEMPLATE_PRODUCT_TEST_CASE_SIG(...) INTERNAL_CATCH_TEMPLATE_PRODUCT_TEST_CASE_SIG(__VA_ARGS__) -#define TEMPLATE_PRODUCT_TEST_CASE_METHOD(className, ...) \ - INTERNAL_CATCH_TEMPLATE_PRODUCT_TEST_CASE_METHOD(className, __VA_ARGS__) -#define TEMPLATE_PRODUCT_TEST_CASE_METHOD_SIG(className, ...) \ - INTERNAL_CATCH_TEMPLATE_PRODUCT_TEST_CASE_METHOD_SIG(className, __VA_ARGS__) -#define TEMPLATE_LIST_TEST_CASE(...) INTERNAL_CATCH_TEMPLATE_LIST_TEST_CASE(__VA_ARGS__) -#define TEMPLATE_LIST_TEST_CASE_METHOD(className, ...) INTERNAL_CATCH_TEMPLATE_LIST_TEST_CASE_METHOD(className, __VA_ARGS__) -#else -#define TEMPLATE_TEST_CASE(...) INTERNAL_CATCH_EXPAND_VARGS(INTERNAL_CATCH_TEMPLATE_TEST_CASE(__VA_ARGS__)) -#define TEMPLATE_TEST_CASE_SIG(...) INTERNAL_CATCH_EXPAND_VARGS(INTERNAL_CATCH_TEMPLATE_TEST_CASE_SIG(__VA_ARGS__)) -#define TEMPLATE_TEST_CASE_METHOD(className, ...) \ - INTERNAL_CATCH_EXPAND_VARGS(INTERNAL_CATCH_TEMPLATE_TEST_CASE_METHOD(className, __VA_ARGS__)) -#define TEMPLATE_TEST_CASE_METHOD_SIG(className, ...) \ - INTERNAL_CATCH_EXPAND_VARGS(INTERNAL_CATCH_TEMPLATE_TEST_CASE_METHOD_SIG(className, __VA_ARGS__)) -#define TEMPLATE_PRODUCT_TEST_CASE(...) INTERNAL_CATCH_EXPAND_VARGS(INTERNAL_CATCH_TEMPLATE_PRODUCT_TEST_CASE(__VA_ARGS__)) -#define TEMPLATE_PRODUCT_TEST_CASE_SIG(...) \ - INTERNAL_CATCH_EXPAND_VARGS(INTERNAL_CATCH_TEMPLATE_PRODUCT_TEST_CASE_SIG(__VA_ARGS__)) -#define TEMPLATE_PRODUCT_TEST_CASE_METHOD(className, ...) \ - INTERNAL_CATCH_EXPAND_VARGS(INTERNAL_CATCH_TEMPLATE_PRODUCT_TEST_CASE_METHOD(className, __VA_ARGS__)) -#define TEMPLATE_PRODUCT_TEST_CASE_METHOD_SIG(className, ...) \ - INTERNAL_CATCH_EXPAND_VARGS(INTERNAL_CATCH_TEMPLATE_PRODUCT_TEST_CASE_METHOD_SIG(className, __VA_ARGS__)) -#define TEMPLATE_LIST_TEST_CASE(...) INTERNAL_CATCH_EXPAND_VARGS(INTERNAL_CATCH_TEMPLATE_LIST_TEST_CASE(__VA_ARGS__)) -#define TEMPLATE_LIST_TEST_CASE_METHOD(className, ...) \ - INTERNAL_CATCH_EXPAND_VARGS(INTERNAL_CATCH_TEMPLATE_LIST_TEST_CASE_METHOD(className, __VA_ARGS__)) -#endif - -#if !defined(CATCH_CONFIG_RUNTIME_STATIC_REQUIRE) -#define STATIC_REQUIRE(...) \ - static_assert(__VA_ARGS__, #__VA_ARGS__); \ - SUCCEED(#__VA_ARGS__) -#define STATIC_REQUIRE_FALSE(...) \ - static_assert(!(__VA_ARGS__), "!(" #__VA_ARGS__ ")"); \ - SUCCEED("!(" #__VA_ARGS__ ")") -#else -#define STATIC_REQUIRE(...) REQUIRE(__VA_ARGS__) -#define STATIC_REQUIRE_FALSE(...) REQUIRE_FALSE(__VA_ARGS__) -#endif - -#endif - -#define CATCH_TRANSLATE_EXCEPTION(signature) INTERNAL_CATCH_TRANSLATE_EXCEPTION(signature) - -// "BDD-style" convenience wrappers -#define SCENARIO(...) TEST_CASE("Scenario: " __VA_ARGS__) -#define SCENARIO_METHOD(className, ...) INTERNAL_CATCH_TEST_CASE_METHOD(className, "Scenario: " __VA_ARGS__) - -#define GIVEN(desc) INTERNAL_CATCH_DYNAMIC_SECTION(" Given: " << desc) -#define AND_GIVEN(desc) INTERNAL_CATCH_DYNAMIC_SECTION("And given: " << desc) -#define WHEN(desc) INTERNAL_CATCH_DYNAMIC_SECTION(" When: " << desc) -#define AND_WHEN(desc) INTERNAL_CATCH_DYNAMIC_SECTION(" And when: " << desc) -#define THEN(desc) INTERNAL_CATCH_DYNAMIC_SECTION(" Then: " << desc) -#define AND_THEN(desc) INTERNAL_CATCH_DYNAMIC_SECTION(" And: " << desc) - -#if defined(CATCH_CONFIG_ENABLE_BENCHMARKING) -#define BENCHMARK(...) \ - INTERNAL_CATCH_BENCHMARK(INTERNAL_CATCH_UNIQUE_NAME(____C_A_T_C_H____B_E_N_C_H____), \ - INTERNAL_CATCH_GET_1_ARG(__VA_ARGS__, , ), \ - INTERNAL_CATCH_GET_2_ARG(__VA_ARGS__, , )) -#define BENCHMARK_ADVANCED(name) \ - INTERNAL_CATCH_BENCHMARK_ADVANCED(INTERNAL_CATCH_UNIQUE_NAME(____C_A_T_C_H____B_E_N_C_H____), name) -#endif // CATCH_CONFIG_ENABLE_BENCHMARKING - -using Catch::Detail::Approx; - -#else // CATCH_CONFIG_DISABLE - -////// -// If this config identifier is defined then all CATCH macros are prefixed with CATCH_ -#ifdef CATCH_CONFIG_PREFIX_ALL - -#define CATCH_REQUIRE(...) (void)(0) -#define CATCH_REQUIRE_FALSE(...) (void)(0) - -#define CATCH_REQUIRE_THROWS(...) (void)(0) -#define CATCH_REQUIRE_THROWS_AS(expr, exceptionType) (void)(0) -#define CATCH_REQUIRE_THROWS_WITH(expr, matcher) (void)(0) -#if !defined(CATCH_CONFIG_DISABLE_MATCHERS) -#define CATCH_REQUIRE_THROWS_MATCHES(expr, exceptionType, matcher) (void)(0) -#endif // CATCH_CONFIG_DISABLE_MATCHERS -#define CATCH_REQUIRE_NOTHROW(...) (void)(0) - -#define CATCH_CHECK(...) (void)(0) -#define CATCH_CHECK_FALSE(...) (void)(0) -#define CATCH_CHECKED_IF(...) if (__VA_ARGS__) -#define CATCH_CHECKED_ELSE(...) if (!(__VA_ARGS__)) -#define CATCH_CHECK_NOFAIL(...) (void)(0) - -#define CATCH_CHECK_THROWS(...) (void)(0) -#define CATCH_CHECK_THROWS_AS(expr, exceptionType) (void)(0) -#define CATCH_CHECK_THROWS_WITH(expr, matcher) (void)(0) -#if !defined(CATCH_CONFIG_DISABLE_MATCHERS) -#define CATCH_CHECK_THROWS_MATCHES(expr, exceptionType, matcher) (void)(0) -#endif // CATCH_CONFIG_DISABLE_MATCHERS -#define CATCH_CHECK_NOTHROW(...) (void)(0) - -#if !defined(CATCH_CONFIG_DISABLE_MATCHERS) -#define CATCH_CHECK_THAT(arg, matcher) (void)(0) - -#define CATCH_REQUIRE_THAT(arg, matcher) (void)(0) -#endif // CATCH_CONFIG_DISABLE_MATCHERS - -#define CATCH_INFO(msg) (void)(0) -#define CATCH_UNSCOPED_INFO(msg) (void)(0) -#define CATCH_WARN(msg) (void)(0) -#define CATCH_CAPTURE(msg) (void)(0) - -#define CATCH_TEST_CASE(...) INTERNAL_CATCH_TESTCASE_NO_REGISTRATION(INTERNAL_CATCH_UNIQUE_NAME(____C_A_T_C_H____T_E_S_T____)) -#define CATCH_TEST_CASE_METHOD(className, ...) \ - INTERNAL_CATCH_TESTCASE_NO_REGISTRATION(INTERNAL_CATCH_UNIQUE_NAME(____C_A_T_C_H____T_E_S_T____)) -#define CATCH_METHOD_AS_TEST_CASE(method, ...) -#define CATCH_REGISTER_TEST_CASE(Function, ...) (void)(0) -#define CATCH_SECTION(...) -#define CATCH_DYNAMIC_SECTION(...) -#define CATCH_FAIL(...) (void)(0) -#define CATCH_FAIL_CHECK(...) (void)(0) -#define CATCH_SUCCEED(...) (void)(0) - -#define CATCH_ANON_TEST_CASE() INTERNAL_CATCH_TESTCASE_NO_REGISTRATION(INTERNAL_CATCH_UNIQUE_NAME(____C_A_T_C_H____T_E_S_T____)) - -#ifndef CATCH_CONFIG_TRADITIONAL_MSVC_PREPROCESSOR -#define CATCH_TEMPLATE_TEST_CASE(...) INTERNAL_CATCH_TEMPLATE_TEST_CASE_NO_REGISTRATION(__VA_ARGS__) -#define CATCH_TEMPLATE_TEST_CASE_SIG(...) INTERNAL_CATCH_TEMPLATE_TEST_CASE_SIG_NO_REGISTRATION(__VA_ARGS__) -#define CATCH_TEMPLATE_TEST_CASE_METHOD(className, ...) \ - INTERNAL_CATCH_TEMPLATE_TEST_CASE_METHOD_NO_REGISTRATION(className, __VA_ARGS__) -#define CATCH_TEMPLATE_TEST_CASE_METHOD_SIG(className, ...) \ - INTERNAL_CATCH_TEMPLATE_TEST_CASE_METHOD_SIG_NO_REGISTRATION(className, __VA_ARGS__) -#define CATCH_TEMPLATE_PRODUCT_TEST_CASE(...) CATCH_TEMPLATE_TEST_CASE(__VA_ARGS__) -#define CATCH_TEMPLATE_PRODUCT_TEST_CASE_SIG(...) CATCH_TEMPLATE_TEST_CASE(__VA_ARGS__) -#define CATCH_TEMPLATE_PRODUCT_TEST_CASE_METHOD(className, ...) CATCH_TEMPLATE_TEST_CASE_METHOD(className, __VA_ARGS__) -#define CATCH_TEMPLATE_PRODUCT_TEST_CASE_METHOD_SIG(className, ...) CATCH_TEMPLATE_TEST_CASE_METHOD(className, __VA_ARGS__) -#else -#define CATCH_TEMPLATE_TEST_CASE(...) \ - INTERNAL_CATCH_EXPAND_VARGS(INTERNAL_CATCH_TEMPLATE_TEST_CASE_NO_REGISTRATION(__VA_ARGS__)) -#define CATCH_TEMPLATE_TEST_CASE_SIG(...) \ - INTERNAL_CATCH_EXPAND_VARGS(INTERNAL_CATCH_TEMPLATE_TEST_CASE_SIG_NO_REGISTRATION(__VA_ARGS__)) -#define CATCH_TEMPLATE_TEST_CASE_METHOD(className, ...) \ - INTERNAL_CATCH_EXPAND_VARGS(INTERNAL_CATCH_TEMPLATE_TEST_CASE_METHOD_NO_REGISTRATION(className, __VA_ARGS__)) -#define CATCH_TEMPLATE_TEST_CASE_METHOD_SIG(className, ...) \ - INTERNAL_CATCH_EXPAND_VARGS(INTERNAL_CATCH_TEMPLATE_TEST_CASE_METHOD_SIG_NO_REGISTRATION(className, __VA_ARGS__)) -#define CATCH_TEMPLATE_PRODUCT_TEST_CASE(...) CATCH_TEMPLATE_TEST_CASE(__VA_ARGS__) -#define CATCH_TEMPLATE_PRODUCT_TEST_CASE_SIG(...) CATCH_TEMPLATE_TEST_CASE(__VA_ARGS__) -#define CATCH_TEMPLATE_PRODUCT_TEST_CASE_METHOD(className, ...) CATCH_TEMPLATE_TEST_CASE_METHOD(className, __VA_ARGS__) -#define CATCH_TEMPLATE_PRODUCT_TEST_CASE_METHOD_SIG(className, ...) CATCH_TEMPLATE_TEST_CASE_METHOD(className, __VA_ARGS__) -#endif - -// "BDD-style" convenience wrappers -#define CATCH_SCENARIO(...) INTERNAL_CATCH_TESTCASE_NO_REGISTRATION(INTERNAL_CATCH_UNIQUE_NAME(____C_A_T_C_H____T_E_S_T____)) -#define CATCH_SCENARIO_METHOD(className, ...) \ - INTERNAL_CATCH_TESTCASE_METHOD_NO_REGISTRATION(INTERNAL_CATCH_UNIQUE_NAME(____C_A_T_C_H____T_E_S_T____), className) -#define CATCH_GIVEN(desc) -#define CATCH_AND_GIVEN(desc) -#define CATCH_WHEN(desc) -#define CATCH_AND_WHEN(desc) -#define CATCH_THEN(desc) -#define CATCH_AND_THEN(desc) - -#define CATCH_STATIC_REQUIRE(...) (void)(0) -#define CATCH_STATIC_REQUIRE_FALSE(...) (void)(0) - -// If CATCH_CONFIG_PREFIX_ALL is not defined then the CATCH_ prefix is not required -#else - -#define REQUIRE(...) (void)(0) -#define REQUIRE_FALSE(...) (void)(0) - -#define REQUIRE_THROWS(...) (void)(0) -#define REQUIRE_THROWS_AS(expr, exceptionType) (void)(0) -#define REQUIRE_THROWS_WITH(expr, matcher) (void)(0) -#if !defined(CATCH_CONFIG_DISABLE_MATCHERS) -#define REQUIRE_THROWS_MATCHES(expr, exceptionType, matcher) (void)(0) -#endif // CATCH_CONFIG_DISABLE_MATCHERS -#define REQUIRE_NOTHROW(...) (void)(0) - -#define CHECK(...) (void)(0) -#define CHECK_FALSE(...) (void)(0) -#define CHECKED_IF(...) if (__VA_ARGS__) -#define CHECKED_ELSE(...) if (!(__VA_ARGS__)) -#define CHECK_NOFAIL(...) (void)(0) - -#define CHECK_THROWS(...) (void)(0) -#define CHECK_THROWS_AS(expr, exceptionType) (void)(0) -#define CHECK_THROWS_WITH(expr, matcher) (void)(0) -#if !defined(CATCH_CONFIG_DISABLE_MATCHERS) -#define CHECK_THROWS_MATCHES(expr, exceptionType, matcher) (void)(0) -#endif // CATCH_CONFIG_DISABLE_MATCHERS -#define CHECK_NOTHROW(...) (void)(0) - -#if !defined(CATCH_CONFIG_DISABLE_MATCHERS) -#define CHECK_THAT(arg, matcher) (void)(0) - -#define REQUIRE_THAT(arg, matcher) (void)(0) -#endif // CATCH_CONFIG_DISABLE_MATCHERS - -#define INFO(msg) (void)(0) -#define UNSCOPED_INFO(msg) (void)(0) -#define WARN(msg) (void)(0) -#define CAPTURE(msg) (void)(0) - -#define TEST_CASE(...) INTERNAL_CATCH_TESTCASE_NO_REGISTRATION(INTERNAL_CATCH_UNIQUE_NAME(____C_A_T_C_H____T_E_S_T____)) -#define TEST_CASE_METHOD(className, ...) \ - INTERNAL_CATCH_TESTCASE_NO_REGISTRATION(INTERNAL_CATCH_UNIQUE_NAME(____C_A_T_C_H____T_E_S_T____)) -#define METHOD_AS_TEST_CASE(method, ...) -#define REGISTER_TEST_CASE(Function, ...) (void)(0) -#define SECTION(...) -#define DYNAMIC_SECTION(...) -#define FAIL(...) (void)(0) -#define FAIL_CHECK(...) (void)(0) -#define SUCCEED(...) (void)(0) -#define ANON_TEST_CASE() INTERNAL_CATCH_TESTCASE_NO_REGISTRATION(INTERNAL_CATCH_UNIQUE_NAME(____C_A_T_C_H____T_E_S_T____)) - -#ifndef CATCH_CONFIG_TRADITIONAL_MSVC_PREPROCESSOR -#define TEMPLATE_TEST_CASE(...) INTERNAL_CATCH_TEMPLATE_TEST_CASE_NO_REGISTRATION(__VA_ARGS__) -#define TEMPLATE_TEST_CASE_SIG(...) INTERNAL_CATCH_TEMPLATE_TEST_CASE_SIG_NO_REGISTRATION(__VA_ARGS__) -#define TEMPLATE_TEST_CASE_METHOD(className, ...) \ - INTERNAL_CATCH_TEMPLATE_TEST_CASE_METHOD_NO_REGISTRATION(className, __VA_ARGS__) -#define TEMPLATE_TEST_CASE_METHOD_SIG(className, ...) \ - INTERNAL_CATCH_TEMPLATE_TEST_CASE_METHOD_SIG_NO_REGISTRATION(className, __VA_ARGS__) -#define TEMPLATE_PRODUCT_TEST_CASE(...) TEMPLATE_TEST_CASE(__VA_ARGS__) -#define TEMPLATE_PRODUCT_TEST_CASE_SIG(...) TEMPLATE_TEST_CASE(__VA_ARGS__) -#define TEMPLATE_PRODUCT_TEST_CASE_METHOD(className, ...) TEMPLATE_TEST_CASE_METHOD(className, __VA_ARGS__) -#define TEMPLATE_PRODUCT_TEST_CASE_METHOD_SIG(className, ...) TEMPLATE_TEST_CASE_METHOD(className, __VA_ARGS__) -#else -#define TEMPLATE_TEST_CASE(...) INTERNAL_CATCH_EXPAND_VARGS(INTERNAL_CATCH_TEMPLATE_TEST_CASE_NO_REGISTRATION(__VA_ARGS__)) -#define TEMPLATE_TEST_CASE_SIG(...) \ - INTERNAL_CATCH_EXPAND_VARGS(INTERNAL_CATCH_TEMPLATE_TEST_CASE_SIG_NO_REGISTRATION(__VA_ARGS__)) -#define TEMPLATE_TEST_CASE_METHOD(className, ...) \ - INTERNAL_CATCH_EXPAND_VARGS(INTERNAL_CATCH_TEMPLATE_TEST_CASE_METHOD_NO_REGISTRATION(className, __VA_ARGS__)) -#define TEMPLATE_TEST_CASE_METHOD_SIG(className, ...) \ - INTERNAL_CATCH_EXPAND_VARGS(INTERNAL_CATCH_TEMPLATE_TEST_CASE_METHOD_SIG_NO_REGISTRATION(className, __VA_ARGS__)) -#define TEMPLATE_PRODUCT_TEST_CASE(...) TEMPLATE_TEST_CASE(__VA_ARGS__) -#define TEMPLATE_PRODUCT_TEST_CASE_SIG(...) TEMPLATE_TEST_CASE(__VA_ARGS__) -#define TEMPLATE_PRODUCT_TEST_CASE_METHOD(className, ...) TEMPLATE_TEST_CASE_METHOD(className, __VA_ARGS__) -#define TEMPLATE_PRODUCT_TEST_CASE_METHOD_SIG(className, ...) TEMPLATE_TEST_CASE_METHOD(className, __VA_ARGS__) -#endif - -#define STATIC_REQUIRE(...) (void)(0) -#define STATIC_REQUIRE_FALSE(...) (void)(0) - -#endif - -#define CATCH_TRANSLATE_EXCEPTION(signature) \ - INTERNAL_CATCH_TRANSLATE_EXCEPTION_NO_REG(INTERNAL_CATCH_UNIQUE_NAME(catch_internal_ExceptionTranslator), signature) - -// "BDD-style" convenience wrappers -#define SCENARIO(...) INTERNAL_CATCH_TESTCASE_NO_REGISTRATION(INTERNAL_CATCH_UNIQUE_NAME(____C_A_T_C_H____T_E_S_T____)) -#define SCENARIO_METHOD(className, ...) \ - INTERNAL_CATCH_TESTCASE_METHOD_NO_REGISTRATION(INTERNAL_CATCH_UNIQUE_NAME(____C_A_T_C_H____T_E_S_T____), className) - -#define GIVEN(desc) -#define AND_GIVEN(desc) -#define WHEN(desc) -#define AND_WHEN(desc) -#define THEN(desc) -#define AND_THEN(desc) - -using Catch::Detail::Approx; - -#endif - -#endif // ! CATCH_CONFIG_IMPL_ONLY - -// start catch_reenable_warnings.h - -#ifdef __clang__ -#ifdef __ICC // icpc defines the __clang__ macro -#pragma warning(pop) -#else -#pragma clang diagnostic pop -#endif -#elif defined __GNUC__ -#pragma GCC diagnostic pop -#endif - -// end catch_reenable_warnings.h -// end catch.hpp -#endif // TWOBLUECUBES_SINGLE_INCLUDE_CATCH_HPP_INCLUDED diff --git a/unittests/catch_amalgamated.cpp b/unittests/catch_amalgamated.cpp new file mode 100644 index 000000000..a92443851 --- /dev/null +++ b/unittests/catch_amalgamated.cpp @@ -0,0 +1,11844 @@ + +// Copyright Catch2 Authors +// Distributed under the Boost Software License, Version 1.0. +// (See accompanying file LICENSE.txt or copy at +// https://www.boost.org/LICENSE_1_0.txt) + +// SPDX-License-Identifier: BSL-1.0 + +// Catch v3.14.0 +// Generated: 2026-04-05 15:03:01.631668 +// ---------------------------------------------------------- +// This file is an amalgamation of multiple different files. +// You probably shouldn't edit it directly. +// ---------------------------------------------------------- + +#include "catch_amalgamated.hpp" + +#ifndef CATCH_WINDOWS_H_PROXY_HPP_INCLUDED +#define CATCH_WINDOWS_H_PROXY_HPP_INCLUDED + +#if defined(CATCH_PLATFORM_WINDOWS) + +// We might end up with the define made globally through the compiler, +// and we don't want to trigger warnings for this +#if !defined(NOMINMAX) +#define NOMINMAX +#endif +#if !defined(WIN32_LEAN_AND_MEAN) +#define WIN32_LEAN_AND_MEAN +#endif + +#include + +#endif // defined(CATCH_PLATFORM_WINDOWS) + +#endif // CATCH_WINDOWS_H_PROXY_HPP_INCLUDED + +namespace Catch { + namespace Benchmark { + namespace Detail { + ChronometerConcept::~ChronometerConcept() = default; + } // namespace Detail + } // namespace Benchmark +} // namespace Catch + +// Adapted from donated nonius code. + +#include + +namespace Catch { + namespace Benchmark { + namespace Detail { + SampleAnalysis analyse(const IConfig &cfg, FDuration *first, FDuration *last) { + if (!cfg.benchmarkNoAnalysis()) { + std::vector samples; + samples.reserve(static_cast(last - first)); + for (auto current = first; current != last; ++current) { + samples.push_back(current->count()); + } + + auto analysis = Catch::Benchmark::Detail::analyse_samples( + cfg.benchmarkConfidenceInterval(), + cfg.benchmarkResamples(), + samples.data(), + samples.data() + samples.size()); + auto outliers = Catch::Benchmark::Detail::classify_outliers( + samples.data(), samples.data() + samples.size()); + + auto wrap_estimate = [](Estimate e) { + return Estimate{ + FDuration(e.point), + FDuration(e.lower_bound), + FDuration(e.upper_bound), + e.confidence_interval, + }; + }; + std::vector samples2; + samples2.reserve(samples.size()); + for (auto s : samples) { + samples2.push_back(FDuration(s)); + } + + return { + CATCH_MOVE(samples2), + wrap_estimate(analysis.mean), + wrap_estimate(analysis.standard_deviation), + outliers, + analysis.outlier_variance, + }; + } else { + std::vector samples; + samples.reserve(static_cast(last - first)); + + FDuration mean = FDuration(0); + int i = 0; + for (auto it = first; it < last; ++it, ++i) { + samples.push_back(*it); + mean += *it; + } + mean /= i; + + return SampleAnalysis{ + CATCH_MOVE(samples), + Estimate{mean, mean, mean, 0.0}, + Estimate{FDuration(0), + FDuration(0), + FDuration(0), + 0.0}, + OutlierClassification{}, + 0.0}; + } + } + } // namespace Detail + } // namespace Benchmark +} // namespace Catch + +namespace Catch { + namespace Benchmark { + namespace Detail { + struct do_nothing { + void operator()() const {} + }; + + BenchmarkFunction::callable::~callable() = default; + BenchmarkFunction::BenchmarkFunction() + : f(new model{{}}) {} + } // namespace Detail + } // namespace Benchmark +} // namespace Catch + +#include + +namespace Catch { + namespace Benchmark { + namespace Detail { + struct optimized_away_error : std::exception { + const char *what() const noexcept override; + }; + + const char *optimized_away_error::what() const noexcept { + return "could not measure benchmark, maybe it was optimized away"; + } + + void throw_optimized_away_error() { + Catch::throw_exception(optimized_away_error{}); + } + + } // namespace Detail + } // namespace Benchmark +} // namespace Catch + +// Adapted from donated nonius code. + +#include +#include +#include +#include +#include +#include + +#if defined(CATCH_CONFIG_USE_ASYNC) +#include +#endif + +namespace Catch { + namespace Benchmark { + namespace Detail { + namespace { + + template + static sample + resample(URng &rng, + unsigned int resamples, + double const *first, + double const *last, + Estimator &estimator) { + auto n = static_cast(last - first); + Catch::uniform_integer_distribution dist(0, n - 1); + + sample out; + out.reserve(resamples); + std::vector resampled; + resampled.reserve(n); + for (size_t i = 0; i < resamples; ++i) { + resampled.clear(); + for (size_t s = 0; s < n; ++s) { + resampled.push_back(first[dist(rng)]); + } + const auto estimate = estimator(resampled.data(), resampled.data() + resampled.size()); + out.push_back(estimate); + } + std::sort(out.begin(), out.end()); + return out; + } + + static double outlier_variance(Estimate mean, + Estimate stddev, + int n) { + double sb = stddev.point; + double mn = mean.point / n; + double mg_min = mn / 2.; + double sg = (std::min)(mg_min / 4., sb / std::sqrt(n)); + double sg2 = sg * sg; + double sb2 = sb * sb; + + auto c_max = [n, mn, sb2, sg2](double x) -> double { + double k = mn - x; + double d = k * k; + double nd = n * d; + double k0 = -n * nd; + double k1 = sb2 - n * sg2 + nd; + double det = k1 * k1 - 4 * sg2 * k0; + return static_cast(-2. * k0 / (k1 + std::sqrt(det))); + }; + + auto var_out = [n, sb2, sg2](double c) { + double nc = n - c; + return (nc / n) * (sb2 - nc * sg2); + }; + + return (std::min)(var_out(1), + var_out( + (std::min)(c_max(0.), + c_max(mg_min)))) + / sb2; + } + + static double erf_inv(double x) { + // Code accompanying the article "Approximating the erfinv + // function" in GPU Computing Gems, Volume 2 + double w, p; + + w = -log((1.0 - x) * (1.0 + x)); + + if (w < 6.250000) { + w = w - 3.125000; + p = -3.6444120640178196996e-21; + p = -1.685059138182016589e-19 + p * w; + p = 1.2858480715256400167e-18 + p * w; + p = 1.115787767802518096e-17 + p * w; + p = -1.333171662854620906e-16 + p * w; + p = 2.0972767875968561637e-17 + p * w; + p = 6.6376381343583238325e-15 + p * w; + p = -4.0545662729752068639e-14 + p * w; + p = -8.1519341976054721522e-14 + p * w; + p = 2.6335093153082322977e-12 + p * w; + p = -1.2975133253453532498e-11 + p * w; + p = -5.4154120542946279317e-11 + p * w; + p = 1.051212273321532285e-09 + p * w; + p = -4.1126339803469836976e-09 + p * w; + p = -2.9070369957882005086e-08 + p * w; + p = 4.2347877827932403518e-07 + p * w; + p = -1.3654692000834678645e-06 + p * w; + p = -1.3882523362786468719e-05 + p * w; + p = 0.0001867342080340571352 + p * w; + p = -0.00074070253416626697512 + p * w; + p = -0.0060336708714301490533 + p * w; + p = 0.24015818242558961693 + p * w; + p = 1.6536545626831027356 + p * w; + } else if (w < 16.000000) { + w = sqrt(w) - 3.250000; + p = 2.2137376921775787049e-09; + p = 9.0756561938885390979e-08 + p * w; + p = -2.7517406297064545428e-07 + p * w; + p = 1.8239629214389227755e-08 + p * w; + p = 1.5027403968909827627e-06 + p * w; + p = -4.013867526981545969e-06 + p * w; + p = 2.9234449089955446044e-06 + p * w; + p = 1.2475304481671778723e-05 + p * w; + p = -4.7318229009055733981e-05 + p * w; + p = 6.8284851459573175448e-05 + p * w; + p = 2.4031110387097893999e-05 + p * w; + p = -0.0003550375203628474796 + p * w; + p = 0.00095328937973738049703 + p * w; + p = -0.0016882755560235047313 + p * w; + p = 0.0024914420961078508066 + p * w; + p = -0.0037512085075692412107 + p * w; + p = 0.005370914553590063617 + p * w; + p = 1.0052589676941592334 + p * w; + p = 3.0838856104922207635 + p * w; + } else { + w = sqrt(w) - 5.000000; + p = -2.7109920616438573243e-11; + p = -2.5556418169965252055e-10 + p * w; + p = 1.5076572693500548083e-09 + p * w; + p = -3.7894654401267369937e-09 + p * w; + p = 7.6157012080783393804e-09 + p * w; + p = -1.4960026627149240478e-08 + p * w; + p = 2.9147953450901080826e-08 + p * w; + p = -6.7711997758452339498e-08 + p * w; + p = 2.2900482228026654717e-07 + p * w; + p = -9.9298272942317002539e-07 + p * w; + p = 4.5260625972231537039e-06 + p * w; + p = -1.9681778105531670567e-05 + p * w; + p = 7.5995277030017761139e-05 + p * w; + p = -0.00021503011930044477347 + p * w; + p = -0.00013871931833623122026 + p * w; + p = 1.0103004648645343977 + p * w; + p = 4.8499064014085844221 + p * w; + } + return p * x; + } + + static double + standard_deviation(double const *first, double const *last) { + auto m = Catch::Benchmark::Detail::mean(first, last); + double variance = std::accumulate(first, + last, + 0., + [m](double a, double b) { + double diff = b - m; + return a + diff * diff; + }) + / static_cast(last - first); + return std::sqrt(variance); + } + + static sample jackknife(double (*estimator)(double const *, + double const *), + double *first, + double *last) { + const auto second = first + 1; + sample results; + results.reserve(static_cast(last - first)); + + for (auto it = first; it != last; ++it) { + std::iter_swap(it, first); + results.push_back(estimator(second, last)); + } + + return results; + } + + } // namespace + } // namespace Detail + } // namespace Benchmark +} // namespace Catch + +namespace Catch { + namespace Benchmark { + namespace Detail { + + double weighted_average_quantile(int k, + int q, + double *first, + double *last) { + auto count = last - first; + double idx = static_cast((count - 1) * k) / static_cast(q); + int j = static_cast(idx); + double g = idx - j; + std::nth_element(first, first + j, last); + auto xj = first[j]; + if (Catch::Detail::directCompare(g, 0)) { + return xj; + } + + auto xj1 = *std::min_element(first + (j + 1), last); + return xj + g * (xj1 - xj); + } + + OutlierClassification + classify_outliers(double const *first, double const *last) { + std::vector copy(first, last); + + auto q1 = weighted_average_quantile(1, 4, copy.data(), copy.data() + copy.size()); + auto q3 = weighted_average_quantile(3, 4, copy.data(), copy.data() + copy.size()); + auto iqr = q3 - q1; + auto los = q1 - (iqr * 3.); + auto lom = q1 - (iqr * 1.5); + auto him = q3 + (iqr * 1.5); + auto his = q3 + (iqr * 3.); + + OutlierClassification o; + for (; first != last; ++first) { + const double t = *first; + if (t < los) { + ++o.low_severe; + } else if (t < lom) { + ++o.low_mild; + } else if (t > his) { + ++o.high_severe; + } else if (t > him) { + ++o.high_mild; + } + ++o.samples_seen; + } + return o; + } + + double mean(double const *first, double const *last) { + auto count = last - first; + double sum = 0.; + while (first != last) { + sum += *first; + ++first; + } + return sum / static_cast(count); + } + + double normal_cdf(double x) { + return std::erfc(-x / std::sqrt(2.0)) / 2.0; + } + + double erfc_inv(double x) { + return erf_inv(1.0 - x); + } + + double normal_quantile(double p) { + static const double ROOT_TWO = std::sqrt(2.0); + + double result = 0.0; + assert(p >= 0 && p <= 1); + if (p < 0 || p > 1) { + return result; + } + + result = -erfc_inv(2.0 * p); + // result *= normal distribution standard deviation (1.0) * sqrt(2) + result *= /*sd * */ ROOT_TWO; + // result += normal disttribution mean (0) + return result; + } + + Estimate + bootstrap(double confidence_level, + double *first, + double *last, + sample const &resample, + double (*estimator)(double const *, double const *)) { + auto n_samples = last - first; + + double point = estimator(first, last); + // Degenerate case with a single sample + if (n_samples == 1) + return {point, point, point, confidence_level}; + + sample jack = jackknife(estimator, first, last); + double jack_mean = mean(jack.data(), jack.data() + jack.size()); + double sum_squares = 0, sum_cubes = 0; + for (double x : jack) { + auto difference = jack_mean - x; + auto square = difference * difference; + auto cube = square * difference; + sum_squares += square; + sum_cubes += cube; + } + + double accel = sum_cubes / (6 * std::pow(sum_squares, 1.5)); + long n = static_cast(resample.size()); + double prob_n = static_cast( + std::count_if(resample.begin(), + resample.end(), + [point](double x) { return x < point; })) + / static_cast(n); + // degenerate case with uniform samples + if (Catch::Detail::directCompare(prob_n, 0.)) { + return {point, point, point, confidence_level}; + } + + double bias = normal_quantile(prob_n); + double z1 = normal_quantile((1. - confidence_level) / 2.); + + auto cumn = [n](double x) -> long { + return std::lround(normal_cdf(x) * static_cast(n)); + }; + auto a = [bias, accel](double b) { + return bias + b / (1. - accel * b); + }; + double b1 = bias + z1; + double b2 = bias - z1; + double a1 = a(b1); + double a2 = a(b2); + auto lo = static_cast((std::max)(cumn(a1), 0l)); + auto hi = static_cast((std::min)(cumn(a2), n - 1)); + + return {point, resample[lo], resample[hi], confidence_level}; + } + + bootstrap_analysis analyse_samples(double confidence_level, + unsigned int n_resamples, + double *first, + double *last) { + auto mean = &Detail::mean; + auto stddev = &standard_deviation; + +#if defined(CATCH_CONFIG_USE_ASYNC) + auto Estimate = [=](double (*f)(double const *, double const *)) { + std::random_device rd; + auto seed = rd(); + return std::async(std::launch::async, [=] { + SimplePcg32 rng(seed); + auto resampled = resample(rng, n_resamples, first, last, f); + return bootstrap(confidence_level, first, last, resampled, f); + }); + }; + + auto mean_future = Estimate(mean); + auto stddev_future = Estimate(stddev); + + auto mean_estimate = mean_future.get(); + auto stddev_estimate = stddev_future.get(); +#else + auto Estimate = [=](double (*f)(double const *, double const *)) { + std::random_device rd; + auto seed = rd(); + SimplePcg32 rng(seed); + auto resampled = resample(rng, n_resamples, first, last, f); + return bootstrap(confidence_level, first, last, resampled, f); + }; + + auto mean_estimate = Estimate(mean); + auto stddev_estimate = Estimate(stddev); +#endif // CATCH_USE_ASYNC + + auto n = static_cast(last - first); // seriously, one can't use integral types without hell in C++ + double outlier_variance = Detail::outlier_variance(mean_estimate, stddev_estimate, n); + + return {mean_estimate, stddev_estimate, outlier_variance}; + } + } // namespace Detail + } // namespace Benchmark +} // namespace Catch + +#include +#include + +namespace { + + // Performs equivalent check of std::fabs(lhs - rhs) <= margin + // But without the subtraction to allow for INFINITY in comparison + bool marginComparison(double lhs, double rhs, double margin) { + return (lhs + margin >= rhs) && (rhs + margin >= lhs); + } + +} // namespace + +namespace Catch { + + Approx::Approx(double value) + : m_epsilon(static_cast(std::numeric_limits::epsilon()) * 100.), m_margin(0.0), m_scale(0.0), m_value(value) {} + + Approx Approx::custom() { + return Approx(0); + } + + Approx Approx::operator-() const { + auto temp(*this); + temp.m_value = -temp.m_value; + return temp; + } + + std::string Approx::toString() const { + ReusableStringStream rss; + rss << "Approx( " << ::Catch::Detail::stringify(m_value) << " )"; + return rss.str(); + } + + bool Approx::equalityComparisonImpl(const double other) const { + // First try with fixed margin, then compute margin based on epsilon, scale and Approx's value + // Thanks to Richard Harris for his help refining the scaled margin value + return marginComparison(m_value, other, m_margin) + || marginComparison(m_value, other, m_epsilon * (m_scale + std::fabs(std::isinf(m_value) ? 0 : m_value))); + } + + void Approx::setMargin(double newMargin) { + CATCH_ENFORCE(newMargin >= 0, + "Invalid Approx::margin: " << newMargin << '.' + << " Approx::Margin has to be non-negative."); + m_margin = newMargin; + } + + void Approx::setEpsilon(double newEpsilon) { + CATCH_ENFORCE(newEpsilon >= 0 && newEpsilon <= 1.0, + "Invalid Approx::epsilon: " << newEpsilon << '.' + << " Approx::epsilon has to be in [0, 1]"); + m_epsilon = newEpsilon; + } + + namespace literals { + Approx operator""_a(long double val) { + return Approx(val); + } + Approx operator""_a(unsigned long long val) { + return Approx(val); + } + } // end namespace literals + + std::string StringMaker::convert(Catch::Approx const &value) { + return value.toString(); + } + +} // end namespace Catch + +namespace Catch { + + AssertionResultData::AssertionResultData(ResultWas::OfType _resultType, LazyExpression const &_lazyExpression) + : lazyExpression(_lazyExpression), resultType(_resultType) {} + + std::string AssertionResultData::reconstructExpression() const { + if (reconstructedExpression.empty()) { + if (lazyExpression) { + ReusableStringStream rss; + rss << lazyExpression; + reconstructedExpression = rss.str(); + } + } + return reconstructedExpression; + } + + AssertionResult::AssertionResult(AssertionInfo const &info, AssertionResultData &&data) + : m_info(info), m_resultData(CATCH_MOVE(data)) {} + + // Result was a success + bool AssertionResult::succeeded() const { + return Catch::isOk(m_resultData.resultType); + } + + // Result was a success, or failure is suppressed + bool AssertionResult::isOk() const { + return Catch::isOk(m_resultData.resultType) || shouldSuppressFailure(m_info.resultDisposition); + } + + ResultWas::OfType AssertionResult::getResultType() const { + return m_resultData.resultType; + } + + bool AssertionResult::hasExpression() const { + return !m_info.capturedExpression.empty(); + } + + bool AssertionResult::hasMessage() const { + return !m_resultData.message.empty(); + } + + std::string AssertionResult::getExpression() const { + // Possibly overallocating by 3 characters should be basically free + std::string expr; + expr.reserve(m_info.capturedExpression.size() + 3); + if (isFalseTest(m_info.resultDisposition)) { + expr += "!("; + } + expr += m_info.capturedExpression; + if (isFalseTest(m_info.resultDisposition)) { + expr += ')'; + } + return expr; + } + + std::string AssertionResult::getExpressionInMacro() const { + if (m_info.macroName.empty()) { + return static_cast(m_info.capturedExpression); + } + std::string expr; + expr.reserve(m_info.macroName.size() + m_info.capturedExpression.size() + 4); + expr += m_info.macroName; + expr += "( "; + expr += m_info.capturedExpression; + expr += " )"; + return expr; + } + + bool AssertionResult::hasExpandedExpression() const { + return hasExpression() && getExpandedExpression() != getExpression(); + } + + std::string AssertionResult::getExpandedExpression() const { + std::string expr = m_resultData.reconstructExpression(); + return expr.empty() + ? getExpression() + : expr; + } + + StringRef AssertionResult::getMessage() const { + return m_resultData.message; + } + SourceLineInfo AssertionResult::getSourceInfo() const { + return m_info.lineInfo; + } + + StringRef AssertionResult::getTestMacroName() const { + return m_info.macroName; + } + +} // end namespace Catch + +#include + +namespace Catch { + + namespace { + static bool enableBazelEnvSupport() { +#if defined(CATCH_CONFIG_BAZEL_SUPPORT) + return true; +#else + return Detail::getEnv("BAZEL_TEST") != nullptr; +#endif + } + + struct bazelShardingOptions { + unsigned int shardIndex, shardCount; + std::string shardFilePath; + }; + + static Optional readBazelShardingOptions() { + const auto bazelShardIndex = Detail::getEnv("TEST_SHARD_INDEX"); + const auto bazelShardTotal = Detail::getEnv("TEST_TOTAL_SHARDS"); + const auto bazelShardInfoFile = Detail::getEnv("TEST_SHARD_STATUS_FILE"); + + const bool has_all = bazelShardIndex && bazelShardTotal && bazelShardInfoFile; + if (!has_all) { + // We provide nice warning message if the input is + // misconfigured. + auto warn = [](const char *env_var) { + Catch::cerr() + << "Warning: Bazel shard configuration is missing '" + << env_var << "'. Shard configuration is skipped.\n"; + }; + if (!bazelShardIndex) { + warn("TEST_SHARD_INDEX"); + } + if (!bazelShardTotal) { + warn("TEST_TOTAL_SHARDS"); + } + if (!bazelShardInfoFile) { + warn("TEST_SHARD_STATUS_FILE"); + } + return {}; + } + + auto shardIndex = parseUInt(bazelShardIndex); + if (!shardIndex) { + Catch::cerr() + << "Warning: could not parse 'TEST_SHARD_INDEX' ('" << bazelShardIndex + << "') as unsigned int.\n"; + return {}; + } + auto shardTotal = parseUInt(bazelShardTotal); + if (!shardTotal) { + Catch::cerr() + << "Warning: could not parse 'TEST_TOTAL_SHARD' ('" + << bazelShardTotal << "') as unsigned int.\n"; + return {}; + } + + return bazelShardingOptions{ + *shardIndex, *shardTotal, bazelShardInfoFile}; + } + } // end namespace + + bool operator==(ProcessedReporterSpec const &lhs, + ProcessedReporterSpec const &rhs) { + return lhs.name == rhs.name && lhs.outputFilename == rhs.outputFilename && lhs.colourMode == rhs.colourMode && lhs.customOptions == rhs.customOptions; + } + + bool operator==(PathFilter const &lhs, PathFilter const &rhs) { + return lhs.type == rhs.type && lhs.filter == rhs.filter; + } + + Config::Config(ConfigData const &data) + : m_data(data) { + // We need to trim filter specs to avoid trouble with superfluous + // whitespace (esp. important for bdd macros, as those are manually + // aligned with whitespace). + + for (auto &elem : m_data.testsOrTags) { + elem = trim(elem); + } + + // Insert the default reporter if user hasn't asked for a specific one + if (m_data.reporterSpecifications.empty()) { +#if defined(CATCH_CONFIG_DEFAULT_REPORTER) + const auto default_spec = CATCH_CONFIG_DEFAULT_REPORTER; +#else + const auto default_spec = "console"; +#endif + auto parsed = parseReporterSpec(default_spec); + CATCH_ENFORCE(parsed, + "Cannot parse the provided default reporter spec: '" + << default_spec << '\''); + m_data.reporterSpecifications.push_back(std::move(*parsed)); + } + + // Reading bazel env vars can change some parts of the config data, + // so we have to process the bazel env before acting on the config. + if (enableBazelEnvSupport()) { + readBazelEnvVars(); + } + + // Bazel support can modify the test specs, so parsing has to happen + // after reading Bazel env vars. + TestSpecParser parser(ITagAliasRegistry::get()); + if (!m_data.testsOrTags.empty()) { + m_hasTestFilters = true; + for (auto const &testOrTags : m_data.testsOrTags) { + parser.parse(testOrTags); + } + } + m_testSpec = parser.testSpec(); + + // We now fixup the reporter specs to handle default output spec, + // default colour spec, etc + bool defaultOutputUsed = false; + for (auto const &reporterSpec : m_data.reporterSpecifications) { + // We do the default-output check separately, while always + // using the default output below to make the code simpler + // and avoid superfluous copies. + if (reporterSpec.outputFile().none()) { + CATCH_ENFORCE(!defaultOutputUsed, + "Internal error: cannot use default output for " + "multiple reporters"); + defaultOutputUsed = true; + } + + m_processedReporterSpecs.push_back(ProcessedReporterSpec{ + reporterSpec.name(), + reporterSpec.outputFile() ? *reporterSpec.outputFile() + : data.defaultOutputFilename, + reporterSpec.colourMode().valueOr(data.defaultColourMode), + reporterSpec.customOptions()}); + } + } + + Config::~Config() = default; + + bool Config::listTests() const { + return m_data.listTests; + } + bool Config::listTags() const { + return m_data.listTags; + } + bool Config::listReporters() const { + return m_data.listReporters; + } + bool Config::listListeners() const { + return m_data.listListeners; + } + + std::vector const &Config::getTestsOrTags() const { + return m_data.testsOrTags; + } + std::vector const &Config::getPathFilters() const { + return m_data.pathFilters; + } + bool Config::useNewFilterBehaviour() const { + return m_data.useNewPathFilteringBehaviour; + } + + std::vector const &Config::getReporterSpecs() const { + return m_data.reporterSpecifications; + } + + std::vector const & + Config::getProcessedReporterSpecs() const { + return m_processedReporterSpecs; + } + + TestSpec const &Config::testSpec() const { + return m_testSpec; + } + bool Config::hasTestFilters() const { + return m_hasTestFilters; + } + + bool Config::showHelp() const { + return m_data.showHelp; + } + + std::string const &Config::getExitGuardFilePath() const { + return m_data.prematureExitGuardFilePath; + } + + // IConfig interface + bool Config::allowThrows() const { + return !m_data.noThrow; + } + StringRef Config::name() const { + return m_data.name.empty() ? m_data.processName : m_data.name; + } + bool Config::includeSuccessfulResults() const { + return m_data.showSuccessfulTests; + } + bool Config::warnAboutMissingAssertions() const { + return !!(m_data.warnings & WarnAbout::NoAssertions); + } + bool Config::warnAboutUnmatchedTestSpecs() const { + return !!(m_data.warnings & WarnAbout::UnmatchedTestSpec); + } + bool Config::warnAboutInfiniteGenerators() const { + return !!(m_data.warnings & WarnAbout::InfiniteGenerator); + } + bool Config::zeroTestsCountAsSuccess() const { + return m_data.allowZeroTests; + } + ShowDurations Config::showDurations() const { + return m_data.showDurations; + } + double Config::minDuration() const { + return m_data.minDuration; + } + TestRunOrder Config::runOrder() const { + return m_data.runOrder; + } + uint32_t Config::rngSeed() const { + return m_data.rngSeed; + } + unsigned int Config::shardCount() const { + return m_data.shardCount; + } + unsigned int Config::shardIndex() const { + return m_data.shardIndex; + } + ColourMode Config::defaultColourMode() const { + return m_data.defaultColourMode; + } + bool Config::shouldDebugBreak() const { + return m_data.shouldDebugBreak; + } + int Config::abortAfter() const { + return m_data.abortAfter; + } + bool Config::showInvisibles() const { + return m_data.showInvisibles; + } + Verbosity Config::verbosity() const { + return m_data.verbosity; + } + + bool Config::skipBenchmarks() const { + return m_data.skipBenchmarks; + } + bool Config::benchmarkNoAnalysis() const { + return m_data.benchmarkNoAnalysis; + } + unsigned int Config::benchmarkSamples() const { + return m_data.benchmarkSamples; + } + double Config::benchmarkConfidenceInterval() const { + return m_data.benchmarkConfidenceInterval; + } + unsigned int Config::benchmarkResamples() const { + return m_data.benchmarkResamples; + } + std::chrono::milliseconds Config::benchmarkWarmupTime() const { + return std::chrono::milliseconds(m_data.benchmarkWarmupTime); + } + + void Config::readBazelEnvVars() { + // Register a JUnit reporter for Bazel. Bazel sets an environment + // variable with the path to XML output. If this file is written to + // during test, Bazel will not generate a default XML output. + // This allows the XML output file to contain higher level of detail + // than what is possible otherwise. + const auto bazelOutputFile = Detail::getEnv("XML_OUTPUT_FILE"); + + if (bazelOutputFile) { + m_data.reporterSpecifications.push_back( + {"junit", std::string(bazelOutputFile), {}, {}}); + } + + const auto bazelTestSpec = Detail::getEnv("TESTBRIDGE_TEST_ONLY"); + if (bazelTestSpec) { + // Presumably the test spec from environment should overwrite + // the one we got from CLI (if we got any) + m_data.testsOrTags.clear(); + m_data.testsOrTags.push_back(bazelTestSpec); + } + + const auto bazelShardOptions = readBazelShardingOptions(); + if (bazelShardOptions) { + std::ofstream f(bazelShardOptions->shardFilePath, + std::ios_base::out | std::ios_base::trunc); + if (f.is_open()) { + f << ""; + m_data.shardIndex = bazelShardOptions->shardIndex; + m_data.shardCount = bazelShardOptions->shardCount; + } + } + + const auto bazelExitGuardFile = Detail::getEnv("TEST_PREMATURE_EXIT_FILE"); + if (bazelExitGuardFile) { + m_data.prematureExitGuardFilePath = bazelExitGuardFile; + } + + const auto bazelRandomSeed = Detail::getEnv("TEST_RANDOM_SEED"); + if (bazelRandomSeed) { + auto parsedSeed = parseUInt(bazelRandomSeed, 0); + if (!parsedSeed) { + // Currently we handle issues with parsing other Bazel Env + // options by warning and ignoring the issue. So we do the + // same for random seed option. + Catch::cerr() + << "Warning: could not parse 'TEST_RANDOM_SEED' ('" + << bazelRandomSeed << "') as proper seed.\n"; + } else { + m_data.rngSeed = *parsedSeed; + } + } + } + +} // end namespace Catch + +namespace Catch { + std::uint32_t getSeed() { + return getCurrentContext().getConfig()->rngSeed(); + } +} // namespace Catch + +#include +#include + +namespace Catch { + + //////////////////////////////////////////////////////////////////////////// + + ScopedMessage::ScopedMessage(MessageBuilder &&builder) + : m_messageId(builder.m_info.sequence) { + MessageInfo info(CATCH_MOVE(builder.m_info)); + info.message = builder.m_stream.str(); + IResultCapture::pushScopedMessage(CATCH_MOVE(info)); + } + + ScopedMessage::ScopedMessage(ScopedMessage &&old) noexcept + : m_messageId(old.m_messageId) { + old.m_moved = true; + } + + ScopedMessage::~ScopedMessage() { + if (!m_moved) { IResultCapture::popScopedMessage(m_messageId); } + } + + Capturer::Capturer(StringRef macroName, + SourceLineInfo const &lineInfo, + ResultWas::OfType resultType, + StringRef names, + bool isScoped) + : m_isScoped(isScoped) { + auto trimmed = [&](size_t start, size_t end) { + while (names[start] == ',' || isspace(static_cast(names[start]))) { + ++start; + } + while (names[end] == ',' || isspace(static_cast(names[end]))) { + --end; + } + return names.substr(start, end - start + 1); + }; + auto skipq = [&](size_t start, char quote) { + for (auto i = start + 1; i < names.size(); ++i) { + if (names[i] == quote) + return i; + if (names[i] == '\\') + ++i; + } + CATCH_INTERNAL_ERROR("CAPTURE parsing encountered unmatched quote"); + }; + + size_t start = 0; + std::stack openings; + for (size_t pos = 0; pos < names.size(); ++pos) { + char c = names[pos]; + switch (c) { + case '[': + case '{': + case '(': + // It is basically impossible to disambiguate between + // comparison and start of template args in this context + // case '<': + openings.push(c); + break; + case ']': + case '}': + case ')': + // case '>': + openings.pop(); + break; + case '"': + case '\'': + pos = skipq(pos, c); + break; + case ',': + if (start != pos && openings.empty()) { + m_messages.emplace_back(macroName, lineInfo, resultType); + m_messages.back().message += trimmed(start, pos); + m_messages.back().message += " := "_sr; + start = pos; + } + break; + default:; // noop + } + } + assert(openings.empty() && "Mismatched openings"); + m_messages.emplace_back(macroName, lineInfo, resultType); + m_messages.back().message += trimmed(start, names.size() - 1); + m_messages.back().message += " := "_sr; + } + Capturer::~Capturer() { + assert(m_captured == m_messages.size()); + if (m_isScoped) { + for (auto const &message : m_messages) { + IResultCapture::popScopedMessage(message.sequence); + } + } + } + + void Capturer::captureValue(size_t index, std::string const &value) { + assert(index < m_messages.size()); + m_messages[index].message += value; + if (m_isScoped) { + IResultCapture::pushScopedMessage(CATCH_MOVE(m_messages[index])); + } else { + IResultCapture::addUnscopedMessage(CATCH_MOVE(m_messages[index])); + } + m_captured++; + } + +} // end namespace Catch + +#include + +namespace Catch { + + namespace { + + class RegistryHub : public IRegistryHub, + public IMutableRegistryHub, + private Detail::NonCopyable { + public: // IRegistryHub + RegistryHub() = default; + ReporterRegistry const &getReporterRegistry() const override { + return m_reporterRegistry; + } + ITestCaseRegistry const &getTestCaseRegistry() const override { + return m_testCaseRegistry; + } + IExceptionTranslatorRegistry const &getExceptionTranslatorRegistry() const override { + return m_exceptionTranslatorRegistry; + } + ITagAliasRegistry const &getTagAliasRegistry() const override { + return m_tagAliasRegistry; + } + StartupExceptionRegistry const &getStartupExceptionRegistry() const override { + return m_exceptionRegistry; + } + + public: // IMutableRegistryHub + void registerReporter(std::string const &name, IReporterFactoryPtr factory) override { + m_reporterRegistry.registerReporter(name, CATCH_MOVE(factory)); + } + void registerListener(Detail::unique_ptr factory) override { + m_reporterRegistry.registerListener(CATCH_MOVE(factory)); + } + void registerTest(Detail::unique_ptr &&testInfo, Detail::unique_ptr &&invoker) override { + m_testCaseRegistry.registerTest(CATCH_MOVE(testInfo), CATCH_MOVE(invoker)); + } + void registerTranslator(Detail::unique_ptr &&translator) override { + m_exceptionTranslatorRegistry.registerTranslator(CATCH_MOVE(translator)); + } + void registerTagAlias(std::string const &alias, std::string const &tag, SourceLineInfo const &lineInfo) override { + m_tagAliasRegistry.add(alias, tag, lineInfo); + } + void registerStartupException() noexcept override { +#if !defined(CATCH_CONFIG_DISABLE_EXCEPTIONS) + m_exceptionRegistry.add(std::current_exception()); +#else + CATCH_INTERNAL_ERROR("Attempted to register active exception under CATCH_CONFIG_DISABLE_EXCEPTIONS!"); +#endif + } + IMutableEnumValuesRegistry &getMutableEnumValuesRegistry() override { + return m_enumValuesRegistry; + } + + private: + TestRegistry m_testCaseRegistry; + ReporterRegistry m_reporterRegistry; + ExceptionTranslatorRegistry m_exceptionTranslatorRegistry; + TagAliasRegistry m_tagAliasRegistry; + StartupExceptionRegistry m_exceptionRegistry; + Detail::EnumValuesRegistry m_enumValuesRegistry; + }; + } // namespace + + using RegistryHubSingleton = Singleton; + + IRegistryHub const &getRegistryHub() { + return RegistryHubSingleton::get(); + } + IMutableRegistryHub &getMutableRegistryHub() { + return RegistryHubSingleton::getMutable(); + } + void cleanUp() { + cleanupSingletons(); + } + std::string translateActiveException() { + return getRegistryHub().getExceptionTranslatorRegistry().translateActiveException(); + } + +} // end namespace Catch + +#include +#include +#include +#include +#include +#include + +namespace Catch { + + namespace { + IEventListenerPtr createReporter(std::string const &reporterName, ReporterConfig &&config) { + auto reporter = Catch::getRegistryHub().getReporterRegistry().create(reporterName, CATCH_MOVE(config)); + CATCH_ENFORCE(reporter, "No reporter registered with name: '" << reporterName << '\''); + + return reporter; + } + + IEventListenerPtr prepareReporters(Config const *config) { + if (Catch::getRegistryHub().getReporterRegistry().getListeners().empty() + && config->getProcessedReporterSpecs().size() == 1) { + auto const &spec = config->getProcessedReporterSpecs()[0]; + return createReporter( + spec.name, + ReporterConfig(config, + makeStream(spec.outputFilename), + spec.colourMode, + spec.customOptions)); + } + + auto multi = Detail::make_unique(config); + + auto const &listeners = Catch::getRegistryHub().getReporterRegistry().getListeners(); + for (auto const &listener : listeners) { + multi->addListener(listener->create(config)); + } + + for (auto const &reporterSpec : config->getProcessedReporterSpecs()) { + multi->addReporter(createReporter( + reporterSpec.name, + ReporterConfig(config, + makeStream(reporterSpec.outputFilename), + reporterSpec.colourMode, + reporterSpec.customOptions))); + } + + return multi; + } + + class TestGroup { + public: + explicit TestGroup(IEventListenerPtr &&reporter, Config const *config) + : m_reporter(reporter.get()), m_config{config}, m_context{config, CATCH_MOVE(reporter)} { + assert(m_config->testSpec().getInvalidSpecs().empty() && "Invalid test specs should be handled before running tests"); + + auto const &allTestCases = getAllTestCasesSorted(*m_config); + auto const &testSpec = m_config->testSpec(); + if (!testSpec.hasFilters()) { + for (auto const &test : allTestCases) { + if (!test.getTestCaseInfo().isHidden()) { + m_tests.emplace(&test); + } + } + } else { + m_matches = testSpec.matchesByFilter(allTestCases, *m_config); + for (auto const &match : m_matches) { + m_tests.insert(match.tests.begin(), + match.tests.end()); + } + } + + m_tests = createShard(m_tests, m_config->shardCount(), m_config->shardIndex()); + } + + Totals execute() { + Totals totals; + for (auto const &testCase : m_tests) { + if (!m_context.aborting()) + totals += m_context.runTest(*testCase); + else + m_reporter->skipTest(testCase->getTestCaseInfo()); + } + + for (auto const &match : m_matches) { + if (match.tests.empty()) { + m_unmatchedTestSpecs = true; + m_reporter->noMatchingTestCases(match.name); + } + } + + return totals; + } + + bool hadUnmatchedTestSpecs() const { + return m_unmatchedTestSpecs; + } + + private: + IEventListener *m_reporter; + Config const *m_config; + RunContext m_context; + std::set m_tests; + TestSpec::Matches m_matches; + bool m_unmatchedTestSpecs = false; + }; + + void applyFilenamesAsTags() { + for (auto const &testInfo : getRegistryHub().getTestCaseRegistry().getAllInfos()) { + testInfo->addFilenameTag(); + } + } + + // Creates empty file at path. The path must be writable, we do not + // try to create directories in path because that's hard in C++14. + void setUpGuardFile(std::string const &guardFilePath) { + if (!guardFilePath.empty()) { +#if defined(_MSC_VER) + std::FILE *file = nullptr; + if (fopen_s(&file, guardFilePath.c_str(), "w")) { + char msgBuffer[100]; + const auto err = errno; + std::string errMsg; + if (!strerror_s(msgBuffer, err)) { + errMsg = msgBuffer; + } else { + errMsg = "Could not translate errno to a string"; + } + +#else + std::FILE *file = std::fopen(guardFilePath.c_str(), "w"); + if (!file) { + const auto err = errno; + const char *errMsg = std::strerror(err); +#endif + + CATCH_RUNTIME_ERROR("Could not open the exit guard file '" + << guardFilePath << "' because '" + << errMsg << "' (" << err << ')'); + } + const int ret = std::fclose(file); + CATCH_ENFORCE( + ret == 0, + "Error when closing the exit guard file: " << ret); + } + } + + // Removes file at path. Assuming we created it in setUpGuardFile. + void tearDownGuardFile(std::string const &guardFilePath) { + if (!guardFilePath.empty()) { + const int ret = std::remove(guardFilePath.c_str()); + CATCH_ENFORCE( + ret == 0, + "Error when removing the exit guard file: " << ret); + } + } + + } // namespace + + Session::Session() { + static bool alreadyInstantiated = false; + if (alreadyInstantiated) { + CATCH_TRY { + CATCH_INTERNAL_ERROR("Only one instance of Catch::Session can ever be used"); + } + CATCH_CATCH_ALL { + getMutableRegistryHub().registerStartupException(); + } + } + + // There cannot be exceptions at startup in no-exception mode. +#if !defined(CATCH_CONFIG_DISABLE_EXCEPTIONS) + const auto &exceptions = getRegistryHub().getStartupExceptionRegistry().getExceptions(); + if (!exceptions.empty()) { + config(); + getCurrentMutableContext().setConfig(m_config.get()); + + m_startupExceptions = true; + auto errStream = makeStream("%stderr"); + auto colourImpl = makeColourImpl( + ColourMode::PlatformDefault, errStream.get()); + auto guard = colourImpl->guardColour(Colour::Red); + errStream->stream() << "Errors occurred during startup!" << '\n'; + // iterate over all exceptions and notify user + for (const auto &ex_ptr : exceptions) { + try { + std::rethrow_exception(ex_ptr); + } catch (std::exception const &ex) { + errStream->stream() << TextFlow::Column(ex.what()).indent(2) << '\n'; + } + } + } +#endif + + alreadyInstantiated = true; + m_cli = makeCommandLineParser(m_configData); + } + Session::~Session() { + Catch::cleanUp(); + } + + void Session::showHelp() const { + Catch::cout() + << "\nCatch2 v" << libraryVersion() << '\n' + << m_cli << '\n' + << "For more detailed usage please see the project docs\n\n" + << std::flush; + } + void Session::libIdentify() { + Catch::cout() + << std::left << std::setw(16) << "description: " << "A Catch2 test executable\n" + << std::left << std::setw(16) << "category: " << "testframework\n" + << std::left << std::setw(16) << "framework: " << "Catch2\n" + << std::left << std::setw(16) << "version: " << libraryVersion() << '\n' + << std::flush; + } + + int Session::applyCommandLine(int argc, char const *const *argv) { + if (m_startupExceptions) { return UnspecifiedErrorExitCode; } + + auto result = m_cli.parse(Clara::Args(argc, argv)); + + if (!result) { + config(); + getCurrentMutableContext().setConfig(m_config.get()); + auto errStream = makeStream("%stderr"); + auto colour = makeColourImpl(ColourMode::PlatformDefault, errStream.get()); + + errStream->stream() + << colour->guardColour(Colour::Red) + << "\nError(s) in input:\n" + << TextFlow::Column(result.errorMessage()).indent(2) + << "\n\n"; + errStream->stream() << "Run with -? for usage\n\n" + << std::flush; + return UnspecifiedErrorExitCode; + } + + if (m_configData.showHelp) + showHelp(); + if (m_configData.libIdentify) + libIdentify(); + + m_config.reset(); + return 0; + } + +#if defined(CATCH_CONFIG_WCHAR) && defined(_WIN32) && defined(UNICODE) + int Session::applyCommandLine(int argc, wchar_t const *const *argv) { + char **utf8Argv = new char *[argc]; + + for (int i = 0; i < argc; ++i) { + int bufSize = WideCharToMultiByte(CP_UTF8, 0, argv[i], -1, nullptr, 0, nullptr, nullptr); + + utf8Argv[i] = new char[bufSize]; + + WideCharToMultiByte(CP_UTF8, 0, argv[i], -1, utf8Argv[i], bufSize, nullptr, nullptr); + } + + int returnCode = applyCommandLine(argc, utf8Argv); + + for (int i = 0; i < argc; ++i) + delete[] utf8Argv[i]; + + delete[] utf8Argv; + + return returnCode; + } +#endif + + void Session::useConfigData(ConfigData const &configData) { + m_configData = configData; + m_config.reset(); + } + + int Session::run() { + if ((m_configData.waitForKeypress & WaitForKeypress::BeforeStart) != 0) { + Catch::cout() << "...waiting for enter/ return before starting\n" + << std::flush; + static_cast(std::getchar()); + } + int exitCode = runInternal(); + + if ((m_configData.waitForKeypress & WaitForKeypress::BeforeExit) != 0) { + Catch::cout() << "...waiting for enter/ return before exiting, with code: " << exitCode << '\n' + << std::flush; + static_cast(std::getchar()); + } + return exitCode; + } + + Clara::Parser const &Session::cli() const { + return m_cli; + } + void Session::cli(Clara::Parser const &newParser) { + m_cli = newParser; + } + ConfigData &Session::configData() { + return m_configData; + } + Config &Session::config() { + if (!m_config) + m_config = Detail::make_unique(m_configData); + return *m_config; + } + + int Session::runInternal() { + if (m_startupExceptions) { return UnspecifiedErrorExitCode; } + + if (m_configData.showHelp || m_configData.libIdentify) { + return 0; + } + + if (m_configData.shardIndex >= m_configData.shardCount) { + Catch::cerr() << "The shard count (" << m_configData.shardCount + << ") must be greater than the shard index (" + << m_configData.shardIndex << ")\n" + << std::flush; + return UnspecifiedErrorExitCode; + } + + CATCH_TRY { + config(); // Force config to be constructed + + // We need to retrieve potential Bazel config with the full Config + // constructor, so we have to create the guard file after it is created. + setUpGuardFile(m_config->getExitGuardFilePath()); + + seedRng(*m_config); + + if (m_configData.filenamesAsTags) { + applyFilenamesAsTags(); + } + + // Set up global config instance before we start calling into other functions + getCurrentMutableContext().setConfig(m_config.get()); + + // Create reporter(s) so we can route listings through them + auto reporter = prepareReporters(m_config.get()); + + auto const &invalidSpecs = m_config->testSpec().getInvalidSpecs(); + if (!invalidSpecs.empty()) { + for (auto const &spec : invalidSpecs) { + reporter->reportInvalidTestSpec(spec); + } + return InvalidTestSpecExitCode; + } + + // Handle list request + if (list(*reporter, *m_config)) { + return 0; + } + + TestGroup tests{CATCH_MOVE(reporter), m_config.get()}; + auto const totals = tests.execute(); + + // If we got here, running the tests finished normally-enough. + // They might've failed, but that would've been reported elsewhere. + tearDownGuardFile(m_config->getExitGuardFilePath()); + + if (tests.hadUnmatchedTestSpecs() + && m_config->warnAboutUnmatchedTestSpecs()) { + return UnmatchedTestSpecExitCode; + } + + if (totals.testCases.total() == 0 + && !m_config->zeroTestsCountAsSuccess()) { + return NoTestsRunExitCode; + } + + if (totals.testCases.total() > 0 && totals.testCases.total() == totals.testCases.skipped + && !m_config->zeroTestsCountAsSuccess()) { + return AllTestsSkippedExitCode; + } + + if (totals.assertions.failed) { return TestFailureExitCode; } + return 0; + } +#if !defined(CATCH_CONFIG_DISABLE_EXCEPTIONS) + catch (std::exception &ex) { + Catch::cerr() << ex.what() << '\n' + << std::flush; + return UnspecifiedErrorExitCode; + } +#endif + } + +} // end namespace Catch + +namespace Catch { + + RegistrarForTagAliases::RegistrarForTagAliases(char const *alias, char const *tag, SourceLineInfo const &lineInfo) { + CATCH_TRY { + getMutableRegistryHub().registerTagAlias(alias, tag, lineInfo); + } + CATCH_CATCH_ALL { + // Do not throw when constructing global objects, instead register the exception to be processed later + getMutableRegistryHub().registerStartupException(); + } + } + +} // namespace Catch + +#include +#include +#include + +namespace Catch { + + namespace { + using TCP_underlying_type = uint8_t; + static_assert(sizeof(TestCaseProperties) == sizeof(TCP_underlying_type), + "The size of the TestCaseProperties is different from the assumed size"); + + constexpr TestCaseProperties operator|(TestCaseProperties lhs, TestCaseProperties rhs) { + return static_cast( + static_cast(lhs) | static_cast(rhs)); + } + + constexpr TestCaseProperties &operator|=(TestCaseProperties &lhs, TestCaseProperties rhs) { + lhs = static_cast( + static_cast(lhs) | static_cast(rhs)); + return lhs; + } + + constexpr TestCaseProperties operator&(TestCaseProperties lhs, TestCaseProperties rhs) { + return static_cast( + static_cast(lhs) & static_cast(rhs)); + } + + constexpr bool applies(TestCaseProperties tcp) { + static_assert(static_cast(TestCaseProperties::None) == 0, + "TestCaseProperties::None must be equal to 0"); + return tcp != TestCaseProperties::None; + } + + TestCaseProperties parseSpecialTag(StringRef tag) { + if (!tag.empty() && tag[0] == '.') + return TestCaseProperties::IsHidden; + else if (tag == "!throws"_sr) + return TestCaseProperties::Throws; + else if (tag == "!shouldfail"_sr) + return TestCaseProperties::ShouldFail; + else if (tag == "!mayfail"_sr) + return TestCaseProperties::MayFail; + else if (tag == "!nonportable"_sr) + return TestCaseProperties::NonPortable; + else if (tag == "!benchmark"_sr) + return TestCaseProperties::Benchmark | TestCaseProperties::IsHidden; + else + return TestCaseProperties::None; + } + bool isReservedTag(StringRef tag) { + return parseSpecialTag(tag) == TestCaseProperties::None + && tag.size() > 0 + && !std::isalnum(static_cast(tag[0])); + } + void enforceNotReservedTag(StringRef tag, SourceLineInfo const &_lineInfo) { + CATCH_ENFORCE(!isReservedTag(tag), + "Tag name: [" << tag << "] is not allowed.\n" + << "Tag names starting with non alphanumeric characters are reserved\n" + << _lineInfo); + } + + std::string makeDefaultName() { + static size_t counter = 0; + return "Anonymous test case " + std::to_string(++counter); + } + + constexpr StringRef extractFilenamePart(StringRef filename) { + size_t lastDot = filename.size(); + while (lastDot > 0 && filename[lastDot - 1] != '.') { + --lastDot; + } + // In theory we could have filename without any extension in it + if (lastDot == 0) { return StringRef(); } + + --lastDot; + size_t nameStart = lastDot; + while (nameStart > 0 && filename[nameStart - 1] != '/' && filename[nameStart - 1] != '\\') { + --nameStart; + } + + return filename.substr(nameStart, lastDot - nameStart); + } + + // Returns the upper bound on size of extra tags ([#file]+[.]) + constexpr size_t sizeOfExtraTags(StringRef filepath) { + // [.] is 3, [#] is another 3 + const size_t extras = 3 + 3; + return extractFilenamePart(filepath).size() + extras; + } + } // end unnamed namespace + + bool operator<(Tag const &lhs, Tag const &rhs) { + Detail::CaseInsensitiveLess cmp; + return cmp(lhs.original, rhs.original); + } + bool operator==(Tag const &lhs, Tag const &rhs) { + Detail::CaseInsensitiveEqualTo cmp; + return cmp(lhs.original, rhs.original); + } + + Detail::unique_ptr + makeTestCaseInfo(StringRef _className, + NameAndTags const &nameAndTags, + SourceLineInfo const &_lineInfo) { + return Detail::make_unique(_className, nameAndTags, _lineInfo); + } + + TestCaseInfo::TestCaseInfo(StringRef _className, + NameAndTags const &_nameAndTags, + SourceLineInfo const &_lineInfo) + : name(_nameAndTags.name.empty() ? makeDefaultName() : _nameAndTags.name), className(_className), lineInfo(_lineInfo) { + StringRef originalTags = _nameAndTags.tags; + // We need to reserve enough space to store all of the tags + // (including optional hidden tag and filename tag) + auto requiredSize = originalTags.size() + sizeOfExtraTags(_lineInfo.file); + backingTags.reserve(requiredSize); + + // We cannot copy the tags directly, as we need to normalize + // some tags, so that [.foo] is copied as [.][foo]. + size_t tagStart = 0; + size_t tagEnd = 0; + bool inTag = false; + for (size_t idx = 0; idx < originalTags.size(); ++idx) { + auto c = originalTags[idx]; + if (c == '[') { + CATCH_ENFORCE( + !inTag, + "Found '[' inside a tag while registering test case '" + << _nameAndTags.name << "' at " << _lineInfo); + + inTag = true; + tagStart = idx; + } + if (c == ']') { + CATCH_ENFORCE( + inTag, + "Found unmatched ']' while registering test case '" + << _nameAndTags.name << "' at " << _lineInfo); + + inTag = false; + tagEnd = idx; + assert(tagStart < tagEnd); + + // We need to check the tag for special meanings, copy + // it over to backing storage and actually reference the + // backing storage in the saved tags + StringRef tagStr = originalTags.substr(tagStart + 1, tagEnd - tagStart - 1); + CATCH_ENFORCE(!tagStr.empty(), + "Found an empty tag while registering test case '" + << _nameAndTags.name << "' at " + << _lineInfo); + + enforceNotReservedTag(tagStr, lineInfo); + properties |= parseSpecialTag(tagStr); + // When copying a tag to the backing storage, we need to + // check if it is a merged hide tag, such as [.foo], and + // if it is, we need to handle it as if it was [foo]. + if (tagStr.size() > 1 && tagStr[0] == '.') { + tagStr = tagStr.substr(1, tagStr.size() - 1); + } + // We skip over dealing with the [.] tag, as we will add + // it later unconditionally and then sort and unique all + // the tags. + internalAppendTag(tagStr); + } + } + CATCH_ENFORCE(!inTag, + "Found an unclosed tag while registering test case '" + << _nameAndTags.name << "' at " << _lineInfo); + + // Add [.] if relevant + if (isHidden()) { + internalAppendTag("."_sr); + } + + // Sort and prepare tags + std::sort(begin(tags), end(tags)); + tags.erase(std::unique(begin(tags), end(tags)), + end(tags)); + } + + bool TestCaseInfo::isHidden() const { + return applies(properties & TestCaseProperties::IsHidden); + } + bool TestCaseInfo::throws() const { + return applies(properties & TestCaseProperties::Throws); + } + bool TestCaseInfo::okToFail() const { + return applies(properties & (TestCaseProperties::ShouldFail | TestCaseProperties::MayFail)); + } + bool TestCaseInfo::expectedToFail() const { + return applies(properties & (TestCaseProperties::ShouldFail)); + } + + void TestCaseInfo::addFilenameTag() { + std::string combined("#"); + combined += extractFilenamePart(lineInfo.file); + internalAppendTag(combined); + } + + std::string TestCaseInfo::tagsAsString() const { + std::string ret; + // '[' and ']' per tag + std::size_t full_size = 2 * tags.size(); + for (const auto &tag : tags) { + full_size += tag.original.size(); + } + ret.reserve(full_size); + for (const auto &tag : tags) { + ret.push_back('['); + ret += tag.original; + ret.push_back(']'); + } + + return ret; + } + + void TestCaseInfo::internalAppendTag(StringRef tagStr) { + backingTags += '['; + const auto backingStart = backingTags.size(); + backingTags += tagStr; + const auto backingEnd = backingTags.size(); + backingTags += ']'; + tags.emplace_back(StringRef(backingTags.c_str() + backingStart, backingEnd - backingStart)); + } + + bool operator<(TestCaseInfo const &lhs, TestCaseInfo const &rhs) { + // We want to avoid redoing the string comparisons multiple times, + // so we store the result of a three-way comparison before using + // it in the actual comparison logic. + const auto cmpName = lhs.name.compare(rhs.name); + if (cmpName != 0) { + return cmpName < 0; + } + const auto cmpClassName = lhs.className.compare(rhs.className); + if (cmpClassName != 0) { + return cmpClassName < 0; + } + return lhs.tags < rhs.tags; + } + +} // end namespace Catch + +#include +#include +#include +#include + +namespace Catch { + + TestSpec::Pattern::Pattern(std::string const &name) + : m_name(name) {} + + TestSpec::Pattern::~Pattern() = default; + + std::string const &TestSpec::Pattern::name() const { + return m_name; + } + + TestSpec::NamePattern::NamePattern(std::string const &name, std::string const &filterString) + : Pattern(filterString) + , m_wildcardPattern(toLower(name), CaseSensitive::No) {} + + bool TestSpec::NamePattern::matches(TestCaseInfo const &testCase) const { + return m_wildcardPattern.matches(testCase.name); + } + + void TestSpec::NamePattern::serializeTo(std::ostream &out) const { + out << '"' << name() << '"'; + } + + TestSpec::TagPattern::TagPattern(std::string const &tag, std::string const &filterString) + : Pattern(filterString) + , m_tag(tag) {} + + bool TestSpec::TagPattern::matches(TestCaseInfo const &testCase) const { + return std::find(begin(testCase.tags), + end(testCase.tags), + Tag(m_tag)) + != end(testCase.tags); + } + + void TestSpec::TagPattern::serializeTo(std::ostream &out) const { + out << name(); + } + + bool TestSpec::Filter::matches(TestCaseInfo const &testCase) const { + bool should_use = !testCase.isHidden(); + for (auto const &pattern : m_required) { + should_use = true; + if (!pattern->matches(testCase)) { + return false; + } + } + for (auto const &pattern : m_forbidden) { + if (pattern->matches(testCase)) { + return false; + } + } + return should_use; + } + + void TestSpec::Filter::serializeTo(std::ostream &out) const { + bool first = true; + for (auto const &pattern : m_required) { + if (!first) { + out << ' '; + } + out << *pattern; + first = false; + } + for (auto const &pattern : m_forbidden) { + if (!first) { + out << ' '; + } + out << *pattern; + first = false; + } + } + + std::string TestSpec::extractFilterName(Filter const &filter) { + Catch::ReusableStringStream sstr; + sstr << filter; + return sstr.str(); + } + + bool TestSpec::hasFilters() const { + return !m_filters.empty(); + } + + bool TestSpec::matches(TestCaseInfo const &testCase) const { + return std::any_of(m_filters.begin(), m_filters.end(), [&](Filter const &f) { return f.matches(testCase); }); + } + + TestSpec::Matches TestSpec::matchesByFilter(std::vector const &testCases, IConfig const &config) const { + Matches matches; + matches.reserve(m_filters.size()); + for (auto const &filter : m_filters) { + std::vector currentMatches; + for (auto const &test : testCases) + if (isThrowSafe(test, config) && filter.matches(test.getTestCaseInfo())) + currentMatches.emplace_back(&test); + matches.push_back( + FilterMatch{extractFilterName(filter), currentMatches}); + } + return matches; + } + + const TestSpec::vectorStrings &TestSpec::getInvalidSpecs() const { + return m_invalidSpecs; + } + + void TestSpec::serializeTo(std::ostream &out) const { + bool first = true; + for (auto const &filter : m_filters) { + if (!first) { + out << ','; + } + out << filter; + first = false; + } + } + +} // namespace Catch + +#include + +namespace Catch { + + namespace { + static auto getCurrentNanosecondsSinceEpoch() -> uint64_t { + return std::chrono::duration_cast(std::chrono::steady_clock::now().time_since_epoch()).count(); + } + } // end unnamed namespace + + void Timer::start() { + m_nanoseconds = getCurrentNanosecondsSinceEpoch(); + } + auto Timer::getElapsedNanoseconds() const -> uint64_t { + return getCurrentNanosecondsSinceEpoch() - m_nanoseconds; + } + auto Timer::getElapsedMicroseconds() const -> uint64_t { + return getElapsedNanoseconds() / 1000; + } + auto Timer::getElapsedMilliseconds() const -> unsigned int { + return static_cast(getElapsedMicroseconds() / 1000); + } + auto Timer::getElapsedSeconds() const -> double { + return static_cast(getElapsedMicroseconds()) / 1000000.0; + } + +} // namespace Catch + +#include + +namespace Catch { + + namespace Detail { + + namespace { + const int hexThreshold = 255; + + struct Endianness { + enum Arch : uint8_t { + Big, + Little + }; + + static Arch which() { + int one = 1; + // If the lowest byte we read is non-zero, we can assume + // that little endian format is used. + auto value = *reinterpret_cast(&one); + return value ? Little : Big; + } + }; + + template + std::string fpToString(T value, int precision) { + if (Catch::isnan(value)) { + return "nan"; + } + + ReusableStringStream rss; + rss << std::setprecision(precision) + << std::fixed + << value; + std::string d = rss.str(); + std::size_t i = d.find_last_not_of('0'); + if (i != std::string::npos && i != d.size() - 1) { + if (d[i] == '.') + i++; + d = d.substr(0, i + 1); + } + return d; + } + } // end unnamed namespace + + std::size_t catch_strnlen(const char *str, std::size_t n) { + auto ret = std::char_traits::find(str, n, '\0'); + if (ret != nullptr) { return static_cast(ret - str); } + return n; + } + + std::string formatTimeT(std::time_t time) { +#ifdef _MSC_VER + std::tm timeInfo = {}; + const auto err = gmtime_s(&timeInfo, &time); + if (err) { + return "gmtime from provided timepoint has failed. This " + "happens e.g. with pre-1970 dates using Microsoft libc"; + } +#else + std::tm *timeInfo = std::gmtime(&time); +#endif + + auto const timeStampSize = sizeof("2017-01-16T17:06:45Z"); + char timeStamp[timeStampSize]; + const char *const fmt = "%Y-%m-%dT%H:%M:%SZ"; + +#ifdef _MSC_VER + std::strftime(timeStamp, timeStampSize, fmt, &timeInfo); +#else + std::strftime(timeStamp, timeStampSize, fmt, timeInfo); +#endif + return std::string(timeStamp, timeStampSize - 1); + } + + std::string convertIntoString(StringRef string, bool escapeInvisibles) { + std::string ret; + // This is enough for the "don't escape invisibles" case, and a good + // lower bound on the "escape invisibles" case. + ret.reserve(string.size() + 2); + + if (!escapeInvisibles) { + ret += '"'; + ret += string; + ret += '"'; + return ret; + } + + size_t last_start = 0; + auto write_to = [&](size_t idx) { + if (last_start < idx) { + ret += string.substr(last_start, idx - last_start); + } + last_start = idx + 1; + }; + + ret += '"'; + for (size_t i = 0; i < string.size(); ++i) { + const char c = string[i]; + if (c == '\r' || c == '\n' || c == '\t' || c == '\f') { + write_to(i); + if (c == '\r') { ret.append("\\r"); } + if (c == '\n') { ret.append("\\n"); } + if (c == '\t') { ret.append("\\t"); } + if (c == '\f') { ret.append("\\f"); } + } + } + write_to(string.size()); + ret += '"'; + + return ret; + } + + std::string convertIntoString(StringRef string) { + return convertIntoString(string, getCurrentContext().getConfig()->showInvisibles()); + } + + std::string rawMemoryToString(const void *object, std::size_t size) { + // Reverse order for little endian architectures + int i = 0, end = static_cast(size), inc = 1; + if (Endianness::which() == Endianness::Little) { + i = end - 1; + end = inc = -1; + } + + unsigned char const *bytes = static_cast(object); + ReusableStringStream rss; + rss << "0x" << std::setfill('0') << std::hex; + for (; i != end; i += inc) + rss << std::setw(2) << static_cast(bytes[i]); + return rss.str(); + } + + std::string makeExceptionHappenedString() { + return "{ stringification failed with an exception: \"" + translateActiveException() + "\" }"; + } + + } // namespace Detail + + //// ======================================================= //// + // + // Out-of-line defs for full specialization of StringMaker + // + //// ======================================================= //// + + std::string StringMaker::convert(const std::string &str) { + return Detail::convertIntoString(str); + } + +#ifdef CATCH_CONFIG_CPP17_STRING_VIEW + std::string StringMaker::convert(std::string_view str) { + return Detail::convertIntoString(StringRef(str.data(), str.size())); + } +#endif + + std::string StringMaker::convert(char const *str) { + if (str) { + return Detail::convertIntoString(str); + } else { + return {"{null string}"}; + } + } + std::string StringMaker::convert(char *str) { // NOLINT(readability-non-const-parameter) + if (str) { + return Detail::convertIntoString(str); + } else { + return {"{null string}"}; + } + } + +#ifdef CATCH_CONFIG_WCHAR + std::string StringMaker::convert(const std::wstring &wstr) { + std::string s; + s.reserve(wstr.size()); + for (auto c : wstr) { + s += (c <= 0xff) ? static_cast(c) : '?'; + } + return ::Catch::Detail::stringify(s); + } + +#ifdef CATCH_CONFIG_CPP17_STRING_VIEW + std::string StringMaker::convert(std::wstring_view str) { + return StringMaker::convert(std::wstring(str)); + } +#endif + + std::string StringMaker::convert(wchar_t const *str) { + if (str) { + return ::Catch::Detail::stringify(std::wstring{str}); + } else { + return {"{null string}"}; + } + } + std::string StringMaker::convert(wchar_t *str) { + if (str) { + return ::Catch::Detail::stringify(std::wstring{str}); + } else { + return {"{null string}"}; + } + } +#endif + +#if defined(CATCH_CONFIG_CPP17_BYTE) +#include + std::string StringMaker::convert(std::byte value) { + return ::Catch::Detail::stringify(std::to_integer(value)); + } +#endif // defined(CATCH_CONFIG_CPP17_BYTE) + + std::string StringMaker::convert(int value) { + return ::Catch::Detail::stringify(static_cast(value)); + } + std::string StringMaker::convert(long value) { + return ::Catch::Detail::stringify(static_cast(value)); + } + std::string StringMaker::convert(long long value) { + ReusableStringStream rss; + rss << value; + if (value > Detail::hexThreshold) { + rss << " (0x" << std::hex << value << ')'; + } + return rss.str(); + } + + std::string StringMaker::convert(unsigned int value) { + return ::Catch::Detail::stringify(static_cast(value)); + } + std::string StringMaker::convert(unsigned long value) { + return ::Catch::Detail::stringify(static_cast(value)); + } + std::string StringMaker::convert(unsigned long long value) { + ReusableStringStream rss; + rss << value; + if (value > Detail::hexThreshold) { + rss << " (0x" << std::hex << value << ')'; + } + return rss.str(); + } + + std::string StringMaker::convert(signed char value) { + if (value == '\r') { + return "'\\r'"; + } else if (value == '\f') { + return "'\\f'"; + } else if (value == '\n') { + return "'\\n'"; + } else if (value == '\t') { + return "'\\t'"; + } else if ('\0' <= value && value < ' ') { + return ::Catch::Detail::stringify(static_cast(value)); + } else { + char chstr[] = "' '"; + chstr[1] = value; + return chstr; + } + } + std::string StringMaker::convert(char c) { + return ::Catch::Detail::stringify(static_cast(c)); + } + std::string StringMaker::convert(unsigned char value) { + return ::Catch::Detail::stringify(static_cast(value)); + } + + int StringMaker::precision = std::numeric_limits::max_digits10; + + std::string StringMaker::convert(float value) { + return Detail::fpToString(value, precision) + 'f'; + } + + int StringMaker::precision = std::numeric_limits::max_digits10; + + std::string StringMaker::convert(double value) { + return Detail::fpToString(value, precision); + } + +} // end namespace Catch + +namespace Catch { + + Counts Counts::operator-(Counts const &other) const { + Counts diff; + diff.passed = passed - other.passed; + diff.failed = failed - other.failed; + diff.failedButOk = failedButOk - other.failedButOk; + diff.skipped = skipped - other.skipped; + return diff; + } + + Counts &Counts::operator+=(Counts const &other) { + passed += other.passed; + failed += other.failed; + failedButOk += other.failedButOk; + skipped += other.skipped; + return *this; + } + + std::uint64_t Counts::total() const { + return passed + failed + failedButOk + skipped; + } + bool Counts::allPassed() const { + return failed == 0 && failedButOk == 0 && skipped == 0; + } + bool Counts::allOk() const { + return failed == 0; + } + + Totals Totals::operator-(Totals const &other) const { + Totals diff; + diff.assertions = assertions - other.assertions; + diff.testCases = testCases - other.testCases; + return diff; + } + + Totals &Totals::operator+=(Totals const &other) { + assertions += other.assertions; + testCases += other.testCases; + return *this; + } + + Totals Totals::delta(Totals const &prevTotals) const { + Totals diff = *this - prevTotals; + if (diff.assertions.failed > 0) + ++diff.testCases.failed; + else if (diff.assertions.failedButOk > 0) + ++diff.testCases.failedButOk; + else if (diff.assertions.skipped > 0) + ++diff.testCases.skipped; + else + ++diff.testCases.passed; + return diff; + } + +} // namespace Catch + +namespace Catch { + namespace Detail { + void registerTranslatorImpl( + Detail::unique_ptr &&translator) { + getMutableRegistryHub().registerTranslator( + CATCH_MOVE(translator)); + } + } // namespace Detail +} // namespace Catch + +#include + +namespace Catch { + + Version::Version(unsigned int _majorVersion, + unsigned int _minorVersion, + unsigned int _patchNumber, + char const *const _branchName, + unsigned int _buildNumber) + : majorVersion(_majorVersion), minorVersion(_minorVersion), patchNumber(_patchNumber), branchName(_branchName), buildNumber(_buildNumber) {} + + std::ostream &operator<<(std::ostream &os, Version const &version) { + os << version.majorVersion << '.' + << version.minorVersion << '.' + << version.patchNumber; + // branchName is never null -> 0th char is \0 if it is empty + if (version.branchName[0]) { + os << '-' << version.branchName + << '.' << version.buildNumber; + } + return os; + } + + Version const &libraryVersion() { + static Version version(3, 14, 0, "", 0); + return version; + } + +} // namespace Catch + +namespace Catch { + + const char *GeneratorException::what() const noexcept { + return m_msg; + } + +} // end namespace Catch + +namespace Catch { + + IGeneratorTracker::~IGeneratorTracker() = default; + + namespace Generators { + + GeneratorUntypedBase::~GeneratorUntypedBase() = default; + + IGeneratorTracker *acquireGeneratorTracker(StringRef generatorName, SourceLineInfo const &lineInfo) { + return getResultCapture().acquireGeneratorTracker(generatorName, lineInfo); + } + + IGeneratorTracker *createGeneratorTracker(StringRef generatorName, + SourceLineInfo lineInfo, + GeneratorBasePtr &&generator) { + return getResultCapture().createGeneratorTracker( + generatorName, lineInfo, CATCH_MOVE(generator)); + } + + } // namespace Generators +} // namespace Catch + +#include + +namespace Catch { + namespace Generators { + namespace Detail { + std::uint32_t getSeed() { + return sharedRng()(); + } + } // namespace Detail + + struct RandomFloatingGenerator::PImpl { + PImpl(long double a, long double b, uint32_t seed) + : rng(seed), dist(a, b) {} + + Catch::SimplePcg32 rng; + std::uniform_real_distribution dist; + }; + + RandomFloatingGenerator::RandomFloatingGenerator( + long double a, long double b, std::uint32_t seed) + : m_pimpl(Catch::Detail::make_unique(a, b, seed)) { + static_cast(next()); + } + + RandomFloatingGenerator::~RandomFloatingGenerator() = default; + bool RandomFloatingGenerator::next() { + m_current_number = m_pimpl->dist(m_pimpl->rng); + return true; + } + + bool RandomFloatingGenerator::isFinite() const { + return false; + } + + } // namespace Generators +} // namespace Catch + +namespace Catch { + namespace Generators { + namespace Detail { + + [[noreturn]] + void throw_generator_exception(char const *msg) { + Catch::throw_exception(GeneratorException{msg}); + } + + } // namespace Detail + } // namespace Generators +} // namespace Catch + +namespace Catch { + namespace Detail { + void missingCaptureInstance() { + CATCH_INTERNAL_ERROR("No result capture instance"); + } + } // namespace Detail + + IResultCapture::~IResultCapture() = default; +} // namespace Catch + +namespace Catch { + IConfig::~IConfig() = default; +} + +namespace Catch { + IExceptionTranslator::~IExceptionTranslator() = default; + IExceptionTranslatorRegistry::~IExceptionTranslatorRegistry() = default; +} // namespace Catch + +#include + +namespace Catch { + namespace Generators { + + bool GeneratorUntypedBase::countedNext() { + auto ret = next(); + if (ret) { + m_stringReprCache.clear(); + ++m_currentElementIndex; + } + return ret; + } + + void GeneratorUntypedBase::skipToNthElementImpl(std::size_t n) { + for (size_t i = m_currentElementIndex; i < n; ++i) { + bool isValid = next(); + if (!isValid) { + Detail::throw_generator_exception( + "Coud not jump to Nth element: not enough elements"); + } + } + } + + void GeneratorUntypedBase::skipToNthElement(std::size_t n) { + if (n < m_currentElementIndex) { + Detail::throw_generator_exception( + "Tried to jump generator backwards"); + } + if (n == m_currentElementIndex) { return; } + + skipToNthElementImpl(n); + // Fixup tracking after moving the generator forward + // * Ensure that the correct element index is set after skipping + // * Invalidate cache + m_currentElementIndex = n; + m_stringReprCache.clear(); + } + + StringRef GeneratorUntypedBase::currentElementAsString() const { + if (m_stringReprCache.empty()) { + m_stringReprCache = stringifyImpl(); + } + return m_stringReprCache; + } + + bool GeneratorUntypedBase::isFinite() const { + return true; + } + + } // namespace Generators +} // namespace Catch + +namespace Catch { + IRegistryHub::~IRegistryHub() = default; + IMutableRegistryHub::~IMutableRegistryHub() = default; +} // namespace Catch + +#include + +namespace Catch { + + ReporterConfig::ReporterConfig( + IConfig const *_fullConfig, + Detail::unique_ptr _stream, + ColourMode colourMode, + std::map customOptions) + : m_stream(CATCH_MOVE(_stream)), m_fullConfig(_fullConfig), m_colourMode(colourMode), m_customOptions(CATCH_MOVE(customOptions)) {} + + Detail::unique_ptr ReporterConfig::takeStream() && { + assert(m_stream); + return CATCH_MOVE(m_stream); + } + IConfig const *ReporterConfig::fullConfig() const { + return m_fullConfig; + } + ColourMode ReporterConfig::colourMode() const { + return m_colourMode; + } + + std::map const & + ReporterConfig::customOptions() const { + return m_customOptions; + } + + ReporterConfig::~ReporterConfig() = default; + + AssertionStats::AssertionStats(AssertionResult const &_assertionResult, + std::vector const &_infoMessages, + Totals const &_totals) + : assertionResult(_assertionResult), infoMessages(_infoMessages), totals(_totals) { + if (assertionResult.hasMessage()) { + // Copy message into messages list. + // !TBD This should have been done earlier, somewhere + MessageBuilder builder(assertionResult.getTestMacroName(), assertionResult.getSourceInfo(), assertionResult.getResultType()); + builder.m_info.message = static_cast(assertionResult.getMessage()); + + infoMessages.push_back(CATCH_MOVE(builder.m_info)); + } + } + + SectionStats::SectionStats(SectionInfo &&_sectionInfo, + Counts const &_assertions, + double _durationInSeconds, + bool _missingAssertions) + : sectionInfo(CATCH_MOVE(_sectionInfo)), assertions(_assertions), durationInSeconds(_durationInSeconds), missingAssertions(_missingAssertions) {} + + TestCaseStats::TestCaseStats(TestCaseInfo const &_testInfo, + Totals const &_totals, + std::string &&_stdOut, + std::string &&_stdErr, + bool _aborting) + : testInfo(&_testInfo), totals(_totals), stdOut(CATCH_MOVE(_stdOut)), stdErr(CATCH_MOVE(_stdErr)), aborting(_aborting) {} + + TestRunStats::TestRunStats(TestRunInfo const &_runInfo, + Totals const &_totals, + bool _aborting) + : runInfo(_runInfo), totals(_totals), aborting(_aborting) {} + + IEventListener::~IEventListener() = default; + +} // end namespace Catch + +namespace Catch { + IReporterFactory::~IReporterFactory() = default; + EventListenerFactory::~EventListenerFactory() = default; +} // namespace Catch + +namespace Catch { + ITestCaseRegistry::~ITestCaseRegistry() = default; +} + +namespace Catch { + + AssertionHandler::AssertionHandler(StringRef macroName, + SourceLineInfo const &lineInfo, + StringRef capturedExpression, + ResultDisposition::Flags resultDisposition) + : m_assertionInfo{macroName, lineInfo, capturedExpression, resultDisposition}, m_resultCapture(getResultCapture()) { + m_resultCapture.notifyAssertionStarted(m_assertionInfo); + } + + void AssertionHandler::handleExpr(ITransientExpression const &expr) { + m_resultCapture.handleExpr(m_assertionInfo, expr, m_reaction); + } + void AssertionHandler::handleMessage(ResultWas::OfType resultType, std::string &&message) { + m_resultCapture.handleMessage(m_assertionInfo, resultType, CATCH_MOVE(message), m_reaction); + } + + auto AssertionHandler::allowThrows() const -> bool { + return getCurrentContext().getConfig()->allowThrows(); + } + + void AssertionHandler::complete() { + m_completed = true; + if (m_reaction.shouldDebugBreak) { + // If you find your debugger stopping you here then go one level up on the + // call-stack for the code that caused it (typically a failed assertion) + + // (To go back to the test and change execution, jump over the throw, next) + CATCH_BREAK_INTO_DEBUGGER(); + } + if (m_reaction.shouldThrow) { + throw_test_failure_exception(); + } + if (m_reaction.shouldSkip) { + throw_test_skip_exception(); + } + } + + void AssertionHandler::handleUnexpectedInflightException() { + m_resultCapture.handleUnexpectedInflightException(m_assertionInfo, Catch::translateActiveException(), m_reaction); + } + + void AssertionHandler::handleExceptionThrownAsExpected() { + m_resultCapture.handleNonExpr(m_assertionInfo, ResultWas::Ok, m_reaction); + } + void AssertionHandler::handleExceptionNotThrownAsExpected() { + m_resultCapture.handleNonExpr(m_assertionInfo, ResultWas::Ok, m_reaction); + } + + void AssertionHandler::handleUnexpectedExceptionNotThrown() { + m_resultCapture.handleUnexpectedExceptionNotThrown(m_assertionInfo, m_reaction); + } + + void AssertionHandler::handleThrowingCallSkipped() { + m_resultCapture.handleNonExpr(m_assertionInfo, ResultWas::Ok, m_reaction); + } + + // This is the overload that takes a string and infers the Equals matcher from it + // The more general overload, that takes any string matcher, is in catch_capture_matchers.cpp + void handleExceptionMatchExpr(AssertionHandler &handler, std::string const &str) { + handleExceptionMatchExpr(handler, Matchers::Equals(str)); + } + +} // namespace Catch + +#include + +namespace Catch { + namespace Detail { + + bool CaseInsensitiveLess::operator()(StringRef lhs, + StringRef rhs) const { + return std::lexicographical_compare( + lhs.begin(), lhs.end(), + rhs.begin(), rhs.end(), + [](char l, char r) { return toLower(l) < toLower(r); }); + } + + bool + CaseInsensitiveEqualTo::operator()(StringRef lhs, + StringRef rhs) const { + return std::equal( + lhs.begin(), lhs.end(), + rhs.begin(), rhs.end(), + [](char l, char r) { return toLower(l) == toLower(r); }); + } + + } // namespace Detail +} // namespace Catch + +#include +#include + +namespace { + bool isOptPrefix(char c) { + return c == '-' +#ifdef CATCH_PLATFORM_WINDOWS + || c == '/' +#endif + ; + } + + Catch::StringRef normaliseOpt(Catch::StringRef optName) { + if (optName[0] == '-' +#if defined(CATCH_PLATFORM_WINDOWS) + || optName[0] == '/' +#endif + ) { + return optName.substr(1, optName.size()); + } + + return optName; + } + + static size_t find_first_separator(Catch::StringRef sr) { + auto is_separator = [](char c) { + return c == ' ' || c == ':' || c == '='; + }; + size_t pos = 0; + while (pos < sr.size()) { + if (is_separator(sr[pos])) { return pos; } + ++pos; + } + + return Catch::StringRef::npos; + } + +} // namespace + +namespace Catch { + namespace Clara { + namespace Detail { + + void TokenStream::loadBuffer() { + m_tokenBuffer.clear(); + + // Skip any empty strings + while (it != itEnd && it->empty()) { + ++it; + } + + if (it != itEnd) { + StringRef next = *it; + if (isOptPrefix(next[0])) { + auto delimiterPos = find_first_separator(next); + if (delimiterPos != StringRef::npos) { + m_tokenBuffer.push_back( + {TokenType::Option, + next.substr(0, delimiterPos)}); + m_tokenBuffer.push_back( + {TokenType::Argument, + next.substr(delimiterPos + 1, next.size())}); + } else { + if (next.size() > 1 && next[1] != '-' && next.size() > 2) { + // Combined short args, e.g. "-ab" for "-a -b" + for (size_t i = 1; i < next.size(); ++i) { + m_tokenBuffer.push_back( + {TokenType::Option, + next.substr(i, 1)}); + } + } else { + m_tokenBuffer.push_back( + {TokenType::Option, next}); + } + } + } else { + m_tokenBuffer.push_back( + {TokenType::Argument, next}); + } + } + } + + TokenStream::TokenStream(Args const &args) + : TokenStream(args.m_args.begin(), args.m_args.end()) {} + + TokenStream::TokenStream(Iterator it_, Iterator itEnd_) + : it(it_), itEnd(itEnd_) { + loadBuffer(); + } + + TokenStream &TokenStream::operator++() { + if (m_tokenBuffer.size() >= 2) { + m_tokenBuffer.erase(m_tokenBuffer.begin()); + } else { + if (it != itEnd) + ++it; + loadBuffer(); + } + return *this; + } + + ParserResult convertInto(std::string const &source, + std::string &target) { + target = source; + return ParserResult::ok(ParseResultType::Matched); + } + + ParserResult convertInto(std::string const &source, + bool &target) { + std::string srcLC = toLower(source); + + if (srcLC == "y" || srcLC == "1" || srcLC == "true" || srcLC == "yes" || srcLC == "on") { + target = true; + } else if (srcLC == "n" || srcLC == "0" || srcLC == "false" || srcLC == "no" || srcLC == "off") { + target = false; + } else { + return ParserResult::runtimeError( + "Expected a boolean value but did not recognise: '" + source + '\''); + } + return ParserResult::ok(ParseResultType::Matched); + } + + size_t ParserBase::cardinality() const { + return 1; + } + + InternalParseResult ParserBase::parse(Args const &args) const { + return parse(static_cast(args.exeName()), TokenStream(args)); + } + + ParseState::ParseState(ParseResultType type, + TokenStream remainingTokens) + : m_type(type), m_remainingTokens(CATCH_MOVE(remainingTokens)) {} + + ParserResult BoundFlagRef::setFlag(bool flag) { + m_ref = flag; + return ParserResult::ok(ParseResultType::Matched); + } + + ResultBase::~ResultBase() = default; + + bool BoundRef::isContainer() const { + return false; + } + + bool BoundRef::isFlag() const { + return false; + } + + bool BoundFlagRefBase::isFlag() const { + return true; + } + + } // namespace Detail + + Detail::InternalParseResult Arg::parse(std::string const &, + Detail::TokenStream tokens) const { + auto validationResult = validate(); + if (!validationResult) + return Detail::InternalParseResult(validationResult); + + auto token = *tokens; + if (token.type != Detail::TokenType::Argument) + return Detail::InternalParseResult::ok(Detail::ParseState( + ParseResultType::NoMatch, CATCH_MOVE(tokens))); + + assert(!m_ref->isFlag()); + auto valueRef = static_cast(m_ref.get()); + + auto result = valueRef->setValue(static_cast(token.token)); + if (!result) + return Detail::InternalParseResult(result); + else + return Detail::InternalParseResult::ok( + Detail::ParseState(ParseResultType::Matched, + CATCH_MOVE(++tokens))); + } + + Opt::Opt(bool &ref) + : ParserRefImpl(std::make_shared(ref)) {} + + Detail::HelpColumns Opt::getHelpColumns() const { + ReusableStringStream oss; + bool first = true; + for (auto const &opt : m_optNames) { + if (first) + first = false; + else + oss << ", "; + oss << opt; + } + if (!m_hint.empty()) + oss << " <" << m_hint << '>'; + return {oss.str(), m_description}; + } + + bool Opt::isMatch(StringRef optToken) const { + auto normalisedToken = normaliseOpt(optToken); + for (auto const &name : m_optNames) { + if (normaliseOpt(name) == normalisedToken) + return true; + } + return false; + } + + Detail::InternalParseResult Opt::parse(std::string const &, + Detail::TokenStream tokens) const { + auto validationResult = validate(); + if (!validationResult) + return Detail::InternalParseResult(validationResult); + + if (tokens && tokens->type == Detail::TokenType::Option) { + auto const &token = *tokens; + if (isMatch(token.token)) { + if (m_ref->isFlag()) { + auto flagRef = static_cast( + m_ref.get()); + auto result = flagRef->setFlag(true); + if (!result) + return Detail::InternalParseResult(result); + if (result.value() == ParseResultType::ShortCircuitAll) + return Detail::InternalParseResult::ok(Detail::ParseState( + result.value(), CATCH_MOVE(tokens))); + } else { + auto valueRef = static_cast( + m_ref.get()); + ++tokens; + if (!tokens) + return Detail::InternalParseResult::runtimeError( + "Expected argument following " + token.token); + auto const &argToken = *tokens; + if (argToken.type != Detail::TokenType::Argument) + return Detail::InternalParseResult::runtimeError( + "Expected argument following " + token.token); + const auto result = valueRef->setValue(static_cast(argToken.token)); + if (!result) + return Detail::InternalParseResult(result); + if (result.value() == ParseResultType::ShortCircuitAll) + return Detail::InternalParseResult::ok(Detail::ParseState( + result.value(), CATCH_MOVE(tokens))); + } + return Detail::InternalParseResult::ok(Detail::ParseState( + ParseResultType::Matched, CATCH_MOVE(++tokens))); + } + } + return Detail::InternalParseResult::ok( + Detail::ParseState(ParseResultType::NoMatch, CATCH_MOVE(tokens))); + } + + Detail::Result Opt::validate() const { + if (m_optNames.empty()) + return Detail::Result::logicError("No options supplied to Opt"); + for (auto const &name : m_optNames) { + if (name.empty()) + return Detail::Result::logicError( + "Option name cannot be empty"); +#ifdef CATCH_PLATFORM_WINDOWS + if (name[0] != '-' && name[0] != '/') + return Detail::Result::logicError( + "Option name must begin with '-' or '/'"); +#else + if (name[0] != '-') + return Detail::Result::logicError( + "Option name must begin with '-'"); +#endif + } + return ParserRefImpl::validate(); + } + + ExeName::ExeName() + : m_name(std::make_shared("")) {} + + ExeName::ExeName(std::string &ref) + : ExeName() { + m_ref = std::make_shared>(ref); + } + + Detail::InternalParseResult + ExeName::parse(std::string const &, + Detail::TokenStream tokens) const { + return Detail::InternalParseResult::ok( + Detail::ParseState(ParseResultType::NoMatch, CATCH_MOVE(tokens))); + } + + ParserResult ExeName::set(std::string const &newName) { + auto lastSlash = newName.find_last_of("\\/"); + auto filename = (lastSlash == std::string::npos) + ? newName + : newName.substr(lastSlash + 1); + + *m_name = filename; + if (m_ref) + return m_ref->setValue(filename); + else + return ParserResult::ok(ParseResultType::Matched); + } + + Parser &Parser::operator|=(Parser const &other) { + m_options.insert(m_options.end(), + other.m_options.begin(), + other.m_options.end()); + m_args.insert( + m_args.end(), other.m_args.begin(), other.m_args.end()); + return *this; + } + + std::vector Parser::getHelpColumns() const { + std::vector cols; + cols.reserve(m_options.size()); + for (auto const &o : m_options) { + cols.push_back(o.getHelpColumns()); + } + return cols; + } + + void Parser::writeToStream(std::ostream &os) const { + if (!m_exeName.name().empty()) { + os << "usage:\n" + << " " << m_exeName.name() << ' '; + bool required = true, first = true; + for (auto const &arg : m_args) { + if (first) + first = false; + else + os << ' '; + if (arg.isOptional() && required) { + os << '['; + required = false; + } + os << '<' << arg.hint() << '>'; + if (arg.cardinality() == 0) + os << " ... "; + } + if (!required) + os << ']'; + if (!m_options.empty()) + os << " options"; + os << "\n\nwhere options are:\n"; + } + + auto rows = getHelpColumns(); + size_t consoleWidth = CATCH_CONFIG_CONSOLE_WIDTH; + size_t optWidth = 0; + for (auto const &cols : rows) + optWidth = (std::max)(optWidth, cols.left.size() + 2); + + optWidth = (std::min)(optWidth, consoleWidth / 2); + + for (auto &cols : rows) { + auto row = TextFlow::Column(CATCH_MOVE(cols.left)) + .width(optWidth) + .indent(2) + + TextFlow::Spacer(4) + TextFlow::Column(static_cast(cols.descriptions)).width(consoleWidth - 7 - optWidth); + os << row << '\n'; + } + } + + Detail::Result Parser::validate() const { + for (auto const &opt : m_options) { + auto result = opt.validate(); + if (!result) + return result; + } + for (auto const &arg : m_args) { + auto result = arg.validate(); + if (!result) + return result; + } + return Detail::Result::ok(); + } + + Detail::InternalParseResult + Parser::parse(std::string const &exeName, + Detail::TokenStream tokens) const { + struct ParserInfo { + ParserBase const *parser = nullptr; + size_t count = 0; + }; + std::vector parseInfos; + parseInfos.reserve(m_options.size() + m_args.size()); + for (auto const &opt : m_options) { + parseInfos.push_back({&opt, 0}); + } + for (auto const &arg : m_args) { + parseInfos.push_back({&arg, 0}); + } + + m_exeName.set(exeName); + + auto result = Detail::InternalParseResult::ok( + Detail::ParseState(ParseResultType::NoMatch, CATCH_MOVE(tokens))); + while (result.value().remainingTokens()) { + bool tokenParsed = false; + + for (auto &parseInfo : parseInfos) { + if (parseInfo.parser->cardinality() == 0 || parseInfo.count < parseInfo.parser->cardinality()) { + result = parseInfo.parser->parse( + exeName, CATCH_MOVE(result).value().remainingTokens()); + if (!result) + return result; + if (result.value().type() != ParseResultType::NoMatch) { + tokenParsed = true; + ++parseInfo.count; + break; + } + } + } + + if (result.value().type() == ParseResultType::ShortCircuitAll) + return result; + if (!tokenParsed) + return Detail::InternalParseResult::runtimeError( + "Unrecognised token: " + result.value().remainingTokens()->token); + } + // !TBD Check missing required options + return result; + } + + Args::Args(int argc, char const *const *argv) + : m_exeName(argv[0]), m_args(argv + 1, argv + argc) {} + + Args::Args(std::initializer_list args) + : m_exeName(*args.begin()), m_args(args.begin() + 1, args.end()) {} + + Help::Help(bool &showHelpFlag) + : Opt([&](bool flag) { + showHelpFlag = flag; + return ParserResult::ok(ParseResultType::ShortCircuitAll); + }) { + static_cast(*this)( + "display usage information")["-?"]["-h"]["--help"] + .optional(); + } + + } // namespace Clara +} // namespace Catch + +#include +#include + +namespace Catch { + + Clara::Parser makeCommandLineParser(ConfigData &config) { + using namespace Clara; + + auto const setWarning = [&](std::string const &warning) { + if (warning == "NoAssertions") { + config.warnings = static_cast(config.warnings | WarnAbout::NoAssertions); + return ParserResult::ok(ParseResultType::Matched); + } else if (warning == "UnmatchedTestSpec") { + config.warnings = static_cast(config.warnings | WarnAbout::UnmatchedTestSpec); + return ParserResult::ok(ParseResultType::Matched); + } else if (warning == "InfiniteGenerators") { + config.warnings = static_cast(config.warnings | WarnAbout::InfiniteGenerator); + return ParserResult::ok(ParseResultType::Matched); + } + + return ParserResult ::runtimeError( + "Unrecognised warning option: '" + warning + '\''); + }; + auto const loadTestNamesFromFile = [&](std::string const &filename) { + std::ifstream f(filename.c_str()); + if (!f.is_open()) + return ParserResult::runtimeError("Unable to load input file: '" + filename + '\''); + + std::string line; + while (std::getline(f, line)) { + line = trim(line); + if (!line.empty() && !startsWith(line, '#')) { + if (!startsWith(line, '"')) + line = '"' + CATCH_MOVE(line) + '"'; + config.testsOrTags.push_back(line); + config.testsOrTags.emplace_back(","); + } + } + // Remove comma in the end + if (!config.testsOrTags.empty()) + config.testsOrTags.erase(config.testsOrTags.end() - 1); + + return ParserResult::ok(ParseResultType::Matched); + }; + auto const setTestOrder = [&](std::string const &order) { + if (startsWith("declared", order)) + config.runOrder = TestRunOrder::Declared; + else if (startsWith("lexical", order)) + config.runOrder = TestRunOrder::LexicographicallySorted; + else if (startsWith("random", order)) + config.runOrder = TestRunOrder::Randomized; + else + return ParserResult::runtimeError("Unrecognised ordering: '" + order + '\''); + return ParserResult::ok(ParseResultType::Matched); + }; + auto const setRngSeed = [&](std::string const &seed) { + if (seed == "time") { + config.rngSeed = generateRandomSeed(GenerateFrom::Time); + return ParserResult::ok(ParseResultType::Matched); + } else if (seed == "random-device") { + config.rngSeed = generateRandomSeed(GenerateFrom::RandomDevice); + return ParserResult::ok(ParseResultType::Matched); + } + + // TODO: ideally we should be parsing uint32_t directly + // fix this later when we add new parse overload + auto parsedSeed = parseUInt(seed, 0); + if (!parsedSeed) { + return ParserResult::runtimeError("Could not parse '" + seed + "' as seed"); + } + config.rngSeed = *parsedSeed; + return ParserResult::ok(ParseResultType::Matched); + }; + auto const setDefaultColourMode = [&](std::string const &colourMode) { + Optional maybeMode = Catch::Detail::stringToColourMode(toLower(colourMode)); + if (!maybeMode) { + return ParserResult::runtimeError( + "colour mode must be one of: default, ansi, win32, " + "or none. '" + + colourMode + "' is not recognised"); + } + auto mode = *maybeMode; + if (!isColourImplAvailable(mode)) { + return ParserResult::runtimeError( + "colour mode '" + colourMode + "' is not supported in this binary"); + } + config.defaultColourMode = mode; + return ParserResult::ok(ParseResultType::Matched); + }; + auto const setWaitForKeypress = [&](std::string const &keypress) { + auto keypressLc = toLower(keypress); + if (keypressLc == "never") + config.waitForKeypress = WaitForKeypress::Never; + else if (keypressLc == "start") + config.waitForKeypress = WaitForKeypress::BeforeStart; + else if (keypressLc == "exit") + config.waitForKeypress = WaitForKeypress::BeforeExit; + else if (keypressLc == "both") + config.waitForKeypress = WaitForKeypress::BeforeStartAndExit; + else + return ParserResult::runtimeError("keypress argument must be one of: never, start, exit or both. '" + keypress + "' not recognised"); + return ParserResult::ok(ParseResultType::Matched); + }; + auto const setVerbosity = [&](std::string const &verbosity) { + auto lcVerbosity = toLower(verbosity); + if (lcVerbosity == "quiet") + config.verbosity = Verbosity::Quiet; + else if (lcVerbosity == "normal") + config.verbosity = Verbosity::Normal; + else if (lcVerbosity == "high") + config.verbosity = Verbosity::High; + else + return ParserResult::runtimeError("Unrecognised verbosity, '" + verbosity + '\''); + return ParserResult::ok(ParseResultType::Matched); + }; + auto const setReporter = [&](std::string const &userReporterSpec) { + if (userReporterSpec.empty()) { + return ParserResult::runtimeError("Received empty reporter spec."); + } + + Optional parsed = parseReporterSpec(userReporterSpec); + if (!parsed) { + return ParserResult::runtimeError( + "Could not parse reporter spec '" + userReporterSpec + "'"); + } + + auto const &reporterSpec = *parsed; + + auto const &factories = getRegistryHub().getReporterRegistry().getFactories(); + auto result = factories.find(reporterSpec.name()); + + if (result == factories.end()) { + return ParserResult::runtimeError( + "Unrecognized reporter, '" + reporterSpec.name() + "'. Check available with --list-reporters"); + } + + const bool hadOutputFile = reporterSpec.outputFile().some(); + config.reporterSpecifications.push_back(CATCH_MOVE(*parsed)); + // It would be enough to check this only once at the very end, but + // there is not a place where we could call this check, so do it + // every time it could fail. For valid inputs, this is still called + // at most once. + if (!hadOutputFile) { + int n_reporters_without_file = 0; + for (auto const &spec : config.reporterSpecifications) { + if (spec.outputFile().none()) { + n_reporters_without_file++; + } + } + if (n_reporters_without_file > 1) { + return ParserResult::runtimeError("Only one reporter may have unspecified output file."); + } + } + + return ParserResult::ok(ParseResultType::Matched); + }; + auto const setShardCount = [&](std::string const &shardCount) { + auto parsedCount = parseUInt(shardCount); + if (!parsedCount) { + return ParserResult::runtimeError( + "Could not parse '" + shardCount + "' as shard count"); + } + if (*parsedCount == 0) { + return ParserResult::runtimeError( + "Shard count must be positive"); + } + config.shardCount = *parsedCount; + return ParserResult::ok(ParseResultType::Matched); + }; + auto const setBenchmarkSamples = [&](std::string const &samples) { + auto parsedSamples = parseUInt(samples); + if (!parsedSamples) { + return ParserResult::runtimeError( + "Could not parse '" + samples + "' as benchmark samples"); + } + if (*parsedSamples == 0) { + return ParserResult::runtimeError( + "Benchmark samples must be greater than 0"); + } + config.benchmarkSamples = *parsedSamples; + return ParserResult::ok(ParseResultType::Matched); + }; + + auto const setShardIndex = [&](std::string const &shardIndex) { + auto parsedIndex = parseUInt(shardIndex); + if (!parsedIndex) { + return ParserResult::runtimeError( + "Could not parse '" + shardIndex + "' as shard index"); + } + config.shardIndex = *parsedIndex; + return ParserResult::ok(ParseResultType::Matched); + }; + + auto const setSectionFilter = [&](std::string const §ionFilter) { + config.pathFilters.emplace_back(PathFilter::For::Section, trim(sectionFilter)); + return ParserResult::ok(ParseResultType::Matched); + }; + auto const setGeneratorFilter = [&](std::string const &generatorFilter) { + if (generatorFilter != "*") { + // TODO: avoid re-parsing the index? + auto parsedIndex = parseUInt(generatorFilter); + if (!parsedIndex) { + return ParserResult::runtimeError("Could not parse '" + generatorFilter + "' as generator index"); + } + } + config.useNewPathFilteringBehaviour = true; + config.pathFilters.emplace_back(PathFilter::For::Generator, trim(generatorFilter)); + return ParserResult::ok(ParseResultType::Matched); + }; + // Copy-capturing other `setFoo` functions enables calling them later, + // as the config ref remains valid, but the local lambda vars won't. + auto const setPathFilter = [=, &config](std::string const &pathFilter) { + config.useNewPathFilteringBehaviour = true; + if (pathFilter.size() < 3) { + return ParserResult::runtimeError( + "Path filter '" + pathFilter + "' is too short"); + } + if (startsWith(pathFilter, "g:")) { + return setGeneratorFilter(pathFilter.substr(2)); + } + if (startsWith(pathFilter, "c:")) { + return setSectionFilter(pathFilter.substr(2)); + } + return ParserResult::runtimeError("Path filter '" + pathFilter + "' has unknown type prefix"); + }; + + auto cli + = ExeName(config.processName) + | Help(config.showHelp) + | Opt(config.showSuccessfulTests) + ["-s"]["--success"]("include successful tests in output") + | Opt(config.shouldDebugBreak) + ["-b"]["--break"]("break into debugger on failure") + | Opt(config.noThrow) + ["-e"]["--nothrow"]("skip exception tests") + | Opt(config.showInvisibles) + ["-i"]["--invisibles"]("show invisibles (tabs, newlines)") + | Opt(config.defaultOutputFilename, "filename") + ["-o"]["--out"]("default output filename") + | Opt(accept_many, setReporter, "name[::key=value]*") + ["-r"]["--reporter"]("reporter to use (defaults to console)") + | Opt(config.name, "name") + ["-n"]["--name"]("suite name") + | Opt([&](bool) { config.abortAfter = 1; }) + ["-a"]["--abort"]("abort at first failure") + | Opt([&](int x) { config.abortAfter = x; }, "no. failures") + ["-x"]["--abortx"]("abort after x failures") + | Opt(accept_many, setWarning, "warning name") + ["-w"]["--warn"]("enable warnings") + | Opt([&](bool flag) { config.showDurations = flag ? ShowDurations::Always : ShowDurations::Never; }, "yes|no") + ["-d"]["--durations"]("show test durations") + | Opt(config.minDuration, "seconds") + ["-D"]["--min-duration"]("show test durations for tests taking at least the given number of seconds") + | Opt(loadTestNamesFromFile, "filename") + ["-f"]["--input-file"]("load test names to run from a file") + | Opt(config.filenamesAsTags) + ["-#"]["--filenames-as-tags"]("adds a tag for the filename") + | Opt(accept_many, setSectionFilter, "section name") + ["-c"]["--section"]("specify section to run") + | Opt(accept_many, setGeneratorFilter, "index spec") + ["-g"]["--generator-index"]("specify generator elements to try") + | Opt(accept_many, setPathFilter, "path filter spec") + ["-p"]["--path-filter"]("qualified path filter") + | Opt(setVerbosity, "quiet|normal|high") + ["-v"]["--verbosity"]("set output verbosity") + | Opt(config.listTests) + ["--list-tests"]("list all/matching test cases") + | Opt(config.listTags) + ["--list-tags"]("list all/matching tags") + | Opt(config.listReporters) + ["--list-reporters"]("list all available reporters") + | Opt(config.listListeners) + ["--list-listeners"]("list all listeners") + | Opt(setTestOrder, "decl|lex|rand") + ["--order"]("test case order (defaults to rand)") + | Opt(setRngSeed, "'time'|'random-device'|number") + ["--rng-seed"]("set a specific seed for random numbers") + | Opt(setDefaultColourMode, "ansi|win32|none|default") + ["--colour-mode"]("what color mode should be used as default") + | Opt(config.libIdentify) + ["--libidentify"]("report name and version according to libidentify standard") + | Opt(setWaitForKeypress, "never|start|exit|both") + ["--wait-for-keypress"]("waits for a keypress before exiting") + | Opt(config.skipBenchmarks) + ["--skip-benchmarks"]("disable running benchmarks") + | Opt(setBenchmarkSamples, "samples") + ["--benchmark-samples"]("number of samples to collect (default: 100)") + | Opt(config.benchmarkResamples, "resamples") + ["--benchmark-resamples"]("number of resamples for the bootstrap (default: 100000)") + | Opt(config.benchmarkConfidenceInterval, "confidence interval") + ["--benchmark-confidence-interval"]("confidence interval for the bootstrap (between 0 and 1, default: 0.95)") + | Opt(config.benchmarkNoAnalysis) + ["--benchmark-no-analysis"]("perform only measurements; do not perform any analysis") + | Opt(config.benchmarkWarmupTime, "benchmarkWarmupTime") + ["--benchmark-warmup-time"]("amount of time in milliseconds spent on warming up each test (default: 100)") + | Opt(setShardCount, "shard count") + ["--shard-count"]("split the tests to execute into this many groups") + | Opt(setShardIndex, "shard index") + ["--shard-index"]("index of the group of tests to execute (see --shard-count)") + | Opt(config.allowZeroTests) + ["--allow-running-no-tests"]("Treat 'No tests run' as a success") + | Opt(config.prematureExitGuardFilePath, "path") + ["--premature-exit-guard-file"]("create a file before running tests and delete it during clean exit") + | Arg(config.testsOrTags, "test name|pattern|tags")("which test or tests to use"); + + return cli; + } + +} // end namespace Catch + +#if defined(__clang__) +#pragma clang diagnostic push +#pragma clang diagnostic ignored "-Wexit-time-destructors" +#endif + +#include +#include +#include + +namespace Catch { + + ColourImpl::~ColourImpl() = default; + + ColourImpl::ColourGuard ColourImpl::guardColour(Colour::Code colourCode) { + return ColourGuard(colourCode, this); + } + + void ColourImpl::ColourGuard::engageImpl(std::ostream &stream) { + assert(&stream == &m_colourImpl->m_stream->stream() && "Engaging colour guard for different stream than used by the " + "parent colour implementation"); + static_cast(stream); + + m_engaged = true; + m_colourImpl->use(m_code); + } + + ColourImpl::ColourGuard::ColourGuard(Colour::Code code, + ColourImpl const *colour) + : m_colourImpl(colour), m_code(code) { + } + ColourImpl::ColourGuard::ColourGuard(ColourGuard &&rhs) noexcept + : m_colourImpl(rhs.m_colourImpl), m_code(rhs.m_code), m_engaged(rhs.m_engaged) { + rhs.m_engaged = false; + } + ColourImpl::ColourGuard & + ColourImpl::ColourGuard::operator=(ColourGuard &&rhs) noexcept { + using std::swap; + swap(m_colourImpl, rhs.m_colourImpl); + swap(m_code, rhs.m_code); + swap(m_engaged, rhs.m_engaged); + + return *this; + } + ColourImpl::ColourGuard::~ColourGuard() { + if (m_engaged) { + m_colourImpl->use(Colour::None); + } + } + + ColourImpl::ColourGuard & + ColourImpl::ColourGuard::engage(std::ostream &stream) & { + engageImpl(stream); + return *this; + } + + ColourImpl::ColourGuard && + ColourImpl::ColourGuard::engage(std::ostream &stream) && { + engageImpl(stream); + return CATCH_MOVE(*this); + } + + namespace { + //! A do-nothing implementation of colour, used as fallback for unknown + //! platforms, and when the user asks to deactivate all colours. + class NoColourImpl final : public ColourImpl { + public: + NoColourImpl(IStream *stream) + : ColourImpl(stream) {} + + private: + void use(Colour::Code) const override {} + }; + } // namespace + +} // namespace Catch + +#if defined(CATCH_CONFIG_COLOUR_WIN32) ///////////////////////////////////////// + +namespace Catch { + namespace { + + class Win32ColourImpl final : public ColourImpl { + public: + Win32ColourImpl(IStream *stream) + : ColourImpl(stream) { + CONSOLE_SCREEN_BUFFER_INFO csbiInfo; + GetConsoleScreenBufferInfo(GetStdHandle(STD_OUTPUT_HANDLE), + &csbiInfo); + originalForegroundAttributes = csbiInfo.wAttributes & ~(BACKGROUND_GREEN | BACKGROUND_RED | BACKGROUND_BLUE | BACKGROUND_INTENSITY); + originalBackgroundAttributes = csbiInfo.wAttributes & ~(FOREGROUND_GREEN | FOREGROUND_RED | FOREGROUND_BLUE | FOREGROUND_INTENSITY); + } + + static bool useImplementationForStream(IStream const &stream) { + // Win32 text colour APIs can only be used on console streams + // We cannot check that the output hasn't been redirected, + // so we just check that the original stream is console stream. + return stream.isConsole(); + } + + private: + void use(Colour::Code _colourCode) const override { + switch (_colourCode) { + case Colour::None: return setTextAttribute(originalForegroundAttributes); + case Colour::White: return setTextAttribute(FOREGROUND_GREEN | FOREGROUND_RED | FOREGROUND_BLUE); + case Colour::Red: return setTextAttribute(FOREGROUND_RED); + case Colour::Green: return setTextAttribute(FOREGROUND_GREEN); + case Colour::Blue: return setTextAttribute(FOREGROUND_BLUE); + case Colour::Cyan: return setTextAttribute(FOREGROUND_BLUE | FOREGROUND_GREEN); + case Colour::Yellow: return setTextAttribute(FOREGROUND_RED | FOREGROUND_GREEN); + case Colour::Grey: return setTextAttribute(0); + + case Colour::LightGrey: return setTextAttribute(FOREGROUND_INTENSITY); + case Colour::BrightRed: return setTextAttribute(FOREGROUND_INTENSITY | FOREGROUND_RED); + case Colour::BrightGreen: return setTextAttribute(FOREGROUND_INTENSITY | FOREGROUND_GREEN); + case Colour::BrightWhite: return setTextAttribute(FOREGROUND_INTENSITY | FOREGROUND_GREEN | FOREGROUND_RED | FOREGROUND_BLUE); + case Colour::BrightYellow: return setTextAttribute(FOREGROUND_INTENSITY | FOREGROUND_RED | FOREGROUND_GREEN); + + case Colour::Bright: CATCH_INTERNAL_ERROR("not a colour"); + + default: + CATCH_ERROR("Unknown colour requested"); + } + } + + void setTextAttribute(WORD _textAttribute) const { + SetConsoleTextAttribute(GetStdHandle(STD_OUTPUT_HANDLE), + _textAttribute | originalBackgroundAttributes); + } + WORD originalForegroundAttributes; + WORD originalBackgroundAttributes; + }; + + } // namespace +} // end namespace Catch + +#endif // Windows/ ANSI/ None + +#if defined(CATCH_PLATFORM_LINUX) \ + || defined(CATCH_PLATFORM_MAC) \ + || defined(__GLIBC__) \ + || (defined(__FreeBSD__) /* PlayStation platform does not have `isatty()` */ \ + && !defined(CATCH_PLATFORM_PLAYSTATION)) \ + || defined(CATCH_PLATFORM_QNX) +#define CATCH_INTERNAL_HAS_ISATTY +#include +#endif + +namespace Catch { + namespace { + + class ANSIColourImpl final : public ColourImpl { + public: + ANSIColourImpl(IStream *stream) + : ColourImpl(stream) {} + + static bool useImplementationForStream(IStream const &stream) { + // This is kinda messy due to trying to support a bunch of + // different platforms at once. + // The basic idea is that if we are asked to do autodetection (as + // opposed to being told to use posixy colours outright), then we + // only want to use the colours if we are writing to console. + // However, console might be redirected, so we make an attempt at + // checking for that on platforms where we know how to do that. + bool useColour = stream.isConsole(); +#if defined(CATCH_INTERNAL_HAS_ISATTY) && !(defined(__DJGPP__) && defined(__STRICT_ANSI__)) + ErrnoGuard _; // for isatty + useColour = useColour && isatty(STDOUT_FILENO); +#endif +#if defined(CATCH_PLATFORM_MAC) || defined(CATCH_PLATFORM_IPHONE) + useColour = useColour && !isDebuggerActive(); +#endif + + return useColour; + } + + private: + void use(Colour::Code _colourCode) const override { + auto setColour = [&out = m_stream->stream()](char const *escapeCode) { + // The escape sequence must be flushed to console, otherwise + // if stdin and stderr are intermixed, we'd get accidentally + // coloured output. + out << '\033' << escapeCode << std::flush; + }; + switch (_colourCode) { + case Colour::None: + case Colour::White: return setColour("[0m"); + case Colour::Red: return setColour("[0;31m"); + case Colour::Green: return setColour("[0;32m"); + case Colour::Blue: return setColour("[0;34m"); + case Colour::Cyan: return setColour("[0;36m"); + case Colour::Yellow: return setColour("[0;33m"); + case Colour::Grey: return setColour("[1;30m"); + + case Colour::LightGrey: return setColour("[0;37m"); + case Colour::BrightRed: return setColour("[1;31m"); + case Colour::BrightGreen: return setColour("[1;32m"); + case Colour::BrightWhite: return setColour("[1;37m"); + case Colour::BrightYellow: return setColour("[1;33m"); + + case Colour::Bright: CATCH_INTERNAL_ERROR("not a colour"); + default: CATCH_INTERNAL_ERROR("Unknown colour requested"); + } + } + }; + + } // namespace +} // end namespace Catch + +namespace Catch { + + Detail::unique_ptr makeColourImpl(ColourMode colourSelection, + IStream *stream) { +#if defined(CATCH_CONFIG_COLOUR_WIN32) + if (colourSelection == ColourMode::Win32) { + return Detail::make_unique(stream); + } +#endif + if (colourSelection == ColourMode::ANSI) { + return Detail::make_unique(stream); + } + if (colourSelection == ColourMode::None) { + return Detail::make_unique(stream); + } + + if (colourSelection == ColourMode::PlatformDefault) { +#if defined(CATCH_CONFIG_COLOUR_WIN32) + if (Win32ColourImpl::useImplementationForStream(*stream)) { + return Detail::make_unique(stream); + } +#endif + if (ANSIColourImpl::useImplementationForStream(*stream)) { + return Detail::make_unique(stream); + } + return Detail::make_unique(stream); + } + + CATCH_ERROR("Could not create colour impl for selection " << static_cast(colourSelection)); + } + + bool isColourImplAvailable(ColourMode colourSelection) { + switch (colourSelection) { +#if defined(CATCH_CONFIG_COLOUR_WIN32) + case ColourMode::Win32: +#endif + case ColourMode::ANSI: + case ColourMode::None: + case ColourMode::PlatformDefault: + return true; + default: + return false; + } + } + +} // end namespace Catch + +#if defined(__clang__) +#pragma clang diagnostic pop +#endif + +namespace Catch { + + Context Context::currentContext; + + Context &getCurrentMutableContext() { + return Context::currentContext; + } + + SimplePcg32 &sharedRng() { + static SimplePcg32 s_rng; + return s_rng; + } + +} // namespace Catch + +#include + +#if defined(CATCH_CONFIG_ANDROID_LOGWRITE) +#include + +namespace Catch { + void writeToDebugConsole(std::string const &text) { + __android_log_write(ANDROID_LOG_DEBUG, "Catch", text.c_str()); + } +} // namespace Catch + +#elif defined(CATCH_PLATFORM_WINDOWS) + +namespace Catch { + void writeToDebugConsole(std::string const &text) { + ::OutputDebugStringA(text.c_str()); + } +} // namespace Catch + +#else + +namespace Catch { + void writeToDebugConsole(std::string const &text) { + // !TBD: Need a version for Mac/ XCode and other IDEs + Catch::cout() << text; + } +} + +#endif // Platform + +#if defined(CATCH_PLATFORM_MAC) || defined(CATCH_PLATFORM_IPHONE) + +#include +#include +#include +#include +#include + +#ifdef __apple_build_version__ +// These headers will only compile with AppleClang (XCode) +// For other compilers (Clang, GCC, ... ) we need to exclude them +#include +#endif + +namespace Catch { +#ifdef __apple_build_version__ + // 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]; + struct kinfo_proc info; + std::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, sizeof(mib) / sizeof(*mib), &info, &size, nullptr, 0) != 0) { + Catch::cerr() << "\n** Call to sysctl failed - unable to determine if debugger is active **\n\n" + << std::flush; + return false; + } + + // We're being debugged if the P_TRACED flag is set. + + return ((info.kp_proc.p_flag & P_TRACED) != 0); + } +#else + bool isDebuggerActive() { + // We need to find another way to determine this for non-appleclang compilers on macOS + return false; + } +#endif +} // namespace Catch + +#elif defined(CATCH_PLATFORM_LINUX) || defined(CATCH_PLATFORM_QNX) +#include +#include + +namespace Catch { + // The standard POSIX way of detecting a debugger is to attempt to + // ptrace() the process, but this needs to be done from a child and not + // this process itself to still allow attaching to this process later + // if wanted, so is rather heavy. Under Linux we have the PID of the + // "debugger" (which doesn't need to be gdb, of course, it could also + // be strace, for example) in /proc/$PID/status, so just get it from + // there instead. + bool isDebuggerActive() { + // Libstdc++ has a bug, where std::ifstream sets errno to 0 + // This way our users can properly assert over errno values + 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) { + // We're traced if the PID is not 0 and no other PID starts + // with 0 digit, so it's enough to check for just a single + // character. + return line.length() > PREFIX_LEN && line[PREFIX_LEN] != '0'; + } + } + + return false; + } +} // namespace Catch +#elif defined(_MSC_VER) +extern "C" __declspec(dllimport) int __stdcall IsDebuggerPresent(); +namespace Catch { + bool isDebuggerActive() { + return IsDebuggerPresent() != 0; + } +} +#elif defined(__MINGW32__) +extern "C" __declspec(dllimport) int __stdcall IsDebuggerPresent(); +namespace Catch { + bool isDebuggerActive() { + return IsDebuggerPresent() != 0; + } +} +#else +namespace Catch { + bool isDebuggerActive() { + return false; + } +} +#endif // Platform + +namespace Catch { + + void ITransientExpression::streamReconstructedExpression( + std::ostream &os) const { + // We can't make this function pure virtual to keep ITransientExpression + // constexpr, so we write error message instead + os << "Some class derived from ITransientExpression without overriding streamReconstructedExpression"; + } + + void formatReconstructedExpression(std::ostream &os, std::string const &lhs, StringRef op, std::string const &rhs) { + if (lhs.size() + rhs.size() < 40 && lhs.find('\n') == std::string::npos && rhs.find('\n') == std::string::npos) + os << lhs << ' ' << op << ' ' << rhs; + else + os << lhs << '\n' + << op << '\n' + << rhs; + } +} // namespace Catch + +#include + +namespace Catch { +#if defined(CATCH_CONFIG_DISABLE_EXCEPTIONS) && !defined(CATCH_CONFIG_DISABLE_EXCEPTIONS_CUSTOM_HANDLER) + [[noreturn]] + void throw_exception(std::exception const &e) { + Catch::cerr() << "Catch will terminate because it needed to throw an exception.\n" + << "The message was: " << e.what() << '\n'; + std::terminate(); + } +#endif + + [[noreturn]] + void throw_logic_error(std::string const &msg) { + throw_exception(std::logic_error(msg)); + } + + [[noreturn]] + void throw_domain_error(std::string const &msg) { + throw_exception(std::domain_error(msg)); + } + + [[noreturn]] + void throw_runtime_error(std::string const &msg) { + throw_exception(std::runtime_error(msg)); + } + +} // namespace Catch + +#include + +namespace Catch { + + IMutableEnumValuesRegistry::~IMutableEnumValuesRegistry() = default; + + namespace Detail { + + namespace { + // Extracts the actual name part of an enum instance + // In other words, it returns the Blue part of Bikeshed::Colour::Blue + StringRef extractInstanceName(StringRef enumInstance) { + // Find last occurrence of ":" + size_t name_start = enumInstance.size(); + while (name_start > 0 && enumInstance[name_start - 1] != ':') { + --name_start; + } + return enumInstance.substr(name_start, enumInstance.size() - name_start); + } + } // namespace + + std::vector parseEnums(StringRef enums) { + auto enumValues = splitStringRef(enums, ','); + std::vector parsed; + parsed.reserve(enumValues.size()); + for (auto const &enumValue : enumValues) { + parsed.push_back(trim(extractInstanceName(enumValue))); + } + return parsed; + } + + EnumInfo::~EnumInfo() = default; + + StringRef EnumInfo::lookup(int value) const { + for (auto const &valueToName : m_values) { + if (valueToName.first == value) + return valueToName.second; + } + return "{** unexpected enum value **}"_sr; + } + + Catch::Detail::unique_ptr makeEnumInfo(StringRef enumName, StringRef allValueNames, std::vector const &values) { + auto enumInfo = Catch::Detail::make_unique(); + enumInfo->m_name = enumName; + enumInfo->m_values.reserve(values.size()); + + const auto valueNames = Catch::Detail::parseEnums(allValueNames); + assert(valueNames.size() == values.size()); + std::size_t i = 0; + for (auto value : values) + enumInfo->m_values.emplace_back(value, valueNames[i++]); + + return enumInfo; + } + + EnumInfo const &EnumValuesRegistry::registerEnum(StringRef enumName, StringRef allValueNames, std::vector const &values) { + m_enumInfos.push_back(makeEnumInfo(enumName, allValueNames, values)); + return *m_enumInfos.back(); + } + + } // namespace Detail +} // namespace Catch + +#include + +namespace Catch { + ErrnoGuard::ErrnoGuard() + : m_oldErrno(errno) {} + ErrnoGuard::~ErrnoGuard() { + errno = m_oldErrno; + } +} // namespace Catch + +#include + +namespace Catch { + +#if !defined(CATCH_CONFIG_DISABLE_EXCEPTIONS) + namespace { + static std::string tryTranslators( + std::vector< + Detail::unique_ptr> const &translators) { + if (translators.empty()) { + std::rethrow_exception(std::current_exception()); + } else { + return translators[0]->translate(translators.begin() + 1, + translators.end()); + } + } + + } // namespace +#endif //! defined(CATCH_CONFIG_DISABLE_EXCEPTIONS) + + ExceptionTranslatorRegistry::~ExceptionTranslatorRegistry() = default; + + void ExceptionTranslatorRegistry::registerTranslator(Detail::unique_ptr &&translator) { + m_translators.push_back(CATCH_MOVE(translator)); + } + +#if !defined(CATCH_CONFIG_DISABLE_EXCEPTIONS) + std::string ExceptionTranslatorRegistry::translateActiveException() const { + // Compiling a mixed mode project with MSVC means that CLR + // exceptions will be caught in (...) as well. However, these do + // do not fill-in std::current_exception and thus lead to crash + // when attempting rethrow. + // /EHa switch also causes structured exceptions to be caught + // here, but they fill-in current_exception properly, so + // at worst the output should be a little weird, instead of + // causing a crash. + if (std::current_exception() == nullptr) { + return "Non C++ exception. Possibly a CLR exception."; + } + + // First we try user-registered translators. If none of them can + // handle the exception, it will be rethrown handled by our defaults. + try { + return tryTranslators(m_translators); + } + // To avoid having to handle TFE explicitly everywhere, we just + // rethrow it so that it goes back up the caller. + catch (TestFailureException &) { + return "{ nested assertion failed }"; + } catch (TestSkipException &) { + return "{ nested SKIP() called }"; + } catch (std::exception const &ex) { + return ex.what(); + } catch (std::string const &msg) { + return msg; + } catch (const char *msg) { + return msg; + } catch (...) { + return "Unknown exception"; + } + } + +#else // ^^ Exceptions are enabled // Exceptions are disabled vv + std::string ExceptionTranslatorRegistry::translateActiveException() const { + CATCH_INTERNAL_ERROR("Attempted to translate active exception under CATCH_CONFIG_DISABLE_EXCEPTIONS!"); + } +#endif + +} // namespace Catch + +/** \file + * This file provides platform specific implementations of FatalConditionHandler + * + * This means that there is a lot of conditional compilation, and platform + * specific code. Currently, Catch2 supports a dummy handler (if no + * handler is desired), and 2 platform specific handlers: + * * Windows' SEH + * * POSIX signals + * + * Consequently, various pieces of code below are compiled if either of + * the platform specific handlers is enabled, or if none of them are + * enabled. It is assumed that both cannot be enabled at the same time, + * and doing so should cause a compilation error. + * + * If another platform specific handler is added, the compile guards + * below will need to be updated taking these assumptions into account. + */ + +#include + +#if !defined(CATCH_CONFIG_WINDOWS_SEH) && !defined(CATCH_CONFIG_POSIX_SIGNALS) + +namespace Catch { + + // If neither SEH nor signal handling is required, the handler impls + // do not have to do anything, and can be empty. + void FatalConditionHandler::engage_platform() {} + void FatalConditionHandler::disengage_platform() noexcept {} + FatalConditionHandler::FatalConditionHandler() = default; + FatalConditionHandler::~FatalConditionHandler() = default; + +} // end namespace Catch + +#endif // !CATCH_CONFIG_WINDOWS_SEH && !CATCH_CONFIG_POSIX_SIGNALS + +#if defined(CATCH_CONFIG_WINDOWS_SEH) && defined(CATCH_CONFIG_POSIX_SIGNALS) +#error "Inconsistent configuration: Windows' SEH handling and POSIX signals cannot be enabled at the same time" +#endif // CATCH_CONFIG_WINDOWS_SEH && CATCH_CONFIG_POSIX_SIGNALS + +#if defined(CATCH_CONFIG_WINDOWS_SEH) || defined(CATCH_CONFIG_POSIX_SIGNALS) + +namespace { + //! Signals fatal error message to the run context + void reportFatal(char const *const message) { + Catch::getCurrentContext().getResultCapture()->handleFatalErrorCondition(message); + } + + //! Minimal size Catch2 needs for its own fatal error handling. + //! Picked empirically, so it might not be sufficient on all + //! platforms, and for all configurations. + constexpr std::size_t minStackSizeForErrors = 32 * 1024; +} // end unnamed namespace + +#endif // CATCH_CONFIG_WINDOWS_SEH || CATCH_CONFIG_POSIX_SIGNALS + +#if defined(CATCH_CONFIG_WINDOWS_SEH) + +namespace Catch { + + 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. + static constexpr SignalDefs signalDefs[] = { + {EXCEPTION_ILLEGAL_INSTRUCTION, "SIGILL - Illegal instruction signal"}, + {EXCEPTION_STACK_OVERFLOW, "SIGSEGV - Stack overflow"}, + {EXCEPTION_ACCESS_VIOLATION, "SIGSEGV - Segmentation violation signal"}, + {EXCEPTION_INT_DIVIDE_BY_ZERO, "Divide by zero error"}, + }; + + // Since we do not support multiple instantiations, we put these + // into global variables and rely on cleaning them up in outlined + // constructors/destructors + static LPTOP_LEVEL_EXCEPTION_FILTER previousTopLevelExceptionFilter = nullptr; + + static LONG CALLBACK topLevelExceptionFilter(PEXCEPTION_POINTERS ExceptionInfo) { + for (auto const &def : signalDefs) { + if (ExceptionInfo->ExceptionRecord->ExceptionCode == def.id) { + reportFatal(def.name); + } + } + // If a filter was previously registered, invoke it + if (previousTopLevelExceptionFilter) { + return previousTopLevelExceptionFilter(ExceptionInfo); + } + // Otherwise, pass along all exceptions. + // This stops us from eating debugger breaks etc. + return EXCEPTION_CONTINUE_SEARCH; + } + + // For MSVC, we reserve part of the stack memory for handling + // memory overflow structured exception. + FatalConditionHandler::FatalConditionHandler() { + ULONG guaranteeSize = static_cast(minStackSizeForErrors); + if (!SetThreadStackGuarantee(&guaranteeSize)) { + // We do not want to fully error out, because needing + // the stack reserve should be rare enough anyway. + Catch::cerr() + << "Failed to reserve piece of stack." + << " Stack overflows will not be reported successfully."; + } + } + + // We do not attempt to unset the stack guarantee, because + // Windows does not support lowering the stack size guarantee. + FatalConditionHandler::~FatalConditionHandler() = default; + + void FatalConditionHandler::engage_platform() { + // Register as a the top level exception filter. + previousTopLevelExceptionFilter = SetUnhandledExceptionFilter(topLevelExceptionFilter); + } + + void FatalConditionHandler::disengage_platform() noexcept { + if (SetUnhandledExceptionFilter(previousTopLevelExceptionFilter) != topLevelExceptionFilter) { + Catch::cerr() + << "Unexpected SEH unhandled exception filter on disengage." + << " The filter was restored, but might be rolled back unexpectedly."; + } + previousTopLevelExceptionFilter = nullptr; + } + +} // end namespace Catch + +#endif // CATCH_CONFIG_WINDOWS_SEH + +#if defined(CATCH_CONFIG_POSIX_SIGNALS) + +#include + +namespace Catch { + + struct SignalDefs { + int id; + const char *name; + }; + + static constexpr 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"}}; + +// Older GCCs trigger -Wmissing-field-initializers for T foo = {} +// which is zero initialization, but not explicit. We want to avoid +// that. +#if defined(__GNUC__) +#pragma GCC diagnostic push +#pragma GCC diagnostic ignored "-Wmissing-field-initializers" +#endif + + static char *altStackMem = nullptr; + static std::size_t altStackSize = 0; + static stack_t oldSigStack{}; + static struct sigaction oldSigActions[sizeof(signalDefs) / sizeof(SignalDefs)]{}; + + static void restorePreviousSignalHandlers() noexcept { + // We set signal handlers back to the previous ones. Hopefully + // nobody overwrote them in the meantime, and doesn't expect + // their signal handlers to live past ours given that they + // installed them after ours.. + for (std::size_t i = 0; i < sizeof(signalDefs) / sizeof(SignalDefs); ++i) { + sigaction(signalDefs[i].id, &oldSigActions[i], nullptr); + } + // Return the old stack + sigaltstack(&oldSigStack, nullptr); + } + + static void handleSignal(int sig) { + char const *name = ""; + for (auto const &def : signalDefs) { + if (sig == def.id) { + name = def.name; + break; + } + } + // We need to restore previous signal handlers and let them do + // their thing, so that the users can have the debugger break + // when a signal is raised, and so on. + restorePreviousSignalHandlers(); + reportFatal(name); + raise(sig); + } + + FatalConditionHandler::FatalConditionHandler() { + assert(!altStackMem && "Cannot initialize POSIX signal handler when one already exists"); + if (altStackSize == 0) { + altStackSize = std::max(static_cast(SIGSTKSZ), minStackSizeForErrors); + } + altStackMem = new char[altStackSize](); + } + + FatalConditionHandler::~FatalConditionHandler() { + delete[] altStackMem; + // We signal that another instance can be constructed by zeroing + // out the pointer. + altStackMem = nullptr; + } + + void FatalConditionHandler::engage_platform() { + 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 < sizeof(signalDefs) / sizeof(SignalDefs); ++i) { + sigaction(signalDefs[i].id, &sa, &oldSigActions[i]); + } + } + +#if defined(__GNUC__) +#pragma GCC diagnostic pop +#endif + + void FatalConditionHandler::disengage_platform() noexcept { + restorePreviousSignalHandlers(); + } + +} // end namespace Catch + +#endif // CATCH_CONFIG_POSIX_SIGNALS + +#include + +namespace Catch { + namespace Detail { + + uint32_t convertToBits(float f) { + static_assert(sizeof(float) == sizeof(uint32_t), "Important ULP matcher assumption violated"); + uint32_t i; + std::memcpy(&i, &f, sizeof(f)); + return i; + } + + uint64_t convertToBits(double d) { + static_assert(sizeof(double) == sizeof(uint64_t), "Important ULP matcher assumption violated"); + uint64_t i; + std::memcpy(&i, &d, sizeof(d)); + return i; + } + +#if defined(__GNUC__) || defined(__clang__) +#pragma GCC diagnostic push +#pragma GCC diagnostic ignored "-Wfloat-equal" +#endif + bool directCompare(float lhs, float rhs) { + return lhs == rhs; + } + bool directCompare(double lhs, double rhs) { + return lhs == rhs; + } +#if defined(__GNUC__) || defined(__clang__) +#pragma GCC diagnostic pop +#endif + + } // end namespace Detail +} // end namespace Catch + +#include + +namespace Catch { + namespace Detail { + +#if !defined(CATCH_CONFIG_GETENV) + char const *getEnv(char const *) { + return nullptr; + } +#else + + char const *getEnv(char const *varName) { +#if defined(_MSC_VER) +#pragma warning(push) +#pragma warning(disable : 4996) // use getenv_s instead of getenv +#endif + + return std::getenv(varName); + +#if defined(_MSC_VER) +#pragma warning(pop) +#endif + } +#endif + } // namespace Detail +} // namespace Catch + +#include +#include + +namespace Catch { + + Catch::IStream::~IStream() = default; + + namespace Detail { + namespace { + template + class StreamBufImpl final : public std::streambuf { + char data[bufferSize]; + WriterF m_writer; + + public: + StreamBufImpl() { + setp(data, data + sizeof(data)); + } + + ~StreamBufImpl() noexcept override { + StreamBufImpl::sync(); + } + + private: + int overflow(int c) override { + sync(); + + if (c != EOF) { + if (pbase() == epptr()) + m_writer(std::string(1, static_cast(c))); + else + sputc(static_cast(c)); + } + return 0; + } + + int sync() override { + if (pbase() != pptr()) { + m_writer(std::string(pbase(), static_cast(pptr() - pbase()))); + setp(pbase(), epptr()); + } + return 0; + } + }; + + /////////////////////////////////////////////////////////////////////////// + + struct OutputDebugWriter { + void operator()(std::string const &str) { + if (!str.empty()) { + writeToDebugConsole(str); + } + } + }; + + /////////////////////////////////////////////////////////////////////////// + + class FileStream final : public IStream { + std::ofstream m_ofs; + + public: + FileStream(std::string const &filename) { + m_ofs.open(filename.c_str()); + CATCH_ENFORCE(!m_ofs.fail(), "Unable to open file: '" << filename << '\''); + m_ofs << std::unitbuf; + } + + public: // IStream + std::ostream &stream() override { + return m_ofs; + } + }; + + /////////////////////////////////////////////////////////////////////////// + + class CoutStream final : public IStream { + std::ostream m_os; + + public: + // Store the streambuf from cout up-front because + // cout may get redirected when running tests + CoutStream() + : m_os(Catch::cout().rdbuf()) {} + + public: // IStream + std::ostream &stream() override { return m_os; } + bool isConsole() const override { return true; } + }; + + class CerrStream : public IStream { + std::ostream m_os; + + public: + // Store the streambuf from cerr up-front because + // cout may get redirected when running tests + CerrStream() + : m_os(Catch::cerr().rdbuf()) {} + + public: // IStream + std::ostream &stream() override { return m_os; } + bool isConsole() const override { return true; } + }; + + /////////////////////////////////////////////////////////////////////////// + + class DebugOutStream final : public IStream { + Detail::unique_ptr> m_streamBuf; + std::ostream m_os; + + public: + DebugOutStream() + : m_streamBuf(Detail::make_unique>()), m_os(m_streamBuf.get()) {} + + public: // IStream + std::ostream &stream() override { return m_os; } + }; + + } // unnamed namespace + } // namespace Detail + + /////////////////////////////////////////////////////////////////////////// + + auto makeStream(std::string const &filename) -> Detail::unique_ptr { + if (filename.empty() || filename == "-") { + return Detail::make_unique(); + } + if (filename[0] == '%') { + if (filename == "%debug") { + return Detail::make_unique(); + } else if (filename == "%stderr") { + return Detail::make_unique(); + } else if (filename == "%stdout") { + return Detail::make_unique(); + } else { + CATCH_ERROR("Unrecognised stream: '" << filename << '\''); + } + } + return Detail::make_unique(filename); + } + +} // namespace Catch + +namespace Catch { + + namespace { + static bool needsEscape(char c) { + return c == '"' || c == '\\' || c == '\b' || c == '\f' || c == '\n' || c == '\r' || c == '\t'; + } + + static Catch::StringRef makeEscapeStringRef(char c) { + if (c == '"') { + return "\\\""_sr; + } else if (c == '\\') { + return "\\\\"_sr; + } else if (c == '\b') { + return "\\b"_sr; + } else if (c == '\f') { + return "\\f"_sr; + } else if (c == '\n') { + return "\\n"_sr; + } else if (c == '\r') { + return "\\r"_sr; + } else if (c == '\t') { + return "\\t"_sr; + } + Catch::Detail::Unreachable(); + } + } // namespace + + void JsonUtils::indent(std::ostream &os, std::uint64_t level) { + for (std::uint64_t i = 0; i < level; ++i) { + os << " "; + } + } + void JsonUtils::appendCommaNewline(std::ostream &os, + bool &should_comma, + std::uint64_t level) { + if (should_comma) { os << ','; } + should_comma = true; + os << '\n'; + indent(os, level); + } + + JsonObjectWriter::JsonObjectWriter(std::ostream &os) + : JsonObjectWriter{os, 0} {} + + JsonObjectWriter::JsonObjectWriter(std::ostream &os, + std::uint64_t indent_level) + : m_os{os}, m_indent_level{indent_level} { + m_os << '{'; + } + JsonObjectWriter::JsonObjectWriter(JsonObjectWriter &&source) noexcept + : m_os{source.m_os}, m_indent_level{source.m_indent_level}, m_should_comma{source.m_should_comma}, m_active{source.m_active} { + source.m_active = false; + } + + JsonObjectWriter::~JsonObjectWriter() { + if (!m_active) { return; } + + m_os << '\n'; + JsonUtils::indent(m_os, m_indent_level); + m_os << '}'; + } + + JsonValueWriter JsonObjectWriter::write(StringRef key) { + JsonUtils::appendCommaNewline( + m_os, m_should_comma, m_indent_level + 1); + + m_os << '"' << key << "\": "; + return JsonValueWriter{m_os, m_indent_level + 1}; + } + + JsonArrayWriter::JsonArrayWriter(std::ostream &os) + : JsonArrayWriter{os, 0} {} + JsonArrayWriter::JsonArrayWriter(std::ostream &os, + std::uint64_t indent_level) + : m_os{os}, m_indent_level{indent_level} { + m_os << '['; + } + JsonArrayWriter::JsonArrayWriter(JsonArrayWriter &&source) noexcept + : m_os{source.m_os}, m_indent_level{source.m_indent_level}, m_should_comma{source.m_should_comma}, m_active{source.m_active} { + source.m_active = false; + } + JsonArrayWriter::~JsonArrayWriter() { + if (!m_active) { return; } + + m_os << '\n'; + JsonUtils::indent(m_os, m_indent_level); + m_os << ']'; + } + + JsonObjectWriter JsonArrayWriter::writeObject() { + JsonUtils::appendCommaNewline( + m_os, m_should_comma, m_indent_level + 1); + return JsonObjectWriter{m_os, m_indent_level + 1}; + } + + JsonArrayWriter JsonArrayWriter::writeArray() { + JsonUtils::appendCommaNewline( + m_os, m_should_comma, m_indent_level + 1); + return JsonArrayWriter{m_os, m_indent_level + 1}; + } + + JsonArrayWriter &JsonArrayWriter::write(bool value) { + return writeImpl(value); + } + + JsonValueWriter::JsonValueWriter(std::ostream &os) + : JsonValueWriter{os, 0} {} + + JsonValueWriter::JsonValueWriter(std::ostream &os, + std::uint64_t indent_level) + : m_os{os}, m_indent_level{indent_level} {} + + JsonObjectWriter JsonValueWriter::writeObject() && { + return JsonObjectWriter{m_os, m_indent_level}; + } + + JsonArrayWriter JsonValueWriter::writeArray() && { + return JsonArrayWriter{m_os, m_indent_level}; + } + + void JsonValueWriter::write(Catch::StringRef value) && { + writeImpl(value, true); + } + + void JsonValueWriter::write(bool value) && { + writeImpl(value ? "true"_sr : "false"_sr, false); + } + + void JsonValueWriter::writeImpl(Catch::StringRef value, bool quote) { + if (quote) { m_os << '"'; } + size_t current_start = 0; + for (size_t i = 0; i < value.size(); ++i) { + if (needsEscape(value[i])) { + if (current_start < i) { + m_os << value.substr(current_start, i - current_start); + } + m_os << makeEscapeStringRef(value[i]); + current_start = i + 1; + } + } + if (current_start < value.size()) { + m_os << value.substr(current_start, value.size() - current_start); + } + if (quote) { m_os << '"'; } + } + +} // namespace Catch + +namespace Catch { + + auto operator<<(std::ostream &os, LazyExpression const &lazyExpr) -> std::ostream & { + if (lazyExpr.m_isNegated) + os << '!'; + + if (lazyExpr) { + if (lazyExpr.m_isNegated && lazyExpr.m_transientExpression->isBinaryExpression()) + os << '(' << *lazyExpr.m_transientExpression << ')'; + else + os << *lazyExpr.m_transientExpression; + } else { + os << "{** error - unchecked empty expression requested **}"; + } + return os; + } + +} // namespace Catch + +#ifdef CATCH_CONFIG_WINDOWS_CRTDBG +#include + +namespace Catch { + + LeakDetector::LeakDetector() { + int flag = _CrtSetDbgFlag(_CRTDBG_REPORT_FLAG); + flag |= _CRTDBG_LEAK_CHECK_DF; + flag |= _CRTDBG_ALLOC_MEM_DF; + _CrtSetDbgFlag(flag); + _CrtSetReportMode(_CRT_WARN, _CRTDBG_MODE_FILE | _CRTDBG_MODE_DEBUG); + _CrtSetReportFile(_CRT_WARN, _CRTDBG_FILE_STDERR); + // Change this to leaking allocation's number to break there + _CrtSetBreakAlloc(-1); + } +} // namespace Catch + +#else // ^^ Windows crt debug heap enabled // Windows crt debug heap disabled vv + +Catch::LeakDetector::LeakDetector() = default; + +#endif // CATCH_CONFIG_WINDOWS_CRTDBG + +Catch::LeakDetector::~LeakDetector() { + Catch::cleanUp(); +} + +namespace Catch { + namespace { + + void listTests(IEventListener &reporter, IConfig const &config) { + auto const &testSpec = config.testSpec(); + auto matchedTestCases = filterTests(getAllTestCasesSorted(config), testSpec, config); + reporter.listTests(matchedTestCases); + } + + void listTags(IEventListener &reporter, IConfig const &config) { + auto const &testSpec = config.testSpec(); + std::vector matchedTestCases = filterTests(getAllTestCasesSorted(config), testSpec, config); + + std::map tagCounts; + for (auto const &testCase : matchedTestCases) { + for (auto const &tagName : testCase.getTestCaseInfo().tags) { + auto it = tagCounts.find(tagName.original); + if (it == tagCounts.end()) + it = tagCounts.insert(std::make_pair(tagName.original, TagInfo())).first; + it->second.add(tagName.original); + } + } + + std::vector infos; + infos.reserve(tagCounts.size()); + for (auto &tagc : tagCounts) { + infos.push_back(CATCH_MOVE(tagc.second)); + } + + reporter.listTags(infos); + } + + void listReporters(IEventListener &reporter) { + std::vector descriptions; + + auto const &factories = getRegistryHub().getReporterRegistry().getFactories(); + descriptions.reserve(factories.size()); + for (auto const &fac : factories) { + descriptions.push_back({fac.first, fac.second->getDescription()}); + } + + reporter.listReporters(descriptions); + } + + void listListeners(IEventListener &reporter) { + std::vector descriptions; + + auto const &factories = getRegistryHub().getReporterRegistry().getListeners(); + descriptions.reserve(factories.size()); + for (auto const &fac : factories) { + descriptions.push_back({fac->getName(), fac->getDescription()}); + } + + reporter.listListeners(descriptions); + } + + } // end anonymous namespace + + void TagInfo::add(StringRef spelling) { + ++count; + spellings.insert(spelling); + } + + std::string TagInfo::all() const { + // 2 per tag for brackets '[' and ']' + size_t size = spellings.size() * 2; + for (auto const &spelling : spellings) { + size += spelling.size(); + } + + std::string out; + out.reserve(size); + for (auto const &spelling : spellings) { + out += '['; + out += spelling; + out += ']'; + } + return out; + } + + bool list(IEventListener &reporter, Config const &config) { + bool listed = false; + if (config.listTests()) { + listed = true; + listTests(reporter, config); + } + if (config.listTags()) { + listed = true; + listTags(reporter, config); + } + if (config.listReporters()) { + listed = true; + listReporters(reporter); + } + if (config.listListeners()) { + listed = true; + listListeners(reporter); + } + return listed; + } + +} // end namespace Catch + +namespace Catch { + CATCH_INTERNAL_START_WARNINGS_SUPPRESSION + CATCH_INTERNAL_SUPPRESS_GLOBALS_WARNINGS + static const LeakDetector leakDetector; + CATCH_INTERNAL_STOP_WARNINGS_SUPPRESSION +} // namespace Catch + +// Allow users of amalgamated .cpp file to remove our main and provide their own. +#if !defined(CATCH_AMALGAMATED_CUSTOM_MAIN) + +#if defined(CATCH_CONFIG_WCHAR) && defined(CATCH_PLATFORM_WINDOWS) && defined(_UNICODE) && !defined(DO_NOT_USE_WMAIN) +// Standard C/C++ Win32 Unicode wmain entry point +extern "C" int __cdecl wmain(int argc, wchar_t *argv[], wchar_t *[]) { +#else +// Standard C/C++ main entry point +int main(int argc, char *argv[]) { +#endif + + // We want to force the linker not to discard the global variable + // and its constructor, as it (optionally) registers leak detector + (void)&Catch::leakDetector; + + return Catch::Session().run(argc, argv); +} + +#endif // !defined(CATCH_AMALGAMATED_CUSTOM_MAIN + +namespace Catch { + + namespace { + // Messages are owned by their individual threads, so the counter should + // be thread-local as well. Alternative consideration: atomic counter, + // so threads don't share IDs and things are easier to debug. + static CATCH_INTERNAL_THREAD_LOCAL unsigned int messageIDCounter = 0; + } // namespace + + MessageInfo::MessageInfo(StringRef _macroName, + SourceLineInfo const &_lineInfo, + ResultWas::OfType _type) + : macroName(_macroName), lineInfo(_lineInfo), type(_type), sequence(++messageIDCounter) {} + +} // end namespace Catch + +#include +#include +#include +#include + +#if defined(CATCH_CONFIG_NEW_CAPTURE) +#if defined(_MSC_VER) +#include //_dup and _dup2 +#define dup _dup +#define dup2 _dup2 +#define fileno _fileno +#else +#include // dup and dup2 +#endif +#endif + +namespace Catch { + + namespace { + //! A no-op implementation, used if no reporter wants output + //! redirection. + class NoopRedirect : public OutputRedirect { + void activateImpl() override {} + void deactivateImpl() override {} + std::string getStdout() override { return {}; } + std::string getStderr() override { return {}; } + void clearBuffers() override {} + }; + + /** + * Redirects specific stream's rdbuf with another's. + * + * Redirection can be stopped and started on-demand, assumes + * that the underlying stream's rdbuf aren't changed by other + * users. + */ + class RedirectedStreamNew { + std::ostream &m_originalStream; + std::ostream &m_redirectionStream; + std::streambuf *m_prevBuf; + + public: + RedirectedStreamNew(std::ostream &originalStream, + std::ostream &redirectionStream) + : m_originalStream(originalStream), m_redirectionStream(redirectionStream), m_prevBuf(m_originalStream.rdbuf()) {} + + void startRedirect() { + m_originalStream.rdbuf(m_redirectionStream.rdbuf()); + } + void stopRedirect() { m_originalStream.rdbuf(m_prevBuf); } + }; + + /** + * Redirects the `std::cout`, `std::cerr`, `std::clog` streams, + * but does not touch the actual `stdout`/`stderr` file descriptors. + */ + class StreamRedirect : public OutputRedirect { + ReusableStringStream m_redirectedOut, m_redirectedErr; + RedirectedStreamNew m_cout, m_cerr, m_clog; + + public: + StreamRedirect() + : m_cout(Catch::cout(), m_redirectedOut.get()), m_cerr(Catch::cerr(), m_redirectedErr.get()), m_clog(Catch::clog(), m_redirectedErr.get()) {} + + void activateImpl() override { + m_cout.startRedirect(); + m_cerr.startRedirect(); + m_clog.startRedirect(); + } + void deactivateImpl() override { + m_cout.stopRedirect(); + m_cerr.stopRedirect(); + m_clog.stopRedirect(); + } + std::string getStdout() override { return m_redirectedOut.str(); } + std::string getStderr() override { return m_redirectedErr.str(); } + void clearBuffers() override { + m_redirectedOut.str(""); + m_redirectedErr.str(""); + } + }; + +#if defined(CATCH_CONFIG_NEW_CAPTURE) + + // Windows's implementation of std::tmpfile is terrible (it tries + // to create a file inside system folder, thus requiring elevated + // privileges for the binary), so we have to use tmpnam(_s) and + // create the file ourselves there. + class TempFile { + public: + TempFile(TempFile const &) = delete; + TempFile &operator=(TempFile const &) = delete; + TempFile(TempFile &&) = delete; + TempFile &operator=(TempFile &&) = delete; + +#if defined(_MSC_VER) + TempFile() { + if (tmpnam_s(m_buffer)) { + CATCH_RUNTIME_ERROR("Could not get a temp filename"); + } + if (fopen_s(&m_file, m_buffer, "wb+")) { + char buffer[100]; + if (strerror_s(buffer, errno)) { + CATCH_RUNTIME_ERROR( + "Could not translate errno to a string"); + } + CATCH_RUNTIME_ERROR("Could not open the temp file: '" + << m_buffer + << "' because: " << buffer); + } + } +#else + TempFile() { + m_file = std::tmpfile(); + if (!m_file) { + CATCH_RUNTIME_ERROR("Could not create a temp file."); + } + } +#endif + + ~TempFile() { + // TBD: What to do about errors here? + std::fclose(m_file); + // We manually create the file on Windows only, on Linux + // it will be autodeleted +#if defined(_MSC_VER) + std::remove(m_buffer); +#endif + } + + std::FILE *getFile() { return m_file; } + std::string getContents() { + ReusableStringStream sstr; + constexpr long buffer_size = 100; + char buffer[buffer_size + 1] = {}; + long current_pos = ftell(m_file); + CATCH_ENFORCE(current_pos >= 0, + "ftell failed, errno: " << errno); + std::rewind(m_file); + while (current_pos > 0) { + auto read_characters = std::fread(buffer, + 1, + std::min(buffer_size, current_pos), + m_file); + buffer[read_characters] = '\0'; + sstr << buffer; + current_pos -= static_cast(read_characters); + } + return sstr.str(); + } + + void clear() { std::rewind(m_file); } + + private: + std::FILE *m_file = nullptr; + char m_buffer[L_tmpnam] = {0}; + }; + + /** + * Redirects the actual `stdout`/`stderr` file descriptors. + * + * Works by replacing the file descriptors numbered 1 and 2 + * with an open temporary file. + */ + class FileRedirect : public OutputRedirect { + TempFile m_outFile, m_errFile; + int m_originalOut = -1; + int m_originalErr = -1; + + // Flushes cout/cerr/clog streams and stdout/stderr FDs + void flushEverything() { + Catch::cout() << std::flush; + fflush(stdout); + // Since we support overriding these streams, we flush cerr + // even though std::cerr is unbuffered + Catch::cerr() << std::flush; + Catch::clog() << std::flush; + fflush(stderr); + } + + public: + FileRedirect() + : m_originalOut(dup(fileno(stdout))), m_originalErr(dup(fileno(stderr))) { + CATCH_ENFORCE(m_originalOut >= 0, "Could not dup stdout"); + CATCH_ENFORCE(m_originalErr >= 0, "Could not dup stderr"); + } + + std::string getStdout() override { return m_outFile.getContents(); } + std::string getStderr() override { return m_errFile.getContents(); } + void clearBuffers() override { + m_outFile.clear(); + m_errFile.clear(); + } + + void activateImpl() override { + // We flush before starting redirect, to ensure that we do + // not capture the end of message sent before activation. + flushEverything(); + + int ret; + ret = dup2(fileno(m_outFile.getFile()), fileno(stdout)); + CATCH_ENFORCE(ret >= 0, + "dup2 to stdout has failed, errno: " << errno); + ret = dup2(fileno(m_errFile.getFile()), fileno(stderr)); + CATCH_ENFORCE(ret >= 0, + "dup2 to stderr has failed, errno: " << errno); + } + void deactivateImpl() override { + // We flush before ending redirect, to ensure that we + // capture all messages sent while the redirect was active. + flushEverything(); + + int ret; + ret = dup2(m_originalOut, fileno(stdout)); + CATCH_ENFORCE( + ret >= 0, + "dup2 of original stdout has failed, errno: " << errno); + ret = dup2(m_originalErr, fileno(stderr)); + CATCH_ENFORCE( + ret >= 0, + "dup2 of original stderr has failed, errno: " << errno); + } + }; + +#endif // CATCH_CONFIG_NEW_CAPTURE + + } // end namespace + + bool isRedirectAvailable(OutputRedirect::Kind kind) { + switch (kind) { + // These two are always available + case OutputRedirect::None: + case OutputRedirect::Streams: + return true; +#if defined(CATCH_CONFIG_NEW_CAPTURE) + case OutputRedirect::FileDescriptors: + return true; +#endif + default: + return false; + } + } + + Detail::unique_ptr makeOutputRedirect(bool actual) { + if (actual) { + // TODO: Clean this up later +#if defined(CATCH_CONFIG_NEW_CAPTURE) + return Detail::make_unique(); +#else + return Detail::make_unique(); +#endif + } else { + return Detail::make_unique(); + } + } + + RedirectGuard scopedActivate(OutputRedirect &redirectImpl) { + return RedirectGuard(true, redirectImpl); + } + + RedirectGuard scopedDeactivate(OutputRedirect &redirectImpl) { + return RedirectGuard(false, redirectImpl); + } + + OutputRedirect::~OutputRedirect() = default; + + RedirectGuard::RedirectGuard(bool activate, OutputRedirect &redirectImpl) + : m_redirect(&redirectImpl), m_activate(activate), m_previouslyActive(redirectImpl.isActive()) { + // Skip cases where there is no actual state change. + if (m_activate == m_previouslyActive) { return; } + + if (m_activate) { + m_redirect->activate(); + } else { + m_redirect->deactivate(); + } + } + + RedirectGuard::~RedirectGuard() noexcept(false) { + if (m_moved) { return; } + // Skip cases where there is no actual state change. + if (m_activate == m_previouslyActive) { return; } + + if (m_activate) { + m_redirect->deactivate(); + } else { + m_redirect->activate(); + } + } + + RedirectGuard::RedirectGuard(RedirectGuard &&rhs) noexcept + : m_redirect(rhs.m_redirect), m_activate(rhs.m_activate), m_previouslyActive(rhs.m_previouslyActive), m_moved(false) { + rhs.m_moved = true; + } + + RedirectGuard &RedirectGuard::operator=(RedirectGuard &&rhs) noexcept { + m_redirect = rhs.m_redirect; + m_activate = rhs.m_activate; + m_previouslyActive = rhs.m_previouslyActive; + m_moved = false; + rhs.m_moved = true; + return *this; + } + +} // namespace Catch + +#if defined(CATCH_CONFIG_NEW_CAPTURE) +#if defined(_MSC_VER) +#undef dup +#undef dup2 +#undef fileno +#endif +#endif + +#include +#include + +namespace Catch { + + Optional parseUInt(std::string const &input, int base) { + auto trimmed = trim(input); + // std::stoull is annoying and accepts numbers starting with '-', + // it just negates them into unsigned int + if (trimmed.empty() || trimmed[0] == '-') { + return {}; + } + + CATCH_TRY { + size_t pos = 0; + const auto ret = std::stoull(trimmed, &pos, base); + + // We did not consume the whole input, so there is an issue + // This can be bunch of different stuff, like multiple numbers + // in the input, or invalid digits/characters and so on. Either + // way, we do not want to return the partially parsed result. + if (pos != trimmed.size()) { + return {}; + } + // Too large + if (ret > std::numeric_limits::max()) { + return {}; + } + return static_cast(ret); + } + CATCH_CATCH_ANON(std::invalid_argument const &) { + // no conversion could be performed + } + CATCH_CATCH_ANON(std::out_of_range const &) { + // the input does not fit into an unsigned long long + } + return {}; + } + +} // namespace Catch + +#include + +namespace Catch { + +#if !defined(CATCH_CONFIG_POLYFILL_ISNAN) + bool isnan(float f) { + return std::isnan(f); + } + bool isnan(double d) { + return std::isnan(d); + } +#else + // For now we only use this for embarcadero + bool isnan(float f) { + return std::_isnan(f); + } + bool isnan(double d) { + return std::_isnan(d); + } +#endif + +#if !defined(CATCH_CONFIG_GLOBAL_NEXTAFTER) + float nextafter(float x, float y) { + return std::nextafter(x, y); + } + double nextafter(double x, double y) { + return std::nextafter(x, y); + } +#else + float nextafter(float x, float y) { + return ::nextafterf(x, y); + } + double nextafter(double x, double y) { + return ::nextafter(x, y); + } +#endif + +} // end namespace Catch + +#if defined(__clang__) +#define CATCH2_CLANG_NO_SANITIZE_INTEGER \ + __attribute__((no_sanitize("unsigned-integer-overflow"))) +#else +#define CATCH2_CLANG_NO_SANITIZE_INTEGER +#endif +namespace Catch { + + namespace { + +#if defined(_MSC_VER) +#pragma warning(push) +#pragma warning(disable : 4146) // we negate uint32 during the rotate +#endif + // Safe rotr implementation thanks to John Regehr + CATCH2_CLANG_NO_SANITIZE_INTEGER + uint32_t rotate_right(uint32_t val, uint32_t count) { + const uint32_t mask = 31; + count &= mask; + return (val >> count) | (val << (-count & mask)); + } + +#if defined(_MSC_VER) +#pragma warning(pop) +#endif + + } // namespace + + SimplePcg32::SimplePcg32(result_type seed_) { + seed(seed_); + } + + void SimplePcg32::seed(result_type seed_) { + m_state = 0; + (*this)(); + m_state += seed_; + (*this)(); + } + + void SimplePcg32::discard(uint64_t skip) { + // We could implement this to run in O(log n) steps, but this + // should suffice for our use case. + for (uint64_t s = 0; s < skip; ++s) { + static_cast((*this)()); + } + } + + CATCH2_CLANG_NO_SANITIZE_INTEGER + SimplePcg32::result_type SimplePcg32::operator()() { + // prepare the output value + const uint32_t xorshifted = static_cast(((m_state >> 18u) ^ m_state) >> 27u); + const auto output = rotate_right(xorshifted, static_cast(m_state >> 59u)); + + // advance state + m_state = m_state * 6364136223846793005ULL + s_inc; + + return output; + } + + bool operator==(SimplePcg32 const &lhs, SimplePcg32 const &rhs) { + return lhs.m_state == rhs.m_state; + } + + bool operator!=(SimplePcg32 const &lhs, SimplePcg32 const &rhs) { + return lhs.m_state != rhs.m_state; + } +} // namespace Catch + +#include +#include + +namespace Catch { + + std::uint32_t generateRandomSeed(GenerateFrom from) { + switch (from) { + case GenerateFrom::Time: + return static_cast(std::time(nullptr)); + + case GenerateFrom::Default: + case GenerateFrom::RandomDevice: { + std::random_device rd; + return Detail::fillBitsFrom(rd); + } + + default: + CATCH_ERROR("Unknown generation method"); + } + } + +} // end namespace Catch + +namespace Catch { + struct ReporterRegistry::ReporterRegistryImpl { + std::vector> listeners; + std::map + factories; + }; + + ReporterRegistry::ReporterRegistry() + : m_impl(Detail::make_unique()) { + // Because it is impossible to move out of initializer list, + // we have to add the elements manually + m_impl->factories["Automake"] = Detail::make_unique>(); + m_impl->factories["compact"] = Detail::make_unique>(); + m_impl->factories["console"] = Detail::make_unique>(); + m_impl->factories["JUnit"] = Detail::make_unique>(); + m_impl->factories["SonarQube"] = Detail::make_unique>(); + m_impl->factories["TAP"] = Detail::make_unique>(); + m_impl->factories["TeamCity"] = Detail::make_unique>(); + m_impl->factories["XML"] = Detail::make_unique>(); + m_impl->factories["JSON"] = Detail::make_unique>(); + } + + ReporterRegistry::~ReporterRegistry() = default; + + IEventListenerPtr + ReporterRegistry::create(std::string const &name, + ReporterConfig &&config) const { + auto it = m_impl->factories.find(name); + if (it == m_impl->factories.end()) return nullptr; + return it->second->create(CATCH_MOVE(config)); + } + + void ReporterRegistry::registerReporter(std::string const &name, + IReporterFactoryPtr factory) { + CATCH_ENFORCE(name.find("::") == name.npos, + "'::' is not allowed in reporter name: '" + name + '\''); + auto ret = m_impl->factories.emplace(name, CATCH_MOVE(factory)); + CATCH_ENFORCE(ret.second, + "reporter using '" + name + "' as name was already registered"); + } + void ReporterRegistry::registerListener( + Detail::unique_ptr factory) { + m_impl->listeners.push_back(CATCH_MOVE(factory)); + } + + std::map const & + ReporterRegistry::getFactories() const { + return m_impl->factories; + } + + std::vector> const & + ReporterRegistry::getListeners() const { + return m_impl->listeners; + } +} // namespace Catch + +#include + +namespace Catch { + + namespace { + struct kvPair { + StringRef key, value; + }; + + kvPair splitKVPair(StringRef kvString) { + auto splitPos = static_cast( + std::find(kvString.begin(), kvString.end(), '=') - kvString.begin()); + + return {kvString.substr(0, splitPos), + kvString.substr(splitPos + 1, kvString.size())}; + } + } // namespace + + namespace Detail { + std::vector splitReporterSpec(StringRef reporterSpec) { + static constexpr auto separator = "::"; + static constexpr size_t separatorSize = 2; + + size_t separatorPos = 0; + auto findNextSeparator = [&reporterSpec](size_t startPos) { + static_assert( + separatorSize == 2, + "The code below currently assumes 2 char separator"); + + auto currentPos = startPos; + do { + while (currentPos < reporterSpec.size() && reporterSpec[currentPos] != separator[0]) { + ++currentPos; + } + if (currentPos + 1 < reporterSpec.size() && reporterSpec[currentPos + 1] == separator[1]) { + return currentPos; + } + ++currentPos; + } while (currentPos < reporterSpec.size()); + + return static_cast(-1); + }; + + std::vector parts; + + while (separatorPos < reporterSpec.size()) { + const auto nextSeparator = findNextSeparator(separatorPos); + parts.push_back(static_cast(reporterSpec.substr( + separatorPos, nextSeparator - separatorPos))); + + if (nextSeparator == static_cast(-1)) { + break; + } + separatorPos = nextSeparator + separatorSize; + } + + // Handle a separator at the end. + // This is not a valid spec, but we want to do validation in a + // centralized place + if (separatorPos == reporterSpec.size()) { + parts.emplace_back(); + } + + return parts; + } + + Optional stringToColourMode(StringRef colourMode) { + if (colourMode == "default") { + return ColourMode::PlatformDefault; + } else if (colourMode == "ansi") { + return ColourMode::ANSI; + } else if (colourMode == "win32") { + return ColourMode::Win32; + } else if (colourMode == "none") { + return ColourMode::None; + } else { + return {}; + } + } + } // namespace Detail + + bool operator==(ReporterSpec const &lhs, ReporterSpec const &rhs) { + return lhs.m_name == rhs.m_name && lhs.m_outputFileName == rhs.m_outputFileName && lhs.m_colourMode == rhs.m_colourMode && lhs.m_customOptions == rhs.m_customOptions; + } + + Optional parseReporterSpec(StringRef reporterSpec) { + auto parts = Detail::splitReporterSpec(reporterSpec); + + assert(parts.size() > 0 && "Split should never return empty vector"); + + std::map kvPairs; + Optional outputFileName; + Optional colourMode; + + // First part is always reporter name, so we skip it + for (size_t i = 1; i < parts.size(); ++i) { + auto kv = splitKVPair(parts[i]); + auto key = kv.key, value = kv.value; + + if (key.empty() || value.empty()) { // NOLINT(bugprone-branch-clone) + return {}; + } else if (key[0] == 'X') { + // This is a reporter-specific option, we don't check these + // apart from basic sanity checks + if (key.size() == 1) { + return {}; + } + + auto ret = kvPairs.emplace(std::string(kv.key), std::string(kv.value)); + if (!ret.second) { + // Duplicated key. We might want to handle this differently, + // e.g. by overwriting the existing value? + return {}; + } + } else if (key == "out") { + // Duplicated key + if (outputFileName) { + return {}; + } + outputFileName = static_cast(value); + } else if (key == "colour-mode") { + // Duplicated key + if (colourMode) { + return {}; + } + colourMode = Detail::stringToColourMode(value); + // Parsing failed + if (!colourMode) { + return {}; + } + } else { + // Unrecognized option + return {}; + } + } + + return ReporterSpec{CATCH_MOVE(parts[0]), + CATCH_MOVE(outputFileName), + CATCH_MOVE(colourMode), + CATCH_MOVE(kvPairs)}; + } + + ReporterSpec::ReporterSpec( + std::string name, + Optional outputFileName, + Optional colourMode, + std::map customOptions) + : m_name(CATCH_MOVE(name)), m_outputFileName(CATCH_MOVE(outputFileName)), m_colourMode(CATCH_MOVE(colourMode)), m_customOptions(CATCH_MOVE(customOptions)) {} + +} // namespace Catch + +#include +#include +#include +#include + +namespace Catch { + + // This class encapsulates the idea of a pool of ostringstreams that can be reused. + struct StringStreams { + std::vector> m_streams; + std::vector m_unused; + std::ostringstream m_referenceStream; // Used for copy state/ flags from + Detail::Mutex m_mutex; + + auto add() -> std::pair { + Detail::LockGuard _(m_mutex); + if (m_unused.empty()) { + m_streams.push_back(Detail::make_unique()); + return {m_streams.size() - 1, m_streams.back().get()}; + } else { + auto index = m_unused.back(); + m_unused.pop_back(); + return {index, m_streams[index].get()}; + } + } + + void release(std::size_t index, std::ostream *originalPtr) { + assert(originalPtr); + originalPtr->copyfmt(m_referenceStream); // Restore initial flags and other state + + Detail::LockGuard _(m_mutex); + assert(originalPtr == m_streams[index].get() && "Mismatch between release index and stream ptr"); + m_unused.push_back(index); + } + }; + + ReusableStringStream::ReusableStringStream() { + std::tie(m_index, m_oss) = Singleton::getMutable().add(); + } + + ReusableStringStream::~ReusableStringStream() { + static_cast(m_oss)->str(""); + m_oss->clear(); + Singleton::getMutable().release(m_index, m_oss); + } + + std::string ReusableStringStream::str() const { + return static_cast(m_oss)->str(); + } + + void ReusableStringStream::str(std::string const &str) { + static_cast(m_oss)->str(str); + } + +} // namespace Catch + +#include +#include + +namespace Catch { + + namespace Generators { + namespace { + struct GeneratorTracker final : TestCaseTracking::TrackerBase, + IGeneratorTracker { + GeneratorBasePtr m_generator; + // Filtered generator has moved to specific index due to + // a filter, it needs special handling of `countedNext()` + bool m_isFiltered = false; + + GeneratorTracker( + TestCaseTracking::NameAndLocation &&nameAndLocation, + TrackerContext &ctx, + ITracker *parent, + GeneratorBasePtr &&generator) + : TrackerBase(CATCH_MOVE(nameAndLocation), ctx, parent), m_generator(CATCH_MOVE(generator)) { + assert(m_generator && "Cannot create tracker without generator"); + + // Handle potential filter and move forward here... + // Old style filters do not affect generators at all + if (m_newStyleFilters && m_allTrackerDepth < m_filterRef->size()) { + auto const &filter = (*m_filterRef)[m_allTrackerDepth]; + // Generator cannot be un-entered the way a section + // can be, so the tracker has to throw for a wrong + // filter to stop the execution flow. + if (filter.type == PathFilter::For::Section) { + // We want the semantics of `SKIP()`, but we inline it + // to avoid issues with conditionally prefixed macros + INTERNAL_CATCH_MSG( + "SKIP", + Catch::ResultWas::ExplicitSkip, + Catch::ResultDisposition::Normal, + ""); + Catch::Detail::Unreachable(); + } + // '*' is the wildcard for "all elements in generator" + // used for filtering sections below the generator, but + // not the generator itself. + if (filter.filter != "*") { + m_isFiltered = true; + // TBD: We assume that the filter was validated as + // number during parsing. We should pass it + // as number from the CLI parser. + size_t targetIndex = std::stoul(filter.filter); + m_generator->skipToNthElement(targetIndex); + } + } + } + + static GeneratorTracker * + acquire(TrackerContext &ctx, + TestCaseTracking::NameAndLocationRef const & + nameAndLocation) { + GeneratorTracker *tracker; + + ITracker ¤tTracker = ctx.currentTracker(); + // Under specific circumstances, the generator we want + // to acquire is also the current tracker. If this is + // the case, we have to avoid looking through current + // tracker's children, and instead return the current + // tracker. + // A case where this check is important is e.g. + // for (int i = 0; i < 5; ++i) { + // int n = GENERATE(1, 2); + // } + // + // without it, the code above creates 5 nested generators. + if (currentTracker.nameAndLocation() == nameAndLocation) { + auto thisTracker = currentTracker.parent()->findChild( + nameAndLocation); + assert(thisTracker); + assert(thisTracker->isGeneratorTracker()); + tracker = static_cast(thisTracker); + } else if (ITracker *childTracker = currentTracker.findChild( + nameAndLocation)) { + assert(childTracker); + assert(childTracker->isGeneratorTracker()); + tracker = static_cast(childTracker); + } else { + return nullptr; + } + + if (!tracker->isComplete()) { tracker->open(); } + + return tracker; + } + + // TrackerBase interface + bool isGeneratorTracker() const override { return true; } + void close() override { + TrackerBase::close(); + // If a generator has a child (it is followed by a section) + // and none of its children have started, then we must wait + // until later to start consuming its values. + // This catches cases where `GENERATE` is placed between two + // `SECTION`s. + // **The check for m_children.empty cannot be removed**. + // doing so would break `GENERATE` _not_ followed by + // `SECTION`s. + const bool should_wait_for_child = [&]() { + // No children -> nobody to wait for + if (m_children.empty()) { return false; } + // If at least one child started executing, don't wait + if (std::find_if( + m_children.begin(), + m_children.end(), + [](TestCaseTracking::ITrackerPtr const & + tracker) { + return tracker->hasStarted(); + }) + != m_children.end()) { + return false; + } + + // No children have started. We need to check if they + // _can_ start, and thus we should wait for them, or + // they cannot start (due to filters), and we shouldn't + // wait for them + + // No filters left -> no restrictions on running sections + size_t childDepth = 1 + (m_newStyleFilters ? m_allTrackerDepth : m_sectionOnlyDepth); + if (childDepth >= m_filterRef->size()) { + return true; + } + + // If we are using the new style filters, we need to check + // whether the successive filter is for section or a generator. + if (m_newStyleFilters + && (*m_filterRef)[childDepth].type != PathFilter::For::Section) { + return false; + } + // Look for any child section that could match the remaining filters + for (auto const &child : m_children) { + if (child->isSectionTracker() && static_cast(*child).trimmedName() == StringRef((*m_filterRef)[childDepth].filter)) { + return true; + } + } + return false; + }(); + + // This check is a bit tricky, because m_generator->next() + // has a side-effect, where it consumes generator's current + // value, but we do not want to invoke the side-effect if + // this generator is still waiting for any child to start. + assert(m_generator && "Tracker without generator"); + if (should_wait_for_child + || (m_runState == CompletedSuccessfully + && !m_isFiltered // filtered generators cannot meaningfully move forward, as they would get past the filter + && m_generator->countedNext())) { + m_children.clear(); + m_runState = Executing; + } + } + + // IGeneratorTracker interface + auto getGenerator() const -> GeneratorBasePtr const & override { + return m_generator; + } + }; + } // namespace + } // namespace Generators + + namespace Detail { + // Assertions are owned by the thread that is executing them. + // This allows for lock-free progress in common cases where we + // do not need to send the assertion events to the reporter. + // This also implies that messages are owned by their respective + // threads, and should not be shared across different threads. + // + // This implies that various pieces of metadata referring to last + // assertion result/source location/message handling, etc + // should also be thread local. For now we just use naked globals + // below, in the future we will want to allocate piece of memory + // from heap, to avoid consuming too much thread-local storage. + // + // Note that we also don't want non-trivial the thread-local variables + // below be initialized for every thread, only for those that touch + // Catch2. To make this work with both GCC/Clang and MSVC, we have to + // make them thread-local magic statics. (Class-level statics have the + // desired semantics on GCC, but not on MSVC). + + // This is used for the "if" part of CHECKED_IF/CHECKED_ELSE + static CATCH_INTERNAL_THREAD_LOCAL bool g_lastAssertionPassed = false; + + // This is the source location for last encountered macro. It is + // used to provide the users with more precise location of error + // when an unexpected exception/fatal error happens. + static CATCH_INTERNAL_THREAD_LOCAL SourceLineInfo + g_lastKnownLineInfo("DummyLocation", static_cast(-1)); + + // Should we clear message scopes before sending off the messages to + // reporter? Set in `assertionPassedFastPath` to avoid doing the full + // clear there for performance reasons. + static CATCH_INTERNAL_THREAD_LOCAL bool g_clearMessageScopes = false; + + // Holds the data for both scoped and unscoped messages together, + // to avoid issues where their lifetimes start in wrong order, + // and then are destroyed in wrong order. + class MessageHolder { + // The actual message vector passed to the reporters + std::vector messages; + // IDs of messages from UNSCOPED_X macros, which we have to + // remove manually. + std::vector unscoped_ids; + + public: + // We do not need to special-case the unscoped messages when + // we only keep around the raw msg ids. + ~MessageHolder() = default; + + void addUnscopedMessage(MessageInfo &&info) { + repairUnscopedMessageInvariant(); + unscoped_ids.push_back(info.sequence); + messages.push_back(CATCH_MOVE(info)); + } + + void addUnscopedMessage(MessageBuilder &&builder) { + MessageInfo info(CATCH_MOVE(builder.m_info)); + info.message = builder.m_stream.str(); + addUnscopedMessage(CATCH_MOVE(info)); + } + + void addScopedMessage(MessageInfo &&info) { + messages.push_back(CATCH_MOVE(info)); + } + + std::vector const &getMessages() const { + return messages; + } + + void removeMessage(unsigned int messageId) { + // Note: On average, it would probably be better to look for + // the message backwards. However, we do not expect to have + // to deal with more messages than low single digits, so + // the improvement is tiny, and we would have to hand-write + // the loop to avoid terrible codegen of reverse iterators + // in debug mode. + auto iter = std::find_if(messages.begin(), + messages.end(), + [messageId](MessageInfo const &msg) { + return msg.sequence == messageId; + }); + assert(iter != messages.end() && "Trying to remove non-existent message."); + messages.erase(iter); + } + + void removeUnscopedMessages() { + for (const auto messageId : unscoped_ids) { + removeMessage(messageId); + } + unscoped_ids.clear(); + g_clearMessageScopes = false; + } + + void repairUnscopedMessageInvariant() { + if (g_clearMessageScopes) { removeUnscopedMessages(); } + g_clearMessageScopes = false; + } + }; + + CATCH_INTERNAL_START_WARNINGS_SUPPRESSION + CATCH_INTERNAL_SUPPRESS_GLOBALS_WARNINGS + static MessageHolder &g_messageHolder() { + static CATCH_INTERNAL_THREAD_LOCAL MessageHolder value; + return value; + } + CATCH_INTERNAL_STOP_WARNINGS_SUPPRESSION + + } // namespace Detail + + RunContext::RunContext(IConfig const *_config, IEventListenerPtr &&reporter) + : m_runInfo(_config->name()), m_config(_config), m_reporter(CATCH_MOVE(reporter)), m_outputRedirect(makeOutputRedirect(m_reporter->getPreferences().shouldRedirectStdOut)), m_abortAfterXFailedAssertions(m_config->abortAfter()), m_reportAssertionStarting(m_reporter->getPreferences().shouldReportAllAssertionStarts), m_includeSuccessfulResults(m_config->includeSuccessfulResults() || m_reporter->getPreferences().shouldReportAllAssertions), m_shouldDebugBreak(m_config->shouldDebugBreak()) { + getCurrentMutableContext().setResultCapture(this); + m_reporter->testRunStarting(m_runInfo); + + // TODO: HACK! + // We need to make sure the underlying cache is initialized + // while we are guaranteed to be running in a single thread, + // because the initialization is not thread-safe. + ReusableStringStream rss; + (void)rss; + } + + RunContext::~RunContext() { + updateTotalsFromAtomics(); + m_reporter->testRunEnded(TestRunStats(m_runInfo, m_totals, aborting())); + } + + Totals RunContext::runTest(TestCaseHandle const &testCase) { + updateTotalsFromAtomics(); + const Totals prevTotals = m_totals; + + auto const &testInfo = testCase.getTestCaseInfo(); + m_reporter->testCaseStarting(testInfo); + testCase.prepareTestCase(); + m_activeTestCase = &testCase; + + ITracker &rootTracker = m_trackerContext.startRun(); + assert(rootTracker.isSectionTracker()); + rootTracker.setFilters(&m_config->getPathFilters(), + m_config->useNewFilterBehaviour()); + + // We intentionally only seed the internal RNG once per test case, + // before it is first invoked. The reason for that is a complex + // interplay of generator/section implementation details and the + // Random*Generator types. + // + // The issue boils down to us needing to seed the Random*Generators + // with different seed each, so that they return different sequences + // of random numbers. We do this by giving them a number from the + // shared RNG instance as their seed. + // + // However, this runs into an issue if the reseeding happens each + // time the test case is entered (as opposed to first time only), + // because multiple generators could get the same seed, e.g. in + // ```cpp + // TEST_CASE() { + // auto i = GENERATE(take(10, random(0, 100)); + // SECTION("A") { + // auto j = GENERATE(take(10, random(0, 100)); + // } + // SECTION("B") { + // auto k = GENERATE(take(10, random(0, 100)); + // } + // } + // ``` + // `i` and `j` would properly return values from different sequences, + // but `i` and `k` would return the same sequence, because their seed + // would be the same. + // (The reason their seeds would be the same is that the generator + // for k would be initialized when the test case is entered the second + // time, after the shared RNG instance was reset to the same value + // it had when the generator for i was initialized.) + seedRng(*m_config); + + uint64_t testRuns = 0; + std::string redirectedCout; + std::string redirectedCerr; + do { + m_trackerContext.startCycle(); + m_testCaseTracker = &SectionTracker::acquire(m_trackerContext, TestCaseTracking::NameAndLocationRef(testInfo.name, testInfo.lineInfo)); + + m_reporter->testCasePartialStarting(testInfo, testRuns); + + updateTotalsFromAtomics(); + const auto beforeRunTotals = m_totals; + runCurrentTest(); + std::string oneRunCout = m_outputRedirect->getStdout(); + std::string oneRunCerr = m_outputRedirect->getStderr(); + m_outputRedirect->clearBuffers(); + redirectedCout += oneRunCout; + redirectedCerr += oneRunCerr; + + updateTotalsFromAtomics(); + const auto singleRunTotals = m_totals.delta(beforeRunTotals); + auto statsForOneRun = TestCaseStats(testInfo, singleRunTotals, CATCH_MOVE(oneRunCout), CATCH_MOVE(oneRunCerr), aborting()); + m_reporter->testCasePartialEnded(statsForOneRun, testRuns); + + ++testRuns; + } while (!m_testCaseTracker->isSuccessfullyCompleted() && !aborting()); + + Totals deltaTotals = m_totals.delta(prevTotals); + if (testInfo.expectedToFail() && deltaTotals.testCases.passed > 0) { + deltaTotals.assertions.failed++; + deltaTotals.testCases.passed--; + deltaTotals.testCases.failed++; + } + m_totals.testCases += deltaTotals.testCases; + testCase.tearDownTestCase(); + m_reporter->testCaseEnded(TestCaseStats(testInfo, + deltaTotals, + CATCH_MOVE(redirectedCout), + CATCH_MOVE(redirectedCerr), + aborting())); + + m_activeTestCase = nullptr; + m_testCaseTracker = nullptr; + + return deltaTotals; + } + + void RunContext::assertionEnded(AssertionResult &&result) { + Detail::g_lastKnownLineInfo = result.m_info.lineInfo; + if (result.getResultType() == ResultWas::Ok) { + m_atomicAssertionCount.passed++; + Detail::g_lastAssertionPassed = true; + } else if (result.getResultType() == ResultWas::ExplicitSkip) { + m_atomicAssertionCount.skipped++; + Detail::g_lastAssertionPassed = true; + } else if (!result.succeeded()) { + Detail::g_lastAssertionPassed = false; + if (result.isOk()) { + } else if (m_activeTestCase->getTestCaseInfo().okToFail()) // Read from a shared state established before the threads could start, this is fine + m_atomicAssertionCount.failedButOk++; + else + m_atomicAssertionCount.failed++; + } else { + Detail::g_lastAssertionPassed = true; + } + + auto &msgHolder = Detail::g_messageHolder(); + msgHolder.repairUnscopedMessageInvariant(); + + // From here, we are touching shared state and need mutex. + Detail::LockGuard lock(m_assertionMutex); + { + auto _ = scopedDeactivate(*m_outputRedirect); + updateTotalsFromAtomics(); + m_reporter->assertionEnded(AssertionStats(result, msgHolder.getMessages(), m_totals)); + } + + if (result.getResultType() != ResultWas::Warning) { + msgHolder.removeUnscopedMessages(); + } + + // Reset working state. assertion info will be reset after + // populateReaction is run if it is needed + m_lastResult = CATCH_MOVE(result); + } + + void RunContext::notifyAssertionStarted(AssertionInfo const &info) { + if (m_reportAssertionStarting) { + Detail::LockGuard lock(m_assertionMutex); + auto _ = scopedDeactivate(*m_outputRedirect); + m_reporter->assertionStarting(info); + } + } + + bool RunContext::sectionStarted(StringRef sectionName, + SourceLineInfo const §ionLineInfo, + Counts &assertions) { + ITracker §ionTracker = SectionTracker::acquire(m_trackerContext, + TestCaseTracking::NameAndLocationRef( + sectionName, sectionLineInfo)); + + if (!sectionTracker.isOpen()) + return false; + m_activeSections.push_back(§ionTracker); + + SectionInfo sectionInfo(sectionLineInfo, static_cast(sectionName)); + Detail::g_lastKnownLineInfo = sectionLineInfo; + + { + auto _ = scopedDeactivate(*m_outputRedirect); + m_reporter->sectionStarting(sectionInfo); + } + + updateTotalsFromAtomics(); + assertions = m_totals.assertions; + + return true; + } + IGeneratorTracker * + RunContext::acquireGeneratorTracker(StringRef generatorName, + SourceLineInfo const &lineInfo) { + auto *tracker = Generators::GeneratorTracker::acquire( + m_trackerContext, + TestCaseTracking::NameAndLocationRef( + generatorName, lineInfo)); + Detail::g_lastKnownLineInfo = lineInfo; + return tracker; + } + + IGeneratorTracker *RunContext::createGeneratorTracker( + StringRef generatorName, + SourceLineInfo lineInfo, + Generators::GeneratorBasePtr &&generator) { + // TBD: Do we want to avoid the warning if the generator is filtered? + if (m_config->warnAboutInfiniteGenerators() && !generator->isFinite()) { + // We want the semantics of `FAIL()`, but we inline it + // to avoid issues with conditionally prefixed macros + INTERNAL_CATCH_MSG("FAIL", + Catch::ResultWas::ExplicitFailure, + Catch::ResultDisposition::Normal, + "GENERATE() would run infinitely"); + } + + auto nameAndLoc = TestCaseTracking::NameAndLocation(static_cast(generatorName), lineInfo); + auto ¤tTracker = m_trackerContext.currentTracker(); + assert( + currentTracker.nameAndLocation() != nameAndLoc && "Trying to create tracker for a generator that already has one"); + + auto newTracker = Catch::Detail::make_unique( + CATCH_MOVE(nameAndLoc), + m_trackerContext, + ¤tTracker, + CATCH_MOVE(generator)); + auto ret = newTracker.get(); + currentTracker.addChild(CATCH_MOVE(newTracker)); + + ret->open(); + return ret; + } + + bool RunContext::testForMissingAssertions(Counts &assertions) { + if (assertions.total() != 0) + return false; + if (!m_config->warnAboutMissingAssertions()) + return false; + if (m_trackerContext.currentTracker().hasChildren()) + return false; + m_atomicAssertionCount.failed++; + assertions.failed++; + return true; + } + + void RunContext::sectionEnded(SectionEndInfo &&endInfo) { + updateTotalsFromAtomics(); + Counts assertions = m_totals.assertions - endInfo.prevAssertions; + bool missingAssertions = testForMissingAssertions(assertions); + + if (!m_activeSections.empty()) { + m_activeSections.back()->close(); + m_activeSections.pop_back(); + } + + { + auto _ = scopedDeactivate(*m_outputRedirect); + m_reporter->sectionEnded( + SectionStats(CATCH_MOVE(endInfo.sectionInfo), + assertions, + endInfo.durationInSeconds, + missingAssertions)); + } + } + + void RunContext::sectionEndedEarly(SectionEndInfo &&endInfo) { + if (m_unfinishedSections.empty()) { + m_activeSections.back()->fail(); + } else { + m_activeSections.back()->close(); + } + m_activeSections.pop_back(); + + m_unfinishedSections.push_back(CATCH_MOVE(endInfo)); + } + + void RunContext::benchmarkPreparing(StringRef name) { + auto _ = scopedDeactivate(*m_outputRedirect); + m_reporter->benchmarkPreparing(name); + } + void RunContext::benchmarkStarting(BenchmarkInfo const &info) { + auto _ = scopedDeactivate(*m_outputRedirect); + m_reporter->benchmarkStarting(info); + } + void RunContext::benchmarkEnded(BenchmarkStats<> const &stats) { + auto _ = scopedDeactivate(*m_outputRedirect); + m_reporter->benchmarkEnded(stats); + } + void RunContext::benchmarkFailed(StringRef error) { + auto _ = scopedDeactivate(*m_outputRedirect); + m_reporter->benchmarkFailed(error); + } + + std::string RunContext::getCurrentTestName() const { + return m_activeTestCase + ? m_activeTestCase->getTestCaseInfo().name + : std::string(); + } + + const AssertionResult *RunContext::getLastResult() const { + // m_lastResult is updated inside the assertion slow-path, under + // a mutex, so the read needs to happen under mutex as well. + + // TBD: The last result only makes sense if it is a thread-local + // thing, because the answer is different per thread, like + // last line info, whether last assertion passed, and so on. + // + // However, the last result was also never updated in the + // assertion fast path, so it was always somewhat broken, + // and since IResultCapture::getLastResult is deprecated, + // we will leave it as is, until it is finally removed. + Detail::LockGuard _(m_assertionMutex); + return &(*m_lastResult); + } + + void RunContext::exceptionEarlyReported() { + m_shouldReportUnexpected = false; + } + + void RunContext::handleFatalErrorCondition(StringRef message) { + // We lock only when touching the reporters directly, to avoid + // deadlocks when we call into other functions that also want + // to lock the mutex before touching reporters. + // + // This does mean that we allow other threads to run while handling + // a fatal error, but this is all a best effort attempt anyway. + { + Detail::LockGuard lock(m_assertionMutex); + // TODO: scoped deactivate here? Just give up and do best effort? + // the deactivation can break things further, OTOH so can the + // capture + auto _ = scopedDeactivate(*m_outputRedirect); + + // First notify reporter that bad things happened + m_reporter->fatalErrorEncountered(message); + } + + // Don't rebuild the result -- the stringification itself can cause more fatal errors + // Instead, fake a result data. + AssertionResultData tempResult(ResultWas::FatalErrorCondition, {false}); + tempResult.message = static_cast(message); + AssertionResult result(makeDummyAssertionInfo(), + CATCH_MOVE(tempResult)); + + assertionEnded(CATCH_MOVE(result)); + + // At this point we touch sections/test cases from this thread + // to try and end them. Technically that is not supported when + // using multiple threads, but the worst thing that can happen + // is that the process aborts harder :-D + Detail::LockGuard lock(m_assertionMutex); + + // Best effort cleanup for sections that have not been destructed yet + // Since this is a fatal error, we have not had and won't have the opportunity to destruct them properly + while (!m_activeSections.empty()) { + auto const &nl = m_activeSections.back()->nameAndLocation(); + SectionEndInfo endInfo{SectionInfo(nl.location, nl.name), {}, 0.0}; + sectionEndedEarly(CATCH_MOVE(endInfo)); + } + handleUnfinishedSections(); + + // Recreate section for test case (as we will lose the one that was in scope) + auto const &testCaseInfo = m_activeTestCase->getTestCaseInfo(); + SectionInfo testCaseSection(testCaseInfo.lineInfo, testCaseInfo.name); + + Counts assertions; + assertions.failed = 1; + SectionStats testCaseSectionStats(CATCH_MOVE(testCaseSection), assertions, 0, false); + m_reporter->sectionEnded(testCaseSectionStats); + + auto const &testInfo = m_activeTestCase->getTestCaseInfo(); + + Totals deltaTotals; + deltaTotals.testCases.failed = 1; + deltaTotals.assertions.failed = 1; + m_reporter->testCaseEnded(TestCaseStats(testInfo, + deltaTotals, + std::string(), + std::string(), + false)); + m_totals.testCases.failed++; + updateTotalsFromAtomics(); + m_reporter->testRunEnded(TestRunStats(m_runInfo, m_totals, false)); + } + + bool RunContext::lastAssertionPassed() { + return Detail::g_lastAssertionPassed; + } + + void RunContext::assertionPassedFastPath(SourceLineInfo lineInfo) { + // We want to save the line info for better experience with unexpected assertions + Detail::g_lastKnownLineInfo = lineInfo; + ++m_atomicAssertionCount.passed; + Detail::g_lastAssertionPassed = true; + Detail::g_clearMessageScopes = true; + } + + void RunContext::updateTotalsFromAtomics() { + m_totals.assertions = Counts{ + m_atomicAssertionCount.passed, + m_atomicAssertionCount.failed, + m_atomicAssertionCount.failedButOk, + m_atomicAssertionCount.skipped, + }; + } + + bool RunContext::aborting() const { + return m_atomicAssertionCount.failed >= m_abortAfterXFailedAssertions; + } + + void RunContext::runCurrentTest() { + auto const &testCaseInfo = m_activeTestCase->getTestCaseInfo(); + SectionInfo testCaseSection(testCaseInfo.lineInfo, testCaseInfo.name); + m_reporter->sectionStarting(testCaseSection); + updateTotalsFromAtomics(); + Counts prevAssertions = m_totals.assertions; + double duration = 0; + m_shouldReportUnexpected = true; + Detail::g_lastKnownLineInfo = testCaseInfo.lineInfo; + + Timer timer; + CATCH_TRY { + { + auto _ = scopedActivate(*m_outputRedirect); + timer.start(); + invokeActiveTestCase(); + } + duration = timer.getElapsedSeconds(); + } + CATCH_CATCH_ANON(TestFailureException &) { + // This just means the test was aborted due to failure + } + CATCH_CATCH_ANON(TestSkipException &) { + // This just means the test was explicitly skipped + } + CATCH_CATCH_ALL { + // Under CATCH_CONFIG_FAST_COMPILE, unexpected exceptions under REQUIRE assertions + // are reported without translation at the point of origin. + if (m_shouldReportUnexpected) { + AssertionReaction dummyReaction; + handleUnexpectedInflightException(makeDummyAssertionInfo(), + translateActiveException(), + dummyReaction); + } + } + updateTotalsFromAtomics(); + Counts assertions = m_totals.assertions - prevAssertions; + bool missingAssertions = testForMissingAssertions(assertions); + + m_testCaseTracker->close(); + handleUnfinishedSections(); + auto &msgHolder = Detail::g_messageHolder(); + msgHolder.removeUnscopedMessages(); + assert(msgHolder.getMessages().empty() && "There should be no leftover messages after the test ends"); + + SectionStats testCaseSectionStats(CATCH_MOVE(testCaseSection), assertions, duration, missingAssertions); + m_reporter->sectionEnded(testCaseSectionStats); + } + + void RunContext::invokeActiveTestCase() { + // We need to engage a handler for signals/structured exceptions + // before running the tests themselves, or the binary can crash + // without failed test being reported. + FatalConditionHandlerGuard _(&m_fatalConditionhandler); + // We keep having issue where some compilers warn about an unused + // variable, even though the type has non-trivial constructor and + // destructor. This is annoying and ugly, but it makes them stfu. + (void)_; + + m_activeTestCase->invoke(); + } + + void RunContext::handleUnfinishedSections() { + // If sections ended prematurely due to an exception we stored their + // infos here so we can tear them down outside the unwind process. + for (auto it = m_unfinishedSections.rbegin(), + itEnd = m_unfinishedSections.rend(); + it != itEnd; + ++it) { + sectionEnded(CATCH_MOVE(*it)); + } + m_unfinishedSections.clear(); + } + + void RunContext::handleExpr( + AssertionInfo const &info, + ITransientExpression const &expr, + AssertionReaction &reaction) { + bool negated = isFalseTest(info.resultDisposition); + bool result = expr.getResult() != negated; + + if (result) { + if (!m_includeSuccessfulResults) { + assertionPassedFastPath(info.lineInfo); + } else { + reportExpr(info, ResultWas::Ok, &expr, negated); + } + } else { + reportExpr(info, ResultWas::ExpressionFailed, &expr, negated); + populateReaction( + reaction, info.resultDisposition & ResultDisposition::Normal); + } + } + void RunContext::reportExpr( + AssertionInfo const &info, + ResultWas::OfType resultType, + ITransientExpression const *expr, + bool negated) { + Detail::g_lastKnownLineInfo = info.lineInfo; + AssertionResultData data(resultType, LazyExpression(negated)); + + AssertionResult assertionResult{info, CATCH_MOVE(data)}; + assertionResult.m_resultData.lazyExpression.m_transientExpression = expr; + + assertionEnded(CATCH_MOVE(assertionResult)); + } + + void RunContext::handleMessage( + AssertionInfo const &info, + ResultWas::OfType resultType, + std::string &&message, + AssertionReaction &reaction) { + Detail::g_lastKnownLineInfo = info.lineInfo; + + AssertionResultData data(resultType, LazyExpression(false)); + data.message = CATCH_MOVE(message); + AssertionResult assertionResult{info, + CATCH_MOVE(data)}; + + const auto isOk = assertionResult.isOk(); + assertionEnded(CATCH_MOVE(assertionResult)); + if (!isOk) { + populateReaction( + reaction, info.resultDisposition & ResultDisposition::Normal); + } else if (resultType == ResultWas::ExplicitSkip) { + // TODO: Need to handle this explicitly, as ExplicitSkip is + // considered "OK" + reaction.shouldSkip = true; + } + } + + void RunContext::handleUnexpectedExceptionNotThrown( + AssertionInfo const &info, + AssertionReaction &reaction) { + handleNonExpr(info, Catch::ResultWas::DidntThrowException, reaction); + } + + void RunContext::handleUnexpectedInflightException( + AssertionInfo const &info, + std::string &&message, + AssertionReaction &reaction) { + Detail::g_lastKnownLineInfo = info.lineInfo; + + AssertionResultData data(ResultWas::ThrewException, LazyExpression(false)); + data.message = CATCH_MOVE(message); + AssertionResult assertionResult{info, CATCH_MOVE(data)}; + assertionEnded(CATCH_MOVE(assertionResult)); + populateReaction(reaction, + info.resultDisposition & ResultDisposition::Normal); + } + + void RunContext::populateReaction(AssertionReaction &reaction, + bool has_normal_disposition) { + reaction.shouldDebugBreak = m_shouldDebugBreak; + reaction.shouldThrow = aborting() || has_normal_disposition; + } + + AssertionInfo RunContext::makeDummyAssertionInfo() { + const bool testCaseJustStarted = Detail::g_lastKnownLineInfo == m_activeTestCase->getTestCaseInfo().lineInfo; + + return AssertionInfo{ + testCaseJustStarted ? "TEST_CASE"_sr : StringRef(), + Detail::g_lastKnownLineInfo, + testCaseJustStarted ? StringRef() : "{Unknown expression after the reported line}"_sr, + ResultDisposition::Normal}; + } + + void RunContext::handleIncomplete( + AssertionInfo const &info) { + using namespace std::string_literals; + Detail::g_lastKnownLineInfo = info.lineInfo; + + AssertionResultData data(ResultWas::ThrewException, LazyExpression(false)); + data.message = "Exception translation was disabled by CATCH_CONFIG_FAST_COMPILE"s; + AssertionResult assertionResult{info, CATCH_MOVE(data)}; + assertionEnded(CATCH_MOVE(assertionResult)); + } + + void RunContext::handleNonExpr( + AssertionInfo const &info, + ResultWas::OfType resultType, + AssertionReaction &reaction) { + AssertionResultData data(resultType, LazyExpression(false)); + AssertionResult assertionResult{info, CATCH_MOVE(data)}; + + const auto isOk = assertionResult.isOk(); + if (isOk && !m_includeSuccessfulResults) { + assertionPassedFastPath(info.lineInfo); + return; + } + + assertionEnded(CATCH_MOVE(assertionResult)); + if (!isOk) { + populateReaction( + reaction, info.resultDisposition & ResultDisposition::Normal); + } + } + + void IResultCapture::pushScopedMessage(MessageInfo &&message) { + Detail::g_messageHolder().addScopedMessage(CATCH_MOVE(message)); + } + + void IResultCapture::popScopedMessage(unsigned int messageId) { + Detail::g_messageHolder().removeMessage(messageId); + } + + void IResultCapture::emplaceUnscopedMessage(MessageBuilder &&builder) { + Detail::g_messageHolder().addUnscopedMessage(CATCH_MOVE(builder)); + } + + void IResultCapture::addUnscopedMessage(MessageInfo &&message) { + Detail::g_messageHolder().addUnscopedMessage(CATCH_MOVE(message)); + } + + void seedRng(IConfig const &config) { + sharedRng().seed(config.rngSeed()); + } + + unsigned int rngSeed() { + return getCurrentContext().getConfig()->rngSeed(); + } + +} // namespace Catch + +namespace Catch { + + Section::Section(SectionInfo &&info) + : m_info(CATCH_MOVE(info)), m_sectionIncluded(getResultCapture().sectionStarted(m_info.name, m_info.lineInfo, m_assertions)) { + // Non-"included" sections will not use the timing information + // anyway, so don't bother with the potential syscall. + if (m_sectionIncluded) { + m_timer.start(); + } + } + + Section::Section(SourceLineInfo const &_lineInfo, + StringRef _name, + const char *const) + : m_info({"invalid", static_cast(-1)}, std::string{}), m_sectionIncluded(getResultCapture().sectionStarted(_name, _lineInfo, m_assertions)) { + // We delay initialization the SectionInfo member until we know + // this section needs it, so we avoid allocating std::string for name. + // We also delay timer start to avoid the potential syscall unless we + // will actually use the result. + if (m_sectionIncluded) { + m_info.name = static_cast(_name); + m_info.lineInfo = _lineInfo; + m_timer.start(); + } + } + + Section::~Section() { + if (m_sectionIncluded) { + SectionEndInfo endInfo{CATCH_MOVE(m_info), m_assertions, m_timer.getElapsedSeconds()}; + if (uncaught_exceptions()) { + getResultCapture().sectionEndedEarly(CATCH_MOVE(endInfo)); + } else { + getResultCapture().sectionEnded(CATCH_MOVE(endInfo)); + } + } + } + + // This indicates whether the section should be executed or not + Section::operator bool() const { + return m_sectionIncluded; + } + +} // end namespace Catch + +#include + +namespace Catch { + + namespace { + static auto getSingletons() -> std::vector *& { + static std::vector *g_singletons = nullptr; + if (!g_singletons) + g_singletons = new std::vector(); + return g_singletons; + } + } // namespace + + ISingleton::~ISingleton() = default; + + void addSingleton(ISingleton *singleton) { + getSingletons()->push_back(singleton); + } + void cleanupSingletons() { + auto &singletons = getSingletons(); + for (auto singleton : *singletons) + delete singleton; + delete singletons; + singletons = nullptr; + } + +} // namespace Catch + +#include +#include + +namespace Catch { + + bool SourceLineInfo::operator==(SourceLineInfo const &other) const noexcept { + return line == other.line && (file == other.file || std::strcmp(file, other.file) == 0); + } + bool SourceLineInfo::operator<(SourceLineInfo const &other) const noexcept { + // We can assume that the same file will usually have the same pointer. + // Thus, if the pointers are the same, there is no point in calling the strcmp + return line < other.line || (line == other.line && file != other.file && (std::strcmp(file, other.file) < 0)); + } + + std::ostream &operator<<(std::ostream &os, SourceLineInfo const &info) { +#ifndef __GNUG__ + os << info.file << '(' << info.line << ')'; +#else + os << info.file << ':' << info.line; +#endif + return os; + } + +} // end namespace Catch + +namespace Catch { +#if !defined(CATCH_CONFIG_DISABLE_EXCEPTIONS) + void StartupExceptionRegistry::add(std::exception_ptr const &exception) noexcept { + CATCH_TRY { + m_exceptions.push_back(exception); + } + CATCH_CATCH_ALL { + // If we run out of memory during start-up there's really not a lot more we can do about it + std::terminate(); + } + } + + std::vector const &StartupExceptionRegistry::getExceptions() const noexcept { + return m_exceptions; + } +#endif + +} // end namespace Catch + +#include + +namespace Catch { + +// If you #define this you must implement these functions +#if !defined(CATCH_CONFIG_NOSTDOUT) + std::ostream &cout() { + return std::cout; + } + std::ostream &cerr() { + return std::cerr; + } + std::ostream &clog() { + return std::clog; + } +#endif + +} // namespace Catch + +#include +#include +#include +#include + +namespace Catch { + + bool startsWith(std::string const &s, std::string const &prefix) { + return s.size() >= prefix.size() && std::equal(prefix.begin(), prefix.end(), s.begin()); + } + bool startsWith(StringRef s, char prefix) { + return !s.empty() && s[0] == prefix; + } + bool endsWith(std::string const &s, std::string const &suffix) { + return s.size() >= suffix.size() && std::equal(suffix.rbegin(), suffix.rend(), s.rbegin()); + } + bool endsWith(std::string const &s, char suffix) { + return !s.empty() && s[s.size() - 1] == suffix; + } + bool contains(std::string const &s, std::string const &infix) { + return s.find(infix) != std::string::npos; + } + void toLowerInPlace(std::string &s) { + for (char &c : s) { + c = toLower(c); + } + } + std::string toLower(std::string const &s) { + std::string lc = s; + toLowerInPlace(lc); + return lc; + } + char toLower(char c) { + return static_cast(std::tolower(static_cast(c))); + } + + std::string trim(std::string const &str) { + static char const *whitespaceChars = "\n\r\t "; + std::string::size_type start = str.find_first_not_of(whitespaceChars); + std::string::size_type end = str.find_last_not_of(whitespaceChars); + + return start != std::string::npos ? str.substr(start, 1 + end - start) : std::string(); + } + + StringRef trim(StringRef ref) { + const auto is_ws = [](char c) { + return c == ' ' || c == '\t' || c == '\n' || c == '\r'; + }; + size_t real_begin = 0; + while (real_begin < ref.size() && is_ws(ref[real_begin])) { ++real_begin; } + size_t real_end = ref.size(); + while (real_end > real_begin && is_ws(ref[real_end - 1])) { --real_end; } + + return ref.substr(real_begin, real_end - real_begin); + } + + bool replaceInPlace(std::string &str, std::string const &replaceThis, std::string const &withThis) { + std::size_t i = str.find(replaceThis); + if (i == std::string::npos) { + return false; + } + std::size_t copyBegin = 0; + std::string origStr = CATCH_MOVE(str); + str.clear(); + // There is at least one replacement, so reserve with the best guess + // we can make without actually counting the number of occurrences. + str.reserve(origStr.size() - replaceThis.size() + withThis.size()); + do { + str.append(origStr, copyBegin, i - copyBegin); + str += withThis; + copyBegin = i + replaceThis.size(); + if (copyBegin < origStr.size()) + i = origStr.find(replaceThis, copyBegin); + else + i = std::string::npos; + } while (i != std::string::npos); + if (copyBegin < origStr.size()) { + str.append(origStr, copyBegin, origStr.size()); + } + return true; + } + + std::vector splitStringRef(StringRef str, char delimiter) { + std::vector subStrings; + std::size_t start = 0; + for (std::size_t pos = 0; pos < str.size(); ++pos) { + if (str[pos] == delimiter) { + if (pos - start > 1) + subStrings.push_back(str.substr(start, pos - start)); + start = pos + 1; + } + } + if (start < str.size()) + subStrings.push_back(str.substr(start, str.size() - start)); + return subStrings; + } + + std::ostream &operator<<(std::ostream &os, pluralise const &pluraliser) { + os << pluraliser.m_count << ' ' << pluraliser.m_label; + if (pluraliser.m_count != 1) + os << 's'; + return os; + } + +} // namespace Catch + +#include +#include +#include + +namespace Catch { + StringRef::StringRef(char const *rawChars) noexcept + : StringRef(rawChars, std::strlen(rawChars)) {} + + bool StringRef::operator<(StringRef rhs) const noexcept { + if (m_size < rhs.m_size) { + return strncmp(m_start, rhs.m_start, m_size) <= 0; + } + return strncmp(m_start, rhs.m_start, rhs.m_size) < 0; + } + + int StringRef::compare(StringRef rhs) const { + auto cmpResult = strncmp(m_start, rhs.m_start, std::min(m_size, rhs.m_size)); + + // This means that strncmp found a difference before the strings + // ended, and we can return it directly + if (cmpResult != 0) { + return cmpResult; + } + + // If strings are equal up to length, then their comparison results on + // their size + if (m_size < rhs.m_size) { + return -1; + } else if (m_size > rhs.m_size) { + return 1; + } else { + return 0; + } + } + + auto operator<<(std::ostream &os, StringRef str) -> std::ostream & { + return os.write(str.data(), static_cast(str.size())); + } + + std::string operator+(StringRef lhs, StringRef rhs) { + std::string ret; + ret.reserve(lhs.size() + rhs.size()); + ret += lhs; + ret += rhs; + return ret; + } + + auto operator+=(std::string &lhs, StringRef rhs) -> std::string & { + lhs.append(rhs.data(), rhs.size()); + return lhs; + } + +} // namespace Catch + +namespace Catch { + + TagAliasRegistry::~TagAliasRegistry() = default; + + TagAlias const *TagAliasRegistry::find(std::string const &alias) const { + auto it = m_registry.find(alias); + if (it != m_registry.end()) + return &(it->second); + else + return nullptr; + } + + std::string TagAliasRegistry::expandAliases(std::string const &unexpandedTestSpec) const { + std::string expandedTestSpec = unexpandedTestSpec; + for (auto const ®istryKvp : m_registry) { + std::size_t pos = expandedTestSpec.find(registryKvp.first); + if (pos != std::string::npos) { + expandedTestSpec = expandedTestSpec.substr(0, pos) + registryKvp.second.tag + expandedTestSpec.substr(pos + registryKvp.first.size()); + } + } + return expandedTestSpec; + } + + void TagAliasRegistry::add(std::string const &alias, std::string const &tag, SourceLineInfo const &lineInfo) { + CATCH_ENFORCE(startsWith(alias, "[@") && endsWith(alias, ']'), + "error: tag alias, '" << alias << "' is not of the form [@alias name].\n" + << lineInfo); + + CATCH_ENFORCE(m_registry.insert(std::make_pair(alias, TagAlias(tag, lineInfo))).second, + "error: tag alias, '" << alias << "' already registered.\n" + << "\tFirst seen at: " << find(alias)->lineInfo << "\n" + << "\tRedefined at: " << lineInfo); + } + + ITagAliasRegistry::~ITagAliasRegistry() = default; + + ITagAliasRegistry const &ITagAliasRegistry::get() { + return getRegistryHub().getTagAliasRegistry(); + } + +} // end namespace Catch + +namespace Catch { + TestCaseInfoHasher::TestCaseInfoHasher(hash_t seed) + : m_seed(seed) {} + + uint32_t TestCaseInfoHasher::operator()(TestCaseInfo const &t) const { + // FNV-1a hash algorithm that is designed for uniqueness: + const hash_t prime = 1099511628211u; + hash_t hash = 14695981039346656037u; + for (const char c : t.name) { + hash ^= c; + hash *= prime; + } + for (const char c : t.className) { + hash ^= c; + hash *= prime; + } + for (const Tag &tag : t.tags) { + for (const char c : tag.original) { + hash ^= c; + hash *= prime; + } + } + hash ^= m_seed; + hash *= prime; + const uint32_t low{static_cast(hash)}; + const uint32_t high{static_cast(hash >> 32)}; + return low * high; + } +} // namespace Catch + +#include +#include + +namespace Catch { + + namespace { + static void enforceNoDuplicateTestCases( + std::vector const &tests) { + auto testInfoCmp = [](TestCaseInfo const *lhs, + TestCaseInfo const *rhs) { + return *lhs < *rhs; + }; + std::set seenTests( + testInfoCmp); + for (auto const &test : tests) { + const auto infoPtr = &test.getTestCaseInfo(); + const auto prev = seenTests.insert(infoPtr); + CATCH_ENFORCE(prev.second, + "error: test case \"" + << infoPtr->name << "\", with tags \"" + << infoPtr->tagsAsString() + << "\" already defined.\n" + << "\tFirst seen at " + << (*prev.first)->lineInfo << "\n" + << "\tRedefined at " << infoPtr->lineInfo); + } + } + + static bool matchTest(TestCaseHandle const &testCase, + TestSpec const &testSpec, + IConfig const &config) { + return testSpec.matches(testCase.getTestCaseInfo()) && isThrowSafe(testCase, config); + } + + } // end unnamed namespace + + std::vector sortTests(IConfig const &config, std::vector const &unsortedTestCases) { + switch (config.runOrder()) { + case TestRunOrder::Declared: + return unsortedTestCases; + + case TestRunOrder::LexicographicallySorted: { + std::vector sorted = unsortedTestCases; + std::sort( + sorted.begin(), + sorted.end(), + [](TestCaseHandle const &lhs, TestCaseHandle const &rhs) { + return lhs.getTestCaseInfo() < rhs.getTestCaseInfo(); + }); + return sorted; + } + case TestRunOrder::Randomized: { + using TestWithHash = std::pair; + + TestCaseInfoHasher h{config.rngSeed()}; + std::vector indexed_tests; + indexed_tests.reserve(unsortedTestCases.size()); + + for (auto const &handle : unsortedTestCases) { + indexed_tests.emplace_back(h(handle.getTestCaseInfo()), handle); + } + + std::sort(indexed_tests.begin(), + indexed_tests.end(), + [](TestWithHash const &lhs, TestWithHash const &rhs) { + if (lhs.first == rhs.first) { + return lhs.second.getTestCaseInfo() < rhs.second.getTestCaseInfo(); + } + return lhs.first < rhs.first; + }); + + std::vector randomized; + randomized.reserve(indexed_tests.size()); + + for (auto const &indexed : indexed_tests) { + randomized.push_back(indexed.second); + } + + return randomized; + } + } + + CATCH_INTERNAL_ERROR("Unknown test order value!"); + } + + bool isThrowSafe(TestCaseHandle const &testCase, IConfig const &config) { + return !testCase.getTestCaseInfo().throws() || config.allowThrows(); + } + + std::vector filterTests(std::vector const &testCases, TestSpec const &testSpec, IConfig const &config) { + std::vector filtered; + filtered.reserve(testCases.size()); + for (auto const &testCase : testCases) { + if ((!testSpec.hasFilters() && !testCase.getTestCaseInfo().isHidden()) || (testSpec.hasFilters() && matchTest(testCase, testSpec, config))) { + filtered.push_back(testCase); + } + } + return createShard(filtered, config.shardCount(), config.shardIndex()); + } + std::vector const &getAllTestCasesSorted(IConfig const &config) { + return getRegistryHub().getTestCaseRegistry().getAllTestsSorted(config); + } + + TestRegistry::~TestRegistry() = default; + + void TestRegistry::registerTest(Detail::unique_ptr testInfo, Detail::unique_ptr testInvoker) { + m_handles.emplace_back(testInfo.get(), testInvoker.get()); + m_viewed_test_infos.push_back(testInfo.get()); + m_owned_test_infos.push_back(CATCH_MOVE(testInfo)); + m_invokers.push_back(CATCH_MOVE(testInvoker)); + } + + std::vector const &TestRegistry::getAllInfos() const { + return m_viewed_test_infos; + } + + std::vector const &TestRegistry::getAllTests() const { + return m_handles; + } + std::vector const &TestRegistry::getAllTestsSorted(IConfig const &config) const { + if (m_sortedFunctions.empty()) + enforceNoDuplicateTestCases(m_handles); + + if (m_currentSortOrder != config.runOrder() || m_sortedFunctions.empty()) { + m_sortedFunctions = sortTests(config, m_handles); + m_currentSortOrder = config.runOrder(); + } + return m_sortedFunctions; + } + +} // end namespace Catch + +#include +#include + +#if defined(__clang__) +#pragma clang diagnostic push +#pragma clang diagnostic ignored "-Wexit-time-destructors" +#endif + +namespace Catch { + namespace TestCaseTracking { + + NameAndLocation::NameAndLocation(std::string &&_name, SourceLineInfo const &_location) + : name(CATCH_MOVE(_name)), location(_location) {} + + ITracker::ITracker(NameAndLocation &&nameAndLoc, ITracker *parent) + : m_nameAndLocation(CATCH_MOVE(nameAndLoc)), m_parent(parent) { + if (m_parent) { + m_allTrackerDepth = m_parent->m_allTrackerDepth + 1; + // We leave section trackers to bump themselves up, as + // we cannot use `isSectionTracker` in constructor + m_sectionOnlyDepth = m_parent->m_sectionOnlyDepth; + m_filterRef = m_parent->m_filterRef; + m_newStyleFilters = m_parent->m_newStyleFilters; + } + } + + ITracker::~ITracker() = default; + + void ITracker::markAsNeedingAnotherRun() { + m_runState = NeedsAnotherRun; + } + + void ITracker::addChild(ITrackerPtr &&child) { + m_children.push_back(CATCH_MOVE(child)); + } + + ITracker *ITracker::findChild(NameAndLocationRef const &nameAndLocation) { + auto it = std::find_if( + m_children.begin(), + m_children.end(), + [&nameAndLocation](ITrackerPtr const &tracker) { + auto const &tnameAndLoc = tracker->nameAndLocation(); + if (tnameAndLoc.location.line != nameAndLocation.location.line) { + return false; + } + return tnameAndLoc == nameAndLocation; + }); + return (it != m_children.end()) ? it->get() : nullptr; + } + + bool ITracker::isSectionTracker() const { + return false; + } + bool ITracker::isGeneratorTracker() const { + return false; + } + + bool ITracker::isOpen() const { + return m_runState != NotStarted && !isComplete(); + } + + bool ITracker::hasStarted() const { + return m_runState != NotStarted; + } + + void ITracker::openChild() { + if (m_runState != ExecutingChildren) { + m_runState = ExecutingChildren; + if (m_parent) { + m_parent->openChild(); + } + } + } + + ITracker &TrackerContext::startRun() { + using namespace std::string_literals; + m_rootTracker = Catch::Detail::make_unique( + NameAndLocation("{root}"s, CATCH_INTERNAL_LINEINFO), + *this, + nullptr); + m_currentTracker = nullptr; + m_runState = Executing; + return *m_rootTracker; + } + + void TrackerContext::completeCycle() { + m_runState = CompletedCycle; + } + + bool TrackerContext::completedCycle() const { + return m_runState == CompletedCycle; + } + void TrackerContext::setCurrentTracker(ITracker *tracker) { + m_currentTracker = tracker; + } + + TrackerBase::TrackerBase(NameAndLocation &&nameAndLocation, TrackerContext &ctx, ITracker *parent) + : ITracker(CATCH_MOVE(nameAndLocation), parent), m_ctx(ctx) {} + + bool TrackerBase::isComplete() const { + return m_runState == CompletedSuccessfully || m_runState == Failed; + } + + void TrackerBase::open() { + m_runState = Executing; + moveToThis(); + if (m_parent) + m_parent->openChild(); + } + + void TrackerBase::close() { + // Close any still open children (e.g. generators) + while (&m_ctx.currentTracker() != this) + m_ctx.currentTracker().close(); + + switch (m_runState) { + case NeedsAnotherRun: + break; + + case Executing: + m_runState = CompletedSuccessfully; + break; + case ExecutingChildren: + if (std::all_of(m_children.begin(), m_children.end(), [](ITrackerPtr const &t) { return t->isComplete(); })) + m_runState = CompletedSuccessfully; + break; + + case NotStarted: + case CompletedSuccessfully: + case Failed: + CATCH_INTERNAL_ERROR("Illogical state: " << m_runState); + + default: + CATCH_INTERNAL_ERROR("Unknown state: " << m_runState); + } + moveToParent(); + m_ctx.completeCycle(); + } + void TrackerBase::fail() { + m_runState = Failed; + if (m_parent) + m_parent->markAsNeedingAnotherRun(); + moveToParent(); + m_ctx.completeCycle(); + } + + void TrackerBase::moveToParent() { + assert(m_parent); + m_ctx.setCurrentTracker(m_parent); + } + void TrackerBase::moveToThis() { + m_ctx.setCurrentTracker(this); + } + + SectionTracker::SectionTracker(NameAndLocation &&nameAndLocation, TrackerContext &ctx, ITracker *parent) + : TrackerBase(CATCH_MOVE(nameAndLocation), ctx, parent), m_trimmed_name(trim(StringRef(ITracker::nameAndLocation().name))) { + if (m_parent) { + ++m_sectionOnlyDepth; + } + } + + bool SectionTracker::isComplete() const { + // If there are active filters AND we do not pass them, + // the section is always "completed" + const size_t filterIndex = m_newStyleFilters ? m_allTrackerDepth : m_sectionOnlyDepth; + if (filterIndex < m_filterRef->size()) { + // There is active filter, check it + // 1) New style filter must explicitly target section + if (m_newStyleFilters && (*m_filterRef)[filterIndex].type != PathFilter::For::Section) { + return true; + } + // 2) Both style filters must match the trimmed name exactly + if (m_trimmed_name != StringRef((*m_filterRef)[filterIndex].filter)) { + return true; + } + } + + // Otherwise we delegate to the generic processing + return TrackerBase::isComplete(); + } + + bool SectionTracker::isSectionTracker() const { + return true; + } + + SectionTracker &SectionTracker::acquire(TrackerContext &ctx, NameAndLocationRef const &nameAndLocation) { + SectionTracker *tracker; + + ITracker ¤tTracker = ctx.currentTracker(); + if (ITracker *childTracker = currentTracker.findChild(nameAndLocation)) { + assert(childTracker); + assert(childTracker->isSectionTracker()); + tracker = static_cast(childTracker); + } else { + auto newTracker = Catch::Detail::make_unique( + NameAndLocation{static_cast(nameAndLocation.name), + nameAndLocation.location}, + ctx, + ¤tTracker); + tracker = newTracker.get(); + currentTracker.addChild(CATCH_MOVE(newTracker)); + } + + if (!ctx.completedCycle()) { + tracker->tryOpen(); + } + + return *tracker; + } + + void SectionTracker::tryOpen() { + if (!isComplete()) + open(); + } + + StringRef SectionTracker::trimmedName() const { + return m_trimmed_name; + } + + } // namespace TestCaseTracking + +} // namespace Catch + +#if defined(__clang__) +#pragma clang diagnostic pop +#endif + +namespace Catch { + + void throw_test_failure_exception() { +#if !defined(CATCH_CONFIG_DISABLE_EXCEPTIONS) + throw TestFailureException{}; +#else + CATCH_ERROR("Test failure requires aborting test!"); +#endif + } + + void throw_test_skip_exception() { +#if !defined(CATCH_CONFIG_DISABLE_EXCEPTIONS) + throw Catch::TestSkipException(); +#else + CATCH_ERROR("Explicitly skipping tests during runtime requires exceptions"); +#endif + } + +} // namespace Catch + +#include +#include + +namespace Catch { + void ITestInvoker::prepareTestCase() {} + void ITestInvoker::tearDownTestCase() {} + ITestInvoker::~ITestInvoker() = default; + + namespace { + static StringRef extractClassName(StringRef classOrMethodName) { + if (!startsWith(classOrMethodName, '&')) { + return classOrMethodName; + } + + // Remove the leading '&' to avoid having to special case it later + const auto methodName = classOrMethodName.substr(1, classOrMethodName.size()); + + auto reverseStart = std::make_reverse_iterator(methodName.end()); + auto reverseEnd = std::make_reverse_iterator(methodName.begin()); + + // We make a simplifying assumption that ":" is only present + // in the input as part of "::" from C++ typenames (this is + // relatively safe assumption because the input is generated + // as stringification of type through preprocessor). + auto lastColons = std::find(reverseStart, reverseEnd, ':') + 1; + auto secondLastColons = std::find(lastColons + 1, reverseEnd, ':'); + + auto const startIdx = reverseEnd - secondLastColons; + auto const classNameSize = secondLastColons - lastColons - 1; + + return methodName.substr( + static_cast(startIdx), + static_cast(classNameSize)); + } + + class TestInvokerAsFunction final : public ITestInvoker { + using TestType = void (*)(); + TestType m_testAsFunction; + + public: + constexpr TestInvokerAsFunction(TestType testAsFunction) noexcept + : m_testAsFunction(testAsFunction) {} + + void invoke() const override { m_testAsFunction(); } + }; + + } // namespace + + Detail::unique_ptr makeTestInvoker(void (*testAsFunction)()) { + return Detail::make_unique(testAsFunction); + } + + AutoReg::AutoReg(Detail::unique_ptr invoker, SourceLineInfo const &lineInfo, StringRef classOrMethod, NameAndTags const &nameAndTags) noexcept { + CATCH_TRY { + getMutableRegistryHub() + .registerTest( + makeTestCaseInfo( + extractClassName(classOrMethod), + nameAndTags, + lineInfo), + CATCH_MOVE(invoker)); + } + CATCH_CATCH_ALL { + // Do not throw when constructing global objects, instead register the exception to be processed later + getMutableRegistryHub().registerStartupException(); + } + } +} // namespace Catch + +namespace Catch { + + TestSpecParser::TestSpecParser(ITagAliasRegistry const &tagAliases) + : m_tagAliases(&tagAliases) {} + + TestSpecParser &TestSpecParser::parse(std::string const &arg) { + m_mode = None; + m_exclusion = false; + m_arg = m_tagAliases->expandAliases(arg); + m_escapeChars.clear(); + m_substring.reserve(m_arg.size()); + m_patternName.reserve(m_arg.size()); + m_realPatternPos = 0; + + for (m_pos = 0; m_pos < m_arg.size(); ++m_pos) + // if visitChar fails + if (!visitChar(m_arg[m_pos])) { + m_testSpec.m_invalidSpecs.push_back(arg); + break; + } + endMode(); + return *this; + } + TestSpec TestSpecParser::testSpec() { + addFilter(); + return CATCH_MOVE(m_testSpec); + } + bool TestSpecParser::visitChar(char c) { + if ((m_mode != EscapedName) && (c == '\\')) { + escape(); + addCharToPattern(c); + return true; + } else if ((m_mode != EscapedName) && (c == ',')) { + return separate(); + } + + switch (m_mode) { + case None: + if (processNoneChar(c)) + return true; + break; + case Name: + processNameChar(c); + break; + case EscapedName: + endMode(); + addCharToPattern(c); + return true; + default: + case Tag: + case QuotedName: + if (processOtherChar(c)) + return true; + break; + } + + m_substring += c; + if (!isControlChar(c)) { + m_patternName += c; + m_realPatternPos++; + } + return true; + } + // Two of the processing methods return true to signal the caller to return + // without adding the given character to the current pattern strings + bool TestSpecParser::processNoneChar(char c) { + switch (c) { + case ' ': + return true; + case '~': + m_exclusion = true; + return false; + case '[': + startNewMode(Tag); + return false; + case '"': + startNewMode(QuotedName); + return false; + default: + startNewMode(Name); + return false; + } + } + void TestSpecParser::processNameChar(char c) { + if (c == '[') { + if (m_substring == "exclude:") + m_exclusion = true; + else + endMode(); + startNewMode(Tag); + } + } + bool TestSpecParser::processOtherChar(char c) { + if (!isControlChar(c)) + return false; + m_substring += c; + endMode(); + return true; + } + void TestSpecParser::startNewMode(Mode mode) { + m_mode = mode; + } + void TestSpecParser::endMode() { + switch (m_mode) { + case Name: + case QuotedName: + return addNamePattern(); + case Tag: + return addTagPattern(); + case EscapedName: + revertBackToLastMode(); + return; + case None: + default: + return startNewMode(None); + } + } + void TestSpecParser::escape() { + saveLastMode(); + m_mode = EscapedName; + m_escapeChars.push_back(m_realPatternPos); + } + bool TestSpecParser::isControlChar(char c) const { + switch (m_mode) { + default: + return false; + case None: + return c == '~'; + case Name: + return c == '['; + case EscapedName: + return true; + case QuotedName: + return c == '"'; + case Tag: + return c == '[' || c == ']'; + } + } + + void TestSpecParser::addFilter() { + if (!m_currentFilter.m_required.empty() || !m_currentFilter.m_forbidden.empty()) { + m_testSpec.m_filters.push_back(CATCH_MOVE(m_currentFilter)); + m_currentFilter = TestSpec::Filter(); + } + } + + void TestSpecParser::saveLastMode() { + lastMode = m_mode; + } + + void TestSpecParser::revertBackToLastMode() { + m_mode = lastMode; + } + + bool TestSpecParser::separate() { + if ((m_mode == QuotedName) || (m_mode == Tag)) { + // invalid argument, signal failure to previous scope. + m_mode = None; + m_pos = m_arg.size(); + m_substring.clear(); + m_patternName.clear(); + m_realPatternPos = 0; + return false; + } + endMode(); + addFilter(); + return true; // success + } + + std::string TestSpecParser::preprocessPattern() { + std::string token = m_patternName; + for (std::size_t i = 0; i < m_escapeChars.size(); ++i) + token = token.substr(0, m_escapeChars[i] - i) + token.substr(m_escapeChars[i] - i + 1); + m_escapeChars.clear(); + if (startsWith(token, "exclude:")) { + m_exclusion = true; + token = token.substr(8); + } + + m_patternName.clear(); + m_realPatternPos = 0; + + return token; + } + + void TestSpecParser::addNamePattern() { + auto token = preprocessPattern(); + + if (!token.empty()) { + if (m_exclusion) { + m_currentFilter.m_forbidden.emplace_back(Detail::make_unique(token, m_substring)); + } else { + m_currentFilter.m_required.emplace_back(Detail::make_unique(token, m_substring)); + } + } + m_substring.clear(); + m_exclusion = false; + m_mode = None; + } + + void TestSpecParser::addTagPattern() { + auto token = preprocessPattern(); + + if (!token.empty()) { + // If the tag pattern is the "hide and tag" shorthand (e.g. [.foo]) + // we have to create a separate hide tag and shorten the real one + if (token.size() > 1 && token[0] == '.') { + token.erase(token.begin()); + if (m_exclusion) { + m_currentFilter.m_forbidden.emplace_back(Detail::make_unique(".", m_substring)); + } else { + m_currentFilter.m_required.emplace_back(Detail::make_unique(".", m_substring)); + } + } + if (m_exclusion) { + m_currentFilter.m_forbidden.emplace_back(Detail::make_unique(token, m_substring)); + } else { + m_currentFilter.m_required.emplace_back(Detail::make_unique(token, m_substring)); + } + } + m_substring.clear(); + m_exclusion = false; + m_mode = None; + } + +} // namespace Catch + +#include +#include +#include + +namespace { + bool isWhitespace(char c) { + return c == ' ' || c == '\t' || c == '\n' || c == '\r'; + } + + bool isBreakableBefore(char c) { + static const char chars[] = "[({<|"; + return std::memchr(chars, c, sizeof(chars) - 1) != nullptr; + } + + bool isBreakableAfter(char c) { + static const char chars[] = "])}>.,:;*+-=&/\\"; + return std::memchr(chars, c, sizeof(chars) - 1) != nullptr; + } + + bool isUtf8ContinuationByte(char c) { + return (static_cast(c) & 0xC0) == 0x80; + } + +} // namespace + +namespace Catch { + namespace TextFlow { + void AnsiSkippingString::preprocessString() { + for (auto it = m_string.begin(); it != m_string.end();) { + // try to read through an ansi sequence + while (it != m_string.end() && *it == '\033' && it + 1 != m_string.end() && *(it + 1) == '[') { + auto cursor = it + 2; + while (cursor != m_string.end() && (isdigit(*cursor) || *cursor == ';')) { + ++cursor; + } + if (cursor == m_string.end() || *cursor != 'm') { + break; + } + // 'm' -> 0xff + *cursor = AnsiSkippingString::sentinel; + // if we've read an ansi sequence, set the iterator and + // return to the top of the loop + it = cursor + 1; + } + if (it != m_string.end()) { + ++m_size; + ++it; + // Skip UTF-8 continuation bytes + while (it != m_string.end() && isUtf8ContinuationByte(*it)) { + ++it; + } + } + } + } + + AnsiSkippingString::AnsiSkippingString(std::string const &text) + : m_string(text) { + preprocessString(); + } + + AnsiSkippingString::AnsiSkippingString(std::string &&text) + : m_string(CATCH_MOVE(text)) { + preprocessString(); + } + + AnsiSkippingString::const_iterator AnsiSkippingString::begin() const { + return const_iterator(m_string); + } + + AnsiSkippingString::const_iterator AnsiSkippingString::end() const { + return const_iterator(m_string, const_iterator::EndTag{}); + } + + std::string AnsiSkippingString::substring(const_iterator begin, + const_iterator end) const { + // There's one caveat here to an otherwise simple substring: when + // making a begin iterator we might have skipped ansi sequences at + // the start. If `begin` here is a begin iterator, skipped over + // initial ansi sequences, we'll use the true beginning of the + // string. Lastly: We need to transform any chars we replaced with + // 0xff back to 'm' + auto str = std::string(begin == this->begin() ? m_string.begin() + : begin.m_it, + end.m_it); + std::transform(str.begin(), str.end(), str.begin(), [](char c) { + return c == AnsiSkippingString::sentinel ? 'm' : c; + }); + return str; + } + + void AnsiSkippingString::const_iterator::tryParseAnsiEscapes() { + // check if we've landed on an ansi sequence, and if so read through + // it + while (m_it != m_string->end() && *m_it == '\033' && m_it + 1 != m_string->end() && *(m_it + 1) == '[') { + auto cursor = m_it + 2; + while (cursor != m_string->end() && (isdigit(*cursor) || *cursor == ';')) { + ++cursor; + } + if (cursor == m_string->end() || *cursor != AnsiSkippingString::sentinel) { + break; + } + // if we've read an ansi sequence, set the iterator and + // return to the top of the loop + m_it = cursor + 1; + } + } + + void AnsiSkippingString::const_iterator::advance() { + assert(m_it != m_string->end()); + m_it++; + // Skip UTF-8 continuation bytes + while (m_it != m_string->end() && isUtf8ContinuationByte(*m_it)) { + m_it++; + } + tryParseAnsiEscapes(); + } + + void AnsiSkippingString::const_iterator::unadvance() { + assert(m_it != m_string->begin()); + m_it--; + // if *m_it is 0xff, scan back to the \033 and then m_it-- once more + // (and repeat check) + while (*m_it == AnsiSkippingString::sentinel) { + while (*m_it != '\033') { + assert(m_it != m_string->begin()); + m_it--; + } + // if this happens, we must have been a begin iterator that had + // skipped over ansi sequences at the start of a string + assert(m_it != m_string->begin()); + assert(*m_it == '\033'); + m_it--; + } + // Skip back over UTF-8 continuation bytes to the leading byte + while (isUtf8ContinuationByte(*m_it)) { + assert(m_it != m_string->begin()); + m_it--; + } + } + + static bool isBoundary(AnsiSkippingString const &line, + AnsiSkippingString::const_iterator it) { + return it == line.end() || (isWhitespace(*it) && !isWhitespace(*it.oneBefore())) || isBreakableBefore(*it) || isBreakableAfter(*it.oneBefore()); + } + + void Column::const_iterator::calcLength() { + m_addHyphen = false; + m_parsedTo = m_lineStart; + AnsiSkippingString const ¤t_line = m_column.m_string; + + if (m_parsedTo == current_line.end()) { + m_lineEnd = m_parsedTo; + return; + } + + assert(m_lineStart != current_line.end()); + if (*m_lineStart == '\n') { ++m_parsedTo; } + + const auto maxLineLength = m_column.m_width - indentSize(); + std::size_t lineLength = 0; + while (m_parsedTo != current_line.end() && lineLength < maxLineLength && *m_parsedTo != '\n') { + ++m_parsedTo; + ++lineLength; + } + + // If we encountered a newline before the column is filled, + // then we linebreak at the newline and consider this line + // finished. + if (lineLength < maxLineLength) { + m_lineEnd = m_parsedTo; + } else { + // Look for a natural linebreak boundary in the column + // (We look from the end, so that the first found boundary is + // the right one) + m_lineEnd = m_parsedTo; + while (lineLength > 0 && !isBoundary(current_line, m_lineEnd)) { + --lineLength; + --m_lineEnd; + } + while (lineLength > 0 && isWhitespace(*m_lineEnd.oneBefore())) { + --lineLength; + --m_lineEnd; + } + + // If we found one, then that is where we linebreak, otherwise + // we have to split text with a hyphen + if (lineLength == 0) { + m_addHyphen = true; + m_lineEnd = m_parsedTo.oneBefore(); + } + } + } + + size_t Column::const_iterator::indentSize() const { + auto initial = m_lineStart == m_column.m_string.begin() + ? m_column.m_initialIndent + : std::string::npos; + return initial == std::string::npos ? m_column.m_indent : initial; + } + + std::string Column::const_iterator::addIndentAndSuffix( + AnsiSkippingString::const_iterator start, + AnsiSkippingString::const_iterator end) const { + std::string ret; + const auto desired_indent = indentSize(); + // ret.reserve( desired_indent + (end - start) + m_addHyphen ); + ret.append(desired_indent, ' '); + // ret.append( start, end ); + ret += m_column.m_string.substring(start, end); + if (m_addHyphen) { ret.push_back('-'); } + + return ret; + } + + Column::const_iterator::const_iterator(Column const &column) + : m_column(column), m_lineStart(column.m_string.begin()), m_lineEnd(column.m_string.begin()), m_parsedTo(column.m_string.begin()) { + assert(m_column.m_width > m_column.m_indent); + assert(m_column.m_initialIndent == std::string::npos || m_column.m_width > m_column.m_initialIndent); + calcLength(); + if (m_lineStart == m_lineEnd) { + m_lineStart = m_column.m_string.end(); + } + } + + std::string Column::const_iterator::operator*() const { + assert(m_lineStart <= m_parsedTo); + return addIndentAndSuffix(m_lineStart, m_lineEnd); + } + + Column::const_iterator &Column::const_iterator::operator++() { + m_lineStart = m_lineEnd; + AnsiSkippingString const ¤t_line = m_column.m_string; + if (m_lineStart != current_line.end() && *m_lineStart == '\n') { + m_lineStart++; + } else { + while (m_lineStart != current_line.end() && isWhitespace(*m_lineStart)) { + ++m_lineStart; + } + } + + if (m_lineStart != current_line.end()) { calcLength(); } + return *this; + } + + Column::const_iterator Column::const_iterator::operator++(int) { + const_iterator prev(*this); + operator++(); + return prev; + } + + std::ostream &operator<<(std::ostream &os, Column const &col) { + bool first = true; + for (auto line : col) { + if (first) { + first = false; + } else { + os << '\n'; + } + os << line; + } + return os; + } + + Column Spacer(size_t spaceWidth) { + Column ret{""}; + ret.width(spaceWidth); + return ret; + } + + Columns::iterator::iterator(Columns const &columns, EndTag) + : m_columns(columns.m_columns), m_activeIterators(0) { + m_iterators.reserve(m_columns.size()); + for (auto const &col : m_columns) { + m_iterators.push_back(col.end()); + } + } + + Columns::iterator::iterator(Columns const &columns) + : m_columns(columns.m_columns), m_activeIterators(m_columns.size()) { + m_iterators.reserve(m_columns.size()); + for (auto const &col : m_columns) { + m_iterators.push_back(col.begin()); + } + } + + std::string Columns::iterator::operator*() const { + std::string row, padding; + + for (size_t i = 0; i < m_columns.size(); ++i) { + const auto width = m_columns[i].width(); + if (m_iterators[i] != m_columns[i].end()) { + std::string col = *m_iterators[i]; + row += padding; + row += col; + + padding.clear(); + if (col.size() < width) { + padding.append(width - col.size(), ' '); + } + } else { + padding.append(width, ' '); + } + } + return row; + } + + Columns::iterator &Columns::iterator::operator++() { + for (size_t i = 0; i < m_columns.size(); ++i) { + if (m_iterators[i] != m_columns[i].end()) { + ++m_iterators[i]; + } + } + return *this; + } + + Columns::iterator Columns::iterator::operator++(int) { + iterator prev(*this); + operator++(); + return prev; + } + + std::ostream &operator<<(std::ostream &os, Columns const &cols) { + bool first = true; + for (auto line : cols) { + if (first) { + first = false; + } else { + os << '\n'; + } + os << line; + } + return os; + } + + Columns operator+(Column const &lhs, Column const &rhs) { + Columns cols; + cols += lhs; + cols += rhs; + return cols; + } + Columns operator+(Column &&lhs, Column &&rhs) { + Columns cols; + cols += CATCH_MOVE(lhs); + cols += CATCH_MOVE(rhs); + return cols; + } + + Columns &operator+=(Columns &lhs, Column const &rhs) { + lhs.m_columns.push_back(rhs); + return lhs; + } + Columns &operator+=(Columns &lhs, Column &&rhs) { + lhs.m_columns.push_back(CATCH_MOVE(rhs)); + return lhs; + } + Columns operator+(Columns const &lhs, Column const &rhs) { + auto combined(lhs); + combined += rhs; + return combined; + } + Columns operator+(Columns &&lhs, Column &&rhs) { + lhs += CATCH_MOVE(rhs); + return CATCH_MOVE(lhs); + } + + } // namespace TextFlow +} // namespace Catch + +#include + +namespace Catch { + bool uncaught_exceptions() { +#if defined(CATCH_CONFIG_DISABLE_EXCEPTIONS) + return false; +#elif defined(CATCH_CONFIG_CPP17_UNCAUGHT_EXCEPTIONS) + return std::uncaught_exceptions() > 0; +#else + return std::uncaught_exception(); +#endif + } +} // end namespace Catch + +namespace Catch { + + WildcardPattern::WildcardPattern(std::string const &pattern, + CaseSensitive caseSensitivity) + : m_caseSensitivity(caseSensitivity), m_pattern(normaliseString(pattern)) { + if (startsWith(m_pattern, '*')) { + m_pattern = m_pattern.substr(1); + m_wildcard = WildcardAtStart; + } + if (endsWith(m_pattern, '*')) { + m_pattern = m_pattern.substr(0, m_pattern.size() - 1); + m_wildcard = static_cast(m_wildcard | WildcardAtEnd); + } + } + + bool WildcardPattern::matches(std::string const &str) const { + switch (m_wildcard) { + case NoWildcard: + return m_pattern == normaliseString(str); + case WildcardAtStart: + return endsWith(normaliseString(str), m_pattern); + case WildcardAtEnd: + return startsWith(normaliseString(str), m_pattern); + case WildcardAtBothEnds: + return contains(normaliseString(str), m_pattern); + default: + CATCH_INTERNAL_ERROR("Unknown enum"); + } + } + + std::string WildcardPattern::normaliseString(std::string const &str) const { + return trim(m_caseSensitivity == CaseSensitive::No ? toLower(str) : str); + } +} // namespace Catch + +// Note: swapping these two includes around causes MSVC to error out +// while in /permissive- mode. No, I don't know why. +// Tested on VS 2019, 18.{3, 4}.x + +#include +#include +#include + +namespace Catch { + + 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; + } + CATCH_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; + } + CATCH_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"_sr + << std::uppercase << std::hex << std::setfill('0') << std::setw(2) + << static_cast(c); + os.flags(f); + } + + constexpr bool shouldNewline(XmlFormatting fmt) { + return !!(static_cast>(fmt & XmlFormatting::Newline)); + } + + constexpr bool shouldIndent(XmlFormatting fmt) { + return !!(static_cast>(fmt & XmlFormatting::Indent)); + } + + } // anonymous namespace + + void XmlEncode::encodeTo(std::ostream &os) const { + // Apostrophe escaping not necessary if we always use " to write attributes + // (see: http://www.w3.org/TR/xml/#syntax) + size_t last_start = 0; + auto write_to = [&](size_t idx) { + if (last_start < idx) { + os << m_str.substr(last_start, idx - last_start); + } + last_start = idx + 1; + }; + + for (std::size_t idx = 0; idx < m_str.size(); ++idx) { + unsigned char c = static_cast(m_str[idx]); + switch (c) { + case '<': + write_to(idx); + os << "<"_sr; + break; + case '&': + write_to(idx); + os << "&"_sr; + break; + + case '>': + // See: http://www.w3.org/TR/xml/#syntax + if (idx > 2 && m_str[idx - 1] == ']' && m_str[idx - 2] == ']') { + write_to(idx); + os << ">"_sr; + } + break; + + case '\"': + if (m_forWhat == ForAttributes) { + write_to(idx); + os << """_sr; + } + break; + + default: + // Check for control characters and invalid utf-8 + + // Escape control characters in standard ascii + // see + // http://stackoverflow.com/questions/404107/why-are-control-characters-illegal-in-xml-1-0 + if (c < 0x09 || (c > 0x0D && c < 0x20) || c == 0x7F) { + write_to(idx); + hexEscapeChar(os, c); + break; + } + + // Plain ASCII: Write it to stream + if (c < 0x7F) { + 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) { + write_to(idx); + 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()) { + write_to(idx); + 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) { + unsigned char nc = static_cast(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) || (0x80 <= value && value < 0x800 && encBytes > 2) || (0x800 < value && value < 0x10000 && encBytes > 3) || + // Encoded value out of range + (value >= 0x110000)) { + write_to(idx); + hexEscapeChar(os, c); + break; + } + + // If we got here, this is in fact a valid(ish) utf-8 sequence + idx += encBytes - 1; + break; + } + } + + write_to(m_str.size()); + } + + std::ostream &operator<<(std::ostream &os, XmlEncode const &xmlEncode) { + xmlEncode.encodeTo(os); + return os; + } + + XmlWriter::ScopedElement::ScopedElement(XmlWriter *writer, XmlFormatting fmt) + : m_writer(writer), m_fmt(fmt) {} + + XmlWriter::ScopedElement::ScopedElement(ScopedElement &&other) noexcept + : m_writer(other.m_writer), m_fmt(other.m_fmt) { + other.m_writer = nullptr; + other.m_fmt = XmlFormatting::None; + } + XmlWriter::ScopedElement &XmlWriter::ScopedElement::operator=(ScopedElement &&other) noexcept { + if (m_writer) { + m_writer->endElement(); + } + m_writer = other.m_writer; + other.m_writer = nullptr; + m_fmt = other.m_fmt; + other.m_fmt = XmlFormatting::None; + return *this; + } + + XmlWriter::ScopedElement::~ScopedElement() { + if (m_writer) { + m_writer->endElement(m_fmt); + } + } + + XmlWriter::ScopedElement & + XmlWriter::ScopedElement::writeText(StringRef text, XmlFormatting fmt) { + m_writer->writeText(text, fmt); + return *this; + } + + XmlWriter::ScopedElement & + XmlWriter::ScopedElement::writeAttribute(StringRef name, + StringRef attribute) { + m_writer->writeAttribute(name, attribute); + return *this; + } + + XmlWriter::XmlWriter(std::ostream &os) + : m_os(os) { + writeDeclaration(); + } + + XmlWriter::~XmlWriter() { + while (!m_tags.empty()) { + endElement(); + } + newlineIfNecessary(); + } + + XmlWriter &XmlWriter::startElement(std::string const &name, XmlFormatting fmt) { + ensureTagClosed(); + newlineIfNecessary(); + if (shouldIndent(fmt)) { + m_os << m_indent; + m_indent += " "; + } + m_os << '<' << name; + m_tags.push_back(name); + m_tagIsOpen = true; + applyFormatting(fmt); + return *this; + } + + XmlWriter::ScopedElement XmlWriter::scopedElement(std::string const &name, XmlFormatting fmt) { + ScopedElement scoped(this, fmt); + startElement(name, fmt); + return scoped; + } + + XmlWriter &XmlWriter::endElement(XmlFormatting fmt) { + m_indent = m_indent.substr(0, m_indent.size() - 2); + + if (m_tagIsOpen) { + m_os << "/>"; + m_tagIsOpen = false; + } else { + newlineIfNecessary(); + if (shouldIndent(fmt)) { + m_os << m_indent; + } + m_os << "'; + } + m_os << std::flush; + applyFormatting(fmt); + m_tags.pop_back(); + return *this; + } + + XmlWriter &XmlWriter::writeAttribute(StringRef name, + StringRef attribute) { + if (!name.empty() && !attribute.empty()) + m_os << ' ' << name << "=\"" << XmlEncode(attribute, XmlEncode::ForAttributes) << '"'; + return *this; + } + + XmlWriter &XmlWriter::writeAttribute(StringRef name, bool attribute) { + writeAttribute(name, (attribute ? "true"_sr : "false"_sr)); + return *this; + } + + XmlWriter &XmlWriter::writeAttribute(StringRef name, + char const *attribute) { + writeAttribute(name, StringRef(attribute)); + return *this; + } + + XmlWriter &XmlWriter::writeText(StringRef text, XmlFormatting fmt) { + CATCH_ENFORCE(!m_tags.empty(), "Cannot write text as top level element"); + if (!text.empty()) { + bool tagWasOpen = m_tagIsOpen; + ensureTagClosed(); + if (tagWasOpen && shouldIndent(fmt)) { + m_os << m_indent; + } + m_os << XmlEncode(text, XmlEncode::ForTextNodes); + applyFormatting(fmt); + } + return *this; + } + + XmlWriter &XmlWriter::writeComment(StringRef text, XmlFormatting fmt) { + ensureTagClosed(); + if (shouldIndent(fmt)) { + m_os << m_indent; + } + m_os << ""; + applyFormatting(fmt); + return *this; + } + + void XmlWriter::writeStylesheetRef(StringRef url) { + m_os << R"()" << '\n'; + } + + void XmlWriter::ensureTagClosed() { + if (m_tagIsOpen) { + m_os << '>' << std::flush; + newlineIfNecessary(); + m_tagIsOpen = false; + } + } + + void XmlWriter::applyFormatting(XmlFormatting fmt) { + m_needsNewline = shouldNewline(fmt); + } + + void XmlWriter::writeDeclaration() { + m_os << R"()" << '\n'; + } + + void XmlWriter::newlineIfNecessary() { + if (m_needsNewline) { + m_os << '\n' + << std::flush; + m_needsNewline = false; + } + } +} // namespace Catch + +namespace Catch { + namespace Matchers { + + std::string MatcherUntypedBase::toString() const { + if (m_cachedToString.empty()) { + m_cachedToString = describe(); + } + return m_cachedToString; + } + + MatcherUntypedBase::~MatcherUntypedBase() = default; + + } // namespace Matchers +} // namespace Catch + +namespace Catch { + namespace Matchers { + + std::string IsEmptyMatcher::describe() const { + return "is empty"; + } + + std::string HasSizeMatcher::describe() const { + ReusableStringStream sstr; + sstr << "has size == " << m_target_size; + return sstr.str(); + } + + IsEmptyMatcher IsEmpty() { + return {}; + } + + HasSizeMatcher SizeIs(std::size_t sz) { + return HasSizeMatcher{sz}; + } + + } // end namespace Matchers +} // end namespace Catch + +namespace Catch { + namespace Matchers { + + bool ExceptionMessageMatcher::match(std::exception const &ex) const { + return ex.what() == m_message; + } + + std::string ExceptionMessageMatcher::describe() const { + return "exception message matches \"" + m_message + '"'; + } + + ExceptionMessageMatcher Message(std::string const &message) { + return ExceptionMessageMatcher(message); + } + + } // namespace Matchers +} // namespace Catch + +#include +#include +#include +#include +#include +#include +#include + +namespace Catch { + namespace { + + template + bool almostEqualUlps(FP lhs, FP rhs, uint64_t maxUlpDiff) { + // Comparison with NaN should always be false. + // This way we can rule it out before getting into the ugly details + if (Catch::isnan(lhs) || Catch::isnan(rhs)) { + return false; + } + + // This should also handle positive and negative zeros, infinities + const auto ulpDist = ulpDistance(lhs, rhs); + + return ulpDist <= maxUlpDiff; + } + + template + FP step(FP start, FP direction, uint64_t steps) { + for (uint64_t i = 0; i < steps; ++i) { + start = Catch::nextafter(start, direction); + } + return start; + } + + // Performs equivalent check of std::fabs(lhs - rhs) <= margin + // But without the subtraction to allow for INFINITY in comparison + bool marginComparison(double lhs, double rhs, double margin) { + return (lhs + margin >= rhs) && (rhs + margin >= lhs); + } + + template + void write(std::ostream &out, FloatingPoint num) { + out << std::scientific + << std::setprecision(std::numeric_limits::max_digits10 - 1) + << num; + } + + } // end anonymous namespace + + namespace Matchers { + namespace Detail { + + enum class FloatingPointKind : uint8_t { + Float, + Double + }; + + } // end namespace Detail + + WithinAbsMatcher::WithinAbsMatcher(double target, double margin) + : m_target{target}, m_margin{margin} { + CATCH_ENFORCE(margin >= 0, "Invalid margin: " << margin << '.' << " Margin has to be non-negative."); + } + + // Performs equivalent check of std::fabs(lhs - rhs) <= margin + // But without the subtraction to allow for INFINITY in comparison + bool WithinAbsMatcher::match(double const &matchee) const { + return (matchee + m_margin >= m_target) && (m_target + m_margin >= matchee); + } + + std::string WithinAbsMatcher::describe() const { + return "is within " + ::Catch::Detail::stringify(m_margin) + " of " + ::Catch::Detail::stringify(m_target); + } + + WithinUlpsMatcher::WithinUlpsMatcher(double target, uint64_t ulps, Detail::FloatingPointKind baseType) + : m_target{target}, m_ulps{ulps}, m_type{baseType} { + CATCH_ENFORCE(m_type == Detail::FloatingPointKind::Double + || m_ulps < (std::numeric_limits::max)(), + "Provided ULP is impossibly large for a float comparison."); + CATCH_ENFORCE(std::numeric_limits::is_iec559, + "WithinUlp matcher only supports platforms with " + "IEEE-754 compatible floating point representation"); + } + +#if defined(__clang__) +#pragma clang diagnostic push +// Clang <3.5 reports on the default branch in the switch below +#pragma clang diagnostic ignored "-Wunreachable-code" +#endif + + bool WithinUlpsMatcher::match(double const &matchee) const { + switch (m_type) { + case Detail::FloatingPointKind::Float: + return almostEqualUlps(static_cast(matchee), static_cast(m_target), m_ulps); + case Detail::FloatingPointKind::Double: + return almostEqualUlps(matchee, m_target, m_ulps); + default: + CATCH_INTERNAL_ERROR("Unknown Detail::FloatingPointKind value"); + } + } + +#if defined(__clang__) +#pragma clang diagnostic pop +#endif + + std::string WithinUlpsMatcher::describe() const { + std::stringstream ret; + + ret << "is within " << m_ulps << " ULPs of "; + + if (m_type == Detail::FloatingPointKind::Float) { + write(ret, static_cast(m_target)); + ret << 'f'; + } else { + write(ret, m_target); + } + + ret << " (["; + if (m_type == Detail::FloatingPointKind::Double) { + write(ret, + step(m_target, + -std::numeric_limits::infinity(), + m_ulps)); + ret << ", "; + write(ret, + step(m_target, + std::numeric_limits::infinity(), + m_ulps)); + } else { + // We have to cast INFINITY to float because of MinGW, see #1782 + write(ret, + step(static_cast(m_target), + -std::numeric_limits::infinity(), + m_ulps)); + ret << ", "; + write(ret, + step(static_cast(m_target), + std::numeric_limits::infinity(), + m_ulps)); + } + ret << "])"; + + return ret.str(); + } + + WithinRelMatcher::WithinRelMatcher(double target, double epsilon) + : m_target(target), m_epsilon(epsilon) { + CATCH_ENFORCE(m_epsilon >= 0., "Relative comparison with epsilon < 0 does not make sense."); + CATCH_ENFORCE(m_epsilon < 1., "Relative comparison with epsilon >= 1 does not make sense."); + } + + bool WithinRelMatcher::match(double const &matchee) const { + const auto relMargin = m_epsilon * (std::max)(std::fabs(matchee), std::fabs(m_target)); + return marginComparison(matchee, m_target, + std::isinf(relMargin) ? 0 : relMargin); + } + + std::string WithinRelMatcher::describe() const { + Catch::ReusableStringStream sstr; + sstr << "and " << ::Catch::Detail::stringify(m_target) << " are within " << m_epsilon * 100. << "% of each other"; + return sstr.str(); + } + + WithinUlpsMatcher WithinULP(double target, uint64_t maxUlpDiff) { + return WithinUlpsMatcher(target, maxUlpDiff, Detail::FloatingPointKind::Double); + } + + WithinUlpsMatcher WithinULP(float target, uint64_t maxUlpDiff) { + return WithinUlpsMatcher(target, maxUlpDiff, Detail::FloatingPointKind::Float); + } + + WithinAbsMatcher WithinAbs(double target, double margin) { + return WithinAbsMatcher(target, margin); + } + + WithinRelMatcher WithinRel(double target, double eps) { + return WithinRelMatcher(target, eps); + } + + WithinRelMatcher WithinRel(double target) { + return WithinRelMatcher(target, std::numeric_limits::epsilon() * 100); + } + + WithinRelMatcher WithinRel(float target, float eps) { + return WithinRelMatcher(target, eps); + } + + WithinRelMatcher WithinRel(float target) { + return WithinRelMatcher(target, std::numeric_limits::epsilon() * 100); + } + + bool IsNaNMatcher::match(double const &matchee) const { + return std::isnan(matchee); + } + + std::string IsNaNMatcher::describe() const { + using namespace std::string_literals; + return "is NaN"s; + } + + IsNaNMatcher IsNaN() { + return IsNaNMatcher(); + } + + } // namespace Matchers +} // namespace Catch + +std::string Catch::Matchers::Detail::finalizeDescription(const std::string &desc) { + if (desc.empty()) { + return "matches undescribed predicate"; + } else { + return "matches predicate: \"" + desc + '"'; + } +} + +namespace Catch { + namespace Matchers { + std::string AllTrueMatcher::describe() const { + return "contains only true"; + } + + AllTrueMatcher AllTrue() { + return AllTrueMatcher{}; + } + + std::string NoneTrueMatcher::describe() const { + return "contains no true"; + } + + NoneTrueMatcher NoneTrue() { + return NoneTrueMatcher{}; + } + + std::string AnyTrueMatcher::describe() const { + return "contains at least one true"; + } + + AnyTrueMatcher AnyTrue() { + return AnyTrueMatcher{}; + } + } // namespace Matchers +} // namespace Catch + +#include + +namespace Catch { + namespace Matchers { + + CasedString::CasedString(std::string const &str, CaseSensitive caseSensitivity) + : m_caseSensitivity(caseSensitivity), m_str(adjustString(str)) {} + std::string CasedString::adjustString(std::string const &str) const { + return m_caseSensitivity == CaseSensitive::No + ? toLower(str) + : str; + } + StringRef CasedString::caseSensitivitySuffix() const { + return m_caseSensitivity == CaseSensitive::Yes + ? StringRef() + : " (case insensitive)"_sr; + } + + StringMatcherBase::StringMatcherBase(StringRef operation, CasedString const &comparator) + : m_comparator(comparator), m_operation(operation) { + } + + std::string StringMatcherBase::describe() const { + std::string description; + description.reserve(5 + m_operation.size() + m_comparator.m_str.size() + m_comparator.caseSensitivitySuffix().size()); + description += m_operation; + description += ": \""; + description += m_comparator.m_str; + description += '"'; + description += m_comparator.caseSensitivitySuffix(); + return description; + } + + StringEqualsMatcher::StringEqualsMatcher(CasedString const &comparator) + : StringMatcherBase("equals"_sr, comparator) {} + + bool StringEqualsMatcher::match(std::string const &source) const { + return m_comparator.adjustString(source) == m_comparator.m_str; + } + + StringContainsMatcher::StringContainsMatcher(CasedString const &comparator) + : StringMatcherBase("contains"_sr, comparator) {} + + bool StringContainsMatcher::match(std::string const &source) const { + return contains(m_comparator.adjustString(source), m_comparator.m_str); + } + + StartsWithMatcher::StartsWithMatcher(CasedString const &comparator) + : StringMatcherBase("starts with"_sr, comparator) {} + + bool StartsWithMatcher::match(std::string const &source) const { + return startsWith(m_comparator.adjustString(source), m_comparator.m_str); + } + + EndsWithMatcher::EndsWithMatcher(CasedString const &comparator) + : StringMatcherBase("ends with"_sr, comparator) {} + + bool EndsWithMatcher::match(std::string const &source) const { + return endsWith(m_comparator.adjustString(source), m_comparator.m_str); + } + + RegexMatcher::RegexMatcher(std::string regex, CaseSensitive caseSensitivity) + : m_regex(CATCH_MOVE(regex)), m_caseSensitivity(caseSensitivity) {} + + bool RegexMatcher::match(std::string const &matchee) const { + auto flags = std::regex::ECMAScript; // ECMAScript is the default syntax option anyway + if (m_caseSensitivity == CaseSensitive::No) { + flags |= std::regex::icase; + } + auto reg = std::regex(m_regex, flags); + return std::regex_match(matchee, reg); + } + + std::string RegexMatcher::describe() const { + return "matches " + ::Catch::Detail::stringify(m_regex) + ((m_caseSensitivity == CaseSensitive::Yes) ? " case sensitively" : " case insensitively"); + } + + StringEqualsMatcher Equals(std::string const &str, CaseSensitive caseSensitivity) { + return StringEqualsMatcher(CasedString(str, caseSensitivity)); + } + StringContainsMatcher ContainsSubstring(std::string const &str, CaseSensitive caseSensitivity) { + return StringContainsMatcher(CasedString(str, caseSensitivity)); + } + EndsWithMatcher EndsWith(std::string const &str, CaseSensitive caseSensitivity) { + return EndsWithMatcher(CasedString(str, caseSensitivity)); + } + StartsWithMatcher StartsWith(std::string const &str, CaseSensitive caseSensitivity) { + return StartsWithMatcher(CasedString(str, caseSensitivity)); + } + + RegexMatcher Matches(std::string const ®ex, CaseSensitive caseSensitivity) { + return RegexMatcher(regex, caseSensitivity); + } + + } // namespace Matchers +} // namespace Catch + +namespace Catch { + namespace Matchers { + MatcherGenericBase::~MatcherGenericBase() = default; + + namespace Detail { + + std::string describe_multi_matcher(StringRef combine, std::string const *descriptions_begin, std::string const *descriptions_end) { + std::string description; + std::size_t combined_size = 4; + for (auto desc = descriptions_begin; desc != descriptions_end; ++desc) { + combined_size += desc->size(); + } + combined_size += static_cast(descriptions_end - descriptions_begin - 1) * combine.size(); + + description.reserve(combined_size); + + description += "( "; + bool first = true; + for (auto desc = descriptions_begin; desc != descriptions_end; ++desc) { + if (first) + first = false; + else + description += combine; + description += *desc; + } + description += " )"; + return description; + } + + } // namespace Detail + } // namespace Matchers +} // namespace Catch + +namespace Catch { + + // This is the general overload that takes a any string matcher + // There is another overload, in catch_assertionhandler.h/.cpp, that only takes a string and infers + // the Equals matcher (so the header does not mention matchers) + void handleExceptionMatchExpr(AssertionHandler &handler, StringMatcher const &matcher) { + std::string exceptionMessage = Catch::translateActiveException(); + MatchExpr expr(CATCH_MOVE(exceptionMessage), matcher); + handler.handleExpr(expr); + } + +} // namespace Catch + +#include + +namespace Catch { + + AutomakeReporter::~AutomakeReporter() = default; + + void AutomakeReporter::testCaseEnded(TestCaseStats const &_testCaseStats) { + // Possible values to emit are PASS, XFAIL, SKIP, FAIL, XPASS and ERROR. + m_stream << ":test-result: "; + if (_testCaseStats.totals.testCases.skipped > 0) { + m_stream << "SKIP"; + } else if (_testCaseStats.totals.assertions.allPassed()) { + m_stream << "PASS"; + } else if (_testCaseStats.totals.assertions.allOk()) { + m_stream << "XFAIL"; + } else { + m_stream << "FAIL"; + } + m_stream << ' ' << _testCaseStats.testInfo->name << '\n'; + StreamingReporterBase::testCaseEnded(_testCaseStats); + } + + void AutomakeReporter::skipTest(TestCaseInfo const &testInfo) { + m_stream << ":test-result: SKIP " << testInfo.name << '\n'; + } + +} // end namespace Catch + +namespace Catch { + ReporterBase::ReporterBase(ReporterConfig &&config) + : IEventListener(config.fullConfig()), m_wrapped_stream(CATCH_MOVE(config).takeStream()), m_stream(m_wrapped_stream->stream()), m_colour(makeColourImpl(config.colourMode(), m_wrapped_stream.get())), m_customOptions(config.customOptions()) {} + + ReporterBase::~ReporterBase() = default; + + void ReporterBase::listReporters( + std::vector const &descriptions) { + defaultListReporters(m_stream, descriptions, m_config->verbosity()); + } + + void ReporterBase::listListeners( + std::vector const &descriptions) { + defaultListListeners(m_stream, descriptions, m_config->verbosity()); + } + + void ReporterBase::listTests(std::vector const &tests) { + defaultListTests(m_stream, + m_colour.get(), + tests, + m_config->hasTestFilters(), + m_config->verbosity()); + } + + void ReporterBase::listTags(std::vector const &tags) { + defaultListTags(m_stream, tags, m_config->hasTestFilters(), m_config->verbosity()); + } + +} // namespace Catch + +#include + +namespace Catch { + namespace { + + // Colour::LightGrey + static constexpr Colour::Code compactDimColour = Colour::FileName; + +#ifdef CATCH_PLATFORM_MAC + static constexpr Catch::StringRef compactFailedString = "FAILED"_sr; + static constexpr Catch::StringRef compactPassedString = "PASSED"_sr; +#else + static constexpr Catch::StringRef compactFailedString = "failed"_sr; + static constexpr Catch::StringRef compactPassedString = "passed"_sr; +#endif + + // Implementation of CompactReporter formatting + class AssertionPrinter { + public: + AssertionPrinter &operator=(AssertionPrinter const &) = delete; + AssertionPrinter(AssertionPrinter const &) = delete; + AssertionPrinter(std::ostream &_stream, AssertionStats const &_stats, bool _printInfoMessages, ColourImpl *colourImpl_) + : stream(_stream) + , result(_stats.assertionResult) + , messages(_stats.infoMessages) + , itMessage(_stats.infoMessages.begin()) + , printInfoMessages(_printInfoMessages) + , colourImpl(colourImpl_) {} + + void print() { + printSourceInfo(); + + itMessage = messages.begin(); + + switch (result.getResultType()) { + case ResultWas::Ok: + printResultType(Colour::ResultSuccess, compactPassedString); + printOriginalExpression(); + printReconstructedExpression(); + if (!result.hasExpression()) + printRemainingMessages(Colour::None); + else + printRemainingMessages(); + break; + case ResultWas::ExpressionFailed: + if (result.isOk()) + printResultType(Colour::ResultSuccess, compactFailedString + " - but was ok"_sr); + else + printResultType(Colour::Error, compactFailedString); + printOriginalExpression(); + printReconstructedExpression(); + printRemainingMessages(); + break; + case ResultWas::ThrewException: + printResultType(Colour::Error, compactFailedString); + printIssue("unexpected exception with message:"); + printMessage(); + printExpressionWas(); + printRemainingMessages(); + break; + case ResultWas::FatalErrorCondition: + printResultType(Colour::Error, compactFailedString); + printIssue("fatal error condition with message:"); + printMessage(); + printExpressionWas(); + printRemainingMessages(); + break; + case ResultWas::DidntThrowException: + printResultType(Colour::Error, compactFailedString); + printIssue("expected exception, got none"); + printExpressionWas(); + printRemainingMessages(); + break; + case ResultWas::Info: + printResultType(Colour::None, "info"_sr); + printMessage(); + printRemainingMessages(); + break; + case ResultWas::Warning: + printResultType(Colour::None, "warning"_sr); + printMessage(); + printRemainingMessages(); + break; + case ResultWas::ExplicitFailure: + printResultType(Colour::Error, compactFailedString); + printIssue("explicitly"); + printRemainingMessages(Colour::None); + break; + case ResultWas::ExplicitSkip: + printResultType(Colour::Skip, "skipped"_sr); + printMessage(); + printRemainingMessages(); + break; + // These cases are here to prevent compiler warnings + case ResultWas::Unknown: + case ResultWas::FailureBit: + case ResultWas::Exception: + printResultType(Colour::Error, "** internal error **"); + break; + } + } + + private: + void printSourceInfo() const { + stream << colourImpl->guardColour(Colour::FileName) + << result.getSourceInfo() << ':'; + } + + void printResultType(Colour::Code colour, StringRef passOrFail) const { + if (!passOrFail.empty()) { + stream << colourImpl->guardColour(colour) << ' ' << passOrFail; + stream << ':'; + } + } + + void printIssue(char const *issue) const { + stream << ' ' << issue; + } + + void printExpressionWas() { + if (result.hasExpression()) { + stream << ';'; + { + stream << colourImpl->guardColour(compactDimColour) << " expression was:"; + } + printOriginalExpression(); + } + } + + void printOriginalExpression() const { + if (result.hasExpression()) { + stream << ' ' << result.getExpression(); + } + } + + void printReconstructedExpression() const { + if (result.hasExpandedExpression()) { + stream << colourImpl->guardColour(compactDimColour) << " for: "; + stream << result.getExpandedExpression(); + } + } + + void printMessage() { + if (itMessage != messages.end()) { + stream << " '" << itMessage->message << '\''; + ++itMessage; + } + } + + void printRemainingMessages(Colour::Code colour = compactDimColour) { + if (itMessage == messages.end()) + return; + + const auto itEnd = messages.cend(); + const auto N = static_cast(itEnd - itMessage); + + stream << colourImpl->guardColour(colour) << " with " + << pluralise(N, "message"_sr) << ':'; + + while (itMessage != itEnd) { + // If this assertion is a warning ignore any INFO messages + if (printInfoMessages || itMessage->type != ResultWas::Info) { + printMessage(); + if (itMessage != itEnd) { + stream << colourImpl->guardColour(compactDimColour) << " and"; + } + continue; + } + ++itMessage; + } + } + + private: + std::ostream &stream; + AssertionResult const &result; + std::vector const &messages; + std::vector::const_iterator itMessage; + bool printInfoMessages; + ColourImpl *colourImpl; + }; + + } // namespace + + std::string CompactReporter::getDescription() { + return "Reports test results on a single line, suitable for IDEs"; + } + + void CompactReporter::noMatchingTestCases(StringRef unmatchedSpec) { + m_stream << "No test cases matched '" << unmatchedSpec << "'\n"; + } + + void CompactReporter::testRunStarting(TestRunInfo const &) { + if (m_config->testSpec().hasFilters()) { + m_stream << m_colour->guardColour(Colour::BrightYellow) + << "Filters: " + << m_config->testSpec() + << '\n'; + } + m_stream << "RNG seed: " << getSeed() << '\n' + << std::flush; + } + + void CompactReporter::assertionEnded(AssertionStats const &_assertionStats) { + AssertionResult const &result = _assertionStats.assertionResult; + + bool printInfoMessages = true; + + // Drop out if result was successful and we're not printing those + if (!m_config->includeSuccessfulResults() && result.isOk()) { + if (result.getResultType() != ResultWas::Warning && result.getResultType() != ResultWas::ExplicitSkip) + return; + printInfoMessages = false; + } + + AssertionPrinter printer(m_stream, _assertionStats, printInfoMessages, m_colour.get()); + printer.print(); + + m_stream << '\n' + << std::flush; + } + + void CompactReporter::sectionEnded(SectionStats const &_sectionStats) { + double dur = _sectionStats.durationInSeconds; + if (shouldShowDuration(*m_config, dur)) { + m_stream << getFormattedDuration(dur) << " s: " << _sectionStats.sectionInfo.name << '\n' + << std::flush; + } + } + + void CompactReporter::testRunEnded(TestRunStats const &_testRunStats) { + printTestRunTotals(m_stream, *m_colour, _testRunStats.totals); + m_stream << "\n\n" + << std::flush; + StreamingReporterBase::testRunEnded(_testRunStats); + } + + CompactReporter::~CompactReporter() = default; + +} // end namespace Catch + +#include + +#if defined(_MSC_VER) +#pragma warning(push) +#pragma warning(disable : 4061) // Not all labels are EXPLICITLY handled in switch + // Note that 4062 (not all labels are handled and default is missing) is enabled +#endif + +#if defined(__clang__) +#pragma clang diagnostic push +// For simplicity, benchmarking-only helpers are always enabled +#pragma clang diagnostic ignored "-Wunused-function" +#endif + +namespace Catch { + + namespace { + + // Formatter impl for ConsoleReporter + class ConsoleAssertionPrinter { + public: + ConsoleAssertionPrinter &operator=(ConsoleAssertionPrinter const &) = delete; + ConsoleAssertionPrinter(ConsoleAssertionPrinter const &) = delete; + ConsoleAssertionPrinter(std::ostream &_stream, AssertionStats const &_stats, ColourImpl *colourImpl_, bool _printInfoMessages) + : stream(_stream), stats(_stats), result(_stats.assertionResult), colour(Colour::None), messages(_stats.infoMessages), colourImpl(colourImpl_), printInfoMessages(_printInfoMessages) { + switch (result.getResultType()) { + case ResultWas::Ok: + colour = Colour::Success; + passOrFail = "PASSED"_sr; + // if( result.hasMessage() ) + if (messages.size() == 1) + messageLabel = "with message"_sr; + if (messages.size() > 1) + messageLabel = "with messages"_sr; + break; + case ResultWas::ExpressionFailed: + if (result.isOk()) { + colour = Colour::Success; + passOrFail = "FAILED - but was ok"_sr; + } else { + colour = Colour::Error; + passOrFail = "FAILED"_sr; + } + if (messages.size() == 1) + messageLabel = "with message"_sr; + if (messages.size() > 1) + messageLabel = "with messages"_sr; + break; + case ResultWas::ThrewException: + colour = Colour::Error; + passOrFail = "FAILED"_sr; + // todo switch + switch (messages.size()) { + case 0: + messageLabel = "due to unexpected exception with "_sr; + break; + case 1: + messageLabel = "due to unexpected exception with message"_sr; + break; + default: + messageLabel = "due to unexpected exception with messages"_sr; + break; + } + break; + case ResultWas::FatalErrorCondition: + colour = Colour::Error; + passOrFail = "FAILED"_sr; + messageLabel = "due to a fatal error condition"_sr; + break; + case ResultWas::DidntThrowException: + colour = Colour::Error; + passOrFail = "FAILED"_sr; + messageLabel = "because no exception was thrown where one was expected"_sr; + break; + case ResultWas::Info: + messageLabel = "info"_sr; + break; + case ResultWas::Warning: + messageLabel = "warning"_sr; + break; + case ResultWas::ExplicitFailure: + passOrFail = "FAILED"_sr; + colour = Colour::Error; + if (messages.size() == 1) + messageLabel = "explicitly with message"_sr; + if (messages.size() > 1) + messageLabel = "explicitly with messages"_sr; + break; + case ResultWas::ExplicitSkip: + colour = Colour::Skip; + passOrFail = "SKIPPED"_sr; + if (messages.size() == 1) + messageLabel = "explicitly with message"_sr; + if (messages.size() > 1) + messageLabel = "explicitly with messages"_sr; + break; + // These cases are here to prevent compiler warnings + case ResultWas::Unknown: + case ResultWas::FailureBit: + case ResultWas::Exception: + passOrFail = "** internal error **"_sr; + colour = Colour::Error; + break; + } + } + + void print() const { + printSourceInfo(); + if (stats.totals.assertions.total() > 0) { + printResultType(); + printOriginalExpression(); + printReconstructedExpression(); + } else { + stream << '\n'; + } + printMessage(); + } + + private: + void printResultType() const { + if (!passOrFail.empty()) { + stream << colourImpl->guardColour(colour) << passOrFail << ":\n"; + } + } + void printOriginalExpression() const { + if (result.hasExpression()) { + stream << colourImpl->guardColour(Colour::OriginalExpression) + << " " << result.getExpressionInMacro() << '\n'; + } + } + void printReconstructedExpression() const { + if (result.hasExpandedExpression()) { + stream << "with expansion:\n"; + stream << colourImpl->guardColour(Colour::ReconstructedExpression) + << TextFlow::Column(result.getExpandedExpression()) + .indent(2) + << '\n'; + } + } + void printMessage() const { + if (!messageLabel.empty()) + stream << messageLabel << ':' << '\n'; + for (auto const &msg : messages) { + // If this assertion is a warning ignore any INFO messages + if (printInfoMessages || msg.type != ResultWas::Info) + stream << TextFlow::Column(msg.message).indent(2) << '\n'; + } + } + void printSourceInfo() const { + stream << colourImpl->guardColour(Colour::FileName) + << result.getSourceInfo() << ": "; + } + + std::ostream &stream; + AssertionStats const &stats; + AssertionResult const &result; + Colour::Code colour; + StringRef passOrFail; + StringRef messageLabel; + std::vector const &messages; + ColourImpl *colourImpl; + bool printInfoMessages; + }; + + std::size_t makeRatio(std::uint64_t number, std::uint64_t total) { + const auto ratio = total > 0 ? CATCH_CONFIG_CONSOLE_WIDTH * number / total : 0; + return (ratio == 0 && number > 0) ? 1 : static_cast(ratio); + } + + std::size_t & + findMax(std::size_t &i, std::size_t &j, std::size_t &k, std::size_t &l) { + if (i > j && i > k && i > l) + return i; + else if (j > k && j > l) + return j; + else if (k > l) + return k; + else + return l; + } + + struct ColumnBreak {}; + struct RowBreak {}; + struct OutputFlush {}; + + class Duration { + enum class Unit : uint8_t { + Auto, + Nanoseconds, + Microseconds, + Milliseconds, + Seconds, + Minutes + }; + static const uint64_t s_nanosecondsInAMicrosecond = 1000; + static const uint64_t s_nanosecondsInAMillisecond = 1000 * s_nanosecondsInAMicrosecond; + static const uint64_t s_nanosecondsInASecond = 1000 * s_nanosecondsInAMillisecond; + static const uint64_t s_nanosecondsInAMinute = 60 * s_nanosecondsInASecond; + + double m_inNanoseconds; + Unit m_units; + + public: + explicit Duration(double inNanoseconds, Unit units = Unit::Auto) + : m_inNanoseconds(inNanoseconds), m_units(units) { + if (m_units == Unit::Auto) { + if (m_inNanoseconds < s_nanosecondsInAMicrosecond) + m_units = Unit::Nanoseconds; + else if (m_inNanoseconds < s_nanosecondsInAMillisecond) + m_units = Unit::Microseconds; + else if (m_inNanoseconds < s_nanosecondsInASecond) + m_units = Unit::Milliseconds; + else if (m_inNanoseconds < s_nanosecondsInAMinute) + m_units = Unit::Seconds; + else + m_units = Unit::Minutes; + } + } + + auto value() const -> double { + switch (m_units) { + case Unit::Microseconds: + return m_inNanoseconds / static_cast(s_nanosecondsInAMicrosecond); + case Unit::Milliseconds: + return m_inNanoseconds / static_cast(s_nanosecondsInAMillisecond); + case Unit::Seconds: + return m_inNanoseconds / static_cast(s_nanosecondsInASecond); + case Unit::Minutes: + return m_inNanoseconds / static_cast(s_nanosecondsInAMinute); + default: + return m_inNanoseconds; + } + } + StringRef unitsAsString() const { + switch (m_units) { + case Unit::Nanoseconds: + return "ns"_sr; + case Unit::Microseconds: + return "us"_sr; + case Unit::Milliseconds: + return "ms"_sr; + case Unit::Seconds: + return "s"_sr; + case Unit::Minutes: + return "m"_sr; + default: + return "** internal error **"_sr; + } + } + friend auto operator<<(std::ostream &os, Duration const &duration) -> std::ostream & { + return os << duration.value() << ' ' << duration.unitsAsString(); + } + }; + } // namespace + + enum class Justification : uint8_t { + Left, + Right + }; + + struct ColumnInfo { + std::string name; + std::size_t width; + Justification justification; + }; + + class TablePrinter { + std::ostream &m_os; + std::vector m_columnInfos; + ReusableStringStream m_oss; + int m_currentColumn = -1; + bool m_isOpen = false; + + public: + TablePrinter(std::ostream &os, std::vector columnInfos) + : m_os(os), m_columnInfos(CATCH_MOVE(columnInfos)) {} + + auto columnInfos() const -> std::vector const & { + return m_columnInfos; + } + + void open() { + if (!m_isOpen) { + m_isOpen = true; + *this << RowBreak(); + + TextFlow::Columns headerCols; + for (auto const &info : m_columnInfos) { + assert(info.width > 2); + headerCols += TextFlow::Column(info.name).width(info.width - 2); + headerCols += TextFlow::Spacer(2); + } + m_os << headerCols << '\n'; + + m_os << lineOfChars('-') << '\n'; + } + } + void close() { + if (m_isOpen) { + *this << RowBreak(); + m_os << '\n' + << std::flush; + m_isOpen = false; + } + } + + template + friend TablePrinter &operator<<(TablePrinter &tp, T const &value) { + tp.m_oss << value; + return tp; + } + + friend TablePrinter &operator<<(TablePrinter &tp, ColumnBreak) { + auto colStr = tp.m_oss.str(); + const auto strSize = colStr.size(); + tp.m_oss.str(""); + tp.open(); + if (tp.m_currentColumn == static_cast(tp.m_columnInfos.size() - 1)) { + tp.m_currentColumn = -1; + tp.m_os << '\n'; + } + tp.m_currentColumn++; + + auto colInfo = tp.m_columnInfos[tp.m_currentColumn]; + auto padding = (strSize + 1 < colInfo.width) + ? std::string(colInfo.width - (strSize + 1), ' ') + : std::string(); + if (colInfo.justification == Justification::Left) + tp.m_os << colStr << padding << ' '; + else + tp.m_os << padding << colStr << ' '; + return tp; + } + + friend TablePrinter &operator<<(TablePrinter &tp, RowBreak) { + if (tp.m_currentColumn > 0) { + tp.m_os << '\n'; + tp.m_currentColumn = -1; + } + return tp; + } + + friend TablePrinter &operator<<(TablePrinter &tp, OutputFlush) { + tp.m_os << std::flush; + return tp; + } + }; + + ConsoleReporter::ConsoleReporter(ReporterConfig &&config) + : StreamingReporterBase(CATCH_MOVE(config)), m_tablePrinter(Detail::make_unique(m_stream, [&config]() -> std::vector { + if (config.fullConfig()->benchmarkNoAnalysis()) { + return { + {"benchmark name", CATCH_CONFIG_CONSOLE_WIDTH - 43, Justification::Left}, + {" samples", 14, Justification::Right}, + {" iterations", 14, Justification::Right}, + {" mean", 14, Justification::Right}}; + } else { + return { + {"benchmark name", CATCH_CONFIG_CONSOLE_WIDTH - 43, Justification::Left}, + {"samples mean std dev", 14, Justification::Right}, + {"iterations low mean low std dev", 14, Justification::Right}, + {"est run time high mean high std dev", 14, Justification::Right}}; + } + }())) { + m_preferences.shouldReportAllAssertionStarts = false; + } + + ConsoleReporter::~ConsoleReporter() = default; + + std::string ConsoleReporter::getDescription() { + return "Reports test results as plain lines of text"; + } + + void ConsoleReporter::noMatchingTestCases(StringRef unmatchedSpec) { + m_stream << "No test cases matched '" << unmatchedSpec << "'\n"; + } + + void ConsoleReporter::reportInvalidTestSpec(StringRef arg) { + m_stream << "Invalid Filter: " << arg << '\n'; + } + + void ConsoleReporter::assertionEnded(AssertionStats const &_assertionStats) { + AssertionResult const &result = _assertionStats.assertionResult; + + bool includeResults = m_config->includeSuccessfulResults() || !result.isOk(); + + // Drop out if result was successful but we're not printing them. + // TODO: Make configurable whether skips should be printed + if (!includeResults && result.getResultType() != ResultWas::Warning && result.getResultType() != ResultWas::ExplicitSkip) + return; + + lazyPrint(); + + ConsoleAssertionPrinter printer(m_stream, _assertionStats, m_colour.get(), includeResults); + printer.print(); + m_stream << '\n' + << std::flush; + } + + void ConsoleReporter::sectionStarting(SectionInfo const &_sectionInfo) { + m_tablePrinter->close(); + m_headerPrinted = false; + StreamingReporterBase::sectionStarting(_sectionInfo); + } + void ConsoleReporter::sectionEnded(SectionStats const &_sectionStats) { + m_tablePrinter->close(); + if (_sectionStats.missingAssertions) { + lazyPrint(); + auto guard = m_colour->guardColour(Colour::ResultError).engage(m_stream); + if (m_sectionStack.size() > 1) + m_stream << "\nNo assertions in section"; + else + m_stream << "\nNo assertions in test case"; + m_stream << " '" << _sectionStats.sectionInfo.name << "'\n\n" + << std::flush; + } + double dur = _sectionStats.durationInSeconds; + if (shouldShowDuration(*m_config, dur)) { + m_stream << getFormattedDuration(dur) << " s: " << _sectionStats.sectionInfo.name << '\n' + << std::flush; + } + if (m_headerPrinted) { + m_headerPrinted = false; + } + StreamingReporterBase::sectionEnded(_sectionStats); + } + + void ConsoleReporter::benchmarkPreparing(StringRef name) { + lazyPrintWithoutClosingBenchmarkTable(); + + auto nameCol = TextFlow::Column(static_cast(name)) + .width(m_tablePrinter->columnInfos()[0].width - 2); + + bool firstLine = true; + for (auto line : nameCol) { + if (!firstLine) + (*m_tablePrinter) << ColumnBreak() << ColumnBreak() << ColumnBreak(); + else + firstLine = false; + + (*m_tablePrinter) << line << ColumnBreak(); + } + } + + void ConsoleReporter::benchmarkStarting(BenchmarkInfo const &info) { + (*m_tablePrinter) << info.samples << ColumnBreak() + << info.iterations << ColumnBreak(); + if (!m_config->benchmarkNoAnalysis()) { + (*m_tablePrinter) + << Duration(info.estimatedDuration) << ColumnBreak(); + } + (*m_tablePrinter) << OutputFlush{}; + } + void ConsoleReporter::benchmarkEnded(BenchmarkStats<> const &stats) { + if (m_config->benchmarkNoAnalysis()) { + (*m_tablePrinter) << Duration(stats.mean.point.count()) << ColumnBreak(); + } else { + (*m_tablePrinter) << ColumnBreak() + << Duration(stats.mean.point.count()) << ColumnBreak() + << Duration(stats.mean.lower_bound.count()) << ColumnBreak() + << Duration(stats.mean.upper_bound.count()) << ColumnBreak() << ColumnBreak() + << Duration(stats.standardDeviation.point.count()) << ColumnBreak() + << Duration(stats.standardDeviation.lower_bound.count()) << ColumnBreak() + << Duration(stats.standardDeviation.upper_bound.count()) << ColumnBreak() << ColumnBreak() << ColumnBreak() << ColumnBreak() << ColumnBreak(); + } + } + + void ConsoleReporter::benchmarkFailed(StringRef error) { + auto guard = m_colour->guardColour(Colour::Red).engage(m_stream); + (*m_tablePrinter) + << "Benchmark failed (" << error << ')' + << ColumnBreak() << RowBreak(); + } + + void ConsoleReporter::testCaseEnded(TestCaseStats const &_testCaseStats) { + m_tablePrinter->close(); + StreamingReporterBase::testCaseEnded(_testCaseStats); + m_headerPrinted = false; + } + void ConsoleReporter::testRunEnded(TestRunStats const &_testRunStats) { + printTotalsDivider(_testRunStats.totals); + printTestRunTotals(m_stream, *m_colour, _testRunStats.totals); + m_stream << '\n' + << std::flush; + StreamingReporterBase::testRunEnded(_testRunStats); + } + void ConsoleReporter::testRunStarting(TestRunInfo const &_testRunInfo) { + StreamingReporterBase::testRunStarting(_testRunInfo); + if (m_config->testSpec().hasFilters()) { + m_stream << m_colour->guardColour(Colour::BrightYellow) << "Filters: " + << m_config->testSpec() << '\n'; + } + m_stream << "Randomness seeded to: " << getSeed() << '\n' + << std::flush; + } + + void ConsoleReporter::lazyPrint() { + m_tablePrinter->close(); + lazyPrintWithoutClosingBenchmarkTable(); + } + + void ConsoleReporter::lazyPrintWithoutClosingBenchmarkTable() { + if (!m_testRunInfoPrinted) { + lazyPrintRunInfo(); + } + if (!m_headerPrinted) { + printTestCaseAndSectionHeader(); + m_headerPrinted = true; + } + } + void ConsoleReporter::lazyPrintRunInfo() { + m_stream << '\n' + << lineOfChars('~') << '\n' + << m_colour->guardColour(Colour::SecondaryText) + << currentTestRunInfo.name << " is a Catch2 v" << libraryVersion() + << " host application.\n" + << "Run with -? for options\n\n"; + + m_testRunInfoPrinted = true; + } + void ConsoleReporter::printTestCaseAndSectionHeader() { + assert(!m_sectionStack.empty()); + printOpenHeader(currentTestCaseInfo->name); + + if (m_sectionStack.size() > 1) { + auto guard = m_colour->guardColour(Colour::Headers).engage(m_stream); + + auto + it + = m_sectionStack.begin() + 1, // Skip first section (test case) + itEnd = m_sectionStack.end(); + for (; it != itEnd; ++it) + printHeaderString(it->name, 2); + } + + SourceLineInfo lineInfo = m_sectionStack.back().lineInfo; + + m_stream << lineOfChars('-') << '\n' + << m_colour->guardColour(Colour::FileName) << lineInfo << '\n' + << lineOfChars('.') << "\n\n" + << std::flush; + } + + void ConsoleReporter::printClosedHeader(std::string const &_name) { + printOpenHeader(_name); + m_stream << lineOfChars('.') << '\n'; + } + void ConsoleReporter::printOpenHeader(std::string const &_name) { + m_stream << lineOfChars('-') << '\n'; + { + auto guard = m_colour->guardColour(Colour::Headers).engage(m_stream); + printHeaderString(_name); + } + } + + void ConsoleReporter::printHeaderString(std::string const &_string, std::size_t indent) { + // We want to get a bit fancy with line breaking here, so that subsequent + // lines start after ":" if one is present, e.g. + // ``` + // blablabla: Fancy + // linebreaking + // ``` + // but we also want to avoid problems with overly long indentation causing + // the text to take up too many lines, e.g. + // ``` + // blablabla: F + // a + // n + // c + // y + // . + // . + // . + // ``` + // So we limit the prefix indentation check to first quarter of the possible + // width + std::size_t idx = _string.find(": "); + if (idx != std::string::npos && idx < CATCH_CONFIG_CONSOLE_WIDTH / 4) { + idx += 2; + } else { + idx = 0; + } + m_stream << TextFlow::Column(_string) + .indent(indent + idx) + .initialIndent(indent) + << '\n'; + } + + void ConsoleReporter::printTotalsDivider(Totals const &totals) { + if (totals.testCases.total() > 0) { + std::size_t failedRatio = makeRatio(totals.testCases.failed, totals.testCases.total()); + std::size_t failedButOkRatio = makeRatio(totals.testCases.failedButOk, totals.testCases.total()); + std::size_t passedRatio = makeRatio(totals.testCases.passed, totals.testCases.total()); + std::size_t skippedRatio = makeRatio(totals.testCases.skipped, totals.testCases.total()); + while (failedRatio + failedButOkRatio + passedRatio + skippedRatio < CATCH_CONFIG_CONSOLE_WIDTH - 1) + findMax(failedRatio, failedButOkRatio, passedRatio, skippedRatio)++; + while (failedRatio + failedButOkRatio + passedRatio > CATCH_CONFIG_CONSOLE_WIDTH - 1) + findMax(failedRatio, failedButOkRatio, passedRatio, skippedRatio)--; + + m_stream << m_colour->guardColour(Colour::Error) + << std::string(failedRatio, '=') + << m_colour->guardColour(Colour::ResultExpectedFailure) + << std::string(failedButOkRatio, '='); + if (totals.testCases.allPassed()) { + m_stream << m_colour->guardColour(Colour::ResultSuccess) + << std::string(passedRatio, '='); + } else { + m_stream << m_colour->guardColour(Colour::Success) + << std::string(passedRatio, '='); + } + m_stream << m_colour->guardColour(Colour::Skip) + << std::string(skippedRatio, '='); + } else { + m_stream << m_colour->guardColour(Colour::Warning) + << std::string(CATCH_CONFIG_CONSOLE_WIDTH - 1, '='); + } + m_stream << '\n'; + } + +} // end namespace Catch + +#if defined(_MSC_VER) +#pragma warning(pop) +#endif + +#if defined(__clang__) +#pragma clang diagnostic pop +#endif + +#include +#include + +namespace Catch { + namespace { + struct BySectionInfo { + BySectionInfo(SectionInfo const &other) + : m_other(other) {} + BySectionInfo(BySectionInfo const &other) = default; + bool operator()( + Detail::unique_ptr const & + node) const { + return ( + (node->stats.sectionInfo.name == m_other.name) && (node->stats.sectionInfo.lineInfo == m_other.lineInfo)); + } + void operator=(BySectionInfo const &) = delete; + + private: + SectionInfo const &m_other; + }; + + } // namespace + + namespace Detail { + AssertionOrBenchmarkResult::AssertionOrBenchmarkResult( + AssertionStats const &assertion) + : m_assertion(assertion) {} + + AssertionOrBenchmarkResult::AssertionOrBenchmarkResult( + BenchmarkStats<> const &benchmark) + : m_benchmark(benchmark) {} + + bool AssertionOrBenchmarkResult::isAssertion() const { + return m_assertion.some(); + } + bool AssertionOrBenchmarkResult::isBenchmark() const { + return m_benchmark.some(); + } + + AssertionStats const &AssertionOrBenchmarkResult::asAssertion() const { + assert(m_assertion.some()); + + return *m_assertion; + } + BenchmarkStats<> const &AssertionOrBenchmarkResult::asBenchmark() const { + assert(m_benchmark.some()); + + return *m_benchmark; + } + + } // namespace Detail + + CumulativeReporterBase::~CumulativeReporterBase() = default; + + void CumulativeReporterBase::benchmarkEnded(BenchmarkStats<> const &benchmarkStats) { + m_sectionStack.back()->assertionsAndBenchmarks.emplace_back(benchmarkStats); + } + + void + CumulativeReporterBase::sectionStarting(SectionInfo const §ionInfo) { + // We need a copy, because SectionStats expect to take ownership + SectionStats incompleteStats(SectionInfo(sectionInfo), Counts(), 0, false); + SectionNode *node; + if (m_sectionStack.empty()) { + if (!m_rootSection) { + m_rootSection = Detail::make_unique(incompleteStats); + } + node = m_rootSection.get(); + } else { + SectionNode &parentNode = *m_sectionStack.back(); + auto it = std::find_if(parentNode.childSections.begin(), + parentNode.childSections.end(), + BySectionInfo(sectionInfo)); + if (it == parentNode.childSections.end()) { + auto newNode = Detail::make_unique(incompleteStats); + node = newNode.get(); + parentNode.childSections.push_back(CATCH_MOVE(newNode)); + } else { + node = it->get(); + } + } + + m_deepestSection = node; + m_sectionStack.push_back(node); + } + + void CumulativeReporterBase::assertionEnded( + AssertionStats const &assertionStats) { + assert(!m_sectionStack.empty()); + // AssertionResult holds a pointer to a temporary DecomposedExpression, + // which getExpandedExpression() calls to build the expression string. + // Our section stack copy of the assertionResult will likely outlive the + // temporary, so it must be expanded or discarded now to avoid calling + // a destroyed object later. + if (m_shouldStoreFailedAssertions && !assertionStats.assertionResult.isOk()) { + static_cast( + assertionStats.assertionResult.getExpandedExpression()); + } + if (m_shouldStoreSuccesfulAssertions && assertionStats.assertionResult.isOk()) { + static_cast( + assertionStats.assertionResult.getExpandedExpression()); + } + SectionNode §ionNode = *m_sectionStack.back(); + sectionNode.assertionsAndBenchmarks.emplace_back(assertionStats); + } + + void CumulativeReporterBase::sectionEnded(SectionStats const §ionStats) { + assert(!m_sectionStack.empty()); + SectionNode &node = *m_sectionStack.back(); + node.stats = sectionStats; + m_sectionStack.pop_back(); + } + + void CumulativeReporterBase::testCaseEnded( + TestCaseStats const &testCaseStats) { + auto node = Detail::make_unique(testCaseStats); + assert(m_sectionStack.size() == 0); + node->children.push_back(CATCH_MOVE(m_rootSection)); + m_testCases.push_back(CATCH_MOVE(node)); + + assert(m_deepestSection); + m_deepestSection->stdOut = testCaseStats.stdOut; + m_deepestSection->stdErr = testCaseStats.stdErr; + } + + void CumulativeReporterBase::testRunEnded(TestRunStats const &testRunStats) { + assert(!m_testRun && "CumulativeReporterBase assumes there can only be one test run"); + m_testRun = Detail::make_unique(testRunStats); + m_testRun->children.swap(m_testCases); + testRunEndedCumulative(); + } + + bool CumulativeReporterBase::SectionNode::hasAnyAssertions() const { + return std::any_of( + assertionsAndBenchmarks.begin(), + assertionsAndBenchmarks.end(), + [](Detail::AssertionOrBenchmarkResult const &res) { + return res.isAssertion(); + }); + } + +} // end namespace Catch + +namespace Catch { + + void EventListenerBase::fatalErrorEncountered(StringRef) {} + + void EventListenerBase::benchmarkPreparing(StringRef) {} + void EventListenerBase::benchmarkStarting(BenchmarkInfo const &) {} + void EventListenerBase::benchmarkEnded(BenchmarkStats<> const &) {} + void EventListenerBase::benchmarkFailed(StringRef) {} + + void EventListenerBase::assertionStarting(AssertionInfo const &) {} + + void EventListenerBase::assertionEnded(AssertionStats const &) {} + void EventListenerBase::listReporters( + std::vector const &) {} + void EventListenerBase::listListeners( + std::vector const &) {} + void EventListenerBase::listTests(std::vector const &) {} + void EventListenerBase::listTags(std::vector const &) {} + void EventListenerBase::noMatchingTestCases(StringRef) {} + void EventListenerBase::reportInvalidTestSpec(StringRef) {} + void EventListenerBase::testRunStarting(TestRunInfo const &) {} + void EventListenerBase::testCaseStarting(TestCaseInfo const &) {} + void EventListenerBase::testCasePartialStarting(TestCaseInfo const &, uint64_t) {} + void EventListenerBase::sectionStarting(SectionInfo const &) {} + void EventListenerBase::sectionEnded(SectionStats const &) {} + void EventListenerBase::testCasePartialEnded(TestCaseStats const &, uint64_t) {} + void EventListenerBase::testCaseEnded(TestCaseStats const &) {} + void EventListenerBase::testRunEnded(TestRunStats const &) {} + void EventListenerBase::skipTest(TestCaseInfo const &) {} +} // namespace Catch + +#include +#include +#include +#include +#include +#include + +namespace Catch { + + namespace { + void listTestNamesOnly(std::ostream &out, + std::vector const &tests) { + for (auto const &test : tests) { + auto const &testCaseInfo = test.getTestCaseInfo(); + + if (startsWith(testCaseInfo.name, '#')) { + out << '"' << testCaseInfo.name << '"'; + } else { + out << testCaseInfo.name; + } + + out << '\n'; + } + out << std::flush; + } + } // end unnamed namespace + + // Because formatting using c++ streams is stateful, drop down to C is + // required Alternatively we could use stringstream, but its performance + // is... not good. + std::string getFormattedDuration(double duration) { + // Max exponent + 1 is required to represent the whole part + // + 1 for decimal point + // + 3 for the 3 decimal places + // + 1 for null terminator + const std::size_t maxDoubleSize = DBL_MAX_10_EXP + 1 + 1 + 3 + 1; + char buffer[maxDoubleSize]; + + // Save previous errno, to prevent sprintf from overwriting it + ErrnoGuard guard; +#ifdef _MSC_VER + size_t printedLength = static_cast( + sprintf_s(buffer, "%.3f", duration)); +#else + size_t printedLength = static_cast( + std::snprintf(buffer, maxDoubleSize, "%.3f", duration)); +#endif + return std::string(buffer, printedLength); + } + + bool shouldShowDuration(IConfig const &config, double duration) { + if (config.showDurations() == ShowDurations::Always) { + return true; + } + if (config.showDurations() == ShowDurations::Never) { + return false; + } + const double min = config.minDuration(); + return min >= 0 && duration >= min; + } + + std::string serializeFilters(std::vector const &filters) { + // We add a ' ' separator between each filter + size_t serialized_size = filters.size() - 1; + for (auto const &filter : filters) { + serialized_size += filter.size(); + } + + std::string serialized; + serialized.reserve(serialized_size); + bool first = true; + + for (auto const &filter : filters) { + if (!first) { + serialized.push_back(' '); + } + first = false; + serialized.append(filter); + } + + return serialized; + } + + std::ostream &operator<<(std::ostream &out, lineOfChars value) { + for (size_t idx = 0; idx < CATCH_CONFIG_CONSOLE_WIDTH - 1; ++idx) { + out.put(value.c); + } + return out; + } + + void + defaultListReporters(std::ostream &out, + std::vector const &descriptions, + Verbosity verbosity) { + out << "Available reporters:\n"; + const auto maxNameLen = std::max_element(descriptions.begin(), + descriptions.end(), + [](ReporterDescription const &lhs, + ReporterDescription const &rhs) { + return lhs.name.size() < rhs.name.size(); + }) + ->name.size(); + + for (auto const &desc : descriptions) { + if (verbosity == Verbosity::Quiet) { + out << TextFlow::Column(desc.name) + .indent(2) + .width(5 + maxNameLen) + << '\n'; + } else { + out << TextFlow::Column(desc.name + ':') + .indent(2) + .width(5 + maxNameLen) + + TextFlow::Column(desc.description) + .initialIndent(0) + .indent(2) + .width(CATCH_CONFIG_CONSOLE_WIDTH - maxNameLen - 8) + << '\n'; + } + } + out << '\n' + << std::flush; + } + + void defaultListListeners(std::ostream &out, + std::vector const &descriptions, + Verbosity verbosity) { + if (verbosity == Verbosity::Quiet) { + for (auto const &desc : descriptions) { + out << desc.name << '\n'; + } + return; + } + + out << "Registered listeners:\n"; + + if (descriptions.empty()) { + return; + } + + const auto maxNameLen = std::max_element(descriptions.begin(), + descriptions.end(), + [](ListenerDescription const &lhs, + ListenerDescription const &rhs) { + return lhs.name.size() < rhs.name.size(); + }) + ->name.size(); + + for (auto const &desc : descriptions) { + out << TextFlow::Column(static_cast(desc.name) + ':') + .indent(2) + .width(maxNameLen + 5) + + TextFlow::Column(desc.description) + .initialIndent(0) + .indent(2) + .width(CATCH_CONFIG_CONSOLE_WIDTH - maxNameLen - 8) + << '\n'; + } + + out << '\n' + << std::flush; + } + + void defaultListTags(std::ostream &out, + std::vector const &tags, + bool isFiltered, + Verbosity verbosity) { + if (verbosity == Verbosity::Quiet) { + for (auto const &tagCount : tags) { + out << tagCount.all() << '\n'; + } + return; + } + if (isFiltered) { + out << "Tags for matching test cases:\n"; + } else { + out << "All available tags:\n"; + } + + // minimum whitespace to pad tag counts, possibly overwritten below + size_t maxTagCountLen = 2; + + // determine necessary padding for tag count column + if (!tags.empty()) { + const auto maxTagCount = std::max_element(tags.begin(), + tags.end(), + [](auto const &lhs, auto const &rhs) { + return lhs.count < rhs.count; + }) + ->count; + + // more padding necessary for 3+ digits + if (maxTagCount >= 100) { + auto numDigits = 1 + std::floor(std::log10(maxTagCount)); + maxTagCountLen = static_cast(numDigits); + } + } + + for (auto const &tagCount : tags) { + ReusableStringStream rss; + rss << " " << std::setw(maxTagCountLen) << tagCount.count << " "; + auto str = rss.str(); + auto wrapper = TextFlow::Column(tagCount.all()) + .initialIndent(0) + .indent(str.size()) + .width(CATCH_CONFIG_CONSOLE_WIDTH - 10); + out << str << wrapper << '\n'; + } + out << pluralise(tags.size(), "tag"_sr) << "\n\n" + << std::flush; + } + + void defaultListTests(std::ostream &out, ColourImpl *streamColour, std::vector const &tests, bool isFiltered, Verbosity verbosity) { + // We special case this to provide the equivalent of old + // `--list-test-names-only`, which could then be used by the + // `--input-file` option. + if (verbosity == Verbosity::Quiet) { + listTestNamesOnly(out, tests); + return; + } + + if (isFiltered) { + out << "Matching test cases:\n"; + } else { + out << "All available test cases:\n"; + } + + for (auto const &test : tests) { + auto const &testCaseInfo = test.getTestCaseInfo(); + Colour::Code colour = testCaseInfo.isHidden() + ? Colour::SecondaryText + : Colour::None; + auto colourGuard = streamColour->guardColour(colour).engage(out); + + out << TextFlow::Column(testCaseInfo.name).indent(2) << '\n'; + if (verbosity >= Verbosity::High) { + out << TextFlow::Column(Catch::Detail::stringify(testCaseInfo.lineInfo)).indent(4) << '\n'; + } + if (!testCaseInfo.tags.empty() && verbosity > Verbosity::Quiet) { + out << TextFlow::Column(testCaseInfo.tagsAsString()).indent(6) << '\n'; + } + } + + if (isFiltered) { + out << pluralise(tests.size(), "matching test case"_sr); + } else { + out << pluralise(tests.size(), "test case"_sr); + } + out << "\n\n" + << std::flush; + } + + namespace { + class SummaryColumn { + public: + SummaryColumn(std::string suffix, Colour::Code colour) + : m_suffix(CATCH_MOVE(suffix)), m_colour(colour) {} + + SummaryColumn &&addRow(std::uint64_t count) && { + std::string row = std::to_string(count); + auto const new_width = std::max(m_width, row.size()); + if (new_width > m_width) { + for (auto &oldRow : m_rows) { + oldRow.insert(0, new_width - m_width, ' '); + } + } else { + row.insert(0, m_width - row.size(), ' '); + } + m_width = new_width; + m_rows.push_back(row); + return std::move(*this); + } + + std::string const &getSuffix() const { return m_suffix; } + Colour::Code getColour() const { return m_colour; } + std::string const &getRow(std::size_t index) const { + return m_rows[index]; + } + + private: + std::string m_suffix; + Colour::Code m_colour; + std::size_t m_width = 0; + std::vector m_rows; + }; + + void printSummaryRow(std::ostream &stream, + ColourImpl &colour, + StringRef label, + std::vector const &cols, + std::size_t row) { + for (auto const &col : cols) { + auto const &value = col.getRow(row); + auto const &suffix = col.getSuffix(); + if (suffix.empty()) { + stream << label << ": "; + if (value != "0") { + stream << value; + } else { + stream << colour.guardColour(Colour::Warning) + << "- none -"; + } + } else if (value != "0") { + stream << colour.guardColour(Colour::LightGrey) << " | " + << colour.guardColour(col.getColour()) << value + << ' ' << suffix; + } + } + stream << '\n'; + } + } // namespace + + void printTestRunTotals(std::ostream &stream, + ColourImpl &streamColour, + Totals const &totals) { + if (totals.testCases.total() == 0) { + stream << streamColour.guardColour(Colour::Warning) + << "No tests ran\n"; + return; + } + + if (totals.assertions.total() > 0 && totals.testCases.allPassed()) { + stream << streamColour.guardColour(Colour::ResultSuccess) + << "All tests passed"; + stream << " (" + << pluralise(totals.assertions.passed, "assertion"_sr) + << " in " + << pluralise(totals.testCases.passed, "test case"_sr) + << ')' << '\n'; + return; + } + + std::vector columns; + // Don't include "skipped assertions" in total count + const auto totalAssertionCount = totals.assertions.total() - totals.assertions.skipped; + columns.push_back(SummaryColumn("", Colour::None) + .addRow(totals.testCases.total()) + .addRow(totalAssertionCount)); + columns.push_back(SummaryColumn("passed", Colour::Success) + .addRow(totals.testCases.passed) + .addRow(totals.assertions.passed)); + columns.push_back(SummaryColumn("failed", Colour::ResultError) + .addRow(totals.testCases.failed) + .addRow(totals.assertions.failed)); + columns.push_back(SummaryColumn("skipped", Colour::Skip) + .addRow(totals.testCases.skipped) + // Don't print "skipped assertions" + .addRow(0)); + columns.push_back( + SummaryColumn("failed as expected", Colour::ResultExpectedFailure) + .addRow(totals.testCases.failedButOk) + .addRow(totals.assertions.failedButOk)); + printSummaryRow(stream, streamColour, "test cases"_sr, columns, 0); + printSummaryRow(stream, streamColour, "assertions"_sr, columns, 1); + } + +} // namespace Catch + +// + +namespace Catch { + namespace { + void writeSourceInfo(JsonObjectWriter &writer, + SourceLineInfo const &sourceInfo) { + auto source_location_writer = writer.write("source-location"_sr).writeObject(); + source_location_writer.write("filename"_sr) + .write(sourceInfo.file); + source_location_writer.write("line"_sr).write(sourceInfo.line); + } + + void writeTags(JsonArrayWriter writer, std::vector const &tags) { + for (auto const &tag : tags) { + writer.write(tag.original); + } + } + + void writeProperties(JsonArrayWriter writer, + TestCaseInfo const &info) { + if (info.isHidden()) { writer.write("is-hidden"_sr); } + if (info.okToFail()) { writer.write("ok-to-fail"_sr); } + if (info.expectedToFail()) { + writer.write("expected-to-fail"_sr); + } + if (info.throws()) { writer.write("throws"_sr); } + } + + } // namespace + + JsonReporter::JsonReporter(ReporterConfig &&config) + : StreamingReporterBase{CATCH_MOVE(config)} { + m_preferences.shouldRedirectStdOut = true; + // TBD: Do we want to report all assertions? XML reporter does + // not, but for machine-parseable reporters I think the answer + // should be yes. + m_preferences.shouldReportAllAssertions = true; + // We only handle assertions when they end + m_preferences.shouldReportAllAssertionStarts = false; + + m_objectWriters.emplace(m_stream); + m_writers.emplace(Writer::Object); + auto &writer = m_objectWriters.top(); + + writer.write("version"_sr).write(1); + + { + auto metadata_writer = writer.write("metadata"_sr).writeObject(); + metadata_writer.write("name"_sr).write(m_config->name()); + metadata_writer.write("rng-seed"_sr).write(m_config->rngSeed()); + metadata_writer.write("catch2-version"_sr) + .write(libraryVersion()); + if (m_config->testSpec().hasFilters()) { + metadata_writer.write("filters"_sr) + .write(m_config->testSpec()); + } + } + } + + JsonReporter::~JsonReporter() { + endListing(); + // TODO: Ensure this closes the top level object, add asserts + assert(m_writers.size() == 1 && "Only the top level object should be open"); + assert(m_writers.top() == Writer::Object); + endObject(); + m_stream << '\n' + << std::flush; + assert(m_writers.empty()); + } + + JsonArrayWriter &JsonReporter::startArray() { + m_arrayWriters.emplace(m_arrayWriters.top().writeArray()); + m_writers.emplace(Writer::Array); + return m_arrayWriters.top(); + } + JsonArrayWriter &JsonReporter::startArray(StringRef key) { + m_arrayWriters.emplace( + m_objectWriters.top().write(key).writeArray()); + m_writers.emplace(Writer::Array); + return m_arrayWriters.top(); + } + + JsonObjectWriter &JsonReporter::startObject() { + m_objectWriters.emplace(m_arrayWriters.top().writeObject()); + m_writers.emplace(Writer::Object); + return m_objectWriters.top(); + } + JsonObjectWriter &JsonReporter::startObject(StringRef key) { + m_objectWriters.emplace( + m_objectWriters.top().write(key).writeObject()); + m_writers.emplace(Writer::Object); + return m_objectWriters.top(); + } + + void JsonReporter::endObject() { + assert(isInside(Writer::Object)); + m_objectWriters.pop(); + m_writers.pop(); + } + void JsonReporter::endArray() { + assert(isInside(Writer::Array)); + m_arrayWriters.pop(); + m_writers.pop(); + } + + bool JsonReporter::isInside(Writer writer) { + return !m_writers.empty() && m_writers.top() == writer; + } + + void JsonReporter::startListing() { + if (!m_startedListing) { startObject("listings"_sr); } + m_startedListing = true; + } + void JsonReporter::endListing() { + if (m_startedListing) { endObject(); } + m_startedListing = false; + } + + std::string JsonReporter::getDescription() { + return "Outputs listings as JSON. Test listing is Work-in-Progress!"; + } + + void JsonReporter::testRunStarting(TestRunInfo const &runInfo) { + StreamingReporterBase::testRunStarting(runInfo); + endListing(); + + assert(isInside(Writer::Object)); + startObject("test-run"_sr); + startArray("test-cases"_sr); + } + + static void writeCounts(JsonObjectWriter &&writer, Counts const &counts) { + writer.write("passed"_sr).write(counts.passed); + writer.write("failed"_sr).write(counts.failed); + writer.write("fail-but-ok"_sr).write(counts.failedButOk); + writer.write("skipped"_sr).write(counts.skipped); + } + + void JsonReporter::testRunEnded(TestRunStats const &runStats) { + assert(isInside(Writer::Array)); + // End "test-cases" + endArray(); + + { + auto totals = m_objectWriters.top().write("totals"_sr).writeObject(); + writeCounts(totals.write("assertions"_sr).writeObject(), + runStats.totals.assertions); + writeCounts(totals.write("test-cases"_sr).writeObject(), + runStats.totals.testCases); + } + + // End the "test-run" object + endObject(); + } + + void JsonReporter::testCaseStarting(TestCaseInfo const &tcInfo) { + StreamingReporterBase::testCaseStarting(tcInfo); + + assert(isInside(Writer::Array) && "We should be in the 'test-cases' array"); + startObject(); + // "test-info" prelude + { + auto testInfo = m_objectWriters.top().write("test-info"_sr).writeObject(); + // TODO: handle testName vs className!! + testInfo.write("name"_sr).write(tcInfo.name); + writeSourceInfo(testInfo, tcInfo.lineInfo); + writeTags(testInfo.write("tags"_sr).writeArray(), tcInfo.tags); + writeProperties(testInfo.write("properties"_sr).writeArray(), + tcInfo); + } + + // Start the array for individual test runs (testCasePartial pairs) + startArray("runs"_sr); + } + + void JsonReporter::testCaseEnded(TestCaseStats const &tcStats) { + StreamingReporterBase::testCaseEnded(tcStats); + + // We need to close the 'runs' array before finishing the test case + assert(isInside(Writer::Array)); + endArray(); + + { + auto totals = m_objectWriters.top().write("totals"_sr).writeObject(); + writeCounts(totals.write("assertions"_sr).writeObject(), + tcStats.totals.assertions); + // We do not write the test case totals, because there will always be just one test case here. + // TODO: overall "result" -> success, skip, fail here? Or in partial result? + } + // We do not write out stderr/stdout, because we instead wrote those out in partial runs + + // TODO: aborting? + + // And we also close this test case's object + assert(isInside(Writer::Object)); + endObject(); + } + + void JsonReporter::testCasePartialStarting(TestCaseInfo const & /*tcInfo*/, + uint64_t index) { + startObject(); + m_objectWriters.top().write("run-idx"_sr).write(index); + startArray("path"_sr); + // TODO: we want to delay most of the printing to the 'root' section + // TODO: childSection key name? + } + + void JsonReporter::testCasePartialEnded(TestCaseStats const &tcStats, + uint64_t /*index*/) { + // Fixme: the top level section handles this. + //// path object + endArray(); + if (!tcStats.stdOut.empty()) { + m_objectWriters.top() + .write("captured-stdout"_sr) + .write(tcStats.stdOut); + } + if (!tcStats.stdErr.empty()) { + m_objectWriters.top() + .write("captured-stderr"_sr) + .write(tcStats.stdErr); + } + { + auto totals = m_objectWriters.top().write("totals"_sr).writeObject(); + writeCounts(totals.write("assertions"_sr).writeObject(), + tcStats.totals.assertions); + // We do not write the test case totals, because there will + // always be just one test case here. + // TODO: overall "result" -> success, skip, fail here? Or in + // partial result? + } + // TODO: aborting? + // run object + endObject(); + } + + void JsonReporter::sectionStarting(SectionInfo const §ionInfo) { + assert(isInside(Writer::Array) && "Section should always start inside an object"); + // We want to nest top level sections, even though it shares name + // and source loc with the TEST_CASE + auto §ionObject = startObject(); + sectionObject.write("kind"_sr).write("section"_sr); + sectionObject.write("name"_sr).write(sectionInfo.name); + writeSourceInfo(m_objectWriters.top(), sectionInfo.lineInfo); + + // TBD: Do we want to create this event lazily? It would become + // rather complex, but we could do it, and it would look + // better for empty sections. OTOH, empty sections should + // be rare. + startArray("path"_sr); + } + void JsonReporter::sectionEnded(SectionStats const & /*sectionStats */) { + // End the subpath array + endArray(); + // TODO: metadata + // TODO: what info do we have here? + + // End the section object + endObject(); + } + + void JsonReporter::assertionEnded(AssertionStats const &assertionStats) { + // TODO: There is lot of different things to handle here, but + // we can fill it in later, after we show that the basic + // outline and streaming reporter impl works well enough. + // if ( !m_config->includeSuccessfulResults() + // && assertionStats.assertionResult.isOk() ) { + // return; + //} + assert(isInside(Writer::Array)); + auto assertionObject = m_arrayWriters.top().writeObject(); + + assertionObject.write("kind"_sr).write("assertion"_sr); + writeSourceInfo(assertionObject, + assertionStats.assertionResult.getSourceInfo()); + assertionObject.write("status"_sr) + .write(assertionStats.assertionResult.isOk()); + // TODO: handling of result. + // TODO: messages + // TODO: totals? + } + + void JsonReporter::benchmarkPreparing(StringRef name) { + (void)name; + } + void JsonReporter::benchmarkStarting(BenchmarkInfo const &) {} + void JsonReporter::benchmarkEnded(BenchmarkStats<> const &) {} + void JsonReporter::benchmarkFailed(StringRef error) { + (void)error; + } + + void JsonReporter::listReporters( + std::vector const &descriptions) { + startListing(); + + auto writer = m_objectWriters.top().write("reporters"_sr).writeArray(); + for (auto const &desc : descriptions) { + auto desc_writer = writer.writeObject(); + desc_writer.write("name"_sr).write(desc.name); + desc_writer.write("description"_sr).write(desc.description); + } + } + void JsonReporter::listListeners( + std::vector const &descriptions) { + startListing(); + + auto writer = m_objectWriters.top().write("listeners"_sr).writeArray(); + + for (auto const &desc : descriptions) { + auto desc_writer = writer.writeObject(); + desc_writer.write("name"_sr).write(desc.name); + desc_writer.write("description"_sr).write(desc.description); + } + } + void JsonReporter::listTests(std::vector const &tests) { + startListing(); + + auto writer = m_objectWriters.top().write("tests"_sr).writeArray(); + + for (auto const &test : tests) { + auto desc_writer = writer.writeObject(); + auto const &info = test.getTestCaseInfo(); + + desc_writer.write("name"_sr).write(info.name); + desc_writer.write("class-name"_sr).write(info.className); + { + auto tag_writer = desc_writer.write("tags"_sr).writeArray(); + for (auto const &tag : info.tags) { + tag_writer.write(tag.original); + } + } + writeSourceInfo(desc_writer, info.lineInfo); + } + } + void JsonReporter::listTags(std::vector const &tags) { + startListing(); + + auto writer = m_objectWriters.top().write("tags"_sr).writeArray(); + for (auto const &tag : tags) { + auto tag_writer = writer.writeObject(); + { + auto aliases_writer = tag_writer.write("aliases"_sr).writeArray(); + for (auto alias : tag.spellings) { + aliases_writer.write(alias); + } + } + tag_writer.write("count"_sr).write(tag.count); + } + } +} // namespace Catch + +#include +#include +#include +#include + +namespace Catch { + + namespace { + std::string getCurrentTimestamp() { + time_t rawtime; + std::time(&rawtime); + + std::tm timeInfo = {}; +#if defined(_MSC_VER) || defined(__MINGW32__) + gmtime_s(&timeInfo, &rawtime); +#elif defined(CATCH_PLATFORM_PLAYSTATION) + gmtime_s(&rawtime, &timeInfo); +#elif defined(__IAR_SYSTEMS_ICC__) + timeInfo = *std::gmtime(&rawtime); +#else + gmtime_r(&rawtime, &timeInfo); +#endif + + auto const timeStampSize = sizeof("2017-01-16T17:06:45Z"); + char timeStamp[timeStampSize]; + const char *const fmt = "%Y-%m-%dT%H:%M:%SZ"; + + std::strftime(timeStamp, timeStampSize, fmt, &timeInfo); + + return std::string(timeStamp, timeStampSize - 1); + } + + std::string fileNameTag(std::vector const &tags) { + auto it = std::find_if(begin(tags), + end(tags), + [](Tag const &tag) { return tag.original.size() > 0 + && tag.original[0] == '#'; }); + if (it != tags.end()) { + return static_cast( + it->original.substr(1, it->original.size() - 1)); + } + return std::string(); + } + + // Formats the duration in seconds to 3 decimal places. + // This is done because some genius defined Maven Surefire schema + // in a way that only accepts 3 decimal places, and tools like + // Jenkins use that schema for validation JUnit reporter output. + std::string formatDuration(double seconds) { + ReusableStringStream rss; + rss << std::fixed << std::setprecision(3) << seconds; + return rss.str(); + } + + static void normalizeNamespaceMarkers(std::string &str) { + std::size_t pos = str.find("::"); + while (pos != std::string::npos) { + str.replace(pos, 2, "."); + pos += 1; + pos = str.find("::", pos); + } + } + + } // anonymous namespace + + JunitReporter::JunitReporter(ReporterConfig &&_config) + : CumulativeReporterBase(CATCH_MOVE(_config)), xml(m_stream) { + m_preferences.shouldRedirectStdOut = true; + m_preferences.shouldReportAllAssertions = false; + m_preferences.shouldReportAllAssertionStarts = false; + m_shouldStoreSuccesfulAssertions = false; + } + + std::string JunitReporter::getDescription() { + return "Reports test results in an XML format that looks like Ant's junitreport target"; + } + + void JunitReporter::testRunStarting(TestRunInfo const &runInfo) { + CumulativeReporterBase::testRunStarting(runInfo); + xml.startElement("testsuites"); + suiteTimer.start(); + stdOutForSuite.clear(); + stdErrForSuite.clear(); + unexpectedExceptions = 0; + } + + void JunitReporter::testCaseStarting(TestCaseInfo const &testCaseInfo) { + m_okToFail = testCaseInfo.okToFail(); + } + + void JunitReporter::assertionEnded(AssertionStats const &assertionStats) { + if (assertionStats.assertionResult.getResultType() == ResultWas::ThrewException && !m_okToFail) + unexpectedExceptions++; + CumulativeReporterBase::assertionEnded(assertionStats); + } + + void JunitReporter::testCaseEnded(TestCaseStats const &testCaseStats) { + stdOutForSuite += testCaseStats.stdOut; + stdErrForSuite += testCaseStats.stdErr; + CumulativeReporterBase::testCaseEnded(testCaseStats); + } + + void JunitReporter::testRunEndedCumulative() { + const auto suiteTime = suiteTimer.getElapsedSeconds(); + writeRun(*m_testRun, suiteTime); + xml.endElement(); + } + + void JunitReporter::writeRun(TestRunNode const &testRunNode, double suiteTime) { + XmlWriter::ScopedElement e = xml.scopedElement("testsuite"); + + TestRunStats const &stats = testRunNode.value; + xml.writeAttribute("name"_sr, stats.runInfo.name); + xml.writeAttribute("errors"_sr, unexpectedExceptions); + xml.writeAttribute("failures"_sr, stats.totals.assertions.failed - unexpectedExceptions); + xml.writeAttribute("skipped"_sr, stats.totals.assertions.skipped); + xml.writeAttribute("tests"_sr, stats.totals.assertions.total()); + xml.writeAttribute("hostname"_sr, "tbd"_sr); // !TBD + if (m_config->showDurations() == ShowDurations::Never) + xml.writeAttribute("time"_sr, ""_sr); + else + xml.writeAttribute("time"_sr, formatDuration(suiteTime)); + xml.writeAttribute("timestamp"_sr, getCurrentTimestamp()); + + // Write properties + { + auto properties = xml.scopedElement("properties"); + xml.scopedElement("property") + .writeAttribute("name"_sr, "random-seed"_sr) + .writeAttribute("value"_sr, m_config->rngSeed()); + if (m_config->testSpec().hasFilters()) { + xml.scopedElement("property") + .writeAttribute("name"_sr, "filters"_sr) + .writeAttribute("value"_sr, m_config->testSpec()); + } + } + + // Write test cases + for (auto const &child : testRunNode.children) + writeTestCase(*child); + + xml.scopedElement("system-out").writeText(trim(stdOutForSuite), XmlFormatting::Newline); + xml.scopedElement("system-err").writeText(trim(stdErrForSuite), XmlFormatting::Newline); + } + + void JunitReporter::writeTestCase(TestCaseNode const &testCaseNode) { + TestCaseStats const &stats = testCaseNode.value; + + // All test cases have exactly one section - which represents the + // test case itself. That section may have 0-n nested sections + assert(testCaseNode.children.size() == 1); + SectionNode const &rootSection = *testCaseNode.children.front(); + + std::string className = static_cast(stats.testInfo->className); + + if (className.empty()) { + className = fileNameTag(stats.testInfo->tags); + if (className.empty()) { + className = "global"; + } + } + + if (!m_config->name().empty()) + className = static_cast(m_config->name()) + '.' + className; + + normalizeNamespaceMarkers(className); + + writeSection(className, "", rootSection, stats.testInfo->okToFail()); + } + + void JunitReporter::writeSection(std::string const &className, + std::string const &rootName, + SectionNode const §ionNode, + bool testOkToFail) { + std::string name = trim(sectionNode.stats.sectionInfo.name); + if (!rootName.empty()) + name = rootName + '/' + name; + + if (sectionNode.stats.assertions.total() > 0 + || !sectionNode.stdOut.empty() + || !sectionNode.stdErr.empty()) { + XmlWriter::ScopedElement e = xml.scopedElement("testcase"); + if (className.empty()) { + xml.writeAttribute("classname"_sr, name); + xml.writeAttribute("name"_sr, "root"_sr); + } else { + xml.writeAttribute("classname"_sr, className); + xml.writeAttribute("name"_sr, name); + } + xml.writeAttribute("time"_sr, formatDuration(sectionNode.stats.durationInSeconds)); + // This is not ideal, but it should be enough to mimic gtest's + // junit output. + // Ideally the JUnit reporter would also handle `skipTest` + // events and write those out appropriately. + xml.writeAttribute("status"_sr, "run"_sr); + + if (sectionNode.stats.assertions.failedButOk) { + xml.scopedElement("skipped") + .writeAttribute("message", "TEST_CASE tagged with !mayfail"); + } + + writeAssertions(sectionNode); + + if (!sectionNode.stdOut.empty()) + xml.scopedElement("system-out").writeText(trim(sectionNode.stdOut), XmlFormatting::Newline); + if (!sectionNode.stdErr.empty()) + xml.scopedElement("system-err").writeText(trim(sectionNode.stdErr), XmlFormatting::Newline); + } + for (auto const &childNode : sectionNode.childSections) + if (className.empty()) + writeSection(name, "", *childNode, testOkToFail); + else + writeSection(className, name, *childNode, testOkToFail); + } + + void JunitReporter::writeAssertions(SectionNode const §ionNode) { + for (auto const &assertionOrBenchmark : sectionNode.assertionsAndBenchmarks) { + if (assertionOrBenchmark.isAssertion()) { + // JUnit XML format supports only 1 error/failure/skip + // assertion elements per test case + if (writeAssertion(assertionOrBenchmark.asAssertion())) { + break; + } + } + } + } + + bool JunitReporter::writeAssertion(AssertionStats const &stats) { + AssertionResult const &result = stats.assertionResult; + if (result.isOk() && result.getResultType() != ResultWas::ExplicitSkip) { + return false; + } + std::string elementName; + switch (result.getResultType()) { + case ResultWas::ThrewException: + case ResultWas::FatalErrorCondition: + elementName = "error"; + break; + case ResultWas::ExplicitFailure: + case ResultWas::ExpressionFailed: + case ResultWas::DidntThrowException: + elementName = "failure"; + break; + case ResultWas::ExplicitSkip: + elementName = "skipped"; + break; + // We should never see these here: + case ResultWas::Info: + case ResultWas::Warning: + case ResultWas::Ok: + case ResultWas::Unknown: + case ResultWas::FailureBit: + case ResultWas::Exception: + elementName = "internalError"; + break; + } + + XmlWriter::ScopedElement e = xml.scopedElement(elementName); + + xml.writeAttribute("message"_sr, result.getExpression()); + xml.writeAttribute("type"_sr, result.getTestMacroName()); + + ReusableStringStream rss; + if (result.getResultType() == ResultWas::ExplicitSkip) { + rss << "SKIPPED\n"; + } else { + rss << "FAILED:\n"; + if (result.hasExpression()) { + rss << " "; + rss << result.getExpressionInMacro(); + rss << '\n'; + } + if (result.hasExpandedExpression()) { + rss << "with expansion:\n"; + rss << TextFlow::Column(result.getExpandedExpression()) + .indent(2) + << '\n'; + } + } + + if (result.hasMessage()) { rss << result.getMessage() << '\n'; } + for (auto const &msg : stats.infoMessages) { + if (msg.type == ResultWas::Info) { rss << msg.message << '\n'; } + } + + rss << "at " << result.getSourceInfo(); + xml.writeText(rss.str(), XmlFormatting::Newline); + return true; + } + +} // end namespace Catch + +#include + +namespace Catch { + void MultiReporter::updatePreferences(IEventListener const &reporterish) { + m_preferences.shouldRedirectStdOut |= reporterish.getPreferences().shouldRedirectStdOut; + m_preferences.shouldReportAllAssertions |= reporterish.getPreferences().shouldReportAllAssertions; + m_preferences.shouldReportAllAssertionStarts |= reporterish.getPreferences().shouldReportAllAssertionStarts; + } + + void MultiReporter::addListener(IEventListenerPtr &&listener) { + updatePreferences(*listener); + m_reporterLikes.insert(m_reporterLikes.begin() + m_insertedListeners, CATCH_MOVE(listener)); + ++m_insertedListeners; + } + + void MultiReporter::addReporter(IEventListenerPtr &&reporter) { + updatePreferences(*reporter); + + // We will need to output the captured stdout if there are reporters + // that do not want it captured. + // We do not consider listeners, because it is generally assumed that + // listeners are output-transparent, even though they can ask for stdout + // capture to do something with it. + m_haveNoncapturingReporters |= !reporter->getPreferences().shouldRedirectStdOut; + + // Reporters can always be placed to the back without breaking the + // reporting order + m_reporterLikes.push_back(CATCH_MOVE(reporter)); + } + + void MultiReporter::noMatchingTestCases(StringRef unmatchedSpec) { + for (auto &reporterish : m_reporterLikes) { + reporterish->noMatchingTestCases(unmatchedSpec); + } + } + + void MultiReporter::fatalErrorEncountered(StringRef error) { + for (auto &reporterish : m_reporterLikes) { + reporterish->fatalErrorEncountered(error); + } + } + + void MultiReporter::reportInvalidTestSpec(StringRef arg) { + for (auto &reporterish : m_reporterLikes) { + reporterish->reportInvalidTestSpec(arg); + } + } + + void MultiReporter::benchmarkPreparing(StringRef name) { + for (auto &reporterish : m_reporterLikes) { + reporterish->benchmarkPreparing(name); + } + } + void MultiReporter::benchmarkStarting(BenchmarkInfo const &benchmarkInfo) { + for (auto &reporterish : m_reporterLikes) { + reporterish->benchmarkStarting(benchmarkInfo); + } + } + void MultiReporter::benchmarkEnded(BenchmarkStats<> const &benchmarkStats) { + for (auto &reporterish : m_reporterLikes) { + reporterish->benchmarkEnded(benchmarkStats); + } + } + + void MultiReporter::benchmarkFailed(StringRef error) { + for (auto &reporterish : m_reporterLikes) { + reporterish->benchmarkFailed(error); + } + } + + void MultiReporter::testRunStarting(TestRunInfo const &testRunInfo) { + for (auto &reporterish : m_reporterLikes) { + reporterish->testRunStarting(testRunInfo); + } + } + + void MultiReporter::testCaseStarting(TestCaseInfo const &testInfo) { + for (auto &reporterish : m_reporterLikes) { + reporterish->testCaseStarting(testInfo); + } + } + + void + MultiReporter::testCasePartialStarting(TestCaseInfo const &testInfo, + uint64_t partNumber) { + for (auto &reporterish : m_reporterLikes) { + reporterish->testCasePartialStarting(testInfo, partNumber); + } + } + + void MultiReporter::sectionStarting(SectionInfo const §ionInfo) { + for (auto &reporterish : m_reporterLikes) { + reporterish->sectionStarting(sectionInfo); + } + } + + void MultiReporter::assertionStarting(AssertionInfo const &assertionInfo) { + for (auto &reporterish : m_reporterLikes) { + reporterish->assertionStarting(assertionInfo); + } + } + + void MultiReporter::assertionEnded(AssertionStats const &assertionStats) { + const bool reportByDefault = assertionStats.assertionResult.getResultType() != ResultWas::Ok || m_config->includeSuccessfulResults(); + + for (auto &reporterish : m_reporterLikes) { + if (reportByDefault || reporterish->getPreferences().shouldReportAllAssertions) { + reporterish->assertionEnded(assertionStats); + } + } + } + + void MultiReporter::sectionEnded(SectionStats const §ionStats) { + for (auto &reporterish : m_reporterLikes) { + reporterish->sectionEnded(sectionStats); + } + } + + void MultiReporter::testCasePartialEnded(TestCaseStats const &testStats, + uint64_t partNumber) { + if (m_preferences.shouldRedirectStdOut && m_haveNoncapturingReporters) { + if (!testStats.stdOut.empty()) { + Catch::cout() << testStats.stdOut << std::flush; + } + if (!testStats.stdErr.empty()) { + Catch::cerr() << testStats.stdErr << std::flush; + } + } + + for (auto &reporterish : m_reporterLikes) { + reporterish->testCasePartialEnded(testStats, partNumber); + } + } + + void MultiReporter::testCaseEnded(TestCaseStats const &testCaseStats) { + for (auto &reporterish : m_reporterLikes) { + reporterish->testCaseEnded(testCaseStats); + } + } + + void MultiReporter::testRunEnded(TestRunStats const &testRunStats) { + for (auto &reporterish : m_reporterLikes) { + reporterish->testRunEnded(testRunStats); + } + } + + void MultiReporter::skipTest(TestCaseInfo const &testInfo) { + for (auto &reporterish : m_reporterLikes) { + reporterish->skipTest(testInfo); + } + } + + void MultiReporter::listReporters(std::vector const &descriptions) { + for (auto &reporterish : m_reporterLikes) { + reporterish->listReporters(descriptions); + } + } + + void MultiReporter::listListeners( + std::vector const &descriptions) { + for (auto &reporterish : m_reporterLikes) { + reporterish->listListeners(descriptions); + } + } + + void MultiReporter::listTests(std::vector const &tests) { + for (auto &reporterish : m_reporterLikes) { + reporterish->listTests(tests); + } + } + + void MultiReporter::listTags(std::vector const &tags) { + for (auto &reporterish : m_reporterLikes) { + reporterish->listTags(tags); + } + } + +} // end namespace Catch + +namespace Catch { + namespace Detail { + + void registerReporterImpl(std::string const &name, + IReporterFactoryPtr reporterPtr) { + CATCH_TRY { + getMutableRegistryHub().registerReporter( + name, CATCH_MOVE(reporterPtr)); + } + CATCH_CATCH_ALL { + // Do not throw when constructing global objects, instead + // register the exception to be processed later + getMutableRegistryHub().registerStartupException(); + } + } + + void registerListenerImpl(Detail::unique_ptr listenerFactory) { + getMutableRegistryHub().registerListener(CATCH_MOVE(listenerFactory)); + } + + } // namespace Detail +} // namespace Catch + +#include + +namespace Catch { + + namespace { + std::string createMetadataString(IConfig const &config) { + ReusableStringStream sstr; + if (config.testSpec().hasFilters()) { + sstr << "filters='" + << config.testSpec() + << "' "; + } + sstr << "rng-seed=" << config.rngSeed(); + return sstr.str(); + } + } // namespace + + void SonarQubeReporter::testRunStarting(TestRunInfo const &testRunInfo) { + CumulativeReporterBase::testRunStarting(testRunInfo); + + xml.writeComment(createMetadataString(*m_config)); + xml.startElement("testExecutions"); + xml.writeAttribute("version"_sr, '1'); + } + + void SonarQubeReporter::writeRun(TestRunNode const &runNode) { + std::map> testsPerFile; + + for (auto const &child : runNode.children) { + testsPerFile[child->value.testInfo->lineInfo.file].push_back( + child.get()); + } + + for (auto const &kv : testsPerFile) { + writeTestFile(kv.first, kv.second); + } + } + + void SonarQubeReporter::writeTestFile(StringRef filename, std::vector const &testCaseNodes) { + XmlWriter::ScopedElement e = xml.scopedElement("file"); + xml.writeAttribute("path"_sr, filename); + + for (auto const &child : testCaseNodes) + writeTestCase(*child); + } + + void SonarQubeReporter::writeTestCase(TestCaseNode const &testCaseNode) { + // All test cases have exactly one section - which represents the + // test case itself. That section may have 0-n nested sections + assert(testCaseNode.children.size() == 1); + SectionNode const &rootSection = *testCaseNode.children.front(); + writeSection("", rootSection, testCaseNode.value.testInfo->okToFail()); + } + + void SonarQubeReporter::writeSection(std::string const &rootName, SectionNode const §ionNode, bool okToFail) { + std::string name = trim(sectionNode.stats.sectionInfo.name); + if (!rootName.empty()) + name = rootName + '/' + name; + + if (sectionNode.stats.assertions.total() > 0 + || !sectionNode.stdOut.empty() + || !sectionNode.stdErr.empty()) { + XmlWriter::ScopedElement e = xml.scopedElement("testCase"); + xml.writeAttribute("name"_sr, name); + xml.writeAttribute("duration"_sr, static_cast(sectionNode.stats.durationInSeconds * 1000)); + + writeAssertions(sectionNode, okToFail); + } + + for (auto const &childNode : sectionNode.childSections) + writeSection(name, *childNode, okToFail); + } + + void SonarQubeReporter::writeAssertions(SectionNode const §ionNode, bool okToFail) { + for (auto const &assertionOrBenchmark : sectionNode.assertionsAndBenchmarks) { + if (assertionOrBenchmark.isAssertion()) { + writeAssertion(assertionOrBenchmark.asAssertion(), okToFail); + } + } + } + + void SonarQubeReporter::writeAssertion(AssertionStats const &stats, bool okToFail) { + AssertionResult const &result = stats.assertionResult; + if (!result.isOk() || result.getResultType() == ResultWas::ExplicitSkip) { + std::string elementName; + if (okToFail) { + elementName = "skipped"; + } else { + switch (result.getResultType()) { + case ResultWas::ThrewException: + case ResultWas::FatalErrorCondition: + elementName = "error"; + break; + case ResultWas::ExplicitFailure: + case ResultWas::ExpressionFailed: + case ResultWas::DidntThrowException: + elementName = "failure"; + break; + case ResultWas::ExplicitSkip: + elementName = "skipped"; + break; + // We should never see these here: + case ResultWas::Info: + case ResultWas::Warning: + case ResultWas::Ok: + case ResultWas::Unknown: + case ResultWas::FailureBit: + case ResultWas::Exception: + elementName = "internalError"; + break; + } + } + + XmlWriter::ScopedElement e = xml.scopedElement(elementName); + + ReusableStringStream messageRss; + messageRss << result.getTestMacroName() << '(' << result.getExpression() << ')'; + xml.writeAttribute("message"_sr, messageRss.str()); + + ReusableStringStream textRss; + if (result.getResultType() == ResultWas::ExplicitSkip) { + textRss << "SKIPPED\n"; + } else { + textRss << "FAILED:\n"; + if (result.hasExpression()) { + textRss << '\t' << result.getExpressionInMacro() << '\n'; + } + if (result.hasExpandedExpression()) { + textRss << "with expansion:\n\t" << result.getExpandedExpression() << '\n'; + } + } + + if (result.hasMessage()) + textRss << result.getMessage() << '\n'; + + for (auto const &msg : stats.infoMessages) + if (msg.type == ResultWas::Info) + textRss << msg.message << '\n'; + + textRss << "at " << result.getSourceInfo(); + xml.writeText(textRss.str(), XmlFormatting::Newline); + } + } + +} // end namespace Catch + +namespace Catch { + + StreamingReporterBase::~StreamingReporterBase() = default; + + void + StreamingReporterBase::testRunStarting(TestRunInfo const &_testRunInfo) { + currentTestRunInfo = _testRunInfo; + } + + void StreamingReporterBase::testRunEnded(TestRunStats const &) { + currentTestCaseInfo = nullptr; + } + +} // end namespace Catch + +#include +#include + +namespace Catch { + + namespace { + // Yes, this has to be outside the class and namespaced by naming. + // Making older compiler happy is hard. + static constexpr StringRef tapFailedString = "not ok"_sr; + static constexpr StringRef tapPassedString = "ok"_sr; + static constexpr Colour::Code tapDimColour = Colour::FileName; + + class TapAssertionPrinter { + public: + TapAssertionPrinter &operator=(TapAssertionPrinter const &) = delete; + TapAssertionPrinter(TapAssertionPrinter const &) = delete; + TapAssertionPrinter(std::ostream &_stream, AssertionStats const &_stats, std::size_t _counter, ColourImpl *colour_) + : stream(_stream) + , result(_stats.assertionResult) + , messages(_stats.infoMessages) + , itMessage(_stats.infoMessages.begin()) + , printInfoMessages(true) + , counter(_counter) + , colourImpl(colour_) {} + + void print() { + itMessage = messages.begin(); + + switch (result.getResultType()) { + case ResultWas::Ok: + printResultType(tapPassedString); + printOriginalExpression(); + printReconstructedExpression(); + if (!result.hasExpression()) + printRemainingMessages(Colour::None); + else + printRemainingMessages(); + break; + case ResultWas::ExpressionFailed: + if (result.isOk()) { + printResultType(tapPassedString); + } else { + printResultType(tapFailedString); + } + printOriginalExpression(); + printReconstructedExpression(); + if (result.isOk()) { + printIssue(" # TODO"); + } + printRemainingMessages(); + break; + case ResultWas::ThrewException: + printResultType(tapFailedString); + printIssue("unexpected exception with message:"_sr); + printMessage(); + printExpressionWas(); + printRemainingMessages(); + break; + case ResultWas::FatalErrorCondition: + printResultType(tapFailedString); + printIssue("fatal error condition with message:"_sr); + printMessage(); + printExpressionWas(); + printRemainingMessages(); + break; + case ResultWas::DidntThrowException: + printResultType(tapFailedString); + printIssue("expected exception, got none"_sr); + printExpressionWas(); + printRemainingMessages(); + break; + case ResultWas::Info: + printResultType("info"_sr); + printMessage(); + printRemainingMessages(); + break; + case ResultWas::Warning: + printResultType("warning"_sr); + printMessage(); + printRemainingMessages(); + break; + case ResultWas::ExplicitFailure: + printResultType(tapFailedString); + printIssue("explicitly"_sr); + printRemainingMessages(Colour::None); + break; + case ResultWas::ExplicitSkip: + printResultType(tapPassedString); + printIssue(" # SKIP"_sr); + printMessage(); + printRemainingMessages(); + break; + // These cases are here to prevent compiler warnings + case ResultWas::Unknown: + case ResultWas::FailureBit: + case ResultWas::Exception: + printResultType("** internal error **"_sr); + break; + } + } + + private: + void printResultType(StringRef passOrFail) const { + if (!passOrFail.empty()) { + stream << passOrFail << ' ' << counter << " -"; + } + } + + void printIssue(StringRef issue) const { + stream << ' ' << issue; + } + + void printExpressionWas() { + if (result.hasExpression()) { + stream << ';'; + stream << colourImpl->guardColour(tapDimColour) + << " expression was:"; + printOriginalExpression(); + } + } + + void printOriginalExpression() const { + if (result.hasExpression()) { + stream << ' ' << result.getExpression(); + } + } + + void printReconstructedExpression() const { + if (result.hasExpandedExpression()) { + stream << colourImpl->guardColour(tapDimColour) << " for: "; + + std::string expr = result.getExpandedExpression(); + std::replace(expr.begin(), expr.end(), '\n', ' '); + stream << expr; + } + } + + void printMessage() { + if (itMessage != messages.end()) { + stream << " '" << itMessage->message << '\''; + ++itMessage; + } + } + + void printRemainingMessages(Colour::Code colour = tapDimColour) { + if (itMessage == messages.end()) { + return; + } + + // using messages.end() directly (or auto) yields compilation error: + std::vector::const_iterator itEnd = messages.end(); + const std::size_t N = static_cast(itEnd - itMessage); + + stream << colourImpl->guardColour(colour) << " with " + << pluralise(N, "message"_sr) << ':'; + + for (; itMessage != itEnd;) { + // If this assertion is a warning ignore any INFO messages + if (printInfoMessages || itMessage->type != ResultWas::Info) { + stream << " '" << itMessage->message << '\''; + if (++itMessage != itEnd) { + stream << colourImpl->guardColour(tapDimColour) << " and"; + } + } + } + } + + private: + std::ostream &stream; + AssertionResult const &result; + std::vector const &messages; + std::vector::const_iterator itMessage; + bool printInfoMessages; + std::size_t counter; + ColourImpl *colourImpl; + }; + + } // End anonymous namespace + + void TAPReporter::testRunStarting(TestRunInfo const &) { + if (m_config->testSpec().hasFilters()) { + m_stream << "# filters: " << m_config->testSpec() << '\n'; + } + m_stream << "# rng-seed: " << m_config->rngSeed() << '\n' + << std::flush; + } + + void TAPReporter::noMatchingTestCases(StringRef unmatchedSpec) { + m_stream << "# No test cases matched '" << unmatchedSpec << "'\n"; + } + + void TAPReporter::assertionEnded(AssertionStats const &_assertionStats) { + ++counter; + + m_stream << "# " << currentTestCaseInfo->name << '\n'; + TapAssertionPrinter printer(m_stream, _assertionStats, counter, m_colour.get()); + printer.print(); + + m_stream << '\n' + << std::flush; + } + + void TAPReporter::testRunEnded(TestRunStats const &_testRunStats) { + m_stream << "1.." << _testRunStats.totals.assertions.total(); + if (_testRunStats.totals.testCases.total() == 0) { + m_stream << " # Skipped: No tests ran."; + } + m_stream << "\n\n" + << std::flush; + StreamingReporterBase::testRunEnded(_testRunStats); + } + +} // end namespace Catch + +#include +#include + +namespace Catch { + + namespace { + // if string has a : in first line will set indent to follow it on + // subsequent lines + void printHeaderString(std::ostream &os, std::string const &_string, std::size_t indent = 0) { + std::size_t i = _string.find(": "); + if (i != std::string::npos) + i += 2; + else + i = 0; + os << TextFlow::Column(_string) + .indent(indent + i) + .initialIndent(indent) + << '\n'; + } + + std::string escape(StringRef str) { + std::string escaped = static_cast(str); + replaceInPlace(escaped, "|", "||"); + replaceInPlace(escaped, "'", "|'"); + replaceInPlace(escaped, "\n", "|n"); + replaceInPlace(escaped, "\r", "|r"); + replaceInPlace(escaped, "[", "|["); + replaceInPlace(escaped, "]", "|]"); + return escaped; + } + } // end anonymous namespace + + TeamCityReporter::~TeamCityReporter() = default; + + void TeamCityReporter::testRunStarting(TestRunInfo const &runInfo) { + m_stream << "##teamcity[testSuiteStarted name='" << escape(runInfo.name) + << "']\n"; + } + + void TeamCityReporter::testRunEnded(TestRunStats const &runStats) { + m_stream << "##teamcity[testSuiteFinished name='" + << escape(runStats.runInfo.name) << "']\n"; + } + + void TeamCityReporter::assertionEnded(AssertionStats const &assertionStats) { + AssertionResult const &result = assertionStats.assertionResult; + if (!result.isOk() || result.getResultType() == ResultWas::ExplicitSkip) { + ReusableStringStream msg; + if (!m_headerPrintedForThisSection) + printSectionHeader(msg.get()); + m_headerPrintedForThisSection = true; + + msg << result.getSourceInfo() << '\n'; + + switch (result.getResultType()) { + case ResultWas::ExpressionFailed: + msg << "expression failed"; + break; + case ResultWas::ThrewException: + msg << "unexpected exception"; + break; + case ResultWas::FatalErrorCondition: + msg << "fatal error condition"; + break; + case ResultWas::DidntThrowException: + msg << "no exception was thrown where one was expected"; + break; + case ResultWas::ExplicitFailure: + msg << "explicit failure"; + break; + case ResultWas::ExplicitSkip: + msg << "explicit skip"; + break; + + // We shouldn't get here because of the isOk() test + case ResultWas::Ok: + case ResultWas::Info: + case ResultWas::Warning: + CATCH_ERROR("Internal error in TeamCity reporter"); + // These cases are here to prevent compiler warnings + case ResultWas::Unknown: + case ResultWas::FailureBit: + case ResultWas::Exception: + CATCH_ERROR("Not implemented"); + } + if (assertionStats.infoMessages.size() == 1) + msg << " with message:"; + if (assertionStats.infoMessages.size() > 1) + msg << " with messages:"; + for (auto const &messageInfo : assertionStats.infoMessages) + msg << "\n \"" << messageInfo.message << '"'; + + if (result.hasExpression()) { + msg << "\n " << result.getExpressionInMacro() << "\n" + "with expansion:\n" + " " + << result.getExpandedExpression() << '\n'; + } + + if (result.getResultType() == ResultWas::ExplicitSkip) { + m_stream << "##teamcity[testIgnored"; + } else if (currentTestCaseInfo->okToFail()) { + msg << "- failure ignore as test marked as 'ok to fail'\n"; + m_stream << "##teamcity[testIgnored"; + } else { + m_stream << "##teamcity[testFailed"; + } + m_stream << " name='" << escape(currentTestCaseInfo->name) << '\'' + << " message='" << escape(msg.str()) << '\'' << "]\n"; + } + m_stream.flush(); + } + + void TeamCityReporter::testCaseStarting(TestCaseInfo const &testInfo) { + m_testTimer.start(); + StreamingReporterBase::testCaseStarting(testInfo); + m_stream << "##teamcity[testStarted name='" + << escape(testInfo.name) << "']\n"; + m_stream.flush(); + } + + void TeamCityReporter::testCaseEnded(TestCaseStats const &testCaseStats) { + StreamingReporterBase::testCaseEnded(testCaseStats); + auto const &testCaseInfo = *testCaseStats.testInfo; + if (!testCaseStats.stdOut.empty()) + m_stream << "##teamcity[testStdOut name='" + << escape(testCaseInfo.name) + << "' out='" << escape(testCaseStats.stdOut) << "']\n"; + if (!testCaseStats.stdErr.empty()) + m_stream << "##teamcity[testStdErr name='" + << escape(testCaseInfo.name) + << "' out='" << escape(testCaseStats.stdErr) << "']\n"; + m_stream << "##teamcity[testFinished name='" + << escape(testCaseInfo.name) << "' duration='" + << m_testTimer.getElapsedMilliseconds() << "']\n"; + m_stream.flush(); + } + + void TeamCityReporter::printSectionHeader(std::ostream &os) { + assert(!m_sectionStack.empty()); + + if (m_sectionStack.size() > 1) { + os << lineOfChars('-') << '\n'; + + std::vector::const_iterator + it + = m_sectionStack.begin() + 1, // Skip first section (test case) + itEnd = m_sectionStack.end(); + for (; it != itEnd; ++it) + printHeaderString(os, it->name); + os << lineOfChars('-') << '\n'; + } + + SourceLineInfo lineInfo = m_sectionStack.front().lineInfo; + + os << lineInfo << '\n'; + os << lineOfChars('.') << "\n\n"; + } + +} // end namespace Catch + +#if defined(_MSC_VER) +#pragma warning(push) +#pragma warning(disable : 4061) // Not all labels are EXPLICITLY handled in switch + // Note that 4062 (not all labels are handled + // and default is missing) is enabled +#endif + +namespace Catch { + XmlReporter::XmlReporter(ReporterConfig &&_config) + : StreamingReporterBase(CATCH_MOVE(_config)), m_xml(m_stream) { + m_preferences.shouldRedirectStdOut = true; + m_preferences.shouldReportAllAssertions = true; + m_preferences.shouldReportAllAssertionStarts = false; + } + + XmlReporter::~XmlReporter() = default; + + std::string XmlReporter::getDescription() { + return "Reports test results as an XML document"; + } + + std::string XmlReporter::getStylesheetRef() const { + return std::string(); + } + + void XmlReporter::writeSourceInfo(SourceLineInfo const &sourceInfo) { + m_xml + .writeAttribute("filename"_sr, sourceInfo.file) + .writeAttribute("line"_sr, sourceInfo.line); + } + + void XmlReporter::testRunStarting(TestRunInfo const &testInfo) { + StreamingReporterBase::testRunStarting(testInfo); + std::string stylesheetRef = getStylesheetRef(); + if (!stylesheetRef.empty()) + m_xml.writeStylesheetRef(stylesheetRef); + m_xml.startElement("Catch2TestRun") + .writeAttribute("name"_sr, m_config->name()) + .writeAttribute("rng-seed"_sr, m_config->rngSeed()) + .writeAttribute("xml-format-version"_sr, 3) + .writeAttribute("catch2-version"_sr, libraryVersion()); + if (m_config->testSpec().hasFilters()) { + m_xml.writeAttribute("filters"_sr, m_config->testSpec()); + } + } + + void XmlReporter::testCaseStarting(TestCaseInfo const &testInfo) { + StreamingReporterBase::testCaseStarting(testInfo); + m_xml.startElement("TestCase") + .writeAttribute("name"_sr, trim(StringRef(testInfo.name))) + .writeAttribute("tags"_sr, testInfo.tagsAsString()); + + writeSourceInfo(testInfo.lineInfo); + + if (m_config->showDurations() == ShowDurations::Always) + m_testCaseTimer.start(); + m_xml.ensureTagClosed(); + } + + void XmlReporter::sectionStarting(SectionInfo const §ionInfo) { + StreamingReporterBase::sectionStarting(sectionInfo); + if (m_sectionDepth++ > 0) { + m_xml.startElement("Section") + .writeAttribute("name"_sr, trim(StringRef(sectionInfo.name))); + writeSourceInfo(sectionInfo.lineInfo); + m_xml.ensureTagClosed(); + } + } + + void XmlReporter::assertionEnded(AssertionStats const &assertionStats) { + AssertionResult const &result = assertionStats.assertionResult; + + bool includeResults = m_config->includeSuccessfulResults() || !result.isOk(); + + if (includeResults || result.getResultType() == ResultWas::Warning) { + // Print any info messages in tags. + for (auto const &msg : assertionStats.infoMessages) { + if (msg.type == ResultWas::Info && includeResults) { + auto t = m_xml.scopedElement("Info"); + writeSourceInfo(msg.lineInfo); + t.writeText(msg.message); + } else if (msg.type == ResultWas::Warning) { + auto t = m_xml.scopedElement("Warning"); + writeSourceInfo(msg.lineInfo); + t.writeText(msg.message); + } + } + } + + // Drop out if result was successful but we're not printing them. + if (!includeResults && result.getResultType() != ResultWas::Warning && result.getResultType() != ResultWas::ExplicitSkip) { + return; + } + + // Print the expression if there is one. + if (result.hasExpression()) { + m_xml.startElement("Expression") + .writeAttribute("success"_sr, result.succeeded()) + .writeAttribute("type"_sr, result.getTestMacroName()); + + writeSourceInfo(result.getSourceInfo()); + + m_xml.scopedElement("Original") + .writeText(result.getExpression()); + m_xml.scopedElement("Expanded") + .writeText(result.getExpandedExpression()); + } + + // And... Print a result applicable to each result type. + switch (result.getResultType()) { + case ResultWas::ThrewException: + m_xml.startElement("Exception"); + writeSourceInfo(result.getSourceInfo()); + m_xml.writeText(result.getMessage()); + m_xml.endElement(); + break; + case ResultWas::FatalErrorCondition: + m_xml.startElement("FatalErrorCondition"); + writeSourceInfo(result.getSourceInfo()); + m_xml.writeText(result.getMessage()); + m_xml.endElement(); + break; + case ResultWas::Info: + m_xml.scopedElement("Info") + .writeText(result.getMessage()); + break; + case ResultWas::Warning: + // Warning will already have been written + break; + case ResultWas::ExplicitFailure: + m_xml.startElement("Failure"); + writeSourceInfo(result.getSourceInfo()); + m_xml.writeText(result.getMessage()); + m_xml.endElement(); + break; + case ResultWas::ExplicitSkip: + m_xml.startElement("Skip"); + writeSourceInfo(result.getSourceInfo()); + m_xml.writeText(result.getMessage()); + m_xml.endElement(); + break; + default: + break; + } + + if (result.hasExpression()) + m_xml.endElement(); + } + + void XmlReporter::sectionEnded(SectionStats const §ionStats) { + StreamingReporterBase::sectionEnded(sectionStats); + if (--m_sectionDepth > 0) { + { + XmlWriter::ScopedElement e = m_xml.scopedElement("OverallResults"); + e.writeAttribute("successes"_sr, sectionStats.assertions.passed); + e.writeAttribute("failures"_sr, sectionStats.assertions.failed); + e.writeAttribute("expectedFailures"_sr, sectionStats.assertions.failedButOk); + e.writeAttribute("skipped"_sr, sectionStats.assertions.skipped > 0); + + if (m_config->showDurations() == ShowDurations::Always) + e.writeAttribute("durationInSeconds"_sr, sectionStats.durationInSeconds); + } + // Ends assertion tag + m_xml.endElement(); + } + } + + void XmlReporter::testCaseEnded(TestCaseStats const &testCaseStats) { + StreamingReporterBase::testCaseEnded(testCaseStats); + XmlWriter::ScopedElement e = m_xml.scopedElement("OverallResult"); + e.writeAttribute("success"_sr, testCaseStats.totals.assertions.allOk()); + e.writeAttribute("skips"_sr, testCaseStats.totals.assertions.skipped); + + if (m_config->showDurations() == ShowDurations::Always) + e.writeAttribute("durationInSeconds"_sr, m_testCaseTimer.getElapsedSeconds()); + if (!testCaseStats.stdOut.empty()) + m_xml.scopedElement("StdOut").writeText(trim(StringRef(testCaseStats.stdOut)), XmlFormatting::Newline); + if (!testCaseStats.stdErr.empty()) + m_xml.scopedElement("StdErr").writeText(trim(StringRef(testCaseStats.stdErr)), XmlFormatting::Newline); + + m_xml.endElement(); + } + + void XmlReporter::testRunEnded(TestRunStats const &testRunStats) { + StreamingReporterBase::testRunEnded(testRunStats); + m_xml.scopedElement("OverallResults") + .writeAttribute("successes"_sr, testRunStats.totals.assertions.passed) + .writeAttribute("failures"_sr, testRunStats.totals.assertions.failed) + .writeAttribute("expectedFailures"_sr, testRunStats.totals.assertions.failedButOk) + .writeAttribute("skips"_sr, testRunStats.totals.assertions.skipped); + m_xml.scopedElement("OverallResultsCases") + .writeAttribute("successes"_sr, testRunStats.totals.testCases.passed) + .writeAttribute("failures"_sr, testRunStats.totals.testCases.failed) + .writeAttribute("expectedFailures"_sr, testRunStats.totals.testCases.failedButOk) + .writeAttribute("skips"_sr, testRunStats.totals.testCases.skipped); + m_xml.endElement(); + } + + void XmlReporter::benchmarkPreparing(StringRef name) { + m_xml.startElement("BenchmarkResults") + .writeAttribute("name"_sr, name); + } + + void XmlReporter::benchmarkStarting(BenchmarkInfo const &info) { + m_xml.writeAttribute("samples"_sr, info.samples) + .writeAttribute("resamples"_sr, info.resamples) + .writeAttribute("iterations"_sr, info.iterations) + .writeAttribute("clockResolution"_sr, info.clockResolution) + .writeAttribute("estimatedDuration"_sr, info.estimatedDuration) + .writeComment("All values in nano seconds"_sr); + } + + void XmlReporter::benchmarkEnded(BenchmarkStats<> const &benchmarkStats) { + m_xml.scopedElement("mean") + .writeAttribute("value"_sr, benchmarkStats.mean.point.count()) + .writeAttribute("lowerBound"_sr, benchmarkStats.mean.lower_bound.count()) + .writeAttribute("upperBound"_sr, benchmarkStats.mean.upper_bound.count()) + .writeAttribute("ci"_sr, benchmarkStats.mean.confidence_interval); + m_xml.scopedElement("standardDeviation") + .writeAttribute("value"_sr, benchmarkStats.standardDeviation.point.count()) + .writeAttribute("lowerBound"_sr, benchmarkStats.standardDeviation.lower_bound.count()) + .writeAttribute("upperBound"_sr, benchmarkStats.standardDeviation.upper_bound.count()) + .writeAttribute("ci"_sr, benchmarkStats.standardDeviation.confidence_interval); + m_xml.scopedElement("outliers") + .writeAttribute("variance"_sr, benchmarkStats.outlierVariance) + .writeAttribute("lowMild"_sr, benchmarkStats.outliers.low_mild) + .writeAttribute("lowSevere"_sr, benchmarkStats.outliers.low_severe) + .writeAttribute("highMild"_sr, benchmarkStats.outliers.high_mild) + .writeAttribute("highSevere"_sr, benchmarkStats.outliers.high_severe); + m_xml.endElement(); + } + + void XmlReporter::benchmarkFailed(StringRef error) { + m_xml.scopedElement("failed").writeAttribute("message"_sr, error); + m_xml.endElement(); + } + + void XmlReporter::listReporters(std::vector const &descriptions) { + auto outerTag = m_xml.scopedElement("AvailableReporters"); + for (auto const &reporter : descriptions) { + auto inner = m_xml.scopedElement("Reporter"); + m_xml.startElement("Name", XmlFormatting::Indent) + .writeText(reporter.name, XmlFormatting::None) + .endElement(XmlFormatting::Newline); + m_xml.startElement("Description", XmlFormatting::Indent) + .writeText(reporter.description, XmlFormatting::None) + .endElement(XmlFormatting::Newline); + } + } + + void XmlReporter::listListeners(std::vector const &descriptions) { + auto outerTag = m_xml.scopedElement("RegisteredListeners"); + for (auto const &listener : descriptions) { + auto inner = m_xml.scopedElement("Listener"); + m_xml.startElement("Name", XmlFormatting::Indent) + .writeText(listener.name, XmlFormatting::None) + .endElement(XmlFormatting::Newline); + m_xml.startElement("Description", XmlFormatting::Indent) + .writeText(listener.description, XmlFormatting::None) + .endElement(XmlFormatting::Newline); + } + } + + void XmlReporter::listTests(std::vector const &tests) { + auto outerTag = m_xml.scopedElement("MatchingTests"); + for (auto const &test : tests) { + auto innerTag = m_xml.scopedElement("TestCase"); + auto const &testInfo = test.getTestCaseInfo(); + m_xml.startElement("Name", XmlFormatting::Indent) + .writeText(testInfo.name, XmlFormatting::None) + .endElement(XmlFormatting::Newline); + m_xml.startElement("ClassName", XmlFormatting::Indent) + .writeText(testInfo.className, XmlFormatting::None) + .endElement(XmlFormatting::Newline); + m_xml.startElement("Tags", XmlFormatting::Indent) + .writeText(testInfo.tagsAsString(), XmlFormatting::None) + .endElement(XmlFormatting::Newline); + + auto sourceTag = m_xml.scopedElement("SourceInfo"); + m_xml.startElement("File", XmlFormatting::Indent) + .writeText(testInfo.lineInfo.file, XmlFormatting::None) + .endElement(XmlFormatting::Newline); + m_xml.startElement("Line", XmlFormatting::Indent) + .writeText(std::to_string(testInfo.lineInfo.line), XmlFormatting::None) + .endElement(XmlFormatting::Newline); + } + } + + void XmlReporter::listTags(std::vector const &tags) { + auto outerTag = m_xml.scopedElement("TagsFromMatchingTests"); + for (auto const &tag : tags) { + auto innerTag = m_xml.scopedElement("Tag"); + m_xml.startElement("Count", XmlFormatting::Indent) + .writeText(std::to_string(tag.count), XmlFormatting::None) + .endElement(XmlFormatting::Newline); + auto aliasTag = m_xml.scopedElement("Aliases"); + for (auto const &alias : tag.spellings) { + m_xml.startElement("Alias", XmlFormatting::Indent) + .writeText(alias, XmlFormatting::None) + .endElement(XmlFormatting::Newline); + } + } + } + +} // end namespace Catch + +#if defined(_MSC_VER) +#pragma warning(pop) +#endif diff --git a/unittests/catch_amalgamated.hpp b/unittests/catch_amalgamated.hpp new file mode 100644 index 000000000..6590fde26 --- /dev/null +++ b/unittests/catch_amalgamated.hpp @@ -0,0 +1,14059 @@ + +// Copyright Catch2 Authors +// Distributed under the Boost Software License, Version 1.0. +// (See accompanying file LICENSE.txt or copy at +// https://www.boost.org/LICENSE_1_0.txt) + +// SPDX-License-Identifier: BSL-1.0 + +// Catch v3.14.0 +// Generated: 2026-04-05 15:03:01.150393 +// ---------------------------------------------------------- +// This file is an amalgamation of multiple different files. +// You probably shouldn't edit it directly. +// ---------------------------------------------------------- + +#if defined(min) && defined(max) +#undef min +#undef max +#endif + +#ifndef CATCH_AMALGAMATED_HPP_INCLUDED +#define CATCH_AMALGAMATED_HPP_INCLUDED + +/** \file + * This is a convenience header for Catch2. It includes **all** of Catch2 headers. + * + * Generally the Catch2 users should use specific includes they need, + * but this header can be used instead for ease-of-experimentation, or + * just plain convenience, at the cost of (significantly) increased + * compilation times. + * + * When a new header is added to either the top level folder, or to the + * corresponding internal subfolder, it should be added here. Headers + * added to the various subparts (e.g. matchers, generators, etc...), + * should go their respective catch-all headers. + */ + +#ifndef CATCH_ALL_HPP_INCLUDED +#define CATCH_ALL_HPP_INCLUDED + +/** \file + * This is a convenience header for Catch2's benchmarking. It includes + * **all** of Catch2 headers related to benchmarking. + * + * Generally the Catch2 users should use specific includes they need, + * but this header can be used instead for ease-of-experimentation, or + * just plain convenience, at the cost of (significantly) increased + * compilation times. + * + * When a new header is added to either the `benchmark` folder, or to + * the corresponding internal (detail) subfolder, it should be added here. + */ + +#ifndef CATCH_BENCHMARK_ALL_HPP_INCLUDED +#define CATCH_BENCHMARK_ALL_HPP_INCLUDED + +// Adapted from donated nonius code. + +#ifndef CATCH_BENCHMARK_HPP_INCLUDED +#define CATCH_BENCHMARK_HPP_INCLUDED + +#ifndef CATCH_COMPILER_CAPABILITIES_HPP_INCLUDED +#define CATCH_COMPILER_CAPABILITIES_HPP_INCLUDED + +// Detect a number of compiler features - by compiler +// The following features are defined: +// +// CATCH_CONFIG_WINDOWS_SEH : is Windows SEH supported? +// CATCH_CONFIG_POSIX_SIGNALS : are POSIX signals supported? +// CATCH_CONFIG_DISABLE_EXCEPTIONS : Are exceptions enabled? +// **************** +// Note to maintainers: if new toggles are added please document them +// in configuration.md, too +// **************** + +// In general each macro has a _NO_ form +// (e.g. CATCH_CONFIG_NO_POSIX_SIGNALS) which disables the feature. +// Many features, at point of detection, define an _INTERNAL_ macro, so they +// can be combined, en-mass, with the _NO_ forms later. + +#ifndef CATCH_PLATFORM_HPP_INCLUDED +#define CATCH_PLATFORM_HPP_INCLUDED + +// See e.g.: +// https://opensource.apple.com/source/CarbonHeaders/CarbonHeaders-18.1/TargetConditionals.h.auto.html +#ifdef __APPLE__ +#ifndef __has_extension +#define __has_extension(x) 0 +#endif +#include +#if (defined(TARGET_OS_OSX) && TARGET_OS_OSX == 1) || (defined(TARGET_OS_MAC) && TARGET_OS_MAC == 1) +#define CATCH_PLATFORM_MAC +#elif (defined(TARGET_OS_IPHONE) && TARGET_OS_IPHONE == 1) +#define CATCH_PLATFORM_IPHONE +#endif + +#elif defined(linux) || defined(__linux) || defined(__linux__) +#define CATCH_PLATFORM_LINUX + +#elif defined(__QNX__) +#define CATCH_PLATFORM_QNX + +#elif defined(WIN32) || defined(__WIN32__) || defined(_WIN32) || defined(_MSC_VER) || defined(__MINGW32__) +#define CATCH_PLATFORM_WINDOWS + +#if defined(WINAPI_FAMILY) && (WINAPI_FAMILY == WINAPI_FAMILY_APP) +#define CATCH_PLATFORM_WINDOWS_UWP +#endif + +#elif defined(__ORBIS__) || defined(__PROSPERO__) +#define CATCH_PLATFORM_PLAYSTATION + +#endif + +#endif // CATCH_PLATFORM_HPP_INCLUDED + +#ifdef __cplusplus + +#if (__cplusplus >= 201703L) || (defined(_MSVC_LANG) && _MSVC_LANG >= 201703L) +#define CATCH_CPP17_OR_GREATER +#endif + +#if (__cplusplus >= 202002L) || (defined(_MSVC_LANG) && _MSVC_LANG >= 202002L) +#define CATCH_CPP20_OR_GREATER +#endif + +#endif + +// Only GCC compiler should be used in this block, so other compilers trying to +// mask themselves as GCC should be ignored. +#if defined(__GNUC__) && !defined(__clang__) && !defined(__ICC) && !defined(__CUDACC__) && !defined(__LCC__) && !defined(__NVCOMPILER) +#define CATCH_INTERNAL_START_WARNINGS_SUPPRESSION _Pragma("GCC diagnostic push") +#define CATCH_INTERNAL_STOP_WARNINGS_SUPPRESSION _Pragma("GCC diagnostic pop") + +// This only works on GCC 9+. so we have to also add a global suppression of Wparentheses +// for older versions of GCC. +#define CATCH_INTERNAL_SUPPRESS_PARENTHESES_WARNINGS \ + _Pragma("GCC diagnostic ignored \"-Wparentheses\"") + +#define CATCH_INTERNAL_SUPPRESS_UNUSED_RESULT \ + _Pragma("GCC diagnostic ignored \"-Wunused-result\"") + +#define CATCH_INTERNAL_SUPPRESS_UNUSED_VARIABLE_WARNINGS \ + _Pragma("GCC diagnostic ignored \"-Wunused-variable\"") + +#define CATCH_INTERNAL_SUPPRESS_USELESS_CAST_WARNINGS \ + _Pragma("GCC diagnostic ignored \"-Wuseless-cast\"") + +#define CATCH_INTERNAL_SUPPRESS_SHADOW_WARNINGS \ + _Pragma("GCC diagnostic ignored \"-Wshadow\"") + +#define CATCH_INTERNAL_CONFIG_USE_BUILTIN_CONSTANT_P + +#endif + +#if defined(__NVCOMPILER) +#define CATCH_INTERNAL_START_WARNINGS_SUPPRESSION _Pragma("diag push") +#define CATCH_INTERNAL_STOP_WARNINGS_SUPPRESSION _Pragma("diag pop") +#define CATCH_INTERNAL_SUPPRESS_UNUSED_VARIABLE_WARNINGS _Pragma("diag_suppress declared_but_not_referenced") +#endif + +#if defined(__CUDACC__) && !defined(__clang__) +#ifdef __NVCC_DIAG_PRAGMA_SUPPORT__ +// New pragmas introduced in CUDA 11.5+ +#define CATCH_INTERNAL_START_WARNINGS_SUPPRESSION _Pragma("nv_diagnostic push") +#define CATCH_INTERNAL_STOP_WARNINGS_SUPPRESSION _Pragma("nv_diagnostic pop") +#define CATCH_INTERNAL_SUPPRESS_UNUSED_VARIABLE_WARNINGS _Pragma("nv_diag_suppress 177") +#else +#define CATCH_INTERNAL_SUPPRESS_UNUSED_VARIABLE_WARNINGS _Pragma("diag_suppress 177") +#endif +#endif + +// clang-cl defines _MSC_VER as well as __clang__, which could cause the +// start/stop internal suppression macros to be double defined. +#if defined(__clang__) && !defined(_MSC_VER) +#define CATCH_INTERNAL_CONFIG_USE_BUILTIN_CONSTANT_P +#define CATCH_INTERNAL_START_WARNINGS_SUPPRESSION _Pragma("clang diagnostic push") +#define CATCH_INTERNAL_STOP_WARNINGS_SUPPRESSION _Pragma("clang diagnostic pop") +#endif // __clang__ && !_MSC_VER + +#if defined(__clang__) + +#define CATCH_INTERNAL_SUPPRESS_GLOBALS_WARNINGS \ + _Pragma("clang diagnostic ignored \"-Wexit-time-destructors\"") \ + _Pragma("clang diagnostic ignored \"-Wglobal-constructors\"") + +#define CATCH_INTERNAL_SUPPRESS_PARENTHESES_WARNINGS \ + _Pragma("clang diagnostic ignored \"-Wparentheses\"") + +#define CATCH_INTERNAL_SUPPRESS_UNUSED_VARIABLE_WARNINGS \ + _Pragma("clang diagnostic ignored \"-Wunused-variable\"") + +#if (__clang_major__ >= 20) +#define CATCH_INTERNAL_SUPPRESS_ZERO_VARIADIC_WARNINGS \ + _Pragma("clang diagnostic ignored \"-Wvariadic-macro-arguments-omitted\"") +#elif (__clang_major__ == 19) +#define CATCH_INTERNAL_SUPPRESS_ZERO_VARIADIC_WARNINGS \ + _Pragma("clang diagnostic ignored \"-Wc++20-extensions\"") +#else +#define CATCH_INTERNAL_SUPPRESS_ZERO_VARIADIC_WARNINGS \ + _Pragma("clang diagnostic ignored \"-Wgnu-zero-variadic-macro-arguments\"") +#endif + +#if (__clang_major__ >= 22) +#define CATCH_INTERNAL_SUPPRESS_COUNTER_WARNINGS \ + _Pragma("clang diagnostic ignored \"-Wc2y-extensions\"") +#endif + +#define CATCH_INTERNAL_SUPPRESS_UNUSED_TEMPLATE_WARNINGS \ + _Pragma("clang diagnostic ignored \"-Wunused-template\"") + +#define CATCH_INTERNAL_SUPPRESS_COMMA_WARNINGS \ + _Pragma("clang diagnostic ignored \"-Wcomma\"") + +#define CATCH_INTERNAL_SUPPRESS_SHADOW_WARNINGS \ + _Pragma("clang diagnostic ignored \"-Wshadow\"") + +#endif // __clang__ + +// As of this writing, IBM XL's implementation of __builtin_constant_p has a bug +// which results in calls to destructors being emitted for each temporary, +// without a matching initialization. In practice, this can result in something +// like `std::string::~string` being called on an uninitialized value. +// +// For example, this code will likely segfault under IBM XL: +// ``` +// REQUIRE(std::string("12") + "34" == "1234") +// ``` +// +// Similarly, NVHPC's implementation of `__builtin_constant_p` has a bug which +// results in calls to the immediately evaluated lambda expressions to be +// reported as unevaluated lambdas. +// https://developer.nvidia.com/nvidia_bug/3321845. +// +// Therefore, `CATCH_INTERNAL_IGNORE_BUT_WARN` is not implemented. +#if defined(__ibmxl__) || defined(__CUDACC__) || defined(__NVCOMPILER) +#define CATCH_INTERNAL_CONFIG_NO_USE_BUILTIN_CONSTANT_P +#endif + +//////////////////////////////////////////////////////////////////////////////// +// We know some environments not to support full POSIX signals +#if defined(CATCH_PLATFORM_WINDOWS) || defined(CATCH_PLATFORM_PLAYSTATION) || defined(__CYGWIN__) || defined(__QNX__) || defined(__EMSCRIPTEN__) || defined(__DJGPP__) || defined(__OS400__) +#define CATCH_INTERNAL_CONFIG_NO_POSIX_SIGNALS +#else +#define CATCH_INTERNAL_CONFIG_POSIX_SIGNALS +#endif + +//////////////////////////////////////////////////////////////////////////////// +// Assume that some platforms do not support getenv. +#if defined(CATCH_PLATFORM_WINDOWS_UWP) || defined(CATCH_PLATFORM_PLAYSTATION) || defined(_GAMING_XBOX) +#define CATCH_INTERNAL_CONFIG_NO_GETENV +#else +#define CATCH_INTERNAL_CONFIG_GETENV +#endif + +//////////////////////////////////////////////////////////////////////////////// +// Android somehow still does not support std::to_string +#if defined(__ANDROID__) +#define CATCH_INTERNAL_CONFIG_NO_CPP11_TO_STRING +#endif + +//////////////////////////////////////////////////////////////////////////////// +// Not all Windows environments support SEH properly +#if defined(__MINGW32__) +#define CATCH_INTERNAL_CONFIG_NO_WINDOWS_SEH +#endif + +//////////////////////////////////////////////////////////////////////////////// +// PS4 +#if defined(__ORBIS__) +#define CATCH_INTERNAL_CONFIG_NO_NEW_CAPTURE +#endif + +//////////////////////////////////////////////////////////////////////////////// +// Cygwin +#ifdef __CYGWIN__ + +// Required for some versions of Cygwin to declare gettimeofday +// see: http://stackoverflow.com/questions/36901803/gettimeofday-not-declared-in-this-scope-cygwin +#define _BSD_SOURCE +// some versions of cygwin (most) do not support std::to_string. Use the libstd check. +// https://gcc.gnu.org/onlinedocs/gcc-4.8.2/libstdc++/api/a01053_source.html line 2812-2813 +#if !((__cplusplus >= 201103L) && defined(_GLIBCXX_USE_C99) \ + && !defined(_GLIBCXX_HAVE_BROKEN_VSWPRINTF)) + +#define CATCH_INTERNAL_CONFIG_NO_CPP11_TO_STRING + +#endif +#endif // __CYGWIN__ + +//////////////////////////////////////////////////////////////////////////////// +// Visual C++ +#if defined(_MSC_VER) && !defined(__clang__) + +// We want to defer to nvcc-specific warning suppression if we are compiled +// with nvcc masquerading for MSVC. +#if !defined(__CUDACC__) +#define CATCH_INTERNAL_START_WARNINGS_SUPPRESSION \ + __pragma(warning(push)) +#define CATCH_INTERNAL_STOP_WARNINGS_SUPPRESSION \ + __pragma(warning(pop)) +#endif + +// Suppress MSVC C++ Core Guidelines checker warning 26426: +// "Global initializer calls a non-constexpr function (i.22)" +#define CATCH_INTERNAL_SUPPRESS_GLOBALS_WARNINGS \ + __pragma(warning(disable : 26426)) + +// Universal Windows platform does not support SEH +#if !defined(CATCH_PLATFORM_WINDOWS_UWP) +#define CATCH_INTERNAL_CONFIG_WINDOWS_SEH +#endif + +// Only some Windows platform families support the console +#if defined(WINAPI_FAMILY_PARTITION) +#if !WINAPI_FAMILY_PARTITION(WINAPI_PARTITION_APP | WINAPI_PARTITION_SYSTEM) +#define CATCH_INTERNAL_CONFIG_NO_COLOUR_WIN32 +#endif +#endif + +// MSVC traditional preprocessor needs some workaround for __VA_ARGS__ +// _MSVC_TRADITIONAL == 0 means new conformant preprocessor +// _MSVC_TRADITIONAL == 1 means old traditional non-conformant preprocessor +#if !defined(__clang__) // Handle Clang masquerading for msvc +#if !defined(_MSVC_TRADITIONAL) || (defined(_MSVC_TRADITIONAL) && _MSVC_TRADITIONAL) +#define CATCH_INTERNAL_CONFIG_TRADITIONAL_MSVC_PREPROCESSOR +#endif // MSVC_TRADITIONAL +#endif // __clang__ + +#endif // _MSC_VER + +#if defined(_REENTRANT) || defined(_MSC_VER) +// Enable async processing, as -pthread is specified or no additional linking is required +#define CATCH_INTERNAL_CONFIG_USE_ASYNC +#endif // _MSC_VER + +//////////////////////////////////////////////////////////////////////////////// +// Check if we are compiled with -fno-exceptions or equivalent +#if defined(__EXCEPTIONS) || defined(__cpp_exceptions) || defined(_CPPUNWIND) +#define CATCH_INTERNAL_CONFIG_EXCEPTIONS_ENABLED +#endif + +//////////////////////////////////////////////////////////////////////////////// +// Embarcadero C++Build +#if defined(__BORLANDC__) +#define CATCH_INTERNAL_CONFIG_POLYFILL_ISNAN +#endif + +//////////////////////////////////////////////////////////////////////////////// + +// RTX is a special version of Windows that is real time. +// This means that it is detected as Windows, but does not provide +// the same set of capabilities as real Windows does. +#if defined(UNDER_RTSS) || defined(RTX64_BUILD) +#define CATCH_INTERNAL_CONFIG_NO_WINDOWS_SEH +#define CATCH_INTERNAL_CONFIG_NO_ASYNC +#define CATCH_INTERNAL_CONFIG_NO_COLOUR_WIN32 +#endif + +#if !defined(_GLIBCXX_USE_C99_MATH_TR1) +#define CATCH_INTERNAL_CONFIG_GLOBAL_NEXTAFTER +#endif + +// Various stdlib support checks that require __has_include +#if defined(__has_include) +// Check if string_view is available and usable +#if __has_include() && defined(CATCH_CPP17_OR_GREATER) +#define CATCH_INTERNAL_CONFIG_CPP17_STRING_VIEW +#endif + +// Check if optional is available and usable +#if __has_include() && defined(CATCH_CPP17_OR_GREATER) +#define CATCH_INTERNAL_CONFIG_CPP17_OPTIONAL +#endif // __has_include() && defined(CATCH_CPP17_OR_GREATER) + +// Check if byte is available and usable +#if __has_include() && defined(CATCH_CPP17_OR_GREATER) +#include +#if defined(__cpp_lib_byte) && (__cpp_lib_byte > 0) +#define CATCH_INTERNAL_CONFIG_CPP17_BYTE +#endif +#endif // __has_include() && defined(CATCH_CPP17_OR_GREATER) + +// Check if variant is available and usable +#if __has_include() && defined(CATCH_CPP17_OR_GREATER) +#if defined(__clang__) && (__clang_major__ < 8) +// work around clang bug with libstdc++ https://bugs.llvm.org/show_bug.cgi?id=31852 +// fix should be in clang 8, workaround in libstdc++ 8.2 +#include +#if defined(__GLIBCXX__) && defined(_GLIBCXX_RELEASE) && (_GLIBCXX_RELEASE < 9) +#define CATCH_CONFIG_NO_CPP17_VARIANT +#else +#define CATCH_INTERNAL_CONFIG_CPP17_VARIANT +#endif // defined(__GLIBCXX__) && defined(_GLIBCXX_RELEASE) && (_GLIBCXX_RELEASE < 9) +#else +#define CATCH_INTERNAL_CONFIG_CPP17_VARIANT +#endif // defined(__clang__) && (__clang_major__ < 8) +#endif // __has_include() && defined(CATCH_CPP17_OR_GREATER) +#endif // defined(__has_include) + +#if defined(CATCH_INTERNAL_CONFIG_WINDOWS_SEH) && !defined(CATCH_CONFIG_NO_WINDOWS_SEH) && !defined(CATCH_CONFIG_WINDOWS_SEH) && !defined(CATCH_INTERNAL_CONFIG_NO_WINDOWS_SEH) +#define CATCH_CONFIG_WINDOWS_SEH +#endif +// This is set by default, because we assume that unix compilers are posix-signal-compatible by default. +#if defined(CATCH_INTERNAL_CONFIG_POSIX_SIGNALS) && !defined(CATCH_INTERNAL_CONFIG_NO_POSIX_SIGNALS) && !defined(CATCH_CONFIG_NO_POSIX_SIGNALS) && !defined(CATCH_CONFIG_POSIX_SIGNALS) +#define CATCH_CONFIG_POSIX_SIGNALS +#endif + +#if defined(CATCH_INTERNAL_CONFIG_GETENV) && !defined(CATCH_INTERNAL_CONFIG_NO_GETENV) && !defined(CATCH_CONFIG_NO_GETENV) && !defined(CATCH_CONFIG_GETENV) +#define CATCH_CONFIG_GETENV +#endif + +#if !defined(CATCH_INTERNAL_CONFIG_NO_CPP11_TO_STRING) && !defined(CATCH_CONFIG_NO_CPP11_TO_STRING) && !defined(CATCH_CONFIG_CPP11_TO_STRING) +#define CATCH_CONFIG_CPP11_TO_STRING +#endif + +#if defined(CATCH_INTERNAL_CONFIG_CPP17_OPTIONAL) && !defined(CATCH_CONFIG_NO_CPP17_OPTIONAL) && !defined(CATCH_CONFIG_CPP17_OPTIONAL) +#define CATCH_CONFIG_CPP17_OPTIONAL +#endif + +#if defined(CATCH_INTERNAL_CONFIG_CPP17_STRING_VIEW) && !defined(CATCH_CONFIG_NO_CPP17_STRING_VIEW) && !defined(CATCH_CONFIG_CPP17_STRING_VIEW) +#define CATCH_CONFIG_CPP17_STRING_VIEW +#endif + +#if defined(CATCH_INTERNAL_CONFIG_CPP17_VARIANT) && !defined(CATCH_CONFIG_NO_CPP17_VARIANT) && !defined(CATCH_CONFIG_CPP17_VARIANT) +#define CATCH_CONFIG_CPP17_VARIANT +#endif + +#if defined(CATCH_INTERNAL_CONFIG_CPP17_BYTE) && !defined(CATCH_CONFIG_NO_CPP17_BYTE) && !defined(CATCH_CONFIG_CPP17_BYTE) +#define CATCH_CONFIG_CPP17_BYTE +#endif + +#if defined(CATCH_CONFIG_EXPERIMENTAL_REDIRECT) +#define CATCH_INTERNAL_CONFIG_NEW_CAPTURE +#endif + +#if defined(CATCH_INTERNAL_CONFIG_NEW_CAPTURE) && !defined(CATCH_INTERNAL_CONFIG_NO_NEW_CAPTURE) && !defined(CATCH_CONFIG_NO_NEW_CAPTURE) && !defined(CATCH_CONFIG_NEW_CAPTURE) +#define CATCH_CONFIG_NEW_CAPTURE +#endif + +#if !defined(CATCH_INTERNAL_CONFIG_EXCEPTIONS_ENABLED) && !defined(CATCH_CONFIG_DISABLE_EXCEPTIONS) && !defined(CATCH_CONFIG_NO_DISABLE_EXCEPTIONS) +#define CATCH_CONFIG_DISABLE_EXCEPTIONS +#endif + +#if defined(CATCH_INTERNAL_CONFIG_POLYFILL_ISNAN) && !defined(CATCH_CONFIG_NO_POLYFILL_ISNAN) && !defined(CATCH_CONFIG_POLYFILL_ISNAN) +#define CATCH_CONFIG_POLYFILL_ISNAN +#endif + +#if defined(CATCH_INTERNAL_CONFIG_USE_ASYNC) && !defined(CATCH_INTERNAL_CONFIG_NO_ASYNC) && !defined(CATCH_CONFIG_NO_USE_ASYNC) && !defined(CATCH_CONFIG_USE_ASYNC) +#define CATCH_CONFIG_USE_ASYNC +#endif + +#if defined(CATCH_INTERNAL_CONFIG_GLOBAL_NEXTAFTER) && !defined(CATCH_CONFIG_NO_GLOBAL_NEXTAFTER) && !defined(CATCH_CONFIG_GLOBAL_NEXTAFTER) +#define CATCH_CONFIG_GLOBAL_NEXTAFTER +#endif + +// The goal of this macro is to avoid evaluation of the arguments, but +// still have the compiler warn on problems inside... +#if defined(CATCH_INTERNAL_CONFIG_USE_BUILTIN_CONSTANT_P) && !defined(CATCH_INTERNAL_CONFIG_NO_USE_BUILTIN_CONSTANT_P) && !defined(CATCH_CONFIG_USE_BUILTIN_CONSTANT_P) +#define CATCH_CONFIG_USE_BUILTIN_CONSTANT_P +#endif + +#if defined(CATCH_CONFIG_USE_BUILTIN_CONSTANT_P) && !defined(CATCH_CONFIG_NO_USE_BUILTIN_CONSTANT_P) +#define CATCH_INTERNAL_IGNORE_BUT_WARN(...) \ + (void)__builtin_constant_p(__VA_ARGS__) /* NOLINT(cppcoreguidelines-pro-type-vararg, \ + hicpp-vararg) */ +#else +#define CATCH_INTERNAL_IGNORE_BUT_WARN(...) +#endif + +// Even if we do not think the compiler has that warning, we still have +// to provide a macro that can be used by the code. +#if !defined(CATCH_INTERNAL_START_WARNINGS_SUPPRESSION) +#define CATCH_INTERNAL_START_WARNINGS_SUPPRESSION +#endif +#if !defined(CATCH_INTERNAL_STOP_WARNINGS_SUPPRESSION) +#define CATCH_INTERNAL_STOP_WARNINGS_SUPPRESSION +#endif +#if !defined(CATCH_INTERNAL_SUPPRESS_PARENTHESES_WARNINGS) +#define CATCH_INTERNAL_SUPPRESS_PARENTHESES_WARNINGS +#endif +#if !defined(CATCH_INTERNAL_SUPPRESS_GLOBALS_WARNINGS) +#define CATCH_INTERNAL_SUPPRESS_GLOBALS_WARNINGS +#endif +#if !defined(CATCH_INTERNAL_SUPPRESS_UNUSED_RESULT) +#define CATCH_INTERNAL_SUPPRESS_UNUSED_RESULT +#endif +#if !defined(CATCH_INTERNAL_SUPPRESS_UNUSED_VARIABLE_WARNINGS) +#define CATCH_INTERNAL_SUPPRESS_UNUSED_VARIABLE_WARNINGS +#endif +#if !defined(CATCH_INTERNAL_SUPPRESS_USELESS_CAST_WARNINGS) +#define CATCH_INTERNAL_SUPPRESS_USELESS_CAST_WARNINGS +#endif +#if !defined(CATCH_INTERNAL_SUPPRESS_ZERO_VARIADIC_WARNINGS) +#define CATCH_INTERNAL_SUPPRESS_ZERO_VARIADIC_WARNINGS +#endif +#if !defined(CATCH_INTERNAL_SUPPRESS_UNUSED_TEMPLATE_WARNINGS) +#define CATCH_INTERNAL_SUPPRESS_UNUSED_TEMPLATE_WARNINGS +#endif +#if !defined(CATCH_INTERNAL_SUPPRESS_COMMA_WARNINGS) +#define CATCH_INTERNAL_SUPPRESS_COMMA_WARNINGS +#endif +#if !defined(CATCH_INTERNAL_SUPPRESS_SHADOW_WARNINGS) +#define CATCH_INTERNAL_SUPPRESS_SHADOW_WARNINGS +#endif +#if !defined(CATCH_INTERNAL_SUPPRESS_COUNTER_WARNINGS) +#define CATCH_INTERNAL_SUPPRESS_COUNTER_WARNINGS +#endif + +#if defined(__APPLE__) && defined(__apple_build_version__) && (__clang_major__ < 10) +#undef CATCH_INTERNAL_SUPPRESS_UNUSED_TEMPLATE_WARNINGS +#elif defined(__clang__) && (__clang_major__ < 5) +#undef CATCH_INTERNAL_SUPPRESS_UNUSED_TEMPLATE_WARNINGS +#endif + +#if defined(CATCH_CONFIG_DISABLE_EXCEPTIONS) +#define CATCH_TRY if ((true)) +#define CATCH_CATCH_ALL if ((false)) +#define CATCH_CATCH_ANON(type) if ((false)) +#else +#define CATCH_TRY try +#define CATCH_CATCH_ALL catch (...) +#define CATCH_CATCH_ANON(type) catch (type) +#endif + +#if defined(CATCH_INTERNAL_CONFIG_TRADITIONAL_MSVC_PREPROCESSOR) && !defined(CATCH_CONFIG_NO_TRADITIONAL_MSVC_PREPROCESSOR) && !defined(CATCH_CONFIG_TRADITIONAL_MSVC_PREPROCESSOR) +#define CATCH_CONFIG_TRADITIONAL_MSVC_PREPROCESSOR +#endif + +#if defined(CATCH_PLATFORM_WINDOWS) && !defined(CATCH_CONFIG_COLOUR_WIN32) && !defined(CATCH_CONFIG_NO_COLOUR_WIN32) && !defined(CATCH_INTERNAL_CONFIG_NO_COLOUR_WIN32) +#define CATCH_CONFIG_COLOUR_WIN32 +#endif + +#if defined(CATCH_CONFIG_SHARED_LIBRARY) && defined(_MSC_VER) && !defined(CATCH_CONFIG_STATIC) +#ifdef Catch2_EXPORTS +#define CATCH_EXPORT //__declspec( dllexport ) // not needed +#else +#define CATCH_EXPORT __declspec(dllimport) +#endif +#else +#define CATCH_EXPORT +#endif + +#endif // CATCH_COMPILER_CAPABILITIES_HPP_INCLUDED + +#ifndef CATCH_CONTEXT_HPP_INCLUDED +#define CATCH_CONTEXT_HPP_INCLUDED + +namespace Catch { + + class IResultCapture; + class IConfig; + + class Context { + IConfig const *m_config = nullptr; + IResultCapture *m_resultCapture = nullptr; + + CATCH_EXPORT static Context currentContext; + friend Context &getCurrentMutableContext(); + friend Context const &getCurrentContext(); + + public: + constexpr IResultCapture *getResultCapture() const { + return m_resultCapture; + } + constexpr IConfig const *getConfig() const { return m_config; } + constexpr void setResultCapture(IResultCapture *resultCapture) { + m_resultCapture = resultCapture; + } + constexpr void setConfig(IConfig const *config) { m_config = config; } + }; + + Context &getCurrentMutableContext(); + + inline Context const &getCurrentContext() { + return Context::currentContext; + } + + class SimplePcg32; + SimplePcg32 &sharedRng(); +} // namespace Catch + +#endif // CATCH_CONTEXT_HPP_INCLUDED + +#ifndef CATCH_MOVE_AND_FORWARD_HPP_INCLUDED +#define CATCH_MOVE_AND_FORWARD_HPP_INCLUDED + +#include + +//! Replacement for std::move with better compile time performance +#define CATCH_MOVE(...) static_cast &&>(__VA_ARGS__) + +//! Replacement for std::forward with better compile time performance +#define CATCH_FORWARD(...) static_cast(__VA_ARGS__) + +#endif // CATCH_MOVE_AND_FORWARD_HPP_INCLUDED + +#ifndef CATCH_TEST_FAILURE_EXCEPTION_HPP_INCLUDED +#define CATCH_TEST_FAILURE_EXCEPTION_HPP_INCLUDED + +namespace Catch { + + //! Used to signal that an assertion macro failed + struct TestFailureException {}; + //! Used to signal that the remainder of a test should be skipped + struct TestSkipException {}; + + /** + * Outlines throwing of `TestFailureException` into a single TU + * + * Also handles `CATCH_CONFIG_DISABLE_EXCEPTIONS` for callers. + */ + [[noreturn]] void throw_test_failure_exception(); + + /** + * Outlines throwing of `TestSkipException` into a single TU + * + * Also handles `CATCH_CONFIG_DISABLE_EXCEPTIONS` for callers. + */ + [[noreturn]] void throw_test_skip_exception(); + +} // namespace Catch + +#endif // CATCH_TEST_FAILURE_EXCEPTION_HPP_INCLUDED + +#ifndef CATCH_UNIQUE_NAME_HPP_INCLUDED +#define CATCH_UNIQUE_NAME_HPP_INCLUDED + +/** \file + * Wrapper for the CONFIG configuration option + * + * When generating internal unique names, there are two options. Either + * we mix in the current line number, or mix in an incrementing number. + * We prefer the latter, using `__COUNTER__`, but users might want to + * use the former. + */ + +#ifndef CATCH_CONFIG_COUNTER_HPP_INCLUDED +#define CATCH_CONFIG_COUNTER_HPP_INCLUDED + +#if (!defined(__JETBRAINS_IDE__) || __JETBRAINS_IDE__ >= 20170300L) +#define CATCH_INTERNAL_CONFIG_COUNTER +#endif + +#if defined(CATCH_INTERNAL_CONFIG_COUNTER) && !defined(CATCH_CONFIG_NO_COUNTER) && !defined(CATCH_CONFIG_COUNTER) +#define CATCH_CONFIG_COUNTER +#endif + +#endif // CATCH_CONFIG_COUNTER_HPP_INCLUDED + +// Fixme: Clang 22 has an annoying bug where the localized suppression +// below does not actually suppress the extension warning from +// using __COUNTER__, so we have to leak the suppression for the +// whole TU. Hopefully Clang 23 fixes this before full release. +// As AppleClang does its own thing version-wise, we ignore it +// completely. +#if defined(__clang__) && (__clang_major__ >= 22) && !defined(__APPLE__) +CATCH_INTERNAL_SUPPRESS_COUNTER_WARNINGS +#endif + +#define INTERNAL_CATCH_UNIQUE_NAME_LINE2(name, line) name##line +#define INTERNAL_CATCH_UNIQUE_NAME_LINE(name, line) INTERNAL_CATCH_UNIQUE_NAME_LINE2(name, line) + +#ifdef CATCH_CONFIG_COUNTER +#define INTERNAL_CATCH_UNIQUE_NAME(name) INTERNAL_CATCH_UNIQUE_NAME_LINE(name, __COUNTER__) +#else +#define INTERNAL_CATCH_UNIQUE_NAME(name) INTERNAL_CATCH_UNIQUE_NAME_LINE(name, __LINE__) +#endif + +#endif // CATCH_UNIQUE_NAME_HPP_INCLUDED + +#ifndef CATCH_INTERFACES_CAPTURE_HPP_INCLUDED +#define CATCH_INTERFACES_CAPTURE_HPP_INCLUDED + +#include + +#ifndef CATCH_STRINGREF_HPP_INCLUDED +#define CATCH_STRINGREF_HPP_INCLUDED + +#ifndef CATCH_LIFETIMEBOUND_HPP_INCLUDED +#define CATCH_LIFETIMEBOUND_HPP_INCLUDED + +#if !defined(__has_cpp_attribute) +#define CATCH_ATTR_LIFETIMEBOUND +#elif __has_cpp_attribute(msvc::lifetimebound) +#define CATCH_ATTR_LIFETIMEBOUND [[msvc::lifetimebound]] +#elif __has_cpp_attribute(clang::lifetimebound) +#define CATCH_ATTR_LIFETIMEBOUND [[clang::lifetimebound]] +#elif __has_cpp_attribute(lifetimebound) +#define CATCH_ATTR_LIFETIMEBOUND [[lifetimebound]] +#else +#define CATCH_ATTR_LIFETIMEBOUND +#endif + +#endif // CATCH_LIFETIMEBOUND_HPP_INCLUDED + +#include +#include +#include +#include +#include + +namespace Catch { + + /// A non-owning string class (similar to the forthcoming std::string_view) + /// Note that, because a StringRef may be a substring of another string, + /// it may not be null terminated. + class StringRef { + public: + using size_type = std::size_t; + using const_iterator = const char *; + + static constexpr size_type npos{static_cast(-1)}; + + private: + static constexpr char const *const s_empty = ""; + + char const *m_start = s_empty; + size_type m_size = 0; + + public: // construction + constexpr StringRef() noexcept = default; + + StringRef(char const *rawChars CATCH_ATTR_LIFETIMEBOUND) noexcept; + + constexpr StringRef(char const *rawChars CATCH_ATTR_LIFETIMEBOUND, + size_type size) noexcept + : m_start(rawChars), m_size(size) {} + + StringRef( + std::string const &stdString CATCH_ATTR_LIFETIMEBOUND) noexcept + : m_start(stdString.c_str()), m_size(stdString.size()) {} + + explicit operator std::string() const { + return std::string(m_start, m_size); + } + + public: // operators + auto operator==(StringRef other) const noexcept -> bool { + return m_size == other.m_size + && (std::memcmp(m_start, other.m_start, m_size) == 0); + } + auto operator!=(StringRef other) const noexcept -> bool { + return !(*this == other); + } + + constexpr auto operator[](size_type index) const noexcept -> char { + assert(index < m_size); + return m_start[index]; + } + + bool operator<(StringRef rhs) const noexcept; + + public: // named queries + constexpr auto empty() const noexcept -> bool { + return m_size == 0; + } + constexpr auto size() const noexcept -> size_type { + return m_size; + } + + // Returns a substring of [start, start + length). + // If start + length > size(), then the substring is [start, size()). + // If start > size(), then the substring is empty. + constexpr StringRef substr(size_type start, size_type length) const noexcept { + if (start < m_size) { + const auto shortened_size = m_size - start; + return StringRef(m_start + start, (shortened_size < length) ? shortened_size : length); + } else { + return StringRef(); + } + } + + // Returns the current start pointer. May not be null-terminated. + constexpr char const *data() const noexcept CATCH_ATTR_LIFETIMEBOUND { + return m_start; + } + + constexpr const_iterator begin() const { return m_start; } + constexpr const_iterator end() const { return m_start + m_size; } + + friend std::string &operator+=(std::string &lhs, StringRef rhs); + friend std::ostream &operator<<(std::ostream &os, StringRef str); + friend std::string operator+(StringRef lhs, StringRef rhs); + + /** + * Provides a three-way comparison with rhs + * + * Returns negative number if lhs < rhs, 0 if lhs == rhs, and a positive + * number if lhs > rhs + */ + int compare(StringRef rhs) const; + }; + + constexpr auto operator""_sr(char const *rawChars, std::size_t size) noexcept -> StringRef { + return StringRef(rawChars, size); + } +} // namespace Catch + +constexpr auto operator""_catch_sr(char const *rawChars, std::size_t size) noexcept -> Catch::StringRef { + return Catch::StringRef(rawChars, size); +} + +#endif // CATCH_STRINGREF_HPP_INCLUDED + +#ifndef CATCH_RESULT_TYPE_HPP_INCLUDED +#define CATCH_RESULT_TYPE_HPP_INCLUDED + +namespace Catch { + + // ResultWas::OfType enum + struct ResultWas { + enum OfType { + Unknown = -1, + Ok = 0, + Info = 1, + Warning = 2, + // TODO: Should explicit skip be considered "not OK" (cf. isOk)? I.e., should it have the failure bit? + ExplicitSkip = 4, + + FailureBit = 0x10, + + ExpressionFailed = FailureBit | 1, + ExplicitFailure = FailureBit | 2, + + Exception = 0x100 | FailureBit, + + ThrewException = Exception | 1, + DidntThrowException = Exception | 2, + + FatalErrorCondition = 0x200 | FailureBit + + }; + }; + + constexpr bool isOk(ResultWas::OfType resultType) { + return (resultType & ResultWas::FailureBit) == 0; + } + constexpr bool isJustInfo(int flags) { + return flags == ResultWas::Info; + } + + // ResultDisposition::Flags enum + struct ResultDisposition { + enum Flags { + Normal = 0x01, + + ContinueOnFailure = 0x02, // Failures fail test, but execution continues + FalseTest = 0x04, // Prefix expression with ! + SuppressFail = 0x08 // Failures are reported but do not fail the test + }; + }; + + constexpr ResultDisposition::Flags operator|(ResultDisposition::Flags lhs, + ResultDisposition::Flags rhs) { + return static_cast(static_cast(lhs) | static_cast(rhs)); + } + + constexpr bool isFalseTest(int flags) { + return (flags & ResultDisposition::FalseTest) != 0; + } + constexpr bool shouldSuppressFailure(int flags) { + return (flags & ResultDisposition::SuppressFail) != 0; + } + +} // end namespace Catch + +#endif // CATCH_RESULT_TYPE_HPP_INCLUDED + +#ifndef CATCH_UNIQUE_PTR_HPP_INCLUDED +#define CATCH_UNIQUE_PTR_HPP_INCLUDED + +#include +#include + +namespace Catch { + namespace Detail { + /** + * A reimplementation of `std::unique_ptr` for improved compilation performance + * + * Does not support arrays nor custom deleters. + */ + template + class unique_ptr { + T *m_ptr; + + public: + constexpr unique_ptr(std::nullptr_t = nullptr) + : m_ptr{} {} + explicit constexpr unique_ptr(T *ptr) + : m_ptr(ptr) {} + + template::value>> + unique_ptr(unique_ptr &&from) + : m_ptr(from.release()) {} + + template::value>> + unique_ptr &operator=(unique_ptr &&from) { + reset(from.release()); + + return *this; + } + + unique_ptr(unique_ptr const &) = delete; + unique_ptr &operator=(unique_ptr const &) = delete; + + unique_ptr(unique_ptr &&rhs) noexcept + : m_ptr(rhs.m_ptr) { + rhs.m_ptr = nullptr; + } + unique_ptr &operator=(unique_ptr &&rhs) noexcept { + reset(rhs.release()); + + return *this; + } + + ~unique_ptr() { + delete m_ptr; + } + + T &operator*() { + assert(m_ptr); + return *m_ptr; + } + T const &operator*() const { + assert(m_ptr); + return *m_ptr; + } + T *operator->() noexcept { + assert(m_ptr); + return m_ptr; + } + T const *operator->() const noexcept { + assert(m_ptr); + return m_ptr; + } + + T *get() { return m_ptr; } + T const *get() const { return m_ptr; } + + void reset(T *ptr = nullptr) { + delete m_ptr; + m_ptr = ptr; + } + + T *release() { + auto temp = m_ptr; + m_ptr = nullptr; + return temp; + } + + explicit operator bool() const { + return m_ptr != nullptr; + } + + friend void swap(unique_ptr &lhs, unique_ptr &rhs) { + auto temp = lhs.m_ptr; + lhs.m_ptr = rhs.m_ptr; + rhs.m_ptr = temp; + } + }; + + //! Specialization to cause compile-time error for arrays + template + class unique_ptr; + + template + unique_ptr make_unique(Args &&...args) { + return unique_ptr(new T(CATCH_FORWARD(args)...)); + } + + } // end namespace Detail +} // end namespace Catch + +#endif // CATCH_UNIQUE_PTR_HPP_INCLUDED + +#ifndef CATCH_BENCHMARK_STATS_FWD_HPP_INCLUDED +#define CATCH_BENCHMARK_STATS_FWD_HPP_INCLUDED + +namespace Catch { + + namespace Detail { + struct DummyTemplateArgPlaceholder; + } + + // We cannot forward declare the type with default template argument + // multiple times, so it is split out into a separate header so that + // we can prevent multiple declarations in dependencies + template + struct BenchmarkStats; + +} // end namespace Catch + +#endif // CATCH_BENCHMARK_STATS_FWD_HPP_INCLUDED + +namespace Catch { + + class AssertionResult; + struct AssertionInfo; + struct SectionInfo; + struct SectionEndInfo; + struct MessageInfo; + struct MessageBuilder; + struct Counts; + struct AssertionReaction; + struct SourceLineInfo; + + class ITransientExpression; + class IGeneratorTracker; + + struct BenchmarkInfo; + + namespace Generators { + class GeneratorUntypedBase; + using GeneratorBasePtr = Catch::Detail::unique_ptr; + } // namespace Generators + + class IResultCapture { + public: + virtual ~IResultCapture(); + + virtual void notifyAssertionStarted(AssertionInfo const &info) = 0; + virtual bool sectionStarted(StringRef sectionName, + SourceLineInfo const §ionLineInfo, + Counts &assertions) + = 0; + virtual void sectionEnded(SectionEndInfo &&endInfo) = 0; + virtual void sectionEndedEarly(SectionEndInfo &&endInfo) = 0; + + virtual IGeneratorTracker * + acquireGeneratorTracker(StringRef generatorName, + SourceLineInfo const &lineInfo) + = 0; + virtual IGeneratorTracker * + createGeneratorTracker(StringRef generatorName, + SourceLineInfo lineInfo, + Generators::GeneratorBasePtr &&generator) + = 0; + + virtual void benchmarkPreparing(StringRef name) = 0; + virtual void benchmarkStarting(BenchmarkInfo const &info) = 0; + virtual void benchmarkEnded(BenchmarkStats<> const &stats) = 0; + virtual void benchmarkFailed(StringRef error) = 0; + + static void pushScopedMessage(MessageInfo &&message); + static void popScopedMessage(unsigned int messageId); + static void addUnscopedMessage(MessageInfo &&message); + static void emplaceUnscopedMessage(MessageBuilder &&builder); + + virtual void handleFatalErrorCondition(StringRef message) = 0; + + virtual void handleExpr(AssertionInfo const &info, + ITransientExpression const &expr, + AssertionReaction &reaction) + = 0; + virtual void handleMessage(AssertionInfo const &info, + ResultWas::OfType resultType, + std::string &&message, + AssertionReaction &reaction) + = 0; + virtual void handleUnexpectedExceptionNotThrown(AssertionInfo const &info, + AssertionReaction &reaction) + = 0; + virtual void handleUnexpectedInflightException(AssertionInfo const &info, + std::string &&message, + AssertionReaction &reaction) + = 0; + virtual void handleIncomplete(AssertionInfo const &info) = 0; + virtual void handleNonExpr(AssertionInfo const &info, + ResultWas::OfType resultType, + AssertionReaction &reaction) + = 0; + + virtual bool lastAssertionPassed() = 0; + + // Deprecated, do not use: + virtual std::string getCurrentTestName() const = 0; + virtual const AssertionResult *getLastResult() const = 0; + virtual void exceptionEarlyReported() = 0; + }; + + namespace Detail { + [[noreturn]] + void missingCaptureInstance(); + } + inline IResultCapture &getResultCapture() { + if (auto *capture = getCurrentContext().getResultCapture()) { + return *capture; + } else { + Detail::missingCaptureInstance(); + } + } + +} // namespace Catch + +#endif // CATCH_INTERFACES_CAPTURE_HPP_INCLUDED + +#ifndef CATCH_INTERFACES_CONFIG_HPP_INCLUDED +#define CATCH_INTERFACES_CONFIG_HPP_INCLUDED + +#ifndef CATCH_NONCOPYABLE_HPP_INCLUDED +#define CATCH_NONCOPYABLE_HPP_INCLUDED + +namespace Catch { + namespace Detail { + + //! Deriving classes become noncopyable and nonmovable + class NonCopyable { + public: + NonCopyable(NonCopyable const &) = delete; + NonCopyable(NonCopyable &&) = delete; + NonCopyable &operator=(NonCopyable const &) = delete; + NonCopyable &operator=(NonCopyable &&) = delete; + + protected: + NonCopyable() noexcept = default; + }; + + } // namespace Detail +} // namespace Catch + +#endif // CATCH_NONCOPYABLE_HPP_INCLUDED + +#include +#include +#include + +namespace Catch { + + enum class Verbosity { + Quiet = 0, + Normal, + High + }; + + struct WarnAbout { + enum What { + Nothing = 0x00, + //! A test case or leaf section did not run any assertions + NoAssertions = 0x01, + //! A command line test spec matched no test cases + UnmatchedTestSpec = 0x02, + //! The resulting generator in GENERATE is infinite + InfiniteGenerator = 0x04, + }; + }; + + enum class ShowDurations { + DefaultForReporter, + Always, + Never + }; + enum class TestRunOrder { + Declared, + LexicographicallySorted, + Randomized + }; + enum class ColourMode : std::uint8_t { + //! Let Catch2 pick implementation based on platform detection + PlatformDefault, + //! Use ANSI colour code escapes + ANSI, + //! Use Win32 console colour API + Win32, + //! Don't use any colour + None + }; + struct WaitForKeypress { + enum When { + Never, + BeforeStart = 1, + BeforeExit = 2, + BeforeStartAndExit = BeforeStart | BeforeExit + }; + }; + + class TestSpec; + class IStream; + struct PathFilter; + + class IConfig : public Detail::NonCopyable { + public: + virtual ~IConfig(); + + virtual bool allowThrows() const = 0; + virtual StringRef name() const = 0; + virtual bool includeSuccessfulResults() const = 0; + virtual bool shouldDebugBreak() const = 0; + virtual bool warnAboutMissingAssertions() const = 0; + virtual bool warnAboutUnmatchedTestSpecs() const = 0; + virtual bool warnAboutInfiniteGenerators() const = 0; + virtual bool zeroTestsCountAsSuccess() const = 0; + virtual int abortAfter() const = 0; + virtual bool showInvisibles() const = 0; + virtual ShowDurations showDurations() const = 0; + virtual double minDuration() const = 0; + virtual TestSpec const &testSpec() const = 0; + virtual bool hasTestFilters() const = 0; + virtual std::vector const &getTestsOrTags() const = 0; + virtual TestRunOrder runOrder() const = 0; + virtual uint32_t rngSeed() const = 0; + virtual unsigned int shardCount() const = 0; + virtual unsigned int shardIndex() const = 0; + virtual ColourMode defaultColourMode() const = 0; + virtual std::vector const &getPathFilters() const = 0; + virtual bool useNewFilterBehaviour() const = 0; + + virtual Verbosity verbosity() const = 0; + + virtual bool skipBenchmarks() const = 0; + virtual bool benchmarkNoAnalysis() const = 0; + virtual unsigned int benchmarkSamples() const = 0; + virtual double benchmarkConfidenceInterval() const = 0; + virtual unsigned int benchmarkResamples() const = 0; + virtual std::chrono::milliseconds benchmarkWarmupTime() const = 0; + }; +} // namespace Catch + +#endif // CATCH_INTERFACES_CONFIG_HPP_INCLUDED + +#ifndef CATCH_INTERFACES_REGISTRY_HUB_HPP_INCLUDED +#define CATCH_INTERFACES_REGISTRY_HUB_HPP_INCLUDED + +#include + +namespace Catch { + + class TestCaseHandle; + struct TestCaseInfo; + class ITestCaseRegistry; + class IExceptionTranslatorRegistry; + class IExceptionTranslator; + class ReporterRegistry; + class IReporterFactory; + class ITagAliasRegistry; + class ITestInvoker; + class IMutableEnumValuesRegistry; + struct SourceLineInfo; + + class StartupExceptionRegistry; + class EventListenerFactory; + + using IReporterFactoryPtr = Detail::unique_ptr; + + class IRegistryHub { + public: + virtual ~IRegistryHub(); // = default + + virtual ReporterRegistry const &getReporterRegistry() const = 0; + virtual ITestCaseRegistry const &getTestCaseRegistry() const = 0; + virtual ITagAliasRegistry const &getTagAliasRegistry() const = 0; + virtual IExceptionTranslatorRegistry const &getExceptionTranslatorRegistry() const = 0; + + virtual StartupExceptionRegistry const &getStartupExceptionRegistry() const = 0; + }; + + class IMutableRegistryHub { + public: + virtual ~IMutableRegistryHub(); // = default + virtual void registerReporter(std::string const &name, IReporterFactoryPtr factory) = 0; + virtual void registerListener(Detail::unique_ptr factory) = 0; + virtual void registerTest(Detail::unique_ptr &&testInfo, Detail::unique_ptr &&invoker) = 0; + virtual void registerTranslator(Detail::unique_ptr &&translator) = 0; + virtual void registerTagAlias(std::string const &alias, std::string const &tag, SourceLineInfo const &lineInfo) = 0; + virtual void registerStartupException() noexcept = 0; + virtual IMutableEnumValuesRegistry &getMutableEnumValuesRegistry() = 0; + }; + + IRegistryHub const &getRegistryHub(); + IMutableRegistryHub &getMutableRegistryHub(); + void cleanUp(); + std::string translateActiveException(); + +} // namespace Catch + +#endif // CATCH_INTERFACES_REGISTRY_HUB_HPP_INCLUDED + +#ifndef CATCH_BENCHMARK_STATS_HPP_INCLUDED +#define CATCH_BENCHMARK_STATS_HPP_INCLUDED + +// Adapted from donated nonius code. + +#ifndef CATCH_CLOCK_HPP_INCLUDED +#define CATCH_CLOCK_HPP_INCLUDED + +#include + +namespace Catch { + namespace Benchmark { + using IDuration = std::chrono::nanoseconds; + using FDuration = std::chrono::duration; + + template + using TimePoint = typename Clock::time_point; + + using default_clock = std::chrono::steady_clock; + } // namespace Benchmark +} // namespace Catch + +#endif // CATCH_CLOCK_HPP_INCLUDED + +// Adapted from donated nonius code. + +#ifndef CATCH_ESTIMATE_HPP_INCLUDED +#define CATCH_ESTIMATE_HPP_INCLUDED + +namespace Catch { + namespace Benchmark { + template + struct Estimate { + Type point; + Type lower_bound; + Type upper_bound; + double confidence_interval; + }; + } // namespace Benchmark +} // namespace Catch + +#endif // CATCH_ESTIMATE_HPP_INCLUDED + +// Adapted from donated nonius code. + +#ifndef CATCH_OUTLIER_CLASSIFICATION_HPP_INCLUDED +#define CATCH_OUTLIER_CLASSIFICATION_HPP_INCLUDED + +namespace Catch { + namespace Benchmark { + struct OutlierClassification { + int samples_seen = 0; + int low_severe = 0; // more than 3 times IQR below Q1 + int low_mild = 0; // 1.5 to 3 times IQR below Q1 + int high_mild = 0; // 1.5 to 3 times IQR above Q3 + int high_severe = 0; // more than 3 times IQR above Q3 + + constexpr int total() const { + return low_severe + low_mild + high_mild + high_severe; + } + }; + } // namespace Benchmark +} // namespace Catch + +#endif // CATCH_OUTLIERS_CLASSIFICATION_HPP_INCLUDED +// The fwd decl & default specialization needs to be seen by VS2017 before +// BenchmarkStats itself, or VS2017 will report compilation error. + +#include +#include + +namespace Catch { + + struct BenchmarkInfo { + std::string name; + double estimatedDuration; + int iterations; + unsigned int samples; + unsigned int resamples; + double clockResolution; + double clockCost; + }; + + // We need to keep template parameter for backwards compatibility, + // but we also do not want to use the template paraneter. + template + struct BenchmarkStats { + BenchmarkInfo info; + + std::vector samples; + Benchmark::Estimate mean; + Benchmark::Estimate standardDeviation; + Benchmark::OutlierClassification outliers; + double outlierVariance; + }; + +} // end namespace Catch + +#endif // CATCH_BENCHMARK_STATS_HPP_INCLUDED + +// Adapted from donated nonius code. + +#ifndef CATCH_ENVIRONMENT_HPP_INCLUDED +#define CATCH_ENVIRONMENT_HPP_INCLUDED + +namespace Catch { + namespace Benchmark { + struct EnvironmentEstimate { + FDuration mean; + OutlierClassification outliers; + }; + struct Environment { + EnvironmentEstimate clock_resolution; + EnvironmentEstimate clock_cost; + }; + } // namespace Benchmark +} // namespace Catch + +#endif // CATCH_ENVIRONMENT_HPP_INCLUDED + +// Adapted from donated nonius code. + +#ifndef CATCH_EXECUTION_PLAN_HPP_INCLUDED +#define CATCH_EXECUTION_PLAN_HPP_INCLUDED + +// Adapted from donated nonius code. + +#ifndef CATCH_BENCHMARK_FUNCTION_HPP_INCLUDED +#define CATCH_BENCHMARK_FUNCTION_HPP_INCLUDED + +// Adapted from donated nonius code. + +#ifndef CATCH_CHRONOMETER_HPP_INCLUDED +#define CATCH_CHRONOMETER_HPP_INCLUDED + +// Adapted from donated nonius code. + +#ifndef CATCH_OPTIMIZER_HPP_INCLUDED +#define CATCH_OPTIMIZER_HPP_INCLUDED + +#if defined(_MSC_VER) || defined(__IAR_SYSTEMS_ICC__) +#include // atomic_thread_fence +#endif + +#include + +namespace Catch { + namespace Benchmark { +#if defined(__GNUC__) || defined(__clang__) + template + inline void keep_memory(T *p) { + asm volatile("" : : "g"(p) : "memory"); + } + inline void keep_memory() { + asm volatile("" : : : "memory"); + } + + namespace Detail { + inline void optimizer_barrier() { + keep_memory(); + } + } // namespace Detail +#elif defined(_MSC_VER) || defined(__IAR_SYSTEMS_ICC__) + +#if defined(_MSVC_VER) +#pragma optimize("", off) +#elif defined(__IAR_SYSTEMS_ICC__) +// For IAR the pragma only affects the following function +#pragma optimize = disable +#endif + template + inline void keep_memory(T *p) { + // thanks @milleniumbug + *reinterpret_cast(p) = *reinterpret_cast(p); + } + // TODO equivalent keep_memory() +#if defined(_MSVC_VER) +#pragma optimize("", on) +#endif + + namespace Detail { + inline void optimizer_barrier() { + std::atomic_thread_fence(std::memory_order_seq_cst); + } + } // namespace Detail + +#endif + + template + inline void deoptimize_value(T &&x) { + keep_memory(&x); + } + + template + inline auto invoke_deoptimized(Fn &&fn, Args &&...args) -> std::enable_if_t::value> { + deoptimize_value(CATCH_FORWARD(fn)(CATCH_FORWARD(args)...)); + } + + template + inline auto invoke_deoptimized(Fn &&fn, Args &&...args) -> std::enable_if_t::value> { + CATCH_FORWARD((fn))(CATCH_FORWARD(args)...); + } + } // namespace Benchmark +} // namespace Catch + +#endif // CATCH_OPTIMIZER_HPP_INCLUDED + +#ifndef CATCH_META_HPP_INCLUDED +#define CATCH_META_HPP_INCLUDED + +#include + +namespace Catch { + template + struct true_given : std::true_type {}; + + struct is_callable_tester { + template + static true_given()(std::declval()...))> test(int); + template + static std::false_type test(...); + }; + + template + struct is_callable; + + template + struct is_callable : decltype(is_callable_tester::test(0)) {}; + +#if defined(__cpp_lib_is_invocable) && __cpp_lib_is_invocable >= 201703 + // std::result_of is deprecated in C++17 and removed in C++20. Hence, it is + // replaced with std::invoke_result here. + template + using FunctionReturnType = std::remove_reference_t>>; +#else + template + using FunctionReturnType = std::remove_reference_t>>; +#endif + +} // namespace Catch + +namespace mpl_ { + struct na; +} + +#endif // CATCH_META_HPP_INCLUDED + +namespace Catch { + namespace Benchmark { + namespace Detail { + struct ChronometerConcept { + virtual void start() = 0; + virtual void finish() = 0; + virtual ~ChronometerConcept(); // = default; + + ChronometerConcept() = default; + ChronometerConcept(ChronometerConcept const &) = default; + ChronometerConcept &operator=(ChronometerConcept const &) = default; + }; + template + struct ChronometerModel final : public ChronometerConcept { + void start() override { started = Clock::now(); } + void finish() override { finished = Clock::now(); } + + IDuration elapsed() const { + return std::chrono::duration_cast( + finished - started); + } + + TimePoint started; + TimePoint finished; + }; + } // namespace Detail + + struct Chronometer { + public: + template + void measure(Fun &&fun) { measure(CATCH_FORWARD(fun), is_callable()); } + + int runs() const { return repeats; } + + Chronometer(Detail::ChronometerConcept &meter, int repeats_) + : impl(&meter) + , repeats(repeats_) {} + + private: + template + void measure(Fun &&fun, std::false_type) { + measure([&fun](int) { return fun(); }, std::true_type()); + } + + template + void measure(Fun &&fun, std::true_type) { + Detail::optimizer_barrier(); + impl->start(); + for (int i = 0; i < repeats; ++i) invoke_deoptimized(fun, i); + impl->finish(); + Detail::optimizer_barrier(); + } + + Detail::ChronometerConcept *impl; + int repeats; + }; + } // namespace Benchmark +} // namespace Catch + +#endif // CATCH_CHRONOMETER_HPP_INCLUDED + +#include + +namespace Catch { + namespace Benchmark { + namespace Detail { + template + static constexpr bool is_related_v = std::is_same, std::decay_t>::value; + + /// We need to reinvent std::function because every piece of code that might add overhead + /// in a measurement context needs to have consistent performance characteristics so that we + /// can account for it in the measurement. + /// Implementations of std::function with optimizations that aren't always applicable, like + /// small buffer optimizations, are not uncommon. + /// This is effectively an implementation of std::function without any such optimizations; + /// it may be slow, but it is consistently slow. + struct BenchmarkFunction { + private: + struct callable { + virtual void call(Chronometer meter) const = 0; + virtual ~callable(); // = default; + + callable() = default; + callable(callable &&) = default; + callable &operator=(callable &&) = default; + }; + template + struct model : public callable { + model(Fun &&fun_) + : fun(CATCH_MOVE(fun_)) {} + model(Fun const &fun_) + : fun(fun_) {} + + void call(Chronometer meter) const override { + call(meter, is_callable()); + } + void call(Chronometer meter, std::true_type) const { + fun(meter); + } + void call(Chronometer meter, std::false_type) const { + meter.measure(fun); + } + + Fun fun; + }; + + public: + BenchmarkFunction(); + + template, int> = 0> + BenchmarkFunction(Fun &&fun) + : f(new model>(CATCH_FORWARD(fun))) {} + + BenchmarkFunction(BenchmarkFunction &&that) noexcept + : f(CATCH_MOVE(that.f)) {} + + BenchmarkFunction & + operator=(BenchmarkFunction &&that) noexcept { + f = CATCH_MOVE(that.f); + return *this; + } + + void operator()(Chronometer meter) const { f->call(meter); } + + private: + Catch::Detail::unique_ptr f; + }; + } // namespace Detail + } // namespace Benchmark +} // namespace Catch + +#endif // CATCH_BENCHMARK_FUNCTION_HPP_INCLUDED + +// Adapted from donated nonius code. + +#ifndef CATCH_REPEAT_HPP_INCLUDED +#define CATCH_REPEAT_HPP_INCLUDED + +#include + +namespace Catch { + namespace Benchmark { + namespace Detail { + template + struct repeater { + void operator()(int k) const { + for (int i = 0; i < k; ++i) { + fun(); + } + } + Fun fun; + }; + template + repeater> repeat(Fun &&fun) { + return {CATCH_FORWARD(fun)}; + } + } // namespace Detail + } // namespace Benchmark +} // namespace Catch + +#endif // CATCH_REPEAT_HPP_INCLUDED + +// Adapted from donated nonius code. + +#ifndef CATCH_RUN_FOR_AT_LEAST_HPP_INCLUDED +#define CATCH_RUN_FOR_AT_LEAST_HPP_INCLUDED + +// Adapted from donated nonius code. + +#ifndef CATCH_MEASURE_HPP_INCLUDED +#define CATCH_MEASURE_HPP_INCLUDED + +// Adapted from donated nonius code. + +#ifndef CATCH_COMPLETE_INVOKE_HPP_INCLUDED +#define CATCH_COMPLETE_INVOKE_HPP_INCLUDED + +namespace Catch { + namespace Benchmark { + namespace Detail { + template + struct CompleteType { + using type = T; + }; + template<> + struct CompleteType { + struct type {}; + }; + + template + using CompleteType_t = typename CompleteType::type; + + template + struct CompleteInvoker { + template + static Result invoke(Fun &&fun, Args &&...args) { + return CATCH_FORWARD(fun)(CATCH_FORWARD(args)...); + } + }; + template<> + struct CompleteInvoker { + template + static CompleteType_t invoke(Fun &&fun, Args &&...args) { + CATCH_FORWARD(fun)(CATCH_FORWARD(args)...); + return {}; + } + }; + + // invoke and not return void :( + template + CompleteType_t> complete_invoke(Fun &&fun, Args &&...args) { + return CompleteInvoker>::invoke(CATCH_FORWARD(fun), CATCH_FORWARD(args)...); + } + + } // namespace Detail + + template + Detail::CompleteType_t> user_code(Fun &&fun) { + return Detail::complete_invoke(CATCH_FORWARD(fun)); + } + } // namespace Benchmark +} // namespace Catch + +#endif // CATCH_COMPLETE_INVOKE_HPP_INCLUDED + +// Adapted from donated nonius code. + +#ifndef CATCH_TIMING_HPP_INCLUDED +#define CATCH_TIMING_HPP_INCLUDED + +namespace Catch { + namespace Benchmark { + template + struct Timing { + IDuration elapsed; + Result result; + int iterations; + }; + template + using TimingOf = Timing>>; + } // namespace Benchmark +} // namespace Catch + +#endif // CATCH_TIMING_HPP_INCLUDED + +namespace Catch { + namespace Benchmark { + namespace Detail { + template + TimingOf measure(Fun &&fun, Args &&...args) { + auto start = Clock::now(); + auto &&r = Detail::complete_invoke(CATCH_FORWARD(fun), CATCH_FORWARD(args)...); + auto end = Clock::now(); + auto delta = end - start; + return {delta, CATCH_FORWARD(r), 1}; + } + } // namespace Detail + } // namespace Benchmark +} // namespace Catch + +#endif // CATCH_MEASURE_HPP_INCLUDED + +#include + +namespace Catch { + namespace Benchmark { + namespace Detail { + template + TimingOf measure_one(Fun &&fun, int iters, std::false_type) { + return Detail::measure(fun, iters); + } + template + TimingOf measure_one(Fun &&fun, int iters, std::true_type) { + Detail::ChronometerModel meter; + auto &&result = Detail::complete_invoke(fun, Chronometer(meter, iters)); + + return {meter.elapsed(), CATCH_MOVE(result), iters}; + } + + template + using run_for_at_least_argument_t = std::conditional_t::value, Chronometer, int>; + + [[noreturn]] + void throw_optimized_away_error(); + + template + TimingOf> + run_for_at_least(IDuration how_long, + const int initial_iterations, + Fun &&fun) { + auto iters = initial_iterations; + while (iters < (1 << 30)) { + auto &&Timing = measure_one(fun, iters, is_callable()); + + if (Timing.elapsed >= how_long) { + return {Timing.elapsed, CATCH_MOVE(Timing.result), iters}; + } + iters *= 2; + } + throw_optimized_away_error(); + } + } // namespace Detail + } // namespace Benchmark +} // namespace Catch + +#endif // CATCH_RUN_FOR_AT_LEAST_HPP_INCLUDED + +#include + +namespace Catch { + namespace Benchmark { + struct ExecutionPlan { + int iterations_per_sample; + FDuration estimated_duration; + Detail::BenchmarkFunction benchmark; + FDuration warmup_time; + int warmup_iterations; + + template + std::vector run(const IConfig &cfg, Environment env) const { + // warmup a bit + Detail::run_for_at_least( + std::chrono::duration_cast(warmup_time), + warmup_iterations, + Detail::repeat([]() { return Clock::now(); })); + + std::vector times; + const auto num_samples = cfg.benchmarkSamples(); + times.reserve(num_samples); + for (size_t i = 0; i < num_samples; ++i) { + Detail::ChronometerModel model; + this->benchmark(Chronometer(model, iterations_per_sample)); + auto sample_time = model.elapsed() - env.clock_cost.mean; + if (sample_time < FDuration::zero()) { + sample_time = FDuration::zero(); + } + times.push_back(sample_time / iterations_per_sample); + } + return times; + } + }; + } // namespace Benchmark +} // namespace Catch + +#endif // CATCH_EXECUTION_PLAN_HPP_INCLUDED + +// Adapted from donated nonius code. + +#ifndef CATCH_ESTIMATE_CLOCK_HPP_INCLUDED +#define CATCH_ESTIMATE_CLOCK_HPP_INCLUDED + +// Adapted from donated nonius code. + +#ifndef CATCH_STATS_HPP_INCLUDED +#define CATCH_STATS_HPP_INCLUDED + +#include + +namespace Catch { + namespace Benchmark { + namespace Detail { + using sample = std::vector; + + double weighted_average_quantile(int k, + int q, + double *first, + double *last); + + OutlierClassification + classify_outliers(double const *first, double const *last); + + double mean(double const *first, double const *last); + + double normal_cdf(double x); + + double erfc_inv(double x); + + double normal_quantile(double p); + + Estimate + bootstrap(double confidence_level, + double *first, + double *last, + sample const &resample, + double (*estimator)(double const *, double const *)); + + struct bootstrap_analysis { + Estimate mean; + Estimate standard_deviation; + double outlier_variance; + }; + + bootstrap_analysis analyse_samples(double confidence_level, + unsigned int n_resamples, + double *first, + double *last); + } // namespace Detail + } // namespace Benchmark +} // namespace Catch + +#endif // CATCH_STATS_HPP_INCLUDED + +#include +#include +#include + +namespace Catch { + namespace Benchmark { + namespace Detail { + template + std::vector resolution(int k) { + const size_t points = static_cast(k + 1); + // To avoid overhead from the branch inside vector::push_back, + // we allocate them all and then overwrite. + std::vector> times(points); + for (auto &time : times) { + time = Clock::now(); + } + + std::vector deltas; + deltas.reserve(static_cast(k)); + for (size_t idx = 1; idx < points; ++idx) { + deltas.push_back(static_cast( + (times[idx] - times[idx - 1]).count())); + } + + return deltas; + } + + constexpr auto warmup_iterations = 10000; + constexpr auto warmup_time = std::chrono::milliseconds(100); + constexpr auto minimum_ticks = 1000; + constexpr auto warmup_seed = 10000; + constexpr auto clock_resolution_estimation_time = std::chrono::milliseconds(500); + constexpr auto clock_cost_estimation_time_limit = std::chrono::seconds(1); + constexpr auto clock_cost_estimation_tick_limit = 100000; + constexpr auto clock_cost_estimation_time = std::chrono::milliseconds(10); + constexpr auto clock_cost_estimation_iterations = 10000; + + template + int warmup() { + return run_for_at_least(warmup_time, warmup_seed, &resolution) + .iterations; + } + template + EnvironmentEstimate estimate_clock_resolution(int iterations) { + auto r = run_for_at_least(clock_resolution_estimation_time, iterations, &resolution) + .result; + return { + FDuration(mean(r.data(), r.data() + r.size())), + classify_outliers(r.data(), r.data() + r.size()), + }; + } + template + EnvironmentEstimate estimate_clock_cost(FDuration resolution) { + auto time_limit = (std::min)( + resolution * clock_cost_estimation_tick_limit, + FDuration(clock_cost_estimation_time_limit)); + auto time_clock = [](int k) { + return Detail::measure([k] { + for (int i = 0; i < k; ++i) { + volatile auto ignored = Clock::now(); + (void)ignored; + } + }) + .elapsed; + }; + time_clock(1); + int iters = clock_cost_estimation_iterations; + auto &&r = run_for_at_least(clock_cost_estimation_time, iters, time_clock); + std::vector times; + int nsamples = static_cast(std::ceil(time_limit / r.elapsed)); + times.reserve(static_cast(nsamples)); + for (int s = 0; s < nsamples; ++s) { + times.push_back(static_cast( + (time_clock(r.iterations) / r.iterations) + .count())); + } + return { + FDuration(mean(times.data(), times.data() + times.size())), + classify_outliers(times.data(), times.data() + times.size()), + }; + } + + template + Environment measure_environment() { +#if defined(__clang__) +#pragma clang diagnostic push +#pragma clang diagnostic ignored "-Wexit-time-destructors" +#endif + static Catch::Detail::unique_ptr env; +#if defined(__clang__) +#pragma clang diagnostic pop +#endif + if (env) { + return *env; + } + + auto iters = Detail::warmup(); + auto resolution = Detail::estimate_clock_resolution(iters); + auto cost = Detail::estimate_clock_cost(resolution.mean); + + env = Catch::Detail::make_unique(Environment{resolution, cost}); + return *env; + } + } // namespace Detail + } // namespace Benchmark +} // namespace Catch + +#endif // CATCH_ESTIMATE_CLOCK_HPP_INCLUDED + +// Adapted from donated nonius code. + +#ifndef CATCH_ANALYSE_HPP_INCLUDED +#define CATCH_ANALYSE_HPP_INCLUDED + +// Adapted from donated nonius code. + +#ifndef CATCH_SAMPLE_ANALYSIS_HPP_INCLUDED +#define CATCH_SAMPLE_ANALYSIS_HPP_INCLUDED + +#include + +namespace Catch { + namespace Benchmark { + struct SampleAnalysis { + std::vector samples; + Estimate mean; + Estimate standard_deviation; + OutlierClassification outliers; + double outlier_variance; + }; + } // namespace Benchmark +} // namespace Catch + +#endif // CATCH_SAMPLE_ANALYSIS_HPP_INCLUDED + +namespace Catch { + class IConfig; + + namespace Benchmark { + namespace Detail { + SampleAnalysis analyse(const IConfig &cfg, FDuration *first, FDuration *last); + } // namespace Detail + } // namespace Benchmark +} // namespace Catch + +#endif // CATCH_ANALYSE_HPP_INCLUDED + +#include +#include +#include +#include +#include + +namespace Catch { + namespace Benchmark { + struct Benchmark { + Benchmark(std::string &&benchmarkName) + : name(CATCH_MOVE(benchmarkName)) {} + + template + Benchmark(std::string &&benchmarkName, FUN &&func) + : fun(CATCH_MOVE(func)), name(CATCH_MOVE(benchmarkName)) {} + + template + ExecutionPlan prepare(const IConfig &cfg, Environment env) { + auto min_time = env.clock_resolution.mean * Detail::minimum_ticks; + auto run_time = std::max(min_time, std::chrono::duration_cast(cfg.benchmarkWarmupTime())); + auto &&test = Detail::run_for_at_least(std::chrono::duration_cast(run_time), 1, fun); + int new_iters = static_cast(std::ceil(min_time * test.iterations / test.elapsed)); + return {new_iters, test.elapsed / test.iterations * new_iters * cfg.benchmarkSamples(), CATCH_MOVE(fun), std::chrono::duration_cast(cfg.benchmarkWarmupTime()), Detail::warmup_iterations}; + } + + template + void run() { + static_assert(Clock::is_steady, + "Benchmarking clock should be steady"); + auto const *cfg = getCurrentContext().getConfig(); + + auto env = Detail::measure_environment(); + + getResultCapture().benchmarkPreparing(name); + CATCH_TRY { + auto plan = user_code([&] { + return prepare(*cfg, env); + }); + + BenchmarkInfo info{ + CATCH_MOVE(name), + plan.estimated_duration.count(), + plan.iterations_per_sample, + cfg->benchmarkSamples(), + cfg->benchmarkResamples(), + env.clock_resolution.mean.count(), + env.clock_cost.mean.count()}; + + getResultCapture().benchmarkStarting(info); + + auto samples = user_code([&] { + return plan.template run(*cfg, env); + }); + + auto analysis = Detail::analyse(*cfg, samples.data(), samples.data() + samples.size()); + BenchmarkStats<> stats{CATCH_MOVE(info), CATCH_MOVE(analysis.samples), analysis.mean, analysis.standard_deviation, analysis.outliers, analysis.outlier_variance}; + getResultCapture().benchmarkEnded(stats); + } + CATCH_CATCH_ALL { + getResultCapture().benchmarkFailed(translateActiveException()); + // We let the exception go further up so that the + // test case is marked as failed. + std::rethrow_exception(std::current_exception()); + } + } + + // sets lambda to be used in fun *and* executes benchmark! + template, int> = 0> + Benchmark &operator=(Fun func) { + auto const *cfg = getCurrentContext().getConfig(); + if (!cfg->skipBenchmarks()) { + fun = Detail::BenchmarkFunction(func); + run(); + } + return *this; + } + + explicit operator bool() { + return true; + } + + private: + Detail::BenchmarkFunction fun; + std::string name; + }; + } // namespace Benchmark +} // namespace Catch + +#define INTERNAL_CATCH_GET_1_ARG(arg1, arg2, ...) arg1 +#define INTERNAL_CATCH_GET_2_ARG(arg1, arg2, ...) arg2 + +#define INTERNAL_CATCH_BENCHMARK(BenchmarkName, name, benchmarkIndex) \ + if (Catch::Benchmark::Benchmark BenchmarkName{name}) \ + BenchmarkName = [&](int benchmarkIndex) + +#define INTERNAL_CATCH_BENCHMARK_ADVANCED(BenchmarkName, name) \ + if (Catch::Benchmark::Benchmark BenchmarkName{name}) \ + BenchmarkName = [&] + +#if defined(CATCH_CONFIG_PREFIX_ALL) + +#define CATCH_BENCHMARK(...) \ + INTERNAL_CATCH_BENCHMARK(INTERNAL_CATCH_UNIQUE_NAME(CATCH2_INTERNAL_BENCHMARK_), INTERNAL_CATCH_GET_1_ARG(__VA_ARGS__, , ), INTERNAL_CATCH_GET_2_ARG(__VA_ARGS__, , )) +#define CATCH_BENCHMARK_ADVANCED(name) \ + INTERNAL_CATCH_BENCHMARK_ADVANCED(INTERNAL_CATCH_UNIQUE_NAME(CATCH2_INTERNAL_BENCHMARK_), name) + +#else + +#define BENCHMARK(...) \ + INTERNAL_CATCH_BENCHMARK(INTERNAL_CATCH_UNIQUE_NAME(CATCH2_INTERNAL_BENCHMARK_), INTERNAL_CATCH_GET_1_ARG(__VA_ARGS__, , ), INTERNAL_CATCH_GET_2_ARG(__VA_ARGS__, , )) +#define BENCHMARK_ADVANCED(name) \ + INTERNAL_CATCH_BENCHMARK_ADVANCED(INTERNAL_CATCH_UNIQUE_NAME(CATCH2_INTERNAL_BENCHMARK_), name) + +#endif + +#endif // CATCH_BENCHMARK_HPP_INCLUDED + +// Adapted from donated nonius code. + +#ifndef CATCH_CONSTRUCTOR_HPP_INCLUDED +#define CATCH_CONSTRUCTOR_HPP_INCLUDED + +#include + +namespace Catch { + namespace Benchmark { + namespace Detail { + template + struct ObjectStorage { + ObjectStorage() = default; + + ObjectStorage(const ObjectStorage &other) { + new (&data) T(other.stored_object()); + } + + ObjectStorage(ObjectStorage &&other) { + new (data) T(CATCH_MOVE(other.stored_object())); + } + + ~ObjectStorage() { destruct_on_exit(); } + + template + void construct(Args &&...args) { + new (data) T(CATCH_FORWARD(args)...); + } + + template + std::enable_if_t destruct() { + stored_object().~T(); + } + + private: + // If this is a constructor benchmark, destruct the underlying object + template + void destruct_on_exit(std::enable_if_t * = nullptr) { destruct(); } + // Otherwise, don't + template + void destruct_on_exit(std::enable_if_t * = nullptr) {} + +#if defined(__GNUC__) && __GNUC__ <= 6 +#pragma GCC diagnostic push +#pragma GCC diagnostic ignored "-Wstrict-aliasing" +#endif + T &stored_object() { return *reinterpret_cast(data); } + + T const &stored_object() const { + return *reinterpret_cast(data); + } +#if defined(__GNUC__) && __GNUC__ <= 6 +#pragma GCC diagnostic pop +#endif + + alignas(T) unsigned char data[sizeof(T)]{}; + }; + } // namespace Detail + + template + using storage_for = Detail::ObjectStorage; + + template + using destructable_object = Detail::ObjectStorage; + } // namespace Benchmark +} // namespace Catch + +#endif // CATCH_CONSTRUCTOR_HPP_INCLUDED + +#endif // CATCH_BENCHMARK_ALL_HPP_INCLUDED + +#ifndef CATCH_APPROX_HPP_INCLUDED +#define CATCH_APPROX_HPP_INCLUDED + +#ifndef CATCH_TOSTRING_HPP_INCLUDED +#define CATCH_TOSTRING_HPP_INCLUDED + +#include +#include +#include +#include +#include + +/** \file + * Wrapper for the WCHAR configuration option + * + * We want to support platforms that do not provide `wchar_t`, so we + * sometimes have to disable providing wchar_t overloads through Catch2, + * e.g. the StringMaker specialization for `std::wstring`. + */ + +#ifndef CATCH_CONFIG_WCHAR_HPP_INCLUDED +#define CATCH_CONFIG_WCHAR_HPP_INCLUDED + +// We assume that WCHAR should be enabled by default, and only disabled +// for a shortlist (so far only DJGPP) of compilers. + +#if defined(__DJGPP__) +#define CATCH_INTERNAL_CONFIG_NO_WCHAR +#endif // __DJGPP__ + +#if !defined(CATCH_INTERNAL_CONFIG_NO_WCHAR) && !defined(CATCH_CONFIG_NO_WCHAR) && !defined(CATCH_CONFIG_WCHAR) +#define CATCH_CONFIG_WCHAR +#endif + +#endif // CATCH_CONFIG_WCHAR_HPP_INCLUDED + +#ifndef CATCH_REUSABLE_STRING_STREAM_HPP_INCLUDED +#define CATCH_REUSABLE_STRING_STREAM_HPP_INCLUDED + +#include +#include +#include +#include + +namespace Catch { + + class ReusableStringStream : Detail::NonCopyable { + std::size_t m_index; + std::ostream *m_oss; + + public: + ReusableStringStream(); + ~ReusableStringStream(); + + //! Returns the serialized state + std::string str() const; + //! Sets internal state to `str` + void str(std::string const &str); + +#if defined(__GNUC__) && !defined(__clang__) +#pragma GCC diagnostic push +// Old versions of GCC do not understand -Wnonnull-compare +#pragma GCC diagnostic ignored "-Wpragmas" +// Streaming a function pointer triggers Waddress and Wnonnull-compare +// on GCC, because it implicitly converts it to bool and then decides +// that the check it uses (a? true : false) is tautological and cannot +// be null... +#pragma GCC diagnostic ignored "-Waddress" +#pragma GCC diagnostic ignored "-Wnonnull-compare" +#endif + + template + auto operator<<(T const &value) -> ReusableStringStream & { + *m_oss << value; + return *this; + } + +#if defined(__GNUC__) && !defined(__clang__) +#pragma GCC diagnostic pop +#endif + auto get() -> std::ostream & { return *m_oss; } + }; +} // namespace Catch + +#endif // CATCH_REUSABLE_STRING_STREAM_HPP_INCLUDED + +#ifndef CATCH_VOID_TYPE_HPP_INCLUDED +#define CATCH_VOID_TYPE_HPP_INCLUDED + +namespace Catch { + namespace Detail { + + template + struct make_void { + using type = void; + }; + + template + using void_t = typename make_void::type; + + } // namespace Detail +} // namespace Catch + +#endif // CATCH_VOID_TYPE_HPP_INCLUDED + +#ifndef CATCH_INTERFACES_ENUM_VALUES_REGISTRY_HPP_INCLUDED +#define CATCH_INTERFACES_ENUM_VALUES_REGISTRY_HPP_INCLUDED + +#include + +namespace Catch { + + namespace Detail { + struct EnumInfo { + StringRef m_name; + std::vector> m_values; + + ~EnumInfo(); + + StringRef lookup(int value) const; + }; + } // namespace Detail + + class IMutableEnumValuesRegistry { + public: + virtual ~IMutableEnumValuesRegistry(); // = default; + + virtual Detail::EnumInfo const ®isterEnum(StringRef enumName, StringRef allEnums, std::vector const &values) = 0; + + template + Detail::EnumInfo const ®isterEnum(StringRef enumName, StringRef allEnums, std::initializer_list values) { + static_assert(sizeof(int) >= sizeof(E), "Cannot serialize enum to int"); + std::vector intValues; + intValues.reserve(values.size()); + for (auto enumValue : values) + intValues.push_back(static_cast(enumValue)); + return registerEnum(enumName, allEnums, intValues); + } + }; + +} // namespace Catch + +#endif // CATCH_INTERFACES_ENUM_VALUES_REGISTRY_HPP_INCLUDED + +#ifdef CATCH_CONFIG_CPP17_STRING_VIEW +#include +#endif + +#ifdef _MSC_VER +#pragma warning(push) +#pragma warning(disable : 4180) // We attempt to stream a function (address) by const&, which MSVC complains about but is harmless +#endif + +// We need a dummy global operator<< so we can bring it into Catch namespace later +struct Catch_global_namespace_dummy {}; +std::ostream &operator<<(std::ostream &, Catch_global_namespace_dummy); + +namespace Catch { + // Bring in global namespace operator<< for ADL lookup in + // `IsStreamInsertable` below. + using ::operator<<; + + namespace Detail { + + std::size_t catch_strnlen(const char *str, std::size_t n); + + std::string formatTimeT(std::time_t time); + + constexpr StringRef unprintableString = "{?}"_sr; + + //! Encases `string in quotes, and optionally escapes invisibles + std::string convertIntoString(StringRef string, bool escapeInvisibles); + + //! Encases `string` in quotes, and escapes invisibles if user requested + //! it via CLI + std::string convertIntoString(StringRef string); + + std::string rawMemoryToString(const void *object, std::size_t size); + + template + std::string rawMemoryToString(const T &object) { + return rawMemoryToString(&object, sizeof(object)); + } + + template + static constexpr bool IsStreamInsertable_v = false; + + template + static constexpr bool IsStreamInsertable_v< + T, + decltype(void(std::declval() << std::declval()))> + = true; + + template + std::string convertUnknownEnumToString(E e); + + template + std::enable_if_t< + !std::is_enum::value && !std::is_base_of::value, + std::string> + convertUnstreamable(T const &) { + return std::string(Detail::unprintableString); + } + template + std::enable_if_t< + !std::is_enum::value && std::is_base_of::value, + std::string> + convertUnstreamable(T const &ex) { + return ex.what(); + } + + template + std::enable_if_t< + std::is_enum::value, + std::string> + convertUnstreamable(T const &value) { + return convertUnknownEnumToString(value); + } + +#if defined(_MANAGED) + //! Convert a CLR string to a utf8 std::string + template + std::string clrReferenceToString(T ^ ref) { + if (ref == nullptr) + return std::string("null"); + auto bytes = System::Text::Encoding::UTF8->GetBytes(ref->ToString()); + cli::pin_ptr p = &bytes[0]; + return std::string(reinterpret_cast(p), bytes->Length); + } +#endif + + } // namespace Detail + + template + struct StringMaker { + template + static std::enable_if_t<::Catch::Detail::IsStreamInsertable_v, std::string> + convert(const Fake &value) { + ReusableStringStream rss; + // NB: call using the function-like syntax to avoid ambiguity with + // user-defined templated operator<< under clang. + rss.operator<<(value); + return rss.str(); + } + + template + static std::enable_if_t, std::string> + convert(const Fake &value) { +#if !defined(CATCH_CONFIG_FALLBACK_STRINGIFIER) + return Detail::convertUnstreamable(value); +#else + return CATCH_CONFIG_FALLBACK_STRINGIFIER(value); +#endif + } + }; + + namespace Detail { + + std::string makeExceptionHappenedString(); + + // This function dispatches all stringification requests inside of Catch. + // Should be preferably called fully qualified, like ::Catch::Detail::stringify + template + std::string stringify(const T &e) { + CATCH_TRY { + return ::Catch::StringMaker< + std::remove_cv_t>>::convert(e); + } + CATCH_CATCH_ALL { + return makeExceptionHappenedString(); + } + } + + template + std::string convertUnknownEnumToString(E e) { + return ::Catch::Detail::stringify(static_cast>(e)); + } + +#if defined(_MANAGED) + template + std::string stringify(T ^ e) { + return ::Catch::StringMaker::convert(e); + } +#endif + + } // namespace Detail + + // Some predefined specializations + + template<> + struct StringMaker { + static std::string convert(const std::string &str); + }; + +#ifdef CATCH_CONFIG_CPP17_STRING_VIEW + template<> + struct StringMaker { + static std::string convert(std::string_view str); + }; +#endif + + template<> + struct StringMaker { + static std::string convert(char const *str); + }; + template<> + struct StringMaker { + static std::string convert(char *str); + }; + +#if defined(CATCH_CONFIG_WCHAR) + template<> + struct StringMaker { + static std::string convert(const std::wstring &wstr); + }; + +#ifdef CATCH_CONFIG_CPP17_STRING_VIEW + template<> + struct StringMaker { + static std::string convert(std::wstring_view str); + }; +#endif + + template<> + struct StringMaker { + static std::string convert(wchar_t const *str); + }; + template<> + struct StringMaker { + static std::string convert(wchar_t *str); + }; +#endif // CATCH_CONFIG_WCHAR + + template + struct StringMaker { + static std::string convert(char const *str) { + return Detail::convertIntoString( + StringRef(str, Detail::catch_strnlen(str, SZ))); + } + }; + template + struct StringMaker { + static std::string convert(signed char const *str) { + auto reinterpreted = reinterpret_cast(str); + return Detail::convertIntoString( + StringRef(reinterpreted, Detail::catch_strnlen(reinterpreted, SZ))); + } + }; + template + struct StringMaker { + static std::string convert(unsigned char const *str) { + auto reinterpreted = reinterpret_cast(str); + return Detail::convertIntoString( + StringRef(reinterpreted, Detail::catch_strnlen(reinterpreted, SZ))); + } + }; + +#if defined(CATCH_CONFIG_CPP17_BYTE) + template<> + struct StringMaker { + static std::string convert(std::byte value); + }; +#endif // defined(CATCH_CONFIG_CPP17_BYTE) + template<> + struct StringMaker { + static std::string convert(int value); + }; + template<> + struct StringMaker { + static std::string convert(long value); + }; + template<> + struct StringMaker { + static std::string convert(long long value); + }; + template<> + struct StringMaker { + static std::string convert(unsigned int value); + }; + template<> + struct StringMaker { + static std::string convert(unsigned long value); + }; + template<> + struct StringMaker { + static std::string convert(unsigned long long value); + }; + + template<> + struct StringMaker { + static std::string convert(bool b) { + using namespace std::string_literals; + return b ? "true"s : "false"s; + } + }; + + template<> + struct StringMaker { + static std::string convert(char c); + }; + template<> + struct StringMaker { + static std::string convert(signed char value); + }; + template<> + struct StringMaker { + static std::string convert(unsigned char value); + }; + + template<> + struct StringMaker { + static std::string convert(std::nullptr_t) { + using namespace std::string_literals; + return "nullptr"s; + } + }; + + template<> + struct StringMaker { + static std::string convert(float value); + CATCH_EXPORT static int precision; + }; + + template<> + struct StringMaker { + static std::string convert(double value); + CATCH_EXPORT static int precision; + }; + + template + struct StringMaker { + template + static std::string convert(U *p) { + if (p) { + return ::Catch::Detail::rawMemoryToString(p); + } else { + return "nullptr"; + } + } + }; + + template + struct StringMaker { + static std::string convert(R C::*p) { + if (p) { + return ::Catch::Detail::rawMemoryToString(p); + } else { + return "nullptr"; + } + } + }; + +#if defined(_MANAGED) + template + struct StringMaker { + static std::string convert(T ^ ref) { + return ::Catch::Detail::clrReferenceToString(ref); + } + }; +#endif + + namespace Detail { + template + std::string rangeToString(InputIterator first, Sentinel last) { + ReusableStringStream rss; + rss << "{ "; + if (first != last) { + rss << ::Catch::Detail::stringify(*first); + for (++first; first != last; ++first) + rss << ", " << ::Catch::Detail::stringify(*first); + } + rss << " }"; + return rss.str(); + } + } // namespace Detail + +} // namespace Catch + +////////////////////////////////////////////////////// +// Separate std-lib types stringification, so it can be selectively enabled +// This means that we do not bring in their headers + +#if defined(CATCH_CONFIG_ENABLE_ALL_STRINGMAKERS) +#define CATCH_CONFIG_ENABLE_PAIR_STRINGMAKER +#define CATCH_CONFIG_ENABLE_TUPLE_STRINGMAKER +#define CATCH_CONFIG_ENABLE_VARIANT_STRINGMAKER +#define CATCH_CONFIG_ENABLE_OPTIONAL_STRINGMAKER +#endif + +// Separate std::pair specialization +#if defined(CATCH_CONFIG_ENABLE_PAIR_STRINGMAKER) +#include +namespace Catch { + template + struct StringMaker> { + static std::string convert(const std::pair &pair) { + ReusableStringStream rss; + rss << "{ " + << ::Catch::Detail::stringify(pair.first) + << ", " + << ::Catch::Detail::stringify(pair.second) + << " }"; + return rss.str(); + } + }; +} // namespace Catch +#endif // CATCH_CONFIG_ENABLE_PAIR_STRINGMAKER + +#if defined(CATCH_CONFIG_ENABLE_OPTIONAL_STRINGMAKER) && defined(CATCH_CONFIG_CPP17_OPTIONAL) +#include +namespace Catch { + template + struct StringMaker> { + static std::string convert(const std::optional &optional) { + if (optional.has_value()) { + return ::Catch::Detail::stringify(*optional); + } else { + return "{ }"; + } + } + }; + template<> + struct StringMaker { + static std::string convert(const std::nullopt_t &) { + return "{ }"; + } + }; +} // namespace Catch +#endif // CATCH_CONFIG_ENABLE_OPTIONAL_STRINGMAKER + +// Separate std::tuple specialization +#if defined(CATCH_CONFIG_ENABLE_TUPLE_STRINGMAKER) +#include +#include +namespace Catch { + namespace Detail { + template + void PrintTuple(const Tuple &tuple, + std::ostream &os, + std::index_sequence) { + // 1 + Account for when the tuple is empty + char a[1 + sizeof...(Is)] = { + ((os << (Is ? ", " : " ") + << ::Catch::Detail::stringify(std::get(tuple))), + '\0')...}; + (void)a; + } + + } // namespace Detail + + template + struct StringMaker> { + static std::string convert(const std::tuple &tuple) { + ReusableStringStream rss; + rss << '{'; + Detail::PrintTuple( + tuple, + rss.get(), + std::make_index_sequence{}); + rss << " }"; + return rss.str(); + } + }; +} // namespace Catch +#endif // CATCH_CONFIG_ENABLE_TUPLE_STRINGMAKER + +#if defined(CATCH_CONFIG_ENABLE_VARIANT_STRINGMAKER) && defined(CATCH_CONFIG_CPP17_VARIANT) +#include +namespace Catch { + template<> + struct StringMaker { + static std::string convert(const std::monostate &) { + return "{ }"; + } + }; + + template + struct StringMaker> { + static std::string convert(const std::variant &variant) { + if (variant.valueless_by_exception()) { + return "{valueless variant}"; + } else { + return std::visit( + [](const auto &value) { + return ::Catch::Detail::stringify(value); + }, + variant); + } + } + }; +} // namespace Catch +#endif // CATCH_CONFIG_ENABLE_VARIANT_STRINGMAKER + +namespace Catch { + // Import begin/ end from std here + using std::begin; + using std::end; + + namespace Detail { + template + struct is_range_impl : std::false_type {}; + + template + struct is_range_impl()))>> : std::true_type {}; + } // namespace Detail + + template + struct is_range : Detail::is_range_impl {}; + +#if defined(_MANAGED) // Managed types are never ranges + template + struct is_range { + static const bool value = false; + }; +#endif + + template + std::string rangeToString(Range const &range) { + return ::Catch::Detail::rangeToString(begin(range), end(range)); + } + + // Handle vector specially + template + std::string rangeToString(std::vector const &v) { + ReusableStringStream rss; + rss << "{ "; + bool first = true; + for (bool b : v) { + if (first) + first = false; + else + rss << ", "; + rss << ::Catch::Detail::stringify(b); + } + rss << " }"; + return rss.str(); + } + + template + struct StringMaker::value && !::Catch::Detail::IsStreamInsertable_v>> { + static std::string convert(R const &range) { + return rangeToString(range); + } + }; + + template + struct StringMaker { + static std::string convert(T const (&arr)[SZ]) { + return rangeToString(arr); + } + }; + +} // namespace Catch + +// Separate std::chrono::duration specialization +#include +#include +#include + +namespace Catch { + + template + struct ratio_string { + static std::string symbol() { + Catch::ReusableStringStream rss; + rss << '[' << Ratio::num << '/' + << Ratio::den << ']'; + return rss.str(); + } + }; + + template<> + struct ratio_string { + static char symbol() { return 'a'; } + }; + template<> + struct ratio_string { + static char symbol() { return 'f'; } + }; + template<> + struct ratio_string { + static char symbol() { return 'p'; } + }; + template<> + struct ratio_string { + static char symbol() { return 'n'; } + }; + template<> + struct ratio_string { + static char symbol() { return 'u'; } + }; + template<> + struct ratio_string { + static char symbol() { return 'm'; } + }; + + //////////// + // std::chrono::duration specializations + template + struct StringMaker> { + static std::string convert(std::chrono::duration const &duration) { + ReusableStringStream rss; + rss << duration.count() << ' ' << ratio_string::symbol() << 's'; + return rss.str(); + } + }; + template + struct StringMaker>> { + static std::string convert(std::chrono::duration> const &duration) { + ReusableStringStream rss; + rss << duration.count() << " s"; + return rss.str(); + } + }; + template + struct StringMaker>> { + static std::string convert(std::chrono::duration> const &duration) { + ReusableStringStream rss; + rss << duration.count() << " m"; + return rss.str(); + } + }; + template + struct StringMaker>> { + static std::string convert(std::chrono::duration> const &duration) { + ReusableStringStream rss; + rss << duration.count() << " h"; + return rss.str(); + } + }; + + //////////// + // std::chrono::time_point specialization + // Generic time_point cannot be specialized, only std::chrono::time_point + template + struct StringMaker> { + static std::string convert(std::chrono::time_point const &time_point) { + return ::Catch::Detail::stringify(time_point.time_since_epoch()) + " since epoch"; + } + }; + // std::chrono::time_point specialization + template + struct StringMaker> { + static std::string convert(std::chrono::time_point const &time_point) { + const auto systemish = std::chrono::time_point_cast< + std::chrono::system_clock::duration>(time_point); + const auto as_time_t = std::chrono::system_clock::to_time_t(systemish); + return ::Catch::Detail::formatTimeT(as_time_t); + } + }; +} // namespace Catch + +#define INTERNAL_CATCH_REGISTER_ENUM(enumName, ...) \ + namespace Catch { \ + template<> \ + struct StringMaker { \ + static std::string convert(enumName value) { \ + static const auto &enumInfo = ::Catch::getMutableRegistryHub().getMutableEnumValuesRegistry().registerEnum(#enumName, #__VA_ARGS__, {__VA_ARGS__}); \ + return static_cast(enumInfo.lookup(static_cast(value))); \ + } \ + }; \ + } + +#define CATCH_REGISTER_ENUM(enumName, ...) INTERNAL_CATCH_REGISTER_ENUM(enumName, __VA_ARGS__) + +#ifdef _MSC_VER +#pragma warning(pop) +#endif + +#endif // CATCH_TOSTRING_HPP_INCLUDED + +#include + +namespace Catch { + + class Approx { + private: + bool equalityComparisonImpl(double other) const; + // Sets and validates the new margin (margin >= 0) + void setMargin(double margin); + // Sets and validates the new epsilon (0 < epsilon < 1) + void setEpsilon(double epsilon); + + public: + explicit Approx(double value); + + static Approx custom(); + + Approx operator-() const; + + template::value>> + Approx operator()(T const &value) const { + Approx approx(static_cast(value)); + approx.m_epsilon = m_epsilon; + approx.m_margin = m_margin; + approx.m_scale = m_scale; + return approx; + } + + template::value>> + explicit Approx(T const &value) + : Approx(static_cast(value)) {} + + template::value>> + friend bool operator==(const T &lhs, Approx const &rhs) { + auto lhs_v = static_cast(lhs); + return rhs.equalityComparisonImpl(lhs_v); + } + + template::value>> + friend bool operator==(Approx const &lhs, const T &rhs) { + return operator==(rhs, lhs); + } + + template::value>> + friend bool operator!=(T const &lhs, Approx const &rhs) { + return !operator==(lhs, rhs); + } + + template::value>> + friend bool operator!=(Approx const &lhs, T const &rhs) { + return !operator==(rhs, lhs); + } + + template::value>> + friend bool operator<=(T const &lhs, Approx const &rhs) { + return static_cast(lhs) < rhs.m_value || lhs == rhs; + } + + template::value>> + friend bool operator<=(Approx const &lhs, T const &rhs) { + return lhs.m_value < static_cast(rhs) || lhs == rhs; + } + + template::value>> + friend bool operator>=(T const &lhs, Approx const &rhs) { + return static_cast(lhs) > rhs.m_value || lhs == rhs; + } + + template::value>> + friend bool operator>=(Approx const &lhs, T const &rhs) { + return lhs.m_value > static_cast(rhs) || lhs == rhs; + } + + template::value>> + Approx &epsilon(T const &newEpsilon) { + const auto epsilonAsDouble = static_cast(newEpsilon); + setEpsilon(epsilonAsDouble); + return *this; + } + + template::value>> + Approx &margin(T const &newMargin) { + const auto marginAsDouble = static_cast(newMargin); + setMargin(marginAsDouble); + return *this; + } + + template::value>> + Approx &scale(T const &newScale) { + m_scale = static_cast(newScale); + return *this; + } + + std::string toString() const; + + private: + double m_epsilon; + double m_margin; + double m_scale; + double m_value; + }; + + namespace literals { + Approx operator""_a(long double val); + Approx operator""_a(unsigned long long val); + } // end namespace literals + + template<> + struct StringMaker { + static std::string convert(Catch::Approx const &value); + }; + +} // end namespace Catch + +#endif // CATCH_APPROX_HPP_INCLUDED + +#ifndef CATCH_ASSERTION_INFO_HPP_INCLUDED +#define CATCH_ASSERTION_INFO_HPP_INCLUDED + +#ifndef CATCH_SOURCE_LINE_INFO_HPP_INCLUDED +#define CATCH_SOURCE_LINE_INFO_HPP_INCLUDED + +#include +#include + +namespace Catch { + + struct SourceLineInfo { + SourceLineInfo() = delete; + constexpr SourceLineInfo(char const *_file, std::size_t _line) noexcept + : file(_file), line(_line) {} + + bool operator==(SourceLineInfo const &other) const noexcept; + bool operator<(SourceLineInfo const &other) const noexcept; + + char const *file; + std::size_t line; + + friend std::ostream &operator<<(std::ostream &os, SourceLineInfo const &info); + }; +} // namespace Catch + +#define CATCH_INTERNAL_LINEINFO \ + ::Catch::SourceLineInfo(__FILE__, static_cast(__LINE__)) + +#endif // CATCH_SOURCE_LINE_INFO_HPP_INCLUDED + +namespace Catch { + + struct AssertionInfo { + // AssertionInfo() = delete; + + StringRef macroName; + SourceLineInfo lineInfo; + StringRef capturedExpression; + ResultDisposition::Flags resultDisposition; + }; + +} // end namespace Catch + +#endif // CATCH_ASSERTION_INFO_HPP_INCLUDED + +#ifndef CATCH_ASSERTION_RESULT_HPP_INCLUDED +#define CATCH_ASSERTION_RESULT_HPP_INCLUDED + +#ifndef CATCH_LAZY_EXPR_HPP_INCLUDED +#define CATCH_LAZY_EXPR_HPP_INCLUDED + +#include + +namespace Catch { + + class ITransientExpression; + + class LazyExpression { + friend class AssertionHandler; + friend struct AssertionStats; + friend class RunContext; + + ITransientExpression const *m_transientExpression = nullptr; + bool m_isNegated; + + public: + constexpr LazyExpression(bool isNegated) + : m_isNegated(isNegated) {} + constexpr LazyExpression(LazyExpression const &other) = default; + LazyExpression &operator=(LazyExpression const &) = delete; + + constexpr explicit operator bool() const { + return m_transientExpression != nullptr; + } + + friend auto operator<<(std::ostream &os, LazyExpression const &lazyExpr) -> std::ostream &; + }; + +} // namespace Catch + +#endif // CATCH_LAZY_EXPR_HPP_INCLUDED + +#include + +namespace Catch { + + struct AssertionResultData { + AssertionResultData() = delete; + + AssertionResultData(ResultWas::OfType _resultType, LazyExpression const &_lazyExpression); + + std::string message; + mutable std::string reconstructedExpression; + LazyExpression lazyExpression; + ResultWas::OfType resultType; + + std::string reconstructExpression() const; + }; + + class AssertionResult { + public: + AssertionResult() = delete; + AssertionResult(AssertionInfo const &info, AssertionResultData &&data); + + bool isOk() const; + bool succeeded() const; + ResultWas::OfType getResultType() const; + bool hasExpression() const; + bool hasMessage() const; + std::string getExpression() const; + std::string getExpressionInMacro() const; + bool hasExpandedExpression() const; + std::string getExpandedExpression() const; + StringRef getMessage() const; + SourceLineInfo getSourceInfo() const; + StringRef getTestMacroName() const; + + // protected: + AssertionInfo m_info; + AssertionResultData m_resultData; + }; + +} // end namespace Catch + +#endif // CATCH_ASSERTION_RESULT_HPP_INCLUDED + +#ifndef CATCH_CASE_SENSITIVE_HPP_INCLUDED +#define CATCH_CASE_SENSITIVE_HPP_INCLUDED + +namespace Catch { + + enum class CaseSensitive { Yes, + No }; + +} // namespace Catch + +#endif // CATCH_CASE_SENSITIVE_HPP_INCLUDED + +#ifndef CATCH_CONFIG_HPP_INCLUDED +#define CATCH_CONFIG_HPP_INCLUDED + +#ifndef CATCH_TEST_SPEC_HPP_INCLUDED +#define CATCH_TEST_SPEC_HPP_INCLUDED + +#ifdef __clang__ +#pragma clang diagnostic push +#pragma clang diagnostic ignored "-Wpadded" +#endif + +#ifndef CATCH_WILDCARD_PATTERN_HPP_INCLUDED +#define CATCH_WILDCARD_PATTERN_HPP_INCLUDED + +#include + +namespace Catch { + class WildcardPattern { + enum WildcardPosition { + NoWildcard = 0, + WildcardAtStart = 1, + WildcardAtEnd = 2, + WildcardAtBothEnds = WildcardAtStart | WildcardAtEnd + }; + + public: + WildcardPattern(std::string const &pattern, CaseSensitive caseSensitivity); + bool matches(std::string const &str) const; + + private: + std::string normaliseString(std::string const &str) const; + CaseSensitive m_caseSensitivity; + WildcardPosition m_wildcard = NoWildcard; + std::string m_pattern; + }; +} // namespace Catch + +#endif // CATCH_WILDCARD_PATTERN_HPP_INCLUDED + +#include +#include +#include + +namespace Catch { + + class IConfig; + struct TestCaseInfo; + class TestCaseHandle; + + class TestSpec { + class Pattern { + public: + explicit Pattern(std::string const &name); + virtual ~Pattern(); + virtual bool matches(TestCaseInfo const &testCase) const = 0; + std::string const &name() const; + + private: + virtual void serializeTo(std::ostream &out) const = 0; + // Writes string that would be reparsed into the pattern + friend std::ostream &operator<<(std::ostream &out, + Pattern const &pattern) { + pattern.serializeTo(out); + return out; + } + + std::string const m_name; + }; + + class NamePattern : public Pattern { + public: + explicit NamePattern(std::string const &name, std::string const &filterString); + bool matches(TestCaseInfo const &testCase) const override; + + private: + void serializeTo(std::ostream &out) const override; + + WildcardPattern m_wildcardPattern; + }; + + class TagPattern : public Pattern { + public: + explicit TagPattern(std::string const &tag, std::string const &filterString); + bool matches(TestCaseInfo const &testCase) const override; + + private: + void serializeTo(std::ostream &out) const override; + + std::string m_tag; + }; + + struct Filter { + std::vector> m_required; + std::vector> m_forbidden; + + //! Serializes this filter into a string that would be parsed into + //! an equivalent filter + void serializeTo(std::ostream &out) const; + friend std::ostream &operator<<(std::ostream &out, Filter const &f) { + f.serializeTo(out); + return out; + } + + bool matches(TestCaseInfo const &testCase) const; + }; + + static std::string extractFilterName(Filter const &filter); + + public: + struct FilterMatch { + std::string name; + std::vector tests; + }; + using Matches = std::vector; + using vectorStrings = std::vector; + + bool hasFilters() const; + bool matches(TestCaseInfo const &testCase) const; + Matches matchesByFilter(std::vector const &testCases, IConfig const &config) const; + const vectorStrings &getInvalidSpecs() const; + + private: + std::vector m_filters; + std::vector m_invalidSpecs; + + friend class TestSpecParser; + //! Serializes this test spec into a string that would be parsed into + //! equivalent test spec + void serializeTo(std::ostream &out) const; + friend std::ostream &operator<<(std::ostream &out, + TestSpec const &spec) { + spec.serializeTo(out); + return out; + } + }; +} // namespace Catch + +#ifdef __clang__ +#pragma clang diagnostic pop +#endif + +#endif // CATCH_TEST_SPEC_HPP_INCLUDED + +#ifndef CATCH_OPTIONAL_HPP_INCLUDED +#define CATCH_OPTIONAL_HPP_INCLUDED + +#include + +namespace Catch { + + // An optional type + template + class Optional { + public: + Optional() + : nullableValue(nullptr) {} + ~Optional() { reset(); } + + Optional(T const &_value) + : nullableValue(new (storage) T(_value)) {} + Optional(T &&_value) + : nullableValue(new (storage) T(CATCH_MOVE(_value))) {} + + Optional &operator=(T const &_value) { + reset(); + nullableValue = new (storage) T(_value); + return *this; + } + Optional &operator=(T &&_value) { + reset(); + nullableValue = new (storage) T(CATCH_MOVE(_value)); + return *this; + } + + Optional(Optional const &_other) + : nullableValue(_other ? new (storage) T(*_other) : nullptr) {} + Optional(Optional &&_other) + : nullableValue(_other ? new (storage) T(CATCH_MOVE(*_other)) + : nullptr) {} + + Optional &operator=(Optional const &_other) { + if (&_other != this) { + reset(); + if (_other) { nullableValue = new (storage) T(*_other); } + } + return *this; + } + Optional &operator=(Optional &&_other) { + if (&_other != this) { + reset(); + if (_other) { + nullableValue = new (storage) T(CATCH_MOVE(*_other)); + } + } + return *this; + } + + void reset() { + if (nullableValue) { nullableValue->~T(); } + nullableValue = nullptr; + } + + T &operator*() { + assert(nullableValue); + return *nullableValue; + } + T const &operator*() const { + assert(nullableValue); + return *nullableValue; + } + T *operator->() { + assert(nullableValue); + return nullableValue; + } + const T *operator->() const { + assert(nullableValue); + return nullableValue; + } + + T valueOr(T const &defaultValue) const { + return nullableValue ? *nullableValue : defaultValue; + } + + bool some() const { return nullableValue != nullptr; } + bool none() const { return nullableValue == nullptr; } + + bool operator!() const { return nullableValue == nullptr; } + explicit operator bool() const { + return some(); + } + + friend bool operator==(Optional const &a, Optional const &b) { + if (a.none() && b.none()) { + return true; + } else if (a.some() && b.some()) { + return *a == *b; + } else { + return false; + } + } + friend bool operator!=(Optional const &a, Optional const &b) { + return !(a == b); + } + + private: + T *nullableValue; + alignas(alignof(T)) char storage[sizeof(T)]; + }; + +} // end namespace Catch + +#endif // CATCH_OPTIONAL_HPP_INCLUDED + +#ifndef CATCH_PATH_FILTER_HPP_INCLUDED +#define CATCH_PATH_FILTER_HPP_INCLUDED + +#include + +namespace Catch { + + struct PathFilter { + enum class For { + Section, + Generator, + }; + PathFilter(For type_, std::string filter_) + : type(type_), filter(CATCH_MOVE(filter_)) {} + + For type; + std::string filter; + + friend bool operator==(PathFilter const &lhs, PathFilter const &rhs); + }; + +} // end namespace Catch + +#endif // CATCH_PATH_FILTER_HPP_INCLUDED + +#ifndef CATCH_RANDOM_SEED_GENERATION_HPP_INCLUDED +#define CATCH_RANDOM_SEED_GENERATION_HPP_INCLUDED + +#include + +namespace Catch { + + enum class GenerateFrom { + Time, + RandomDevice, + //! Currently equivalent to RandomDevice, but can change at any point + Default + }; + + std::uint32_t generateRandomSeed(GenerateFrom from); + +} // end namespace Catch + +#endif // CATCH_RANDOM_SEED_GENERATION_HPP_INCLUDED + +#ifndef CATCH_REPORTER_SPEC_PARSER_HPP_INCLUDED +#define CATCH_REPORTER_SPEC_PARSER_HPP_INCLUDED + +#include +#include +#include + +namespace Catch { + + enum class ColourMode : std::uint8_t; + + namespace Detail { + //! Splits the reporter spec into reporter name and kv-pair options + std::vector splitReporterSpec(StringRef reporterSpec); + + Optional stringToColourMode(StringRef colourMode); + } // namespace Detail + + /** + * Structured reporter spec that a reporter can be created from + * + * Parsing has been validated, but semantics have not. This means e.g. + * that the colour mode is known to Catch2, but it might not be + * compiled into the binary, and the output filename might not be + * openable. + */ + class ReporterSpec { + std::string m_name; + Optional m_outputFileName; + Optional m_colourMode; + std::map m_customOptions; + + friend bool operator==(ReporterSpec const &lhs, + ReporterSpec const &rhs); + friend bool operator!=(ReporterSpec const &lhs, + ReporterSpec const &rhs) { + return !(lhs == rhs); + } + + public: + ReporterSpec( + std::string name, + Optional outputFileName, + Optional colourMode, + std::map customOptions); + + std::string const &name() const { return m_name; } + + Optional const &outputFile() const { + return m_outputFileName; + } + + Optional const &colourMode() const { return m_colourMode; } + + std::map const &customOptions() const { + return m_customOptions; + } + }; + + /** + * Parses provided reporter spec string into + * + * Returns empty optional on errors, e.g. + * * field that is not first and not a key+value pair + * * duplicated keys in kv pair + * * unknown catch reporter option + * * empty key/value in an custom kv pair + * * ... + */ + Optional parseReporterSpec(StringRef reporterSpec); + +} // namespace Catch + +#endif // CATCH_REPORTER_SPEC_PARSER_HPP_INCLUDED + +#include +#include +#include +#include + +namespace Catch { + + class IStream; + + /** + * `ReporterSpec` but with the defaults filled in. + * + * Like `ReporterSpec`, the semantics are unchecked. + */ + struct ProcessedReporterSpec { + std::string name; + std::string outputFilename; + ColourMode colourMode; + std::map customOptions; + friend bool operator==(ProcessedReporterSpec const &lhs, + ProcessedReporterSpec const &rhs); + friend bool operator!=(ProcessedReporterSpec const &lhs, + ProcessedReporterSpec const &rhs) { + return !(lhs == rhs); + } + }; + + struct ConfigData { + bool listTests = false; + bool listTags = false; + bool listReporters = false; + bool listListeners = false; + + bool showSuccessfulTests = false; + bool shouldDebugBreak = false; + bool noThrow = false; + bool showHelp = false; + bool showInvisibles = false; + bool filenamesAsTags = false; + bool libIdentify = false; + bool allowZeroTests = false; + + int abortAfter = -1; + uint32_t rngSeed = generateRandomSeed(GenerateFrom::Default); + + unsigned int shardCount = 1; + unsigned int shardIndex = 0; + + bool skipBenchmarks = false; + bool benchmarkNoAnalysis = false; + unsigned int benchmarkSamples = 100; + double benchmarkConfidenceInterval = 0.95; + unsigned int benchmarkResamples = 100'000; + std::chrono::milliseconds::rep benchmarkWarmupTime = 100; + + Verbosity verbosity = Verbosity::Normal; + WarnAbout::What warnings = WarnAbout::Nothing; + ShowDurations showDurations = ShowDurations::DefaultForReporter; + double minDuration = -1; + TestRunOrder runOrder = TestRunOrder::Randomized; + ColourMode defaultColourMode = ColourMode::PlatformDefault; + WaitForKeypress::When waitForKeypress = WaitForKeypress::Never; + + std::string defaultOutputFilename; + std::string name; + std::string processName; + std::vector reporterSpecifications; + + std::vector testsOrTags; + std::vector pathFilters; + bool useNewPathFilteringBehaviour = false; + + std::string prematureExitGuardFilePath; + }; + + class Config : public IConfig { + public: + Config() = default; + Config(ConfigData const &data); + ~Config() override; // = default in the cpp file + + bool listTests() const; + bool listTags() const; + bool listReporters() const; + bool listListeners() const; + + std::vector const &getReporterSpecs() const; + std::vector const & + getProcessedReporterSpecs() const; + + std::vector const &getTestsOrTags() const override; + std::vector const &getPathFilters() const override; + bool useNewFilterBehaviour() const override; + + TestSpec const &testSpec() const override; + bool hasTestFilters() const override; + + bool showHelp() const; + + std::string const &getExitGuardFilePath() const; + + // IConfig interface + bool allowThrows() const override; + StringRef name() const override; + bool includeSuccessfulResults() const override; + bool warnAboutMissingAssertions() const override; + bool warnAboutUnmatchedTestSpecs() const override; + bool warnAboutInfiniteGenerators() const override; + bool zeroTestsCountAsSuccess() const override; + ShowDurations showDurations() const override; + double minDuration() const override; + TestRunOrder runOrder() const override; + uint32_t rngSeed() const override; + unsigned int shardCount() const override; + unsigned int shardIndex() const override; + ColourMode defaultColourMode() const override; + bool shouldDebugBreak() const override; + int abortAfter() const override; + bool showInvisibles() const override; + Verbosity verbosity() const override; + bool skipBenchmarks() const override; + bool benchmarkNoAnalysis() const override; + unsigned int benchmarkSamples() const override; + double benchmarkConfidenceInterval() const override; + unsigned int benchmarkResamples() const override; + std::chrono::milliseconds benchmarkWarmupTime() const override; + + private: + // Reads Bazel env vars and applies them to the config + void readBazelEnvVars(); + + ConfigData m_data; + std::vector m_processedReporterSpecs; + TestSpec m_testSpec; + bool m_hasTestFilters = false; + }; +} // end namespace Catch + +#endif // CATCH_CONFIG_HPP_INCLUDED + +#ifndef CATCH_GET_RANDOM_SEED_HPP_INCLUDED +#define CATCH_GET_RANDOM_SEED_HPP_INCLUDED + +#include + +namespace Catch { + //! Returns Catch2's current RNG seed. + std::uint32_t getSeed(); +} // namespace Catch + +#endif // CATCH_GET_RANDOM_SEED_HPP_INCLUDED + +#ifndef CATCH_MESSAGE_HPP_INCLUDED +#define CATCH_MESSAGE_HPP_INCLUDED + +/** \file + * Wrapper for the CATCH_CONFIG_PREFIX_MESSAGES configuration option + * + * CATCH_CONFIG_PREFIX_ALL can be used to avoid clashes with other macros + * by prepending CATCH_. This may not be desirable if the only clashes are with + * logger macros such as INFO and WARN. In this cases + * CATCH_CONFIG_PREFIX_MESSAGES can be used to only prefix a small subset + * of relevant macros. + * + */ + +#ifndef CATCH_CONFIG_PREFIX_MESSAGES_HPP_INCLUDED +#define CATCH_CONFIG_PREFIX_MESSAGES_HPP_INCLUDED + +#if defined(CATCH_CONFIG_PREFIX_ALL) && !defined(CATCH_CONFIG_PREFIX_MESSAGES) +#define CATCH_CONFIG_PREFIX_MESSAGES +#endif + +#endif // CATCH_CONFIG_PREFIX_MESSAGES_HPP_INCLUDED + +#ifndef CATCH_STREAM_END_STOP_HPP_INCLUDED +#define CATCH_STREAM_END_STOP_HPP_INCLUDED + +namespace Catch { + + // Use this in variadic streaming macros to allow + // << +StreamEndStop + // as well as + // << stuff +StreamEndStop + struct StreamEndStop { + constexpr StringRef operator+() const { return StringRef(); } + + template + constexpr friend T const &operator+(T const &value, StreamEndStop) { + return value; + } + }; + +} // namespace Catch + +#endif // CATCH_STREAM_END_STOP_HPP_INCLUDED + +#ifndef CATCH_MESSAGE_INFO_HPP_INCLUDED +#define CATCH_MESSAGE_INFO_HPP_INCLUDED + +#ifndef CATCH_DEPRECATION_MACRO_HPP_INCLUDED +#define CATCH_DEPRECATION_MACRO_HPP_INCLUDED + +#if !defined(CATCH_CONFIG_NO_DEPRECATION_ANNOTATIONS) +#define CATCH_DEPRECATED(msg) [[deprecated(msg)]] +#else +#define CATCH_DEPRECATED(msg) +#endif + +#endif // CATCH_DEPRECATION_MACRO_HPP_INCLUDED + +#include + +namespace Catch { + + struct MessageInfo { + MessageInfo(StringRef _macroName, + SourceLineInfo const &_lineInfo, + ResultWas::OfType _type); + + StringRef macroName; + std::string message; + SourceLineInfo lineInfo; + ResultWas::OfType type; + // The "ID" of the message, used to know when to remove it from reporter context. + unsigned int sequence; + + CATCH_DEPRECATED("Explicitly use the 'sequence' member instead") + bool operator==(MessageInfo const &other) const { + return sequence == other.sequence; + } + CATCH_DEPRECATED("Explicitly use the 'sequence' member instead") + bool operator<(MessageInfo const &other) const { + return sequence < other.sequence; + } + }; + +} // end namespace Catch + +#endif // CATCH_MESSAGE_INFO_HPP_INCLUDED + +#include +#include + +namespace Catch { + + struct SourceLineInfo; + class IResultCapture; + + struct MessageStream { + template + MessageStream &operator<<(T const &value) { + m_stream << value; + return *this; + } + + ReusableStringStream m_stream; + }; + + struct MessageBuilder : MessageStream { + MessageBuilder(StringRef macroName, + SourceLineInfo const &lineInfo, + ResultWas::OfType type) + : m_info(macroName, lineInfo, type) {} + + template + MessageBuilder &&operator<<(T const &value) && { + m_stream << value; + return CATCH_MOVE(*this); + } + + MessageInfo m_info; + }; + + class ScopedMessage { + public: + explicit ScopedMessage(MessageBuilder &&builder); + ScopedMessage(ScopedMessage &duplicate) = delete; + ScopedMessage(ScopedMessage &&old) noexcept; + ~ScopedMessage(); + + unsigned int m_messageId; + bool m_moved = false; + }; + + class Capturer { + std::vector m_messages; + size_t m_captured = 0; + bool m_isScoped = false; + + public: + Capturer(StringRef macroName, SourceLineInfo const &lineInfo, ResultWas::OfType resultType, StringRef names, bool isScoped); + + Capturer(Capturer const &) = delete; + Capturer &operator=(Capturer const &) = delete; + + ~Capturer(); + + void captureValue(size_t index, std::string const &value); + + template + void captureValues(size_t index, T const &value) { + captureValue(index, Catch::Detail::stringify(value)); + } + + template + void captureValues(size_t index, T const &value, Ts const &...values) { + captureValue(index, Catch::Detail::stringify(value)); + captureValues(index + 1, values...); + } + }; + +} // end namespace Catch + +/////////////////////////////////////////////////////////////////////////////// +#define INTERNAL_CATCH_MSG(macroName, messageType, resultDisposition, ...) \ + do { \ + Catch::AssertionHandler catchAssertionHandler(macroName##_catch_sr, CATCH_INTERNAL_LINEINFO, Catch::StringRef(), resultDisposition); \ + catchAssertionHandler.handleMessage(messageType, (Catch::MessageStream() << __VA_ARGS__ + ::Catch::StreamEndStop()).m_stream.str()); \ + catchAssertionHandler.complete(); \ + } while (false) + +/////////////////////////////////////////////////////////////////////////////// +#define INTERNAL_CATCH_CAPTURE(varName, macroName, scopedCapture, ...) \ + Catch::Capturer varName(macroName##_catch_sr, \ + CATCH_INTERNAL_LINEINFO, \ + Catch::ResultWas::Info, \ + #__VA_ARGS__##_catch_sr, \ + scopedCapture); \ + varName.captureValues(0, __VA_ARGS__) + +/////////////////////////////////////////////////////////////////////////////// +#define INTERNAL_CATCH_INFO(macroName, log) \ + const Catch::ScopedMessage INTERNAL_CATCH_UNIQUE_NAME(scopedMessage)(Catch::MessageBuilder(macroName##_catch_sr, CATCH_INTERNAL_LINEINFO, Catch::ResultWas::Info) << log) + +/////////////////////////////////////////////////////////////////////////////// +#define INTERNAL_CATCH_UNSCOPED_INFO(macroName, log) \ + Catch::IResultCapture::emplaceUnscopedMessage(Catch::MessageBuilder(macroName##_catch_sr, CATCH_INTERNAL_LINEINFO, Catch::ResultWas::Info) << log) + +#if defined(CATCH_CONFIG_PREFIX_MESSAGES) && !defined(CATCH_CONFIG_DISABLE) + +#define CATCH_INFO(msg) INTERNAL_CATCH_INFO("CATCH_INFO", msg) +#define CATCH_UNSCOPED_INFO(msg) INTERNAL_CATCH_UNSCOPED_INFO("CATCH_UNSCOPED_INFO", msg) +#define CATCH_WARN(msg) INTERNAL_CATCH_MSG("CATCH_WARN", Catch::ResultWas::Warning, Catch::ResultDisposition::ContinueOnFailure, msg) +#define CATCH_CAPTURE(...) INTERNAL_CATCH_CAPTURE(INTERNAL_CATCH_UNIQUE_NAME(capturer), "CATCH_CAPTURE", true, __VA_ARGS__) +#define CATCH_UNSCOPED_CAPTURE(...) INTERNAL_CATCH_CAPTURE(INTERNAL_CATCH_UNIQUE_NAME(capturer), "CATCH_UNSCOPED_CAPTURE", false, __VA_ARGS__) + +#elif defined(CATCH_CONFIG_PREFIX_MESSAGES) && defined(CATCH_CONFIG_DISABLE) + +#define CATCH_INFO(msg) (void)(0) +#define CATCH_UNSCOPED_INFO(msg) (void)(0) +#define CATCH_WARN(msg) (void)(0) +#define CATCH_CAPTURE(...) (void)(0) +#define CATCH_UNSCOPED_CAPTURE(...) (void)(0) + +#elif !defined(CATCH_CONFIG_PREFIX_MESSAGES) && !defined(CATCH_CONFIG_DISABLE) + +#define INFO(msg) INTERNAL_CATCH_INFO("INFO", msg) +#define UNSCOPED_INFO(msg) INTERNAL_CATCH_UNSCOPED_INFO("UNSCOPED_INFO", msg) +#define WARN(msg) INTERNAL_CATCH_MSG("WARN", Catch::ResultWas::Warning, Catch::ResultDisposition::ContinueOnFailure, msg) +#define CAPTURE(...) INTERNAL_CATCH_CAPTURE(INTERNAL_CATCH_UNIQUE_NAME(capturer), "CAPTURE", true, __VA_ARGS__) +#define UNSCOPED_CAPTURE(...) INTERNAL_CATCH_CAPTURE(INTERNAL_CATCH_UNIQUE_NAME(capturer), "UNSCOPED_CAPTURE", false, __VA_ARGS__) + +#elif !defined(CATCH_CONFIG_PREFIX_MESSAGES) && defined(CATCH_CONFIG_DISABLE) + +#define INFO(msg) (void)(0) +#define UNSCOPED_INFO(msg) (void)(0) +#define WARN(msg) (void)(0) +#define CAPTURE(...) (void)(0) +#define UNSCOPED_CAPTURE(...) (void)(0) + +#endif // end of user facing macro declarations + +#endif // CATCH_MESSAGE_HPP_INCLUDED + +#ifndef CATCH_SECTION_INFO_HPP_INCLUDED +#define CATCH_SECTION_INFO_HPP_INCLUDED + +#ifndef CATCH_TOTALS_HPP_INCLUDED +#define CATCH_TOTALS_HPP_INCLUDED + +#include + +namespace Catch { + + struct Counts { + Counts operator-(Counts const &other) const; + Counts &operator+=(Counts const &other); + + std::uint64_t total() const; + bool allPassed() const; + bool allOk() const; + + std::uint64_t passed = 0; + std::uint64_t failed = 0; + std::uint64_t failedButOk = 0; + std::uint64_t skipped = 0; + }; + + struct Totals { + Totals operator-(Totals const &other) const; + Totals &operator+=(Totals const &other); + + Totals delta(Totals const &prevTotals) const; + + Counts assertions; + Counts testCases; + }; +} // namespace Catch + +#endif // CATCH_TOTALS_HPP_INCLUDED + +#include + +namespace Catch { + + struct SectionInfo { + // The last argument is ignored, so that people can write + // SECTION("ShortName", "Proper description that is long") and + // still use the `-c` flag comfortably. + SectionInfo(SourceLineInfo const &_lineInfo, std::string _name, + const char *const = nullptr) + : name(CATCH_MOVE(_name)), lineInfo(_lineInfo) {} + + std::string name; + SourceLineInfo lineInfo; + }; + + struct SectionEndInfo { + SectionInfo sectionInfo; + Counts prevAssertions; + double durationInSeconds; + }; + +} // end namespace Catch + +#endif // CATCH_SECTION_INFO_HPP_INCLUDED + +#ifndef CATCH_SESSION_HPP_INCLUDED +#define CATCH_SESSION_HPP_INCLUDED + +#ifndef CATCH_COMMANDLINE_HPP_INCLUDED +#define CATCH_COMMANDLINE_HPP_INCLUDED + +#ifndef CATCH_CLARA_HPP_INCLUDED +#define CATCH_CLARA_HPP_INCLUDED + +#if defined(__clang__) +#pragma clang diagnostic push +#pragma clang diagnostic ignored "-Wweak-vtables" +#pragma clang diagnostic ignored "-Wshadow" +#pragma clang diagnostic ignored "-Wdeprecated" +#endif + +#if defined(__GNUC__) +#pragma GCC diagnostic push +#pragma GCC diagnostic ignored "-Wsign-conversion" +#endif + +#ifndef CLARA_CONFIG_OPTIONAL_TYPE +#ifdef __has_include +#if __has_include() && __cplusplus >= 201703L +#include +#define CLARA_CONFIG_OPTIONAL_TYPE std::optional +#endif +#endif +#endif + +#include +#include +#include +#include +#include +#include +#include + +namespace Catch { + namespace Clara { + + class Args; + class Parser; + + // enum of result types from a parse + enum class ParseResultType { + Matched, + NoMatch, + ShortCircuitAll, + ShortCircuitSame + }; + + struct accept_many_t {}; + constexpr accept_many_t accept_many{}; + + namespace Detail { + struct fake_arg { + template + operator T(); + }; + + template + static constexpr bool is_unary_function_v = false; + + template + static constexpr bool is_unary_function_v< + F, + Catch::Detail::void_t()( + fake_arg()))>> + = true; + + // Traits for extracting arg and return type of lambdas (for single + // argument lambdas) + template + struct UnaryLambdaTraits + : UnaryLambdaTraits {}; + + template + struct UnaryLambdaTraits { + static const bool isValid = false; + }; + + template + struct UnaryLambdaTraits { + static const bool isValid = true; + using ArgType = std::remove_const_t>; + using ReturnType = ReturnT; + }; + + class TokenStream; + + // Wraps a token coming from a token stream. These may not directly + // correspond to strings as a single string may encode an option + + // its argument if the : or = form is used + enum class TokenType { Option, + Argument }; + struct Token { + TokenType type; + StringRef token; + }; + + // Abstracts iterators into args as a stream of tokens, with option + // arguments uniformly handled + class TokenStream { + using Iterator = std::vector::const_iterator; + Iterator it; + Iterator itEnd; + std::vector m_tokenBuffer; + void loadBuffer(); + + public: + explicit TokenStream(Args const &args); + TokenStream(Iterator it, Iterator itEnd); + + explicit operator bool() const { + return !m_tokenBuffer.empty() || it != itEnd; + } + + size_t count() const { + return m_tokenBuffer.size() + (itEnd - it); + } + + Token operator*() const { + assert(!m_tokenBuffer.empty()); + return m_tokenBuffer.front(); + } + + Token const *operator->() const { + assert(!m_tokenBuffer.empty()); + return &m_tokenBuffer.front(); + } + + TokenStream &operator++(); + }; + + //! Denotes type of a parsing result + enum class ResultType { + Ok, ///< No errors + LogicError, ///< Error in user-specified arguments for + ///< construction + RuntimeError ///< Error in parsing inputs + }; + + class ResultBase { + protected: + ResultBase(ResultType type) + : m_type(type) {} + virtual ~ResultBase(); // = default; + + ResultBase(ResultBase const &) = default; + ResultBase &operator=(ResultBase const &) = default; + ResultBase(ResultBase &&) = default; + ResultBase &operator=(ResultBase &&) = default; + + virtual void enforceOk() const = 0; + + ResultType m_type; + }; + + template + class ResultValueBase : public ResultBase { + public: + T const &value() const & { + enforceOk(); + return m_value; + } + T &&value() && { + enforceOk(); + return CATCH_MOVE(m_value); + } + + protected: + ResultValueBase(ResultType type) + : ResultBase(type) {} + + ResultValueBase(ResultValueBase const &other) + : ResultBase(other) { + if (m_type == ResultType::Ok) + new (&m_value) T(other.m_value); + } + ResultValueBase(ResultValueBase &&other) + : ResultBase(other) { + if (m_type == ResultType::Ok) + new (&m_value) T(CATCH_MOVE(other.m_value)); + } + + ResultValueBase(ResultType, T const &value) + : ResultBase(ResultType::Ok) { + new (&m_value) T(value); + } + ResultValueBase(ResultType, T &&value) + : ResultBase(ResultType::Ok) { + new (&m_value) T(CATCH_MOVE(value)); + } + + ResultValueBase &operator=(ResultValueBase const &other) { + if (m_type == ResultType::Ok) + m_value.~T(); + ResultBase::operator=(other); + if (m_type == ResultType::Ok) + new (&m_value) T(other.m_value); + return *this; + } + ResultValueBase &operator=(ResultValueBase &&other) { + if (m_type == ResultType::Ok) m_value.~T(); + ResultBase::operator=(other); + if (m_type == ResultType::Ok) + new (&m_value) T(CATCH_MOVE(other.m_value)); + return *this; + } + + ~ResultValueBase() override { + if (m_type == ResultType::Ok) + m_value.~T(); + } + + union { + T m_value; + }; + }; + + template<> + class ResultValueBase : public ResultBase { + protected: + using ResultBase::ResultBase; + }; + + template + class BasicResult : public ResultValueBase { + public: + template + explicit BasicResult(BasicResult const &other) + : ResultValueBase(other.type()), m_errorMessage(other.errorMessage()) { + assert(type() != ResultType::Ok); + } + + template + static auto ok(U &&value) -> BasicResult { + return {ResultType::Ok, CATCH_FORWARD(value)}; + } + static auto ok() -> BasicResult { return {ResultType::Ok}; } + static auto logicError(std::string &&message) + -> BasicResult { + return {ResultType::LogicError, CATCH_MOVE(message)}; + } + static auto runtimeError(std::string &&message) + -> BasicResult { + return {ResultType::RuntimeError, CATCH_MOVE(message)}; + } + + explicit operator bool() const { + return m_type == ResultType::Ok; + } + auto type() const -> ResultType { return m_type; } + auto errorMessage() const -> std::string const & { + return m_errorMessage; + } + + protected: + void enforceOk() const override { + // Errors shouldn't reach this point, but if they do + // the actual error message will be in m_errorMessage + assert(m_type != ResultType::LogicError); + assert(m_type != ResultType::RuntimeError); + if (m_type != ResultType::Ok) + std::abort(); + } + + std::string + m_errorMessage; // Only populated if resultType is an error + + BasicResult(ResultType type, + std::string &&message) + : ResultValueBase(type), m_errorMessage(CATCH_MOVE(message)) { + assert(m_type != ResultType::Ok); + } + + using ResultValueBase::ResultValueBase; + using ResultBase::m_type; + }; + + class ParseState { + public: + ParseState(ParseResultType type, + TokenStream remainingTokens); + + ParseResultType type() const { return m_type; } + TokenStream const &remainingTokens() const & { + return m_remainingTokens; + } + TokenStream &&remainingTokens() && { + return CATCH_MOVE(m_remainingTokens); + } + + private: + ParseResultType m_type; + TokenStream m_remainingTokens; + }; + + using Result = BasicResult; + using ParserResult = BasicResult; + using InternalParseResult = BasicResult; + + struct HelpColumns { + std::string left; + StringRef descriptions; + }; + + template + ParserResult convertInto(std::string const &source, T &target) { + std::stringstream ss(source); + ss >> target; + if (ss.fail()) { + return ParserResult::runtimeError( + "Unable to convert '" + source + "' to destination type"); + } else { + return ParserResult::ok(ParseResultType::Matched); + } + } + ParserResult convertInto(std::string const &source, + std::string &target); + ParserResult convertInto(std::string const &source, bool &target); + +#ifdef CLARA_CONFIG_OPTIONAL_TYPE + template + auto convertInto(std::string const &source, + CLARA_CONFIG_OPTIONAL_TYPE &target) + -> ParserResult { + T temp; + auto result = convertInto(source, temp); + if (result) + target = CATCH_MOVE(temp); + return result; + } +#endif // CLARA_CONFIG_OPTIONAL_TYPE + + struct BoundRef : Catch::Detail::NonCopyable { + virtual ~BoundRef() = default; + virtual bool isContainer() const; + virtual bool isFlag() const; + }; + struct BoundValueRefBase : BoundRef { + virtual auto setValue(std::string const &arg) + -> ParserResult + = 0; + }; + struct BoundFlagRefBase : BoundRef { + virtual auto setFlag(bool flag) -> ParserResult = 0; + bool isFlag() const override; + }; + + template + struct BoundValueRef : BoundValueRefBase { + T &m_ref; + + explicit BoundValueRef(T &ref) + : m_ref(ref) {} + + ParserResult setValue(std::string const &arg) override { + return convertInto(arg, m_ref); + } + }; + + template + struct BoundValueRef> : BoundValueRefBase { + std::vector &m_ref; + + explicit BoundValueRef(std::vector &ref) + : m_ref(ref) {} + + auto isContainer() const -> bool override { return true; } + + auto setValue(std::string const &arg) + -> ParserResult override { + T temp; + auto result = convertInto(arg, temp); + if (result) + m_ref.push_back(temp); + return result; + } + }; + + struct BoundFlagRef : BoundFlagRefBase { + bool &m_ref; + + explicit BoundFlagRef(bool &ref) + : m_ref(ref) {} + + ParserResult setFlag(bool flag) override; + }; + + template + struct LambdaInvoker { + static_assert( + std::is_same::value, + "Lambda must return void or clara::ParserResult"); + + template + static auto invoke(L const &lambda, ArgType const &arg) + -> ParserResult { + return lambda(arg); + } + }; + + template<> + struct LambdaInvoker { + template + static auto invoke(L const &lambda, ArgType const &arg) + -> ParserResult { + lambda(arg); + return ParserResult::ok(ParseResultType::Matched); + } + }; + + template + auto invokeLambda(L const &lambda, std::string const &arg) + -> ParserResult { + ArgType temp{}; + auto result = convertInto(arg, temp); + return !result ? result + : LambdaInvoker::ReturnType>::invoke(lambda, temp); + } + + template + struct BoundLambda : BoundValueRefBase { + L m_lambda; + + static_assert( + UnaryLambdaTraits::isValid, + "Supplied lambda must take exactly one argument"); + explicit BoundLambda(L const &lambda) + : m_lambda(lambda) {} + + auto setValue(std::string const &arg) + -> ParserResult override { + return invokeLambda::ArgType>( + m_lambda, arg); + } + }; + + template + struct BoundManyLambda : BoundLambda { + explicit BoundManyLambda(L const &lambda) + : BoundLambda(lambda) {} + bool isContainer() const override { return true; } + }; + + template + struct BoundFlagLambda : BoundFlagRefBase { + L m_lambda; + + static_assert( + UnaryLambdaTraits::isValid, + "Supplied lambda must take exactly one argument"); + static_assert( + std::is_same::ArgType, + bool>::value, + "flags must be boolean"); + + explicit BoundFlagLambda(L const &lambda) + : m_lambda(lambda) {} + + auto setFlag(bool flag) -> ParserResult override { + return LambdaInvoker::ReturnType>::invoke(m_lambda, flag); + } + }; + + enum class Optionality { Optional, + Required }; + + class ParserBase { + public: + virtual ~ParserBase() = default; + virtual auto validate() const -> Result { return Result::ok(); } + virtual auto parse(std::string const &exeName, + TokenStream tokens) const + -> InternalParseResult + = 0; + virtual size_t cardinality() const; + + InternalParseResult parse(Args const &args) const; + }; + + template + class ComposableParserImpl : public ParserBase { + public: + template + auto operator|(T const &other) const -> Parser; + }; + + // Common code and state for Args and Opts + template + class ParserRefImpl : public ComposableParserImpl { + protected: + Optionality m_optionality = Optionality::Optional; + std::shared_ptr m_ref; + StringRef m_hint; + StringRef m_description; + + explicit ParserRefImpl(std::shared_ptr const &ref) + : m_ref(ref) {} + + public: + template + ParserRefImpl(accept_many_t, + LambdaT const &ref, + StringRef hint) + : m_ref(std::make_shared>(ref)), m_hint(hint) {} + + template>> + ParserRefImpl(T &ref, StringRef hint) + : m_ref(std::make_shared>(ref)), m_hint(hint) {} + + template>> + ParserRefImpl(LambdaT const &ref, StringRef hint) + : m_ref(std::make_shared>(ref)), m_hint(hint) {} + + DerivedT &operator()(StringRef description) & { + m_description = description; + return static_cast(*this); + } + DerivedT &&operator()(StringRef description) && { + m_description = description; + return static_cast(*this); + } + + auto optional() -> DerivedT & { + m_optionality = Optionality::Optional; + return static_cast(*this); + } + + auto required() -> DerivedT & { + m_optionality = Optionality::Required; + return static_cast(*this); + } + + auto isOptional() const -> bool { + return m_optionality == Optionality::Optional; + } + + auto cardinality() const -> size_t override { + if (m_ref->isContainer()) + return 0; + else + return 1; + } + + StringRef hint() const { return m_hint; } + }; + + } // namespace Detail + + // A parser for arguments + class Arg : public Detail::ParserRefImpl { + public: + using ParserBase::parse; + using ParserRefImpl::ParserRefImpl; + + Detail::InternalParseResult + parse(std::string const &, + Detail::TokenStream tokens) const override; + }; + + // A parser for options + class Opt : public Detail::ParserRefImpl { + protected: + std::vector m_optNames; + + public: + template + explicit Opt(LambdaT const &ref) + : ParserRefImpl( + std::make_shared>(ref)) {} + + explicit Opt(bool &ref); + + template>> + Opt(LambdaT const &ref, StringRef hint) + : ParserRefImpl(ref, hint) {} + + template + Opt(accept_many_t, LambdaT const &ref, StringRef hint) + : ParserRefImpl(accept_many, ref, hint) {} + + template>> + Opt(T &ref, StringRef hint) + : ParserRefImpl(ref, hint) {} + + Opt &operator[](StringRef optName) & { + m_optNames.push_back(optName); + return *this; + } + Opt &&operator[](StringRef optName) && { + m_optNames.push_back(optName); + return CATCH_MOVE(*this); + } + + Detail::HelpColumns getHelpColumns() const; + + bool isMatch(StringRef optToken) const; + + using ParserBase::parse; + + Detail::InternalParseResult + parse(std::string const &, + Detail::TokenStream tokens) const override; + + Detail::Result validate() const override; + }; + + // Specifies the name of the executable + class ExeName : public Detail::ComposableParserImpl { + std::shared_ptr m_name; + std::shared_ptr m_ref; + + public: + ExeName(); + explicit ExeName(std::string &ref); + + template + explicit ExeName(LambdaT const &lambda) + : ExeName() { + m_ref = std::make_shared>(lambda); + } + + // The exe name is not parsed out of the normal tokens, but is + // handled specially + Detail::InternalParseResult + parse(std::string const &, + Detail::TokenStream tokens) const override; + + std::string const &name() const { return *m_name; } + Detail::ParserResult set(std::string const &newName); + }; + + // A Combined parser + class Parser : Detail::ParserBase { + mutable ExeName m_exeName; + std::vector m_options; + std::vector m_args; + + public: + auto operator|=(ExeName const &exeName) -> Parser & { + m_exeName = exeName; + return *this; + } + + auto operator|=(Arg const &arg) -> Parser & { + m_args.push_back(arg); + return *this; + } + + friend Parser &operator|=(Parser &p, Opt const &opt) { + p.m_options.push_back(opt); + return p; + } + friend Parser &operator|=(Parser &p, Opt &&opt) { + p.m_options.push_back(CATCH_MOVE(opt)); + return p; + } + + Parser &operator|=(Parser const &other); + + template + friend Parser operator|(Parser const &p, T &&rhs) { + Parser temp(p); + temp |= rhs; + return temp; + } + + template + friend Parser operator|(Parser &&p, T &&rhs) { + p |= CATCH_FORWARD(rhs); + return CATCH_MOVE(p); + } + + std::vector getHelpColumns() const; + + void writeToStream(std::ostream &os) const; + + friend auto operator<<(std::ostream &os, Parser const &parser) + -> std::ostream & { + parser.writeToStream(os); + return os; + } + + Detail::Result validate() const override; + + using ParserBase::parse; + Detail::InternalParseResult + parse(std::string const &exeName, + Detail::TokenStream tokens) const override; + }; + + /** + * Wrapper over argc + argv, assumes that the inputs outlive it + */ + class Args { + friend Detail::TokenStream; + StringRef m_exeName; + std::vector m_args; + + public: + Args(int argc, char const *const *argv); + // Helper constructor for testing + Args(std::initializer_list args); + + StringRef exeName() const { return m_exeName; } + }; + + // Convenience wrapper for option parser that specifies the help option + struct Help : Opt { + Help(bool &showHelpFlag); + }; + + // Result type for parser operation + using Detail::ParserResult; + + namespace Detail { + template + template + Parser + ComposableParserImpl::operator|(T const &other) const { + return Parser() | static_cast(*this) | other; + } + } // namespace Detail + + } // namespace Clara +} // namespace Catch + +#if defined(__clang__) +#pragma clang diagnostic pop +#endif + +#if defined(__GNUC__) +#pragma GCC diagnostic pop +#endif + +#endif // CATCH_CLARA_HPP_INCLUDED + +namespace Catch { + + struct ConfigData; + + Clara::Parser makeCommandLineParser(ConfigData &config); + +} // end namespace Catch + +#endif // CATCH_COMMANDLINE_HPP_INCLUDED + +namespace Catch { + + // TODO: Use C++17 `inline` variables + constexpr int UnspecifiedErrorExitCode = 1; + constexpr int NoTestsRunExitCode = 2; + constexpr int UnmatchedTestSpecExitCode = 3; + constexpr int AllTestsSkippedExitCode = 4; + constexpr int InvalidTestSpecExitCode = 5; + constexpr int TestFailureExitCode = 42; + + class Session : Detail::NonCopyable { + public: + Session(); + ~Session(); + + void showHelp() const; + void libIdentify(); + + int applyCommandLine(int argc, char const *const *argv); +#if defined(CATCH_CONFIG_WCHAR) && defined(_WIN32) && defined(UNICODE) + int applyCommandLine(int argc, wchar_t const *const *argv); +#endif + + void useConfigData(ConfigData const &configData); + + template + int run(int argc, CharT const *const argv[]) { + if (m_startupExceptions) + return 1; + int returnCode = applyCommandLine(argc, argv); + if (returnCode == 0) + returnCode = run(); + return returnCode; + } + + int run(); + + Clara::Parser const &cli() const; + void cli(Clara::Parser const &newParser); + ConfigData &configData(); + Config &config(); + + private: + int runInternal(); + + Clara::Parser m_cli; + ConfigData m_configData; + Detail::unique_ptr m_config; + bool m_startupExceptions = false; + }; + +} // end namespace Catch + +#endif // CATCH_SESSION_HPP_INCLUDED + +#ifndef CATCH_TAG_ALIAS_HPP_INCLUDED +#define CATCH_TAG_ALIAS_HPP_INCLUDED + +#include + +namespace Catch { + + struct TagAlias { + TagAlias(std::string const &_tag, SourceLineInfo _lineInfo) + : tag(_tag), lineInfo(_lineInfo) {} + + std::string tag; + SourceLineInfo lineInfo; + }; + +} // end namespace Catch + +#endif // CATCH_TAG_ALIAS_HPP_INCLUDED + +#ifndef CATCH_TAG_ALIAS_AUTOREGISTRAR_HPP_INCLUDED +#define CATCH_TAG_ALIAS_AUTOREGISTRAR_HPP_INCLUDED + +namespace Catch { + + struct RegistrarForTagAliases { + RegistrarForTagAliases(char const *alias, char const *tag, SourceLineInfo const &lineInfo); + }; + +} // end namespace Catch + +#define CATCH_REGISTER_TAG_ALIAS(alias, spec) \ + CATCH_INTERNAL_START_WARNINGS_SUPPRESSION \ + CATCH_INTERNAL_SUPPRESS_GLOBALS_WARNINGS \ + namespace { \ + const Catch::RegistrarForTagAliases INTERNAL_CATCH_UNIQUE_NAME(AutoRegisterTagAlias)(alias, spec, CATCH_INTERNAL_LINEINFO); \ + } \ + CATCH_INTERNAL_STOP_WARNINGS_SUPPRESSION + +#endif // CATCH_TAG_ALIAS_AUTOREGISTRAR_HPP_INCLUDED + +#ifndef CATCH_TEMPLATE_TEST_MACROS_HPP_INCLUDED +#define CATCH_TEMPLATE_TEST_MACROS_HPP_INCLUDED + +// We need this suppression to leak, because it took until GCC 10 +// for the front end to handle local suppression via _Pragma properly +// inside templates (so `TEMPLATE_TEST_CASE` and co). +// **THIS IS DIFFERENT FOR STANDARD TESTS, WHERE GCC 9 IS SUFFICIENT** +#if defined(__GNUC__) && !defined(__clang__) && !defined(__ICC) && __GNUC__ < 10 +#pragma GCC diagnostic ignored "-Wparentheses" +#endif + +#ifndef CATCH_TEST_MACROS_HPP_INCLUDED +#define CATCH_TEST_MACROS_HPP_INCLUDED + +#ifndef CATCH_TEST_MACRO_IMPL_HPP_INCLUDED +#define CATCH_TEST_MACRO_IMPL_HPP_INCLUDED + +#ifndef CATCH_ASSERTION_HANDLER_HPP_INCLUDED +#define CATCH_ASSERTION_HANDLER_HPP_INCLUDED + +#ifndef CATCH_DECOMPOSER_HPP_INCLUDED +#define CATCH_DECOMPOSER_HPP_INCLUDED + +#ifndef CATCH_COMPARE_TRAITS_HPP_INCLUDED +#define CATCH_COMPARE_TRAITS_HPP_INCLUDED + +#include + +namespace Catch { + namespace Detail { + +#if defined(__GNUC__) && !defined(__clang__) +#pragma GCC diagnostic push + // GCC likes to complain about comparing bool with 0, in the decltype() + // that defines the comparable traits below. +#pragma GCC diagnostic ignored "-Wbool-compare" + // "ordered comparison of pointer with integer zero" same as above, + // but it does not have a separate warning flag to suppress +#pragma GCC diagnostic ignored "-Wextra" + // Did you know that comparing floats with `0` directly + // is super-duper dangerous in unevaluated context? +#pragma GCC diagnostic ignored "-Wfloat-equal" +#endif + +#if defined(__clang__) +#pragma clang diagnostic push + // Did you know that comparing floats with `0` directly + // is super-duper dangerous in unevaluated context? +#pragma clang diagnostic ignored "-Wfloat-equal" +#endif + +#define CATCH_DEFINE_COMPARABLE_TRAIT(id, op) \ + template \ + struct is_##id##_comparable : std::false_type {}; \ + template \ + struct is_##id##_comparable< \ + T, \ + U, \ + void_t() op std::declval())>> \ + : std::true_type {}; \ + template \ + struct is_##id##_0_comparable : std::false_type {}; \ + template \ + struct is_##id##_0_comparable() op 0)>> \ + : std::true_type {}; + + // We need all 6 pre-spaceship comparison ops: <, <=, >, >=, ==, != + CATCH_DEFINE_COMPARABLE_TRAIT(lt, <) + CATCH_DEFINE_COMPARABLE_TRAIT(le, <=) + CATCH_DEFINE_COMPARABLE_TRAIT(gt, >) + CATCH_DEFINE_COMPARABLE_TRAIT(ge, >=) + CATCH_DEFINE_COMPARABLE_TRAIT(eq, ==) + CATCH_DEFINE_COMPARABLE_TRAIT(ne, !=) + +#undef CATCH_DEFINE_COMPARABLE_TRAIT + +#if defined(__GNUC__) && !defined(__clang__) +#pragma GCC diagnostic pop +#endif +#if defined(__clang__) +#pragma clang diagnostic pop +#endif + + } // namespace Detail +} // namespace Catch + +#endif // CATCH_COMPARE_TRAITS_HPP_INCLUDED + +#ifndef CATCH_LOGICAL_TRAITS_HPP_INCLUDED +#define CATCH_LOGICAL_TRAITS_HPP_INCLUDED + +#include + +namespace Catch { + namespace Detail { + +#if defined(__cpp_lib_logical_traits) && __cpp_lib_logical_traits >= 201510 + + using std::conjunction; + using std::disjunction; + using std::negation; + +#else + + template + struct conjunction : std::true_type {}; + template + struct conjunction : B1 {}; + template + struct conjunction + : std::conditional_t, B1> {}; + + template + struct disjunction : std::false_type {}; + template + struct disjunction : B1 {}; + template + struct disjunction + : std::conditional_t> {}; + + template + struct negation : std::integral_constant {}; + +#endif + + } // namespace Detail +} // namespace Catch + +#endif // CATCH_LOGICAL_TRAITS_HPP_INCLUDED + +#include +#include + +/** \file + * Why does decomposing look the way it does: + * + * Conceptually, decomposing is simple. We change `REQUIRE( a == b )` into + * `Decomposer{} <= a == b`, so that `Decomposer{} <= a` is evaluated first, + * and our custom operator is used for `a == b`, because `a` is transformed + * into `ExprLhs` and then into `BinaryExpr`. + * + * In practice, decomposing ends up a mess, because we have to support + * various fun things. + * + * 1) Types that are only comparable with literal 0, and they do this by + * comparing against a magic type with pointer constructor and deleted + * other constructors. Example: `REQUIRE((a <=> b) == 0)` in libstdc++ + * + * 2) Types that are only comparable with literal 0, and they do this by + * comparing against a magic type with consteval integer constructor. + * Example: `REQUIRE((a <=> b) == 0)` in current MSVC STL. + * + * 3) Types that have no linkage, and so we cannot form a reference to + * them. Example: some implementations of traits. + * + * 4) Starting with C++20, when the compiler sees `a == b`, it also uses + * `b == a` when constructing the overload set. For us this means that + * when the compiler handles `ExprLhs == b`, it also tries to resolve + * the overload set for `b == ExprLhs`. + * + * To accommodate these use cases, decomposer ended up rather complex. + * + * 1) These types are handled by adding SFINAE overloads to our comparison + * operators, checking whether `T == U` are comparable with the given + * operator, and if not, whether T (or U) are comparable with literal 0. + * If yes, the overload compares T (or U) with 0 literal inline in the + * definition. + * + * Note that for extra correctness, we check that the other type is + * either an `int` (literal 0 is captured as `int` by templates), or + * a `long` (some platforms use 0L for `NULL` and we want to support + * that for pointer comparisons). + * + * 2) For these types, `is_foo_comparable` is true, but letting + * them fall into the overload that actually does `T == int` causes + * compilation error. Handling them requires that the decomposition + * is `constexpr`, so that P2564R3 applies and the `consteval` from + * their accompanying magic type is propagated through the `constexpr` + * call stack. + * + * However this is not enough to handle these types automatically, + * because our default is to capture types by reference, to avoid + * runtime copies. While these references cannot become dangling, + * they outlive the constexpr context and thus the default capture + * path cannot be actually constexpr. + * + * The solution is to capture these types by value, by explicitly + * specializing `Catch::capture_by_value` for them. Catch2 provides + * specialization for `std::foo_ordering`s, but users can specialize + * the trait for their own types as well. + * + * 3) If a type has no linkage, we also cannot capture it by reference. + * The solution is once again to capture them by value. We handle + * the common cases by using `std::is_arithmetic` and `std::is_enum` + * as the default for `Catch::capture_by_value`, but that is only a + * some-effort heuristic. These combine to capture all possible bitfield + * bases, and also some trait-like types. As with 2), users can + * specialize `capture_by_value` for their own types as needed. + * + * 4) To support C++20 and make the SFINAE on our decomposing operators + * work, the SFINAE has to happen in return type, rather than in + * a template type. This is due to our use of logical type traits + * (`conjunction`/`disjunction`/`negation`), that we use to workaround + * an issue in older (9-) versions of GCC. I still blame C++20 for + * this, because without the comparison order switching, the logical + * traits could still be used in template type. + * + * There are also other side concerns, e.g. supporting both `REQUIRE(a)` + * and `REQUIRE(a == b)`, or making `REQUIRE_THAT(a, IsEqual(b))` slot + * nicely into the same expression handling logic, but these are rather + * straightforward and add only a bit of complexity (e.g. common base + * class for decomposed expressions). + */ + +#ifdef _MSC_VER +#pragma warning(push) +#pragma warning(disable : 4389) // '==' : signed/unsigned mismatch +#pragma warning(disable : 4018) // more "signed/unsigned mismatch" +#pragma warning(disable : 4312) // Converting int to T* using reinterpret_cast (issue on x64 platform) +#pragma warning(disable : 4180) // qualifier applied to function type has no meaning +#pragma warning(disable : 4800) // Forcing result to true or false +#endif + +#ifdef __clang__ +#pragma clang diagnostic push +#pragma clang diagnostic ignored "-Wsign-compare" +#pragma clang diagnostic ignored "-Wnon-virtual-dtor" +#elif defined __GNUC__ +#pragma GCC diagnostic push +#pragma GCC diagnostic ignored "-Wsign-compare" +#pragma GCC diagnostic ignored "-Wnon-virtual-dtor" +#endif + +#if defined(CATCH_CPP20_OR_GREATER) && __has_include() +#include +#if defined(__cpp_lib_three_way_comparison) && __cpp_lib_three_way_comparison >= 201907L +#define CATCH_CONFIG_CPP20_COMPARE_OVERLOADS +#endif +#endif + +namespace Catch { + + namespace Detail { + // This was added in C++20, but we require only C++14 for now. + template + using RemoveCVRef_t = std::remove_cv_t>; + } // namespace Detail + + // Note: This is about as much as we can currently reasonably support. + // In an ideal world, we could capture by value small trivially + // copyable types, but the actual `std::is_trivially_copyable` + // trait is a huge mess with standard-violating results on + // GCC and Clang, which are unlikely to be fixed soon due to ABI + // concerns. + // `std::is_scalar` also causes issues due to the `is_pointer` + // component, which causes ambiguity issues with (references-to) + // function pointer. If those are resolved, we still need to + // disambiguate the overload set for arrays, through explicit + // overload for references to sized arrays. + template + struct capture_by_value + : std::integral_constant::value || std::is_enum::value> {}; + +#if defined(CATCH_CONFIG_CPP20_COMPARE_OVERLOADS) + template<> + struct capture_by_value : std::true_type {}; + template<> + struct capture_by_value : std::true_type {}; + template<> + struct capture_by_value : std::true_type {}; +#endif + + template + struct always_false : std::false_type {}; + + class ITransientExpression { + bool m_isBinaryExpression; + bool m_result; + + protected: + ~ITransientExpression() = default; + + public: + constexpr auto isBinaryExpression() const -> bool { return m_isBinaryExpression; } + constexpr auto getResult() const -> bool { return m_result; } + //! This function **has** to be overridden by the derived class. + virtual void streamReconstructedExpression(std::ostream &os) const; + + constexpr ITransientExpression(bool isBinaryExpression, bool result) + : m_isBinaryExpression(isBinaryExpression), m_result(result) {} + + constexpr ITransientExpression(ITransientExpression const &) = default; + constexpr ITransientExpression &operator=(ITransientExpression const &) = default; + + friend std::ostream &operator<<(std::ostream &out, ITransientExpression const &expr) { + expr.streamReconstructedExpression(out); + return out; + } + }; + + void formatReconstructedExpression(std::ostream &os, std::string const &lhs, StringRef op, std::string const &rhs); + + template + class BinaryExpr : public ITransientExpression { + LhsT m_lhs; + StringRef m_op; + RhsT m_rhs; + + void streamReconstructedExpression(std::ostream &os) const override { + formatReconstructedExpression(os, Catch::Detail::stringify(m_lhs), m_op, Catch::Detail::stringify(m_rhs)); + } + + public: + constexpr BinaryExpr(bool comparisonResult, LhsT lhs, StringRef op, RhsT rhs) + : ITransientExpression{true, comparisonResult}, m_lhs(lhs), m_op(op), m_rhs(rhs) {} + + template + auto operator&&(T) const -> BinaryExpr const { + static_assert(always_false::value, + "chained comparisons are not supported inside assertions, " + "wrap the expression inside parentheses, or decompose it"); + } + + template + auto operator||(T) const -> BinaryExpr const { + static_assert(always_false::value, + "chained comparisons are not supported inside assertions, " + "wrap the expression inside parentheses, or decompose it"); + } + + template + auto operator==(T) const -> BinaryExpr const { + static_assert(always_false::value, + "chained comparisons are not supported inside assertions, " + "wrap the expression inside parentheses, or decompose it"); + } + + template + auto operator!=(T) const -> BinaryExpr const { + static_assert(always_false::value, + "chained comparisons are not supported inside assertions, " + "wrap the expression inside parentheses, or decompose it"); + } + + template + auto operator>(T) const -> BinaryExpr const { + static_assert(always_false::value, + "chained comparisons are not supported inside assertions, " + "wrap the expression inside parentheses, or decompose it"); + } + + template + auto operator<(T) const -> BinaryExpr const { + static_assert(always_false::value, + "chained comparisons are not supported inside assertions, " + "wrap the expression inside parentheses, or decompose it"); + } + + template + auto operator>=(T) const -> BinaryExpr const { + static_assert(always_false::value, + "chained comparisons are not supported inside assertions, " + "wrap the expression inside parentheses, or decompose it"); + } + + template + auto operator<=(T) const -> BinaryExpr const { + static_assert(always_false::value, + "chained comparisons are not supported inside assertions, " + "wrap the expression inside parentheses, or decompose it"); + } + }; + + template + class UnaryExpr : public ITransientExpression { + LhsT m_lhs; + + void streamReconstructedExpression(std::ostream &os) const override { + os << Catch::Detail::stringify(m_lhs); + } + + public: + explicit constexpr UnaryExpr(LhsT lhs) + : ITransientExpression{false, static_cast(lhs)}, m_lhs(lhs) {} + }; + + template + class ExprLhs { + LhsT m_lhs; + + public: + explicit constexpr ExprLhs(LhsT lhs) + : m_lhs(lhs) {} + +#define CATCH_INTERNAL_DEFINE_EXPRESSION_EQUALITY_OPERATOR(id, op) \ + template \ + constexpr friend auto operator op(ExprLhs &&lhs, RhsT &&rhs) \ + ->std::enable_if_t< \ + Detail::conjunction, \ + Detail::negation>>>::value, \ + BinaryExpr> { \ + return { \ + static_cast(lhs.m_lhs op rhs), lhs.m_lhs, #op##_sr, rhs}; \ + } \ + template \ + constexpr friend auto operator op(ExprLhs &&lhs, RhsT rhs) \ + ->std::enable_if_t< \ + Detail::conjunction, \ + capture_by_value>::value, \ + BinaryExpr> { \ + return { \ + static_cast(lhs.m_lhs op rhs), lhs.m_lhs, #op##_sr, rhs}; \ + } \ + template \ + constexpr friend auto operator op(ExprLhs &&lhs, RhsT rhs) \ + ->std::enable_if_t< \ + Detail::conjunction< \ + Detail::negation>, \ + Detail::is_eq_0_comparable, /* We allow long because we want `ptr op NULL` to be accepted */ \ + Detail::disjunction, \ + std::is_same>>::value, \ + BinaryExpr> { \ + if (rhs != 0) { throw_test_failure_exception(); } \ + return { \ + static_cast(lhs.m_lhs op 0), lhs.m_lhs, #op##_sr, rhs}; \ + } \ + template \ + constexpr friend auto operator op(ExprLhs &&lhs, RhsT rhs) \ + ->std::enable_if_t< \ + Detail::conjunction< \ + Detail::negation>, \ + Detail::is_eq_0_comparable, /* We allow long because we want `ptr op NULL` to be accepted */ \ + Detail::disjunction, \ + std::is_same>>::value, \ + BinaryExpr> { \ + if (lhs.m_lhs != 0) { throw_test_failure_exception(); } \ + return {static_cast(0 op rhs), lhs.m_lhs, #op##_sr, rhs}; \ + } + + CATCH_INTERNAL_DEFINE_EXPRESSION_EQUALITY_OPERATOR(eq, ==) + CATCH_INTERNAL_DEFINE_EXPRESSION_EQUALITY_OPERATOR(ne, !=) + +#undef CATCH_INTERNAL_DEFINE_EXPRESSION_EQUALITY_OPERATOR + +#define CATCH_INTERNAL_DEFINE_EXPRESSION_COMPARISON_OPERATOR(id, op) \ + template \ + constexpr friend auto operator op(ExprLhs &&lhs, RhsT &&rhs) \ + ->std::enable_if_t< \ + Detail::conjunction, \ + Detail::negation>>>::value, \ + BinaryExpr> { \ + return { \ + static_cast(lhs.m_lhs op rhs), lhs.m_lhs, #op##_sr, rhs}; \ + } \ + template \ + constexpr friend auto operator op(ExprLhs &&lhs, RhsT rhs) \ + ->std::enable_if_t< \ + Detail::conjunction, \ + capture_by_value>::value, \ + BinaryExpr> { \ + return { \ + static_cast(lhs.m_lhs op rhs), lhs.m_lhs, #op##_sr, rhs}; \ + } \ + template \ + constexpr friend auto operator op(ExprLhs &&lhs, RhsT rhs) \ + ->std::enable_if_t< \ + Detail::conjunction< \ + Detail::negation>, \ + Detail::is_##id##_0_comparable, \ + std::is_same>::value, \ + BinaryExpr> { \ + if (rhs != 0) { throw_test_failure_exception(); } \ + return { \ + static_cast(lhs.m_lhs op 0), lhs.m_lhs, #op##_sr, rhs}; \ + } \ + template \ + constexpr friend auto operator op(ExprLhs &&lhs, RhsT rhs) \ + ->std::enable_if_t< \ + Detail::conjunction< \ + Detail::negation>, \ + Detail::is_##id##_0_comparable, \ + std::is_same>::value, \ + BinaryExpr> { \ + if (lhs.m_lhs != 0) { throw_test_failure_exception(); } \ + return {static_cast(0 op rhs), lhs.m_lhs, #op##_sr, rhs}; \ + } + + CATCH_INTERNAL_DEFINE_EXPRESSION_COMPARISON_OPERATOR(lt, <) + CATCH_INTERNAL_DEFINE_EXPRESSION_COMPARISON_OPERATOR(le, <=) + CATCH_INTERNAL_DEFINE_EXPRESSION_COMPARISON_OPERATOR(gt, >) + CATCH_INTERNAL_DEFINE_EXPRESSION_COMPARISON_OPERATOR(ge, >=) + +#undef CATCH_INTERNAL_DEFINE_EXPRESSION_COMPARISON_OPERATOR + +#define CATCH_INTERNAL_DEFINE_EXPRESSION_OPERATOR(op) \ + template \ + constexpr friend auto operator op(ExprLhs &&lhs, RhsT &&rhs) \ + ->std::enable_if_t< \ + !capture_by_value>::value, \ + BinaryExpr> { \ + return { \ + static_cast(lhs.m_lhs op rhs), lhs.m_lhs, #op##_sr, rhs}; \ + } \ + template \ + constexpr friend auto operator op(ExprLhs &&lhs, RhsT rhs) \ + ->std::enable_if_t::value, \ + BinaryExpr> { \ + return { \ + static_cast(lhs.m_lhs op rhs), lhs.m_lhs, #op##_sr, rhs}; \ + } + + CATCH_INTERNAL_DEFINE_EXPRESSION_OPERATOR(|) + CATCH_INTERNAL_DEFINE_EXPRESSION_OPERATOR(&) + CATCH_INTERNAL_DEFINE_EXPRESSION_OPERATOR(^) + +#undef CATCH_INTERNAL_DEFINE_EXPRESSION_OPERATOR + + template + friend auto operator&&(ExprLhs &&, RhsT &&) -> BinaryExpr { + static_assert(always_false::value, + "operator&& is not supported inside assertions, " + "wrap the expression inside parentheses, or decompose it"); + } + + template + friend auto operator||(ExprLhs &&, RhsT &&) -> BinaryExpr { + static_assert(always_false::value, + "operator|| is not supported inside assertions, " + "wrap the expression inside parentheses, or decompose it"); + } + + constexpr auto makeUnaryExpr() const -> UnaryExpr { + return UnaryExpr{m_lhs}; + } + }; + + struct Decomposer { + template>::value, + int> + = 0> + constexpr friend auto operator<=(Decomposer &&, T &&lhs) -> ExprLhs { + return ExprLhs{lhs}; + } + + template::value, int> = 0> + constexpr friend auto operator<=(Decomposer &&, T value) -> ExprLhs { + return ExprLhs{value}; + } + }; + +} // end namespace Catch + +#ifdef _MSC_VER +#pragma warning(pop) +#endif +#ifdef __clang__ +#pragma clang diagnostic pop +#elif defined __GNUC__ +#pragma GCC diagnostic pop +#endif + +#endif // CATCH_DECOMPOSER_HPP_INCLUDED + +#include + +namespace Catch { + + struct AssertionReaction { + bool shouldDebugBreak = false; + bool shouldThrow = false; + bool shouldSkip = false; + }; + + class AssertionHandler { + AssertionInfo m_assertionInfo; + AssertionReaction m_reaction; + bool m_completed = false; + IResultCapture &m_resultCapture; + + public: + AssertionHandler(StringRef macroName, + SourceLineInfo const &lineInfo, + StringRef capturedExpression, + ResultDisposition::Flags resultDisposition); + ~AssertionHandler() { + if (!m_completed) { + m_resultCapture.handleIncomplete(m_assertionInfo); + } + } + + template + constexpr void handleExpr(ExprLhs const &expr) { + handleExpr(expr.makeUnaryExpr()); + } + void handleExpr(ITransientExpression const &expr); + + void handleMessage(ResultWas::OfType resultType, std::string &&message); + + void handleExceptionThrownAsExpected(); + void handleUnexpectedExceptionNotThrown(); + void handleExceptionNotThrownAsExpected(); + void handleThrowingCallSkipped(); + void handleUnexpectedInflightException(); + + void complete(); + + // query + auto allowThrows() const -> bool; + }; + + void handleExceptionMatchExpr(AssertionHandler &handler, std::string const &str); + +} // namespace Catch + +#endif // CATCH_ASSERTION_HANDLER_HPP_INCLUDED + +#ifndef CATCH_PREPROCESSOR_INTERNAL_STRINGIFY_HPP_INCLUDED +#define CATCH_PREPROCESSOR_INTERNAL_STRINGIFY_HPP_INCLUDED + +#if !defined(CATCH_CONFIG_DISABLE_STRINGIFICATION) +#define CATCH_INTERNAL_STRINGIFY(...) #__VA_ARGS__##_catch_sr +#else +#define CATCH_INTERNAL_STRINGIFY(...) "Disabled by CATCH_CONFIG_DISABLE_STRINGIFICATION"_catch_sr +#endif + +#endif // CATCH_PREPROCESSOR_INTERNAL_STRINGIFY_HPP_INCLUDED + +// We need this suppression to leak, because it took until GCC 10 +// for the front end to handle local suppression via _Pragma properly +#if defined(__GNUC__) && !defined(__clang__) && !defined(__ICC) && __GNUC__ <= 9 +#pragma GCC diagnostic ignored "-Wparentheses" +#endif + +#if !defined(CATCH_CONFIG_DISABLE) + +#if defined(CATCH_CONFIG_FAST_COMPILE) || defined(CATCH_CONFIG_DISABLE_EXCEPTIONS) + +/////////////////////////////////////////////////////////////////////////////// +// Another way to speed-up compilation is to omit local try-catch for REQUIRE* +// macros. +#define INTERNAL_CATCH_TRY +#define INTERNAL_CATCH_CATCH(capturer) + +#else // CATCH_CONFIG_FAST_COMPILE + +#define INTERNAL_CATCH_TRY try +#define INTERNAL_CATCH_CATCH(handler) \ + catch (...) { \ + (handler).handleUnexpectedInflightException(); \ + } + +#endif + +/////////////////////////////////////////////////////////////////////////////// +#define INTERNAL_CATCH_TEST(macroName, resultDisposition, ...) \ + do { /* NOLINT(bugprone-infinite-loop) */ \ + /* The expression should not be evaluated, but warnings should hopefully be checked */ \ + CATCH_INTERNAL_IGNORE_BUT_WARN(__VA_ARGS__); \ + Catch::AssertionHandler catchAssertionHandler(macroName##_catch_sr, CATCH_INTERNAL_LINEINFO, CATCH_INTERNAL_STRINGIFY(__VA_ARGS__), resultDisposition); \ + INTERNAL_CATCH_TRY { \ + CATCH_INTERNAL_START_WARNINGS_SUPPRESSION \ + CATCH_INTERNAL_SUPPRESS_PARENTHESES_WARNINGS \ + catchAssertionHandler.handleExpr(Catch::Decomposer() <= __VA_ARGS__); /* NOLINT(bugprone-chained-comparison) */ \ + CATCH_INTERNAL_STOP_WARNINGS_SUPPRESSION \ + } \ + INTERNAL_CATCH_CATCH(catchAssertionHandler) \ + catchAssertionHandler.complete(); \ + } while ((void)0, (false) && static_cast(!!(__VA_ARGS__))) // the expression here is never evaluated at runtime but it forces the compiler to give it a look + // The double negation silences MSVC's C4800 warning, the static_cast forces short-circuit evaluation if the type has overloaded &&. + +/////////////////////////////////////////////////////////////////////////////// +#define INTERNAL_CATCH_IF(macroName, resultDisposition, ...) \ + INTERNAL_CATCH_TEST(macroName, resultDisposition, __VA_ARGS__); \ + if (Catch::getResultCapture().lastAssertionPassed()) + +/////////////////////////////////////////////////////////////////////////////// +#define INTERNAL_CATCH_ELSE(macroName, resultDisposition, ...) \ + INTERNAL_CATCH_TEST(macroName, resultDisposition, __VA_ARGS__); \ + if (!Catch::getResultCapture().lastAssertionPassed()) + +/////////////////////////////////////////////////////////////////////////////// +#define INTERNAL_CATCH_NO_THROW(macroName, resultDisposition, ...) \ + do { \ + Catch::AssertionHandler catchAssertionHandler(macroName##_catch_sr, CATCH_INTERNAL_LINEINFO, CATCH_INTERNAL_STRINGIFY(__VA_ARGS__), resultDisposition); \ + try { \ + CATCH_INTERNAL_START_WARNINGS_SUPPRESSION \ + CATCH_INTERNAL_SUPPRESS_USELESS_CAST_WARNINGS \ + static_cast(__VA_ARGS__); \ + CATCH_INTERNAL_STOP_WARNINGS_SUPPRESSION \ + catchAssertionHandler.handleExceptionNotThrownAsExpected(); \ + } catch (...) { \ + catchAssertionHandler.handleUnexpectedInflightException(); \ + } \ + catchAssertionHandler.complete(); \ + } while (false) + +/////////////////////////////////////////////////////////////////////////////// +#define INTERNAL_CATCH_THROWS(macroName, resultDisposition, ...) \ + do { \ + Catch::AssertionHandler catchAssertionHandler(macroName##_catch_sr, CATCH_INTERNAL_LINEINFO, CATCH_INTERNAL_STRINGIFY(__VA_ARGS__), resultDisposition); \ + if (catchAssertionHandler.allowThrows()) \ + try { \ + CATCH_INTERNAL_START_WARNINGS_SUPPRESSION \ + CATCH_INTERNAL_SUPPRESS_UNUSED_RESULT \ + CATCH_INTERNAL_SUPPRESS_USELESS_CAST_WARNINGS \ + static_cast(__VA_ARGS__); \ + CATCH_INTERNAL_STOP_WARNINGS_SUPPRESSION \ + catchAssertionHandler.handleUnexpectedExceptionNotThrown(); \ + } catch (...) { \ + catchAssertionHandler.handleExceptionThrownAsExpected(); \ + } \ + else \ + catchAssertionHandler.handleThrowingCallSkipped(); \ + catchAssertionHandler.complete(); \ + } while (false) + +/////////////////////////////////////////////////////////////////////////////// +#define INTERNAL_CATCH_THROWS_AS(macroName, exceptionType, resultDisposition, expr) \ + do { \ + Catch::AssertionHandler catchAssertionHandler(macroName##_catch_sr, CATCH_INTERNAL_LINEINFO, CATCH_INTERNAL_STRINGIFY(expr) ", " CATCH_INTERNAL_STRINGIFY(exceptionType), resultDisposition); \ + if (catchAssertionHandler.allowThrows()) \ + try { \ + CATCH_INTERNAL_START_WARNINGS_SUPPRESSION \ + CATCH_INTERNAL_SUPPRESS_UNUSED_RESULT \ + CATCH_INTERNAL_SUPPRESS_USELESS_CAST_WARNINGS \ + static_cast(expr); \ + CATCH_INTERNAL_STOP_WARNINGS_SUPPRESSION \ + catchAssertionHandler.handleUnexpectedExceptionNotThrown(); \ + } catch (exceptionType const &) { \ + catchAssertionHandler.handleExceptionThrownAsExpected(); \ + } catch (...) { \ + catchAssertionHandler.handleUnexpectedInflightException(); \ + } \ + else \ + catchAssertionHandler.handleThrowingCallSkipped(); \ + catchAssertionHandler.complete(); \ + } while (false) + +/////////////////////////////////////////////////////////////////////////////// +// Although this is matcher-based, it can be used with just a string +#define INTERNAL_CATCH_THROWS_STR_MATCHES(macroName, resultDisposition, matcher, ...) \ + do { \ + Catch::AssertionHandler catchAssertionHandler(macroName##_catch_sr, CATCH_INTERNAL_LINEINFO, CATCH_INTERNAL_STRINGIFY(__VA_ARGS__) ", " CATCH_INTERNAL_STRINGIFY(matcher), resultDisposition); \ + if (catchAssertionHandler.allowThrows()) \ + try { \ + CATCH_INTERNAL_START_WARNINGS_SUPPRESSION \ + CATCH_INTERNAL_SUPPRESS_UNUSED_RESULT \ + CATCH_INTERNAL_SUPPRESS_USELESS_CAST_WARNINGS \ + static_cast(__VA_ARGS__); \ + CATCH_INTERNAL_STOP_WARNINGS_SUPPRESSION \ + catchAssertionHandler.handleUnexpectedExceptionNotThrown(); \ + } catch (...) { \ + Catch::handleExceptionMatchExpr(catchAssertionHandler, matcher); \ + } \ + else \ + catchAssertionHandler.handleThrowingCallSkipped(); \ + catchAssertionHandler.complete(); \ + } while (false) + +#endif // CATCH_CONFIG_DISABLE + +#endif // CATCH_TEST_MACRO_IMPL_HPP_INCLUDED + +#ifndef CATCH_SECTION_HPP_INCLUDED +#define CATCH_SECTION_HPP_INCLUDED + +/** \file + * Wrapper for the STATIC_ANALYSIS_SUPPORT configuration option + * + * Some of Catch2's macros can be defined differently to work better with + * static analysis tools, like clang-tidy or coverity. + * Currently the main use case is to show that `SECTION`s are executed + * exclusively, and not all in one run of a `TEST_CASE`. + */ + +#ifndef CATCH_CONFIG_STATIC_ANALYSIS_SUPPORT_HPP_INCLUDED +#define CATCH_CONFIG_STATIC_ANALYSIS_SUPPORT_HPP_INCLUDED + +#if defined(__clang_analyzer__) || defined(__COVERITY__) +#define CATCH_INTERNAL_CONFIG_STATIC_ANALYSIS_SUPPORT +#endif + +#if defined(CATCH_INTERNAL_CONFIG_STATIC_ANALYSIS_SUPPORT) && !defined(CATCH_CONFIG_NO_EXPERIMENTAL_STATIC_ANALYSIS_SUPPORT) && !defined(CATCH_CONFIG_EXPERIMENTAL_STATIC_ANALYSIS_SUPPORT) +#define CATCH_CONFIG_EXPERIMENTAL_STATIC_ANALYSIS_SUPPORT +#endif + +#endif // CATCH_CONFIG_STATIC_ANALYSIS_SUPPORT_HPP_INCLUDED + +#ifndef CATCH_TIMER_HPP_INCLUDED +#define CATCH_TIMER_HPP_INCLUDED + +#include + +namespace Catch { + + class Timer { + uint64_t m_nanoseconds = 0; + + public: + void start(); + auto getElapsedNanoseconds() const -> uint64_t; + auto getElapsedMicroseconds() const -> uint64_t; + auto getElapsedMilliseconds() const -> unsigned int; + auto getElapsedSeconds() const -> double; + }; + +} // namespace Catch + +#endif // CATCH_TIMER_HPP_INCLUDED + +namespace Catch { + + class Section : Detail::NonCopyable { + public: + Section(SectionInfo &&info); + Section(SourceLineInfo const &_lineInfo, + StringRef _name, + const char *const = nullptr); + ~Section(); + + // This indicates whether the section should be executed or not + explicit operator bool() const; + + private: + SectionInfo m_info; + + Counts m_assertions; + bool m_sectionIncluded; + Timer m_timer; + }; + +} // end namespace Catch + +#if !defined(CATCH_CONFIG_EXPERIMENTAL_STATIC_ANALYSIS_SUPPORT) +#define INTERNAL_CATCH_SECTION(...) \ + CATCH_INTERNAL_START_WARNINGS_SUPPRESSION \ + CATCH_INTERNAL_SUPPRESS_UNUSED_VARIABLE_WARNINGS \ + if (Catch::Section const &INTERNAL_CATCH_UNIQUE_NAME( \ + catch_internal_Section) \ + = Catch::Section(CATCH_INTERNAL_LINEINFO, __VA_ARGS__)) \ + CATCH_INTERNAL_STOP_WARNINGS_SUPPRESSION + +#define INTERNAL_CATCH_DYNAMIC_SECTION(...) \ + CATCH_INTERNAL_START_WARNINGS_SUPPRESSION \ + CATCH_INTERNAL_SUPPRESS_UNUSED_VARIABLE_WARNINGS \ + if (Catch::Section const &INTERNAL_CATCH_UNIQUE_NAME( \ + catch_internal_Section) \ + = Catch::SectionInfo( \ + CATCH_INTERNAL_LINEINFO, \ + (Catch::ReusableStringStream() << __VA_ARGS__) \ + .str())) \ + CATCH_INTERNAL_STOP_WARNINGS_SUPPRESSION + +#else + +// These section definitions imply that at most one section at one level +// will be entered (because only one section's __LINE__ can be equal to +// the dummy `catchInternalSectionHint` variable from `TEST_CASE`). + +namespace Catch { + namespace Detail { + // Intentionally without linkage, as it should only be used as a dummy + // symbol for static analysis. + // The arguments are used as a dummy for checking warnings in the passed + // expressions. + int GetNewSectionHint(StringRef, const char *const = nullptr); + } // namespace Detail +} // namespace Catch + +#define INTERNAL_CATCH_SECTION(...) \ + CATCH_INTERNAL_START_WARNINGS_SUPPRESSION \ + CATCH_INTERNAL_SUPPRESS_UNUSED_VARIABLE_WARNINGS \ + CATCH_INTERNAL_SUPPRESS_SHADOW_WARNINGS \ + if ([[maybe_unused]] const int catchInternalPreviousSectionHint = catchInternalSectionHint, \ + catchInternalSectionHint = Catch::Detail::GetNewSectionHint(__VA_ARGS__); \ + catchInternalPreviousSectionHint == __LINE__) \ + CATCH_INTERNAL_STOP_WARNINGS_SUPPRESSION + +#define INTERNAL_CATCH_DYNAMIC_SECTION(...) \ + CATCH_INTERNAL_START_WARNINGS_SUPPRESSION \ + CATCH_INTERNAL_SUPPRESS_UNUSED_VARIABLE_WARNINGS \ + CATCH_INTERNAL_SUPPRESS_SHADOW_WARNINGS \ + if ([[maybe_unused]] const int catchInternalPreviousSectionHint = catchInternalSectionHint, \ + catchInternalSectionHint = Catch::Detail::GetNewSectionHint( \ + (Catch::ReusableStringStream() << __VA_ARGS__).str()); \ + catchInternalPreviousSectionHint == __LINE__) \ + CATCH_INTERNAL_STOP_WARNINGS_SUPPRESSION + +#endif + +#endif // CATCH_SECTION_HPP_INCLUDED + +#ifndef CATCH_TEST_REGISTRY_HPP_INCLUDED +#define CATCH_TEST_REGISTRY_HPP_INCLUDED + +#ifndef CATCH_INTERFACES_TEST_INVOKER_HPP_INCLUDED +#define CATCH_INTERFACES_TEST_INVOKER_HPP_INCLUDED + +namespace Catch { + + class ITestInvoker { + public: + virtual void prepareTestCase(); + virtual void tearDownTestCase(); + virtual void invoke() const = 0; + virtual ~ITestInvoker(); // = default + }; + +} // namespace Catch + +#endif // CATCH_INTERFACES_TEST_INVOKER_HPP_INCLUDED + +#ifndef CATCH_PREPROCESSOR_REMOVE_PARENS_HPP_INCLUDED +#define CATCH_PREPROCESSOR_REMOVE_PARENS_HPP_INCLUDED + +#define INTERNAL_CATCH_EXPAND1(param) INTERNAL_CATCH_EXPAND2(param) +#define INTERNAL_CATCH_EXPAND2(...) INTERNAL_CATCH_NO##__VA_ARGS__ +#define INTERNAL_CATCH_DEF(...) INTERNAL_CATCH_DEF __VA_ARGS__ +#define INTERNAL_CATCH_NOINTERNAL_CATCH_DEF + +#define INTERNAL_CATCH_REMOVE_PARENS(...) \ + INTERNAL_CATCH_EXPAND1(INTERNAL_CATCH_DEF __VA_ARGS__) + +#endif // CATCH_PREPROCESSOR_REMOVE_PARENS_HPP_INCLUDED + +// GCC 5 and older do not properly handle disabling unused-variable warning +// with a _Pragma. This means that we have to leak the suppression to the +// user code as well :-( +#if defined(__GNUC__) && !defined(__clang__) && __GNUC__ <= 5 +#pragma GCC diagnostic ignored "-Wunused-variable" +#endif + +namespace Catch { + + template + class TestInvokerAsMethod : public ITestInvoker { + void (C::*m_testAsMethod)(); + + public: + constexpr TestInvokerAsMethod(void (C::*testAsMethod)()) noexcept + : m_testAsMethod(testAsMethod) {} + + void invoke() const override { + C obj; + (obj.*m_testAsMethod)(); + } + }; + + Detail::unique_ptr makeTestInvoker(void (*testAsFunction)()); + + template + Detail::unique_ptr makeTestInvoker(void (C::*testAsMethod)()) { + return Detail::make_unique>(testAsMethod); + } + + template + class TestInvokerFixture : public ITestInvoker { + void (C::*m_testAsMethod)() const; + Detail::unique_ptr m_fixture = nullptr; + + public: + constexpr TestInvokerFixture(void (C::*testAsMethod)() const) noexcept + : m_testAsMethod(testAsMethod) {} + + void prepareTestCase() override { + m_fixture = Detail::make_unique(); + } + + void tearDownTestCase() override { + m_fixture.reset(); + } + + void invoke() const override { + auto *f = m_fixture.get(); + (f->*m_testAsMethod)(); + } + }; + + template + Detail::unique_ptr makeTestInvokerFixture(void (C::*testAsMethod)() const) { + return Detail::make_unique>(testAsMethod); + } + + struct NameAndTags { + constexpr NameAndTags(StringRef name_ = StringRef(), + StringRef tags_ = StringRef()) noexcept + : name(name_), tags(tags_) {} + StringRef name; + StringRef tags; + }; + + struct AutoReg : Detail::NonCopyable { + AutoReg(Detail::unique_ptr invoker, SourceLineInfo const &lineInfo, StringRef classOrMethod, NameAndTags const &nameAndTags) noexcept; + }; + +} // end namespace Catch + +#if defined(CATCH_CONFIG_DISABLE) +#define INTERNAL_CATCH_TESTCASE_NO_REGISTRATION(TestName, ...) \ + static inline void TestName() +#define INTERNAL_CATCH_TESTCASE_METHOD_NO_REGISTRATION(TestName, ClassName, ...) \ + namespace { \ + struct TestName : INTERNAL_CATCH_REMOVE_PARENS(ClassName) { \ + void test(); \ + }; \ + } \ + void TestName::test() +#endif + +#if !defined(CATCH_CONFIG_EXPERIMENTAL_STATIC_ANALYSIS_SUPPORT) + +/////////////////////////////////////////////////////////////////////////////// +#define INTERNAL_CATCH_TESTCASE2(TestName, ...) \ + static void TestName(); \ + CATCH_INTERNAL_START_WARNINGS_SUPPRESSION \ + CATCH_INTERNAL_SUPPRESS_GLOBALS_WARNINGS \ + CATCH_INTERNAL_SUPPRESS_UNUSED_VARIABLE_WARNINGS \ + namespace { \ + const Catch::AutoReg INTERNAL_CATCH_UNIQUE_NAME(autoRegistrar)(Catch::makeTestInvoker(&TestName), CATCH_INTERNAL_LINEINFO, Catch::StringRef(), Catch::NameAndTags{__VA_ARGS__}); \ + } /* NOLINT */ \ + CATCH_INTERNAL_STOP_WARNINGS_SUPPRESSION \ + static void TestName() +#define INTERNAL_CATCH_TESTCASE(...) \ + INTERNAL_CATCH_TESTCASE2(INTERNAL_CATCH_UNIQUE_NAME(CATCH2_INTERNAL_TEST_), __VA_ARGS__) + +#else // ^^ !CATCH_CONFIG_EXPERIMENTAL_STATIC_ANALYSIS_SUPPORT | vv CATCH_CONFIG_EXPERIMENTAL_STATIC_ANALYSIS_SUPPORT + +// Dummy registrator for the dumy test case macros +namespace Catch { + namespace Detail { + struct DummyUse { + DummyUse(void (*)(int), Catch::NameAndTags const &); + }; + } // namespace Detail +} // namespace Catch + +// Note that both the presence of the argument and its exact name are +// necessary for the section support. + +// We provide a shadowed variable so that a `SECTION` inside non-`TEST_CASE` +// tests can compile. The redefined `TEST_CASE` shadows this with param. +static int catchInternalSectionHint = 0; + +#define INTERNAL_CATCH_TESTCASE2(fname, ...) \ + static void fname(int); \ + CATCH_INTERNAL_START_WARNINGS_SUPPRESSION \ + CATCH_INTERNAL_SUPPRESS_GLOBALS_WARNINGS \ + CATCH_INTERNAL_SUPPRESS_UNUSED_VARIABLE_WARNINGS \ + static const Catch::Detail::DummyUse INTERNAL_CATCH_UNIQUE_NAME( \ + dummyUser)(&(fname), Catch::NameAndTags{__VA_ARGS__}); \ + CATCH_INTERNAL_SUPPRESS_SHADOW_WARNINGS \ + static void fname([[maybe_unused]] int catchInternalSectionHint) \ + CATCH_INTERNAL_STOP_WARNINGS_SUPPRESSION +#define INTERNAL_CATCH_TESTCASE(...) \ + INTERNAL_CATCH_TESTCASE2(INTERNAL_CATCH_UNIQUE_NAME(dummyFunction), __VA_ARGS__) + +#endif // CATCH_CONFIG_EXPERIMENTAL_STATIC_ANALYSIS_SUPPORT + +/////////////////////////////////////////////////////////////////////////////// +#define INTERNAL_CATCH_TEST_CASE_METHOD2(TestName, ClassName, ...) \ + CATCH_INTERNAL_START_WARNINGS_SUPPRESSION \ + CATCH_INTERNAL_SUPPRESS_GLOBALS_WARNINGS \ + CATCH_INTERNAL_SUPPRESS_UNUSED_VARIABLE_WARNINGS \ + namespace { \ + struct TestName : INTERNAL_CATCH_REMOVE_PARENS(ClassName) { \ + void test(); \ + }; \ + const Catch::AutoReg INTERNAL_CATCH_UNIQUE_NAME(autoRegistrar)( \ + Catch::makeTestInvoker(&TestName::test), \ + CATCH_INTERNAL_LINEINFO, \ + #ClassName##_catch_sr, \ + Catch::NameAndTags{__VA_ARGS__}); /* NOLINT */ \ + } \ + CATCH_INTERNAL_STOP_WARNINGS_SUPPRESSION \ + void TestName::test() +#define INTERNAL_CATCH_TEST_CASE_METHOD(ClassName, ...) \ + INTERNAL_CATCH_TEST_CASE_METHOD2(INTERNAL_CATCH_UNIQUE_NAME(CATCH2_INTERNAL_TEST_), ClassName, __VA_ARGS__) + +/////////////////////////////////////////////////////////////////////////////// +#define INTERNAL_CATCH_TEST_CASE_PERSISTENT_FIXTURE2(TestName, ClassName, ...) \ + CATCH_INTERNAL_START_WARNINGS_SUPPRESSION \ + CATCH_INTERNAL_SUPPRESS_GLOBALS_WARNINGS \ + CATCH_INTERNAL_SUPPRESS_UNUSED_VARIABLE_WARNINGS \ + namespace { \ + struct TestName : INTERNAL_CATCH_REMOVE_PARENS(ClassName) { \ + void test() const; \ + }; \ + const Catch::AutoReg INTERNAL_CATCH_UNIQUE_NAME(autoRegistrar)( \ + Catch::makeTestInvokerFixture(&TestName::test), \ + CATCH_INTERNAL_LINEINFO, \ + #ClassName##_catch_sr, \ + Catch::NameAndTags{__VA_ARGS__}); /* NOLINT */ \ + } \ + CATCH_INTERNAL_STOP_WARNINGS_SUPPRESSION \ + void TestName::test() const +#define INTERNAL_CATCH_TEST_CASE_PERSISTENT_FIXTURE(ClassName, ...) \ + INTERNAL_CATCH_TEST_CASE_PERSISTENT_FIXTURE2(INTERNAL_CATCH_UNIQUE_NAME(CATCH2_INTERNAL_TEST_), ClassName, __VA_ARGS__) + +/////////////////////////////////////////////////////////////////////////////// +#define INTERNAL_CATCH_METHOD_AS_TEST_CASE(QualifiedMethod, ...) \ + CATCH_INTERNAL_START_WARNINGS_SUPPRESSION \ + CATCH_INTERNAL_SUPPRESS_GLOBALS_WARNINGS \ + CATCH_INTERNAL_SUPPRESS_UNUSED_VARIABLE_WARNINGS \ + namespace { \ + const Catch::AutoReg INTERNAL_CATCH_UNIQUE_NAME(autoRegistrar)( \ + Catch::makeTestInvoker(&QualifiedMethod), \ + CATCH_INTERNAL_LINEINFO, \ + "&" #QualifiedMethod##_catch_sr, \ + Catch::NameAndTags{__VA_ARGS__}); \ + } /* NOLINT */ \ + CATCH_INTERNAL_STOP_WARNINGS_SUPPRESSION + +/////////////////////////////////////////////////////////////////////////////// +#define INTERNAL_CATCH_REGISTER_TESTCASE(Function, ...) \ + do { \ + CATCH_INTERNAL_START_WARNINGS_SUPPRESSION \ + CATCH_INTERNAL_SUPPRESS_GLOBALS_WARNINGS \ + CATCH_INTERNAL_SUPPRESS_UNUSED_VARIABLE_WARNINGS \ + Catch::AutoReg INTERNAL_CATCH_UNIQUE_NAME(autoRegistrar)(Catch::makeTestInvoker(Function), CATCH_INTERNAL_LINEINFO, Catch::StringRef(), Catch::NameAndTags{__VA_ARGS__}); /* NOLINT */ \ + CATCH_INTERNAL_STOP_WARNINGS_SUPPRESSION \ + } while (false) + +#endif // CATCH_TEST_REGISTRY_HPP_INCLUDED + +#ifndef CATCH_UNREACHABLE_HPP_INCLUDED +#define CATCH_UNREACHABLE_HPP_INCLUDED + +/**\file + * Polyfill `std::unreachable` + * + * We need something like `std::unreachable` to tell the compiler that + * some macros, e.g. `FAIL` or `SKIP`, do not continue execution in normal + * manner, and should handle it as such, e.g. not warn if there is no return + * from non-void function after a `FAIL` or `SKIP`. + */ + +#include + +#if defined(__cpp_lib_unreachable) && __cpp_lib_unreachable > 202202L +#include +namespace Catch { + namespace Detail { + using Unreachable = std::unreachable; + } +} // namespace Catch + +#else // vv If we do not have std::unreachable, we implement something similar + +namespace Catch { + namespace Detail { + + [[noreturn]] + inline void Unreachable() noexcept { +#if defined(NDEBUG) +#if defined(_MSC_VER) && !defined(__clang__) + __assume(false); +#elif defined(__GNUC__) + __builtin_unreachable(); +#else // vv platform without known optimization hint + std::terminate(); +#endif +#else // ^^ NDEBUG + // For non-release builds, we prefer termination on bug over UB + std::terminate(); +#endif // + } + + } // namespace Detail +} // end namespace Catch + +#endif + +#endif // CATCH_UNREACHABLE_HPP_INCLUDED + +// All of our user-facing macros support configuration toggle, that +// forces them to be defined prefixed with CATCH_. We also like to +// support another toggle that can minimize (disable) their implementation. +// Given this, we have 4 different configuration options below + +#if defined(CATCH_CONFIG_PREFIX_ALL) && !defined(CATCH_CONFIG_DISABLE) + +#define CATCH_REQUIRE(...) INTERNAL_CATCH_TEST("CATCH_REQUIRE", Catch::ResultDisposition::Normal, __VA_ARGS__) +#define CATCH_REQUIRE_FALSE(...) INTERNAL_CATCH_TEST("CATCH_REQUIRE_FALSE", Catch::ResultDisposition::Normal | Catch::ResultDisposition::FalseTest, __VA_ARGS__) + +#define CATCH_REQUIRE_THROWS(...) INTERNAL_CATCH_THROWS("CATCH_REQUIRE_THROWS", Catch::ResultDisposition::Normal, __VA_ARGS__) +#define CATCH_REQUIRE_THROWS_AS(expr, exceptionType) INTERNAL_CATCH_THROWS_AS("CATCH_REQUIRE_THROWS_AS", exceptionType, Catch::ResultDisposition::Normal, expr) +#define CATCH_REQUIRE_NOTHROW(...) INTERNAL_CATCH_NO_THROW("CATCH_REQUIRE_NOTHROW", Catch::ResultDisposition::Normal, __VA_ARGS__) + +#define CATCH_CHECK(...) INTERNAL_CATCH_TEST("CATCH_CHECK", Catch::ResultDisposition::ContinueOnFailure, __VA_ARGS__) +#define CATCH_CHECK_FALSE(...) INTERNAL_CATCH_TEST("CATCH_CHECK_FALSE", Catch::ResultDisposition::ContinueOnFailure | Catch::ResultDisposition::FalseTest, __VA_ARGS__) +#define CATCH_CHECKED_IF(...) INTERNAL_CATCH_IF("CATCH_CHECKED_IF", Catch::ResultDisposition::ContinueOnFailure | Catch::ResultDisposition::SuppressFail, __VA_ARGS__) +#define CATCH_CHECKED_ELSE(...) INTERNAL_CATCH_ELSE("CATCH_CHECKED_ELSE", Catch::ResultDisposition::ContinueOnFailure | Catch::ResultDisposition::SuppressFail, __VA_ARGS__) +#define CATCH_CHECK_NOFAIL(...) INTERNAL_CATCH_TEST("CATCH_CHECK_NOFAIL", Catch::ResultDisposition::ContinueOnFailure | Catch::ResultDisposition::SuppressFail, __VA_ARGS__) + +#define CATCH_CHECK_THROWS(...) INTERNAL_CATCH_THROWS("CATCH_CHECK_THROWS", Catch::ResultDisposition::ContinueOnFailure, __VA_ARGS__) +#define CATCH_CHECK_THROWS_AS(expr, exceptionType) INTERNAL_CATCH_THROWS_AS("CATCH_CHECK_THROWS_AS", exceptionType, Catch::ResultDisposition::ContinueOnFailure, expr) +#define CATCH_CHECK_NOTHROW(...) INTERNAL_CATCH_NO_THROW("CATCH_CHECK_NOTHROW", Catch::ResultDisposition::ContinueOnFailure, __VA_ARGS__) + +#define CATCH_TEST_CASE(...) INTERNAL_CATCH_TESTCASE(__VA_ARGS__) +#define CATCH_TEST_CASE_METHOD(className, ...) INTERNAL_CATCH_TEST_CASE_METHOD(className, __VA_ARGS__) +#define CATCH_METHOD_AS_TEST_CASE(method, ...) INTERNAL_CATCH_METHOD_AS_TEST_CASE(method, __VA_ARGS__) +#define CATCH_TEST_CASE_PERSISTENT_FIXTURE(className, ...) INTERNAL_CATCH_TEST_CASE_PERSISTENT_FIXTURE(className, __VA_ARGS__) +#define CATCH_REGISTER_TEST_CASE(Function, ...) INTERNAL_CATCH_REGISTER_TESTCASE(Function, __VA_ARGS__) +#define CATCH_SECTION(...) INTERNAL_CATCH_SECTION(__VA_ARGS__) +#define CATCH_DYNAMIC_SECTION(...) INTERNAL_CATCH_DYNAMIC_SECTION(__VA_ARGS__) +#define CATCH_FAIL(...) \ + do { \ + INTERNAL_CATCH_MSG("CATCH_FAIL", Catch::ResultWas::ExplicitFailure, Catch::ResultDisposition::Normal, __VA_ARGS__); \ + Catch::Detail::Unreachable(); \ + } while (false) +#define CATCH_FAIL_CHECK(...) INTERNAL_CATCH_MSG("CATCH_FAIL_CHECK", Catch::ResultWas::ExplicitFailure, Catch::ResultDisposition::ContinueOnFailure, __VA_ARGS__) +#define CATCH_SUCCEED(...) INTERNAL_CATCH_MSG("CATCH_SUCCEED", Catch::ResultWas::Ok, Catch::ResultDisposition::ContinueOnFailure, __VA_ARGS__) +#define CATCH_SKIP(...) \ + do { \ + INTERNAL_CATCH_MSG("CATCH_SKIP", Catch::ResultWas::ExplicitSkip, Catch::ResultDisposition::Normal, __VA_ARGS__); \ + Catch::Detail::Unreachable(); \ + } while (false) + +#if !defined(CATCH_CONFIG_RUNTIME_STATIC_REQUIRE) +#define CATCH_STATIC_REQUIRE(...) \ + static_assert(__VA_ARGS__, #__VA_ARGS__); \ + CATCH_SUCCEED(#__VA_ARGS__) +#define CATCH_STATIC_REQUIRE_FALSE(...) \ + static_assert(!(__VA_ARGS__), "!(" #__VA_ARGS__ ")"); \ + CATCH_SUCCEED(#__VA_ARGS__) +#define CATCH_STATIC_CHECK(...) \ + static_assert(__VA_ARGS__, #__VA_ARGS__); \ + CATCH_SUCCEED(#__VA_ARGS__) +#define CATCH_STATIC_CHECK_FALSE(...) \ + static_assert(!(__VA_ARGS__), "!(" #__VA_ARGS__ ")"); \ + CATCH_SUCCEED(#__VA_ARGS__) +#else +#define CATCH_STATIC_REQUIRE(...) CATCH_REQUIRE(__VA_ARGS__) +#define CATCH_STATIC_REQUIRE_FALSE(...) CATCH_REQUIRE_FALSE(__VA_ARGS__) +#define CATCH_STATIC_CHECK(...) CATCH_CHECK(__VA_ARGS__) +#define CATCH_STATIC_CHECK_FALSE(...) CATCH_CHECK_FALSE(__VA_ARGS__) +#endif + +// "BDD-style" convenience wrappers +#define CATCH_SCENARIO(...) CATCH_TEST_CASE("Scenario: " __VA_ARGS__) +#define CATCH_SCENARIO_METHOD(className, ...) INTERNAL_CATCH_TEST_CASE_METHOD(className, "Scenario: " __VA_ARGS__) +#define CATCH_GIVEN(desc) INTERNAL_CATCH_DYNAMIC_SECTION(" Given: " << desc) +#define CATCH_AND_GIVEN(desc) INTERNAL_CATCH_DYNAMIC_SECTION("And given: " << desc) +#define CATCH_WHEN(desc) INTERNAL_CATCH_DYNAMIC_SECTION(" When: " << desc) +#define CATCH_AND_WHEN(desc) INTERNAL_CATCH_DYNAMIC_SECTION(" And when: " << desc) +#define CATCH_THEN(desc) INTERNAL_CATCH_DYNAMIC_SECTION(" Then: " << desc) +#define CATCH_AND_THEN(desc) INTERNAL_CATCH_DYNAMIC_SECTION(" And: " << desc) + +#elif defined(CATCH_CONFIG_PREFIX_ALL) && defined(CATCH_CONFIG_DISABLE) // ^^ prefixed, implemented | vv prefixed, disabled + +#define CATCH_REQUIRE(...) (void)(0) +#define CATCH_REQUIRE_FALSE(...) (void)(0) + +#define CATCH_REQUIRE_THROWS(...) (void)(0) +#define CATCH_REQUIRE_THROWS_AS(expr, exceptionType) (void)(0) +#define CATCH_REQUIRE_NOTHROW(...) (void)(0) + +#define CATCH_CHECK(...) (void)(0) +#define CATCH_CHECK_FALSE(...) (void)(0) +#define CATCH_CHECKED_IF(...) if (__VA_ARGS__) +#define CATCH_CHECKED_ELSE(...) if (!(__VA_ARGS__)) +#define CATCH_CHECK_NOFAIL(...) (void)(0) + +#define CATCH_CHECK_THROWS(...) (void)(0) +#define CATCH_CHECK_THROWS_AS(expr, exceptionType) (void)(0) +#define CATCH_CHECK_NOTHROW(...) (void)(0) + +#define CATCH_TEST_CASE(...) INTERNAL_CATCH_TESTCASE_NO_REGISTRATION(INTERNAL_CATCH_UNIQUE_NAME(CATCH2_INTERNAL_TEST_)) +#define CATCH_TEST_CASE_METHOD(className, ...) INTERNAL_CATCH_TESTCASE_NO_REGISTRATION(INTERNAL_CATCH_UNIQUE_NAME(CATCH2_INTERNAL_TEST_)) +#define CATCH_METHOD_AS_TEST_CASE(method, ...) +#define CATCH_TEST_CASE_PERSISTENT_FIXTURE(className, ...) INTERNAL_CATCH_TESTCASE_NO_REGISTRATION(INTERNAL_CATCH_UNIQUE_NAME(CATCH2_INTERNAL_TEST_)) +#define CATCH_REGISTER_TEST_CASE(Function, ...) (void)(0) +#define CATCH_SECTION(...) +#define CATCH_DYNAMIC_SECTION(...) +#define CATCH_FAIL(...) (void)(0) +#define CATCH_FAIL_CHECK(...) (void)(0) +#define CATCH_SUCCEED(...) (void)(0) +#define CATCH_SKIP(...) (void)(0) + +#define CATCH_STATIC_REQUIRE(...) (void)(0) +#define CATCH_STATIC_REQUIRE_FALSE(...) (void)(0) +#define CATCH_STATIC_CHECK(...) (void)(0) +#define CATCH_STATIC_CHECK_FALSE(...) (void)(0) + +// "BDD-style" convenience wrappers +#define CATCH_SCENARIO(...) INTERNAL_CATCH_TESTCASE_NO_REGISTRATION(INTERNAL_CATCH_UNIQUE_NAME(CATCH2_INTERNAL_TEST_)) +#define CATCH_SCENARIO_METHOD(className, ...) INTERNAL_CATCH_TESTCASE_METHOD_NO_REGISTRATION(INTERNAL_CATCH_UNIQUE_NAME(CATCH2_INTERNAL_TEST_), className) +#define CATCH_GIVEN(desc) +#define CATCH_AND_GIVEN(desc) +#define CATCH_WHEN(desc) +#define CATCH_AND_WHEN(desc) +#define CATCH_THEN(desc) +#define CATCH_AND_THEN(desc) + +#elif !defined(CATCH_CONFIG_PREFIX_ALL) && !defined(CATCH_CONFIG_DISABLE) // ^^ prefixed, disabled | vv unprefixed, implemented + +#define REQUIRE(...) INTERNAL_CATCH_TEST("REQUIRE", Catch::ResultDisposition::Normal, __VA_ARGS__) +#define REQUIRE_FALSE(...) INTERNAL_CATCH_TEST("REQUIRE_FALSE", Catch::ResultDisposition::Normal | Catch::ResultDisposition::FalseTest, __VA_ARGS__) + +#define REQUIRE_THROWS(...) INTERNAL_CATCH_THROWS("REQUIRE_THROWS", Catch::ResultDisposition::Normal, __VA_ARGS__) +#define REQUIRE_THROWS_AS(expr, exceptionType) INTERNAL_CATCH_THROWS_AS("REQUIRE_THROWS_AS", exceptionType, Catch::ResultDisposition::Normal, expr) +#define REQUIRE_NOTHROW(...) INTERNAL_CATCH_NO_THROW("REQUIRE_NOTHROW", Catch::ResultDisposition::Normal, __VA_ARGS__) + +#define CHECK(...) INTERNAL_CATCH_TEST("CHECK", Catch::ResultDisposition::ContinueOnFailure, __VA_ARGS__) +#define CHECK_FALSE(...) INTERNAL_CATCH_TEST("CHECK_FALSE", Catch::ResultDisposition::ContinueOnFailure | Catch::ResultDisposition::FalseTest, __VA_ARGS__) +#define CHECKED_IF(...) INTERNAL_CATCH_IF("CHECKED_IF", Catch::ResultDisposition::ContinueOnFailure | Catch::ResultDisposition::SuppressFail, __VA_ARGS__) +#define CHECKED_ELSE(...) INTERNAL_CATCH_ELSE("CHECKED_ELSE", Catch::ResultDisposition::ContinueOnFailure | Catch::ResultDisposition::SuppressFail, __VA_ARGS__) +#define CHECK_NOFAIL(...) INTERNAL_CATCH_TEST("CHECK_NOFAIL", Catch::ResultDisposition::ContinueOnFailure | Catch::ResultDisposition::SuppressFail, __VA_ARGS__) + +#define CHECK_THROWS(...) INTERNAL_CATCH_THROWS("CHECK_THROWS", Catch::ResultDisposition::ContinueOnFailure, __VA_ARGS__) +#define CHECK_THROWS_AS(expr, exceptionType) INTERNAL_CATCH_THROWS_AS("CHECK_THROWS_AS", exceptionType, Catch::ResultDisposition::ContinueOnFailure, expr) +#define CHECK_NOTHROW(...) INTERNAL_CATCH_NO_THROW("CHECK_NOTHROW", Catch::ResultDisposition::ContinueOnFailure, __VA_ARGS__) + +#define TEST_CASE(...) INTERNAL_CATCH_TESTCASE(__VA_ARGS__) +#define TEST_CASE_METHOD(className, ...) INTERNAL_CATCH_TEST_CASE_METHOD(className, __VA_ARGS__) +#define METHOD_AS_TEST_CASE(method, ...) INTERNAL_CATCH_METHOD_AS_TEST_CASE(method, __VA_ARGS__) +#define TEST_CASE_PERSISTENT_FIXTURE(className, ...) INTERNAL_CATCH_TEST_CASE_PERSISTENT_FIXTURE(className, __VA_ARGS__) +#define REGISTER_TEST_CASE(Function, ...) INTERNAL_CATCH_REGISTER_TESTCASE(Function, __VA_ARGS__) +#define SECTION(...) INTERNAL_CATCH_SECTION(__VA_ARGS__) +#define DYNAMIC_SECTION(...) INTERNAL_CATCH_DYNAMIC_SECTION(__VA_ARGS__) +#define FAIL(...) \ + do { \ + INTERNAL_CATCH_MSG("FAIL", Catch::ResultWas::ExplicitFailure, Catch::ResultDisposition::Normal, __VA_ARGS__); \ + Catch::Detail::Unreachable(); \ + } while (false) +#define FAIL_CHECK(...) INTERNAL_CATCH_MSG("FAIL_CHECK", Catch::ResultWas::ExplicitFailure, Catch::ResultDisposition::ContinueOnFailure, __VA_ARGS__) +#define SUCCEED(...) INTERNAL_CATCH_MSG("SUCCEED", Catch::ResultWas::Ok, Catch::ResultDisposition::ContinueOnFailure, __VA_ARGS__) +#define SKIP(...) \ + do { \ + INTERNAL_CATCH_MSG("SKIP", Catch::ResultWas::ExplicitSkip, Catch::ResultDisposition::Normal, __VA_ARGS__); \ + Catch::Detail::Unreachable(); \ + } while (false) + +#if !defined(CATCH_CONFIG_RUNTIME_STATIC_REQUIRE) +#define STATIC_REQUIRE(...) \ + static_assert(__VA_ARGS__, #__VA_ARGS__); \ + SUCCEED(#__VA_ARGS__) +#define STATIC_REQUIRE_FALSE(...) \ + static_assert(!(__VA_ARGS__), "!(" #__VA_ARGS__ ")"); \ + SUCCEED("!(" #__VA_ARGS__ ")") +#define STATIC_CHECK(...) \ + static_assert(__VA_ARGS__, #__VA_ARGS__); \ + SUCCEED(#__VA_ARGS__) +#define STATIC_CHECK_FALSE(...) \ + static_assert(!(__VA_ARGS__), "!(" #__VA_ARGS__ ")"); \ + SUCCEED("!(" #__VA_ARGS__ ")") +#else +#define STATIC_REQUIRE(...) REQUIRE(__VA_ARGS__) +#define STATIC_REQUIRE_FALSE(...) REQUIRE_FALSE(__VA_ARGS__) +#define STATIC_CHECK(...) CHECK(__VA_ARGS__) +#define STATIC_CHECK_FALSE(...) CHECK_FALSE(__VA_ARGS__) +#endif + +// "BDD-style" convenience wrappers +#define SCENARIO(...) TEST_CASE("Scenario: " __VA_ARGS__) +#define SCENARIO_METHOD(className, ...) INTERNAL_CATCH_TEST_CASE_METHOD(className, "Scenario: " __VA_ARGS__) +#define GIVEN(desc) INTERNAL_CATCH_DYNAMIC_SECTION(" Given: " << desc) +#define AND_GIVEN(desc) INTERNAL_CATCH_DYNAMIC_SECTION("And given: " << desc) +#define WHEN(desc) INTERNAL_CATCH_DYNAMIC_SECTION(" When: " << desc) +#define AND_WHEN(desc) INTERNAL_CATCH_DYNAMIC_SECTION(" And when: " << desc) +#define THEN(desc) INTERNAL_CATCH_DYNAMIC_SECTION(" Then: " << desc) +#define AND_THEN(desc) INTERNAL_CATCH_DYNAMIC_SECTION(" And: " << desc) + +#elif !defined(CATCH_CONFIG_PREFIX_ALL) && defined(CATCH_CONFIG_DISABLE) // ^^ unprefixed, implemented | vv unprefixed, disabled + +#define REQUIRE(...) (void)(0) +#define REQUIRE_FALSE(...) (void)(0) + +#define REQUIRE_THROWS(...) (void)(0) +#define REQUIRE_THROWS_AS(expr, exceptionType) (void)(0) +#define REQUIRE_NOTHROW(...) (void)(0) + +#define CHECK(...) (void)(0) +#define CHECK_FALSE(...) (void)(0) +#define CHECKED_IF(...) if (__VA_ARGS__) +#define CHECKED_ELSE(...) if (!(__VA_ARGS__)) +#define CHECK_NOFAIL(...) (void)(0) + +#define CHECK_THROWS(...) (void)(0) +#define CHECK_THROWS_AS(expr, exceptionType) (void)(0) +#define CHECK_NOTHROW(...) (void)(0) + +#define TEST_CASE(...) INTERNAL_CATCH_TESTCASE_NO_REGISTRATION(INTERNAL_CATCH_UNIQUE_NAME(CATCH2_INTERNAL_TEST_), __VA_ARGS__) +#define TEST_CASE_METHOD(className, ...) INTERNAL_CATCH_TESTCASE_NO_REGISTRATION(INTERNAL_CATCH_UNIQUE_NAME(CATCH2_INTERNAL_TEST_)) +#define METHOD_AS_TEST_CASE(method, ...) +#define TEST_CASE_PERSISTENT_FIXTURE(className, ...) INTERNAL_CATCH_TESTCASE_NO_REGISTRATION(INTERNAL_CATCH_UNIQUE_NAME(CATCH2_INTERNAL_TEST_), __VA_ARGS__) +#define REGISTER_TEST_CASE(Function, ...) (void)(0) +#define SECTION(...) +#define DYNAMIC_SECTION(...) +#define FAIL(...) (void)(0) +#define FAIL_CHECK(...) (void)(0) +#define SUCCEED(...) (void)(0) +#define SKIP(...) (void)(0) + +#define STATIC_REQUIRE(...) (void)(0) +#define STATIC_REQUIRE_FALSE(...) (void)(0) +#define STATIC_CHECK(...) (void)(0) +#define STATIC_CHECK_FALSE(...) (void)(0) + +// "BDD-style" convenience wrappers +#define SCENARIO(...) INTERNAL_CATCH_TESTCASE_NO_REGISTRATION(INTERNAL_CATCH_UNIQUE_NAME(CATCH2_INTERNAL_TEST_)) +#define SCENARIO_METHOD(className, ...) INTERNAL_CATCH_TESTCASE_METHOD_NO_REGISTRATION(INTERNAL_CATCH_UNIQUE_NAME(CATCH2_INTERNAL_TEST_), className) + +#define GIVEN(desc) +#define AND_GIVEN(desc) +#define WHEN(desc) +#define AND_WHEN(desc) +#define THEN(desc) +#define AND_THEN(desc) + +#endif // ^^ unprefixed, disabled + +// end of user facing macros + +#endif // CATCH_TEST_MACROS_HPP_INCLUDED + +#ifndef CATCH_TEMPLATE_TEST_REGISTRY_HPP_INCLUDED +#define CATCH_TEMPLATE_TEST_REGISTRY_HPP_INCLUDED + +#ifndef CATCH_PREPROCESSOR_HPP_INCLUDED +#define CATCH_PREPROCESSOR_HPP_INCLUDED + +#if defined(__GNUC__) +// We need to silence "empty __VA_ARGS__ warning", and using just _Pragma does not work +#pragma GCC system_header +#endif + +namespace Catch { + namespace Detail { + template + struct priority_tag : priority_tag {}; + template<> + struct priority_tag<0> {}; + } // namespace Detail +} // namespace Catch + +#define CATCH_RECURSION_LEVEL0(...) __VA_ARGS__ +#define CATCH_RECURSION_LEVEL1(...) CATCH_RECURSION_LEVEL0(CATCH_RECURSION_LEVEL0(CATCH_RECURSION_LEVEL0(__VA_ARGS__))) +#define CATCH_RECURSION_LEVEL2(...) CATCH_RECURSION_LEVEL1(CATCH_RECURSION_LEVEL1(CATCH_RECURSION_LEVEL1(__VA_ARGS__))) +#define CATCH_RECURSION_LEVEL3(...) CATCH_RECURSION_LEVEL2(CATCH_RECURSION_LEVEL2(CATCH_RECURSION_LEVEL2(__VA_ARGS__))) +#define CATCH_RECURSION_LEVEL4(...) CATCH_RECURSION_LEVEL3(CATCH_RECURSION_LEVEL3(CATCH_RECURSION_LEVEL3(__VA_ARGS__))) +#define CATCH_RECURSION_LEVEL5(...) CATCH_RECURSION_LEVEL4(CATCH_RECURSION_LEVEL4(CATCH_RECURSION_LEVEL4(__VA_ARGS__))) + +#ifdef CATCH_CONFIG_TRADITIONAL_MSVC_PREPROCESSOR +#define INTERNAL_CATCH_EXPAND_VARGS(...) __VA_ARGS__ +// MSVC needs more evaluations +#define CATCH_RECURSION_LEVEL6(...) CATCH_RECURSION_LEVEL5(CATCH_RECURSION_LEVEL5(CATCH_RECURSION_LEVEL5(__VA_ARGS__))) +#define CATCH_RECURSE(...) CATCH_RECURSION_LEVEL6(CATCH_RECURSION_LEVEL6(__VA_ARGS__)) +#else +#define CATCH_RECURSE(...) CATCH_RECURSION_LEVEL5(__VA_ARGS__) +#endif + +#define CATCH_REC_END(...) +#define CATCH_REC_OUT + +#define CATCH_EMPTY() +#define CATCH_DEFER(id) id CATCH_EMPTY() + +#define CATCH_REC_GET_END2() 0, CATCH_REC_END +#define CATCH_REC_GET_END1(...) CATCH_REC_GET_END2 +#define CATCH_REC_GET_END(...) CATCH_REC_GET_END1 +#define CATCH_REC_NEXT0(test, next, ...) next CATCH_REC_OUT +#define CATCH_REC_NEXT1(test, next) CATCH_DEFER(CATCH_REC_NEXT0)(test, next, 0) +#define CATCH_REC_NEXT(test, next) CATCH_REC_NEXT1(CATCH_REC_GET_END test, next) + +#define CATCH_REC_LIST0(f, x, peek, ...) , f(x) CATCH_DEFER(CATCH_REC_NEXT(peek, CATCH_REC_LIST1))(f, peek, __VA_ARGS__) +#define CATCH_REC_LIST1(f, x, peek, ...) , f(x) CATCH_DEFER(CATCH_REC_NEXT(peek, CATCH_REC_LIST0))(f, peek, __VA_ARGS__) +#define CATCH_REC_LIST2(f, x, peek, ...) f(x) CATCH_DEFER(CATCH_REC_NEXT(peek, CATCH_REC_LIST1))(f, peek, __VA_ARGS__) + +#define CATCH_REC_LIST0_UD(f, userdata, x, peek, ...) , f(userdata, x) CATCH_DEFER(CATCH_REC_NEXT(peek, CATCH_REC_LIST1_UD))(f, userdata, peek, __VA_ARGS__) +#define CATCH_REC_LIST1_UD(f, userdata, x, peek, ...) , f(userdata, x) CATCH_DEFER(CATCH_REC_NEXT(peek, CATCH_REC_LIST0_UD))(f, userdata, peek, __VA_ARGS__) +#define CATCH_REC_LIST2_UD(f, userdata, x, peek, ...) f(userdata, x) CATCH_DEFER(CATCH_REC_NEXT(peek, CATCH_REC_LIST1_UD))(f, userdata, peek, __VA_ARGS__) + +// Applies the function macro `f` to each of the remaining parameters, inserts commas between the results, +// and passes userdata as the first parameter to each invocation, +// e.g. CATCH_REC_LIST_UD(f, x, a, b, c) evaluates to f(x, a), f(x, b), f(x, c) +#define CATCH_REC_LIST_UD(f, userdata, ...) CATCH_RECURSE(CATCH_REC_LIST2_UD(f, userdata, __VA_ARGS__, ()()(), ()()(), ()()(), 0)) + +#define CATCH_REC_LIST(f, ...) CATCH_RECURSE(CATCH_REC_LIST2(f, __VA_ARGS__, ()()(), ()()(), ()()(), 0)) + +#define INTERNAL_CATCH_STRINGIZE(...) INTERNAL_CATCH_STRINGIZE2(__VA_ARGS__) +#ifndef CATCH_CONFIG_TRADITIONAL_MSVC_PREPROCESSOR +#define INTERNAL_CATCH_STRINGIZE2(...) #__VA_ARGS__ +#define INTERNAL_CATCH_STRINGIZE_WITHOUT_PARENS(param) INTERNAL_CATCH_STRINGIZE(INTERNAL_CATCH_REMOVE_PARENS(param)) +#else +// MSVC is adding extra space and needs another indirection to expand INTERNAL_CATCH_NOINTERNAL_CATCH_DEF +#define INTERNAL_CATCH_STRINGIZE2(...) INTERNAL_CATCH_STRINGIZE3(__VA_ARGS__) +#define INTERNAL_CATCH_STRINGIZE3(...) #__VA_ARGS__ +#define INTERNAL_CATCH_STRINGIZE_WITHOUT_PARENS(param) (INTERNAL_CATCH_STRINGIZE(INTERNAL_CATCH_REMOVE_PARENS(param)) + 1) +#endif + +#define INTERNAL_CATCH_MAKE_NAMESPACE2(...) ns_##__VA_ARGS__ +#define INTERNAL_CATCH_MAKE_NAMESPACE(name) INTERNAL_CATCH_MAKE_NAMESPACE2(name) + +#ifndef CATCH_CONFIG_TRADITIONAL_MSVC_PREPROCESSOR +#define INTERNAL_CATCH_MAKE_TYPE_LIST2(...) decltype(get_wrapper(Catch::Detail::priority_tag<1>{})) +#define INTERNAL_CATCH_MAKE_TYPE_LIST(...) INTERNAL_CATCH_MAKE_TYPE_LIST2(INTERNAL_CATCH_REMOVE_PARENS(__VA_ARGS__)) +#else +#define INTERNAL_CATCH_MAKE_TYPE_LIST2(...) INTERNAL_CATCH_EXPAND_VARGS(decltype(get_wrapper(Catch::Detail::priority_tag<1>{}))) +#define INTERNAL_CATCH_MAKE_TYPE_LIST(...) INTERNAL_CATCH_EXPAND_VARGS(INTERNAL_CATCH_MAKE_TYPE_LIST2(INTERNAL_CATCH_REMOVE_PARENS(__VA_ARGS__))) +#endif + +#define INTERNAL_CATCH_MAKE_TYPE_LISTS_FROM_TYPES(...) \ + CATCH_REC_LIST(INTERNAL_CATCH_MAKE_TYPE_LIST, __VA_ARGS__) + +#define INTERNAL_CATCH_REMOVE_PARENS_1_ARG(_0) INTERNAL_CATCH_REMOVE_PARENS(_0) +#define INTERNAL_CATCH_REMOVE_PARENS_2_ARG(_0, _1) INTERNAL_CATCH_REMOVE_PARENS(_0), INTERNAL_CATCH_REMOVE_PARENS_1_ARG(_1) +#define INTERNAL_CATCH_REMOVE_PARENS_3_ARG(_0, _1, _2) INTERNAL_CATCH_REMOVE_PARENS(_0), INTERNAL_CATCH_REMOVE_PARENS_2_ARG(_1, _2) +#define INTERNAL_CATCH_REMOVE_PARENS_4_ARG(_0, _1, _2, _3) INTERNAL_CATCH_REMOVE_PARENS(_0), INTERNAL_CATCH_REMOVE_PARENS_3_ARG(_1, _2, _3) +#define INTERNAL_CATCH_REMOVE_PARENS_5_ARG(_0, _1, _2, _3, _4) INTERNAL_CATCH_REMOVE_PARENS(_0), INTERNAL_CATCH_REMOVE_PARENS_4_ARG(_1, _2, _3, _4) +#define INTERNAL_CATCH_REMOVE_PARENS_6_ARG(_0, _1, _2, _3, _4, _5) INTERNAL_CATCH_REMOVE_PARENS(_0), INTERNAL_CATCH_REMOVE_PARENS_5_ARG(_1, _2, _3, _4, _5) +#define INTERNAL_CATCH_REMOVE_PARENS_7_ARG(_0, _1, _2, _3, _4, _5, _6) INTERNAL_CATCH_REMOVE_PARENS(_0), INTERNAL_CATCH_REMOVE_PARENS_6_ARG(_1, _2, _3, _4, _5, _6) +#define INTERNAL_CATCH_REMOVE_PARENS_8_ARG(_0, _1, _2, _3, _4, _5, _6, _7) INTERNAL_CATCH_REMOVE_PARENS(_0), INTERNAL_CATCH_REMOVE_PARENS_7_ARG(_1, _2, _3, _4, _5, _6, _7) +#define INTERNAL_CATCH_REMOVE_PARENS_9_ARG(_0, _1, _2, _3, _4, _5, _6, _7, _8) INTERNAL_CATCH_REMOVE_PARENS(_0), INTERNAL_CATCH_REMOVE_PARENS_8_ARG(_1, _2, _3, _4, _5, _6, _7, _8) +#define INTERNAL_CATCH_REMOVE_PARENS_10_ARG(_0, _1, _2, _3, _4, _5, _6, _7, _8, _9) INTERNAL_CATCH_REMOVE_PARENS(_0), INTERNAL_CATCH_REMOVE_PARENS_9_ARG(_1, _2, _3, _4, _5, _6, _7, _8, _9) +#define INTERNAL_CATCH_REMOVE_PARENS_11_ARG(_0, _1, _2, _3, _4, _5, _6, _7, _8, _9, _10) INTERNAL_CATCH_REMOVE_PARENS(_0), INTERNAL_CATCH_REMOVE_PARENS_10_ARG(_1, _2, _3, _4, _5, _6, _7, _8, _9, _10) + +#define INTERNAL_CATCH_VA_NARGS_IMPL(_0, _1, _2, _3, _4, _5, _6, _7, _8, _9, _10, N, ...) N + +#define INTERNAL_CATCH_TYPE_GEN \ + template \ + struct TypeList {}; \ + template \ + constexpr auto get_wrapper(Catch::Detail::priority_tag<1>) noexcept -> TypeList { \ + return {}; \ + } \ + template class...> \ + struct TemplateTypeList {}; \ + template class... Cs> \ + constexpr auto get_wrapper(Catch::Detail::priority_tag<1>) noexcept -> TemplateTypeList { \ + return {}; \ + } \ + template \ + struct append; \ + template \ + struct rewrap; \ + template class, typename...> \ + struct create; \ + template class, typename> \ + struct convert; \ + \ + template \ + struct append { \ + using type = T; \ + }; \ + template class L1, typename... E1, template class L2, typename... E2, typename... Rest> \ + struct append, L2, Rest...> { \ + using type = typename append, Rest...>::type; \ + }; \ + template class L1, typename... E1, typename... Rest> \ + struct append, TypeList, Rest...> { \ + using type = L1; \ + }; \ + \ + template class Container, template class List, typename... elems> \ + struct rewrap, List> { \ + using type = TypeList>; \ + }; \ + template class Container, template class List, class... Elems, typename... Elements> \ + struct rewrap, List, Elements...> { \ + using type = typename append>, typename rewrap, Elements...>::type>::type; \ + }; \ + \ + template class Final, template class... Containers, typename... Types> \ + struct create, TypeList> { \ + using type = typename append, typename rewrap, Types...>::type...>::type; \ + }; \ + template class Final, template class List, typename... Ts> \ + struct convert> { \ + using type = typename append, TypeList...>::type; \ + }; + +#define INTERNAL_CATCH_NTTP_1(signature, ...) \ + template \ + struct Nttp {}; \ + template \ + constexpr auto get_wrapper(Catch::Detail::priority_tag<0>) noexcept -> Nttp<__VA_ARGS__> { \ + return {}; \ + } \ + template class...> \ + struct NttpTemplateTypeList {}; \ + template class... Cs> \ + constexpr auto get_wrapper(Catch::Detail::priority_tag<0>) noexcept -> NttpTemplateTypeList { \ + return {}; \ + } \ + \ + template class Container, template class List, INTERNAL_CATCH_REMOVE_PARENS(signature)> \ + struct rewrap, List<__VA_ARGS__>> { \ + using type = TypeList>; \ + }; \ + template class Container, template class List, INTERNAL_CATCH_REMOVE_PARENS(signature), typename... Elements> \ + struct rewrap, List<__VA_ARGS__>, Elements...> { \ + using type = typename append>, typename rewrap, Elements...>::type>::type; \ + }; \ + template class Final, template class... Containers, typename... Types> \ + struct create, TypeList> { \ + using type = typename append, typename rewrap, Types...>::type...>::type; \ + }; + +#define INTERNAL_CATCH_DECLARE_SIG_TEST0(TestName) +#define INTERNAL_CATCH_DECLARE_SIG_TEST1(TestName, signature) \ + template \ + static void TestName() +#define INTERNAL_CATCH_DECLARE_SIG_TEST_X(TestName, signature, ...) \ + template \ + static void TestName() + +#define INTERNAL_CATCH_DEFINE_SIG_TEST0(TestName) +#define INTERNAL_CATCH_DEFINE_SIG_TEST1(TestName, signature) \ + template \ + static void TestName() +#define INTERNAL_CATCH_DEFINE_SIG_TEST_X(TestName, signature, ...) \ + template \ + static void TestName() + +#define INTERNAL_CATCH_TYPES_REGISTER(TestFunc) \ + template \ + void reg_test(TypeList, Catch::NameAndTags nameAndTags) { \ + Catch::AutoReg(Catch::makeTestInvoker(&TestFunc), CATCH_INTERNAL_LINEINFO, Catch::StringRef(), nameAndTags); \ + } + +#define INTERNAL_CATCH_NTTP_REGISTER0(TestFunc, signature, ...) +#define INTERNAL_CATCH_NTTP_REGISTER(TestFunc, signature, ...) \ + template \ + void reg_test(Nttp<__VA_ARGS__>, Catch::NameAndTags nameAndTags) { \ + Catch::AutoReg(Catch::makeTestInvoker(&TestFunc<__VA_ARGS__>), CATCH_INTERNAL_LINEINFO, Catch::StringRef(), nameAndTags); \ + } + +#define INTERNAL_CATCH_NTTP_REGISTER_METHOD0(TestName, signature, ...) \ + template \ + void reg_test(TypeList, Catch::StringRef className, Catch::NameAndTags nameAndTags) { \ + Catch::AutoReg(Catch::makeTestInvoker(&TestName::test), CATCH_INTERNAL_LINEINFO, className, nameAndTags); \ + } + +#define INTERNAL_CATCH_NTTP_REGISTER_METHOD(TestName, signature, ...) \ + template \ + void reg_test(Nttp<__VA_ARGS__>, Catch::StringRef className, Catch::NameAndTags nameAndTags) { \ + Catch::AutoReg(Catch::makeTestInvoker(&TestName<__VA_ARGS__>::test), CATCH_INTERNAL_LINEINFO, className, nameAndTags); \ + } + +#define INTERNAL_CATCH_DECLARE_SIG_TEST_METHOD0(TestName, ClassName) +#define INTERNAL_CATCH_DECLARE_SIG_TEST_METHOD1(TestName, ClassName, signature) \ + template \ + struct TestName : INTERNAL_CATCH_REMOVE_PARENS(ClassName) { \ + void test(); \ + } + +#define INTERNAL_CATCH_DECLARE_SIG_TEST_METHOD_X(TestName, ClassName, signature, ...) \ + template \ + struct TestName : INTERNAL_CATCH_REMOVE_PARENS(ClassName)<__VA_ARGS__> { \ + void test(); \ + } + +#define INTERNAL_CATCH_DEFINE_SIG_TEST_METHOD0(TestName) +#define INTERNAL_CATCH_DEFINE_SIG_TEST_METHOD1(TestName, signature) \ + template \ + void INTERNAL_CATCH_MAKE_NAMESPACE(TestName)::TestName::test() +#define INTERNAL_CATCH_DEFINE_SIG_TEST_METHOD_X(TestName, signature, ...) \ + template \ + void INTERNAL_CATCH_MAKE_NAMESPACE(TestName)::TestName<__VA_ARGS__>::test() + +#ifndef CATCH_CONFIG_TRADITIONAL_MSVC_PREPROCESSOR +#define INTERNAL_CATCH_NTTP_0 +#define INTERNAL_CATCH_NTTP_GEN(...) INTERNAL_CATCH_VA_NARGS_IMPL(__VA_ARGS__, INTERNAL_CATCH_NTTP_1(__VA_ARGS__), INTERNAL_CATCH_NTTP_1(__VA_ARGS__), INTERNAL_CATCH_NTTP_1(__VA_ARGS__), INTERNAL_CATCH_NTTP_1(__VA_ARGS__), INTERNAL_CATCH_NTTP_1(__VA_ARGS__), INTERNAL_CATCH_NTTP_1(__VA_ARGS__), INTERNAL_CATCH_NTTP_1(__VA_ARGS__), INTERNAL_CATCH_NTTP_1(__VA_ARGS__), INTERNAL_CATCH_NTTP_1(__VA_ARGS__), INTERNAL_CATCH_NTTP_1(__VA_ARGS__), INTERNAL_CATCH_NTTP_0) +#define INTERNAL_CATCH_DEFINE_SIG_TEST_METHOD(TestName, ...) INTERNAL_CATCH_VA_NARGS_IMPL("dummy", __VA_ARGS__, INTERNAL_CATCH_DEFINE_SIG_TEST_METHOD_X, INTERNAL_CATCH_DEFINE_SIG_TEST_METHOD_X, INTERNAL_CATCH_DEFINE_SIG_TEST_METHOD_X, INTERNAL_CATCH_DEFINE_SIG_TEST_METHOD_X, INTERNAL_CATCH_DEFINE_SIG_TEST_METHOD_X, INTERNAL_CATCH_DEFINE_SIG_TEST_METHOD_X, INTERNAL_CATCH_DEFINE_SIG_TEST_METHOD_X, INTERNAL_CATCH_DEFINE_SIG_TEST_METHOD_X, INTERNAL_CATCH_DEFINE_SIG_TEST_METHOD_X, INTERNAL_CATCH_DEFINE_SIG_TEST_METHOD1, INTERNAL_CATCH_DEFINE_SIG_TEST_METHOD0)(TestName, __VA_ARGS__) +#define INTERNAL_CATCH_DECLARE_SIG_TEST_METHOD(TestName, ClassName, ...) INTERNAL_CATCH_VA_NARGS_IMPL("dummy", __VA_ARGS__, INTERNAL_CATCH_DECLARE_SIG_TEST_METHOD_X, INTERNAL_CATCH_DECLARE_SIG_TEST_METHOD_X, INTERNAL_CATCH_DECLARE_SIG_TEST_METHOD_X, INTERNAL_CATCH_DECLARE_SIG_TEST_METHOD_X, INTERNAL_CATCH_DECLARE_SIG_TEST_METHOD_X, INTERNAL_CATCH_DECLARE_SIG_TEST_METHOD_X, INTERNAL_CATCH_DECLARE_SIG_TEST_METHOD_X, INTERNAL_CATCH_DECLARE_SIG_TEST_METHOD_X, INTERNAL_CATCH_DECLARE_SIG_TEST_METHOD_X, INTERNAL_CATCH_DECLARE_SIG_TEST_METHOD1, INTERNAL_CATCH_DECLARE_SIG_TEST_METHOD0)(TestName, ClassName, __VA_ARGS__) +#define INTERNAL_CATCH_NTTP_REG_METHOD_GEN(TestName, ...) INTERNAL_CATCH_VA_NARGS_IMPL("dummy", __VA_ARGS__, INTERNAL_CATCH_NTTP_REGISTER_METHOD, INTERNAL_CATCH_NTTP_REGISTER_METHOD, INTERNAL_CATCH_NTTP_REGISTER_METHOD, INTERNAL_CATCH_NTTP_REGISTER_METHOD, INTERNAL_CATCH_NTTP_REGISTER_METHOD, INTERNAL_CATCH_NTTP_REGISTER_METHOD, INTERNAL_CATCH_NTTP_REGISTER_METHOD, INTERNAL_CATCH_NTTP_REGISTER_METHOD, INTERNAL_CATCH_NTTP_REGISTER_METHOD, INTERNAL_CATCH_NTTP_REGISTER_METHOD0, INTERNAL_CATCH_NTTP_REGISTER_METHOD0)(TestName, __VA_ARGS__) +#define INTERNAL_CATCH_NTTP_REG_GEN(TestFunc, ...) INTERNAL_CATCH_TYPES_REGISTER(TestFunc) INTERNAL_CATCH_VA_NARGS_IMPL("dummy", __VA_ARGS__, INTERNAL_CATCH_NTTP_REGISTER, INTERNAL_CATCH_NTTP_REGISTER, INTERNAL_CATCH_NTTP_REGISTER, INTERNAL_CATCH_NTTP_REGISTER, INTERNAL_CATCH_NTTP_REGISTER, INTERNAL_CATCH_NTTP_REGISTER, INTERNAL_CATCH_NTTP_REGISTER, INTERNAL_CATCH_NTTP_REGISTER, INTERNAL_CATCH_NTTP_REGISTER, INTERNAL_CATCH_NTTP_REGISTER0, INTERNAL_CATCH_NTTP_REGISTER0)(TestFunc, __VA_ARGS__) +#define INTERNAL_CATCH_DEFINE_SIG_TEST(TestName, ...) INTERNAL_CATCH_VA_NARGS_IMPL("dummy", __VA_ARGS__, INTERNAL_CATCH_DEFINE_SIG_TEST_X, INTERNAL_CATCH_DEFINE_SIG_TEST_X, INTERNAL_CATCH_DEFINE_SIG_TEST_X, INTERNAL_CATCH_DEFINE_SIG_TEST_X, INTERNAL_CATCH_DEFINE_SIG_TEST_X, INTERNAL_CATCH_DEFINE_SIG_TEST_X, INTERNAL_CATCH_DEFINE_SIG_TEST_X, INTERNAL_CATCH_DEFINE_SIG_TEST_X, INTERNAL_CATCH_DEFINE_SIG_TEST_X, INTERNAL_CATCH_DEFINE_SIG_TEST1, INTERNAL_CATCH_DEFINE_SIG_TEST0)(TestName, __VA_ARGS__) +#define INTERNAL_CATCH_DECLARE_SIG_TEST(TestName, ...) INTERNAL_CATCH_VA_NARGS_IMPL("dummy", __VA_ARGS__, INTERNAL_CATCH_DECLARE_SIG_TEST_X, INTERNAL_CATCH_DECLARE_SIG_TEST_X, INTERNAL_CATCH_DECLARE_SIG_TEST_X, INTERNAL_CATCH_DECLARE_SIG_TEST_X, INTERNAL_CATCH_DECLARE_SIG_TEST_X, INTERNAL_CATCH_DECLARE_SIG_TEST_X, INTERNAL_CATCH_DEFINE_SIG_TEST_X, INTERNAL_CATCH_DECLARE_SIG_TEST_X, INTERNAL_CATCH_DECLARE_SIG_TEST_X, INTERNAL_CATCH_DECLARE_SIG_TEST1, INTERNAL_CATCH_DECLARE_SIG_TEST0)(TestName, __VA_ARGS__) +#define INTERNAL_CATCH_REMOVE_PARENS_GEN(...) INTERNAL_CATCH_VA_NARGS_IMPL(__VA_ARGS__, INTERNAL_CATCH_REMOVE_PARENS_11_ARG, INTERNAL_CATCH_REMOVE_PARENS_10_ARG, INTERNAL_CATCH_REMOVE_PARENS_9_ARG, INTERNAL_CATCH_REMOVE_PARENS_8_ARG, INTERNAL_CATCH_REMOVE_PARENS_7_ARG, INTERNAL_CATCH_REMOVE_PARENS_6_ARG, INTERNAL_CATCH_REMOVE_PARENS_5_ARG, INTERNAL_CATCH_REMOVE_PARENS_4_ARG, INTERNAL_CATCH_REMOVE_PARENS_3_ARG, INTERNAL_CATCH_REMOVE_PARENS_2_ARG, INTERNAL_CATCH_REMOVE_PARENS_1_ARG)(__VA_ARGS__) +#else +#define INTERNAL_CATCH_NTTP_0(signature) +#define INTERNAL_CATCH_NTTP_GEN(...) INTERNAL_CATCH_EXPAND_VARGS(INTERNAL_CATCH_VA_NARGS_IMPL(__VA_ARGS__, INTERNAL_CATCH_NTTP_1, INTERNAL_CATCH_NTTP_1, INTERNAL_CATCH_NTTP_1, INTERNAL_CATCH_NTTP_1, INTERNAL_CATCH_NTTP_1, INTERNAL_CATCH_NTTP_1, INTERNAL_CATCH_NTTP_1, INTERNAL_CATCH_NTTP_1, INTERNAL_CATCH_NTTP_1, INTERNAL_CATCH_NTTP_1, INTERNAL_CATCH_NTTP_0)(__VA_ARGS__)) +#define INTERNAL_CATCH_DEFINE_SIG_TEST_METHOD(TestName, ...) INTERNAL_CATCH_EXPAND_VARGS(INTERNAL_CATCH_VA_NARGS_IMPL("dummy", __VA_ARGS__, INTERNAL_CATCH_DEFINE_SIG_TEST_METHOD_X, INTERNAL_CATCH_DEFINE_SIG_TEST_METHOD_X, INTERNAL_CATCH_DEFINE_SIG_TEST_METHOD_X, INTERNAL_CATCH_DEFINE_SIG_TEST_METHOD_X, INTERNAL_CATCH_DEFINE_SIG_TEST_METHOD_X, INTERNAL_CATCH_DEFINE_SIG_TEST_METHOD_X, INTERNAL_CATCH_DEFINE_SIG_TEST_METHOD_X, INTERNAL_CATCH_DEFINE_SIG_TEST_METHOD_X, INTERNAL_CATCH_DEFINE_SIG_TEST_METHOD_X, INTERNAL_CATCH_DEFINE_SIG_TEST_METHOD1, INTERNAL_CATCH_DEFINE_SIG_TEST_METHOD0)(TestName, __VA_ARGS__)) +#define INTERNAL_CATCH_DECLARE_SIG_TEST_METHOD(TestName, ClassName, ...) INTERNAL_CATCH_EXPAND_VARGS(INTERNAL_CATCH_VA_NARGS_IMPL("dummy", __VA_ARGS__, INTERNAL_CATCH_DECLARE_SIG_TEST_METHOD_X, INTERNAL_CATCH_DECLARE_SIG_TEST_METHOD_X, INTERNAL_CATCH_DECLARE_SIG_TEST_METHOD_X, INTERNAL_CATCH_DECLARE_SIG_TEST_METHOD_X, INTERNAL_CATCH_DECLARE_SIG_TEST_METHOD_X, INTERNAL_CATCH_DECLARE_SIG_TEST_METHOD_X, INTERNAL_CATCH_DECLARE_SIG_TEST_METHOD_X, INTERNAL_CATCH_DECLARE_SIG_TEST_METHOD_X, INTERNAL_CATCH_DECLARE_SIG_TEST_METHOD_X, INTERNAL_CATCH_DECLARE_SIG_TEST_METHOD1, INTERNAL_CATCH_DECLARE_SIG_TEST_METHOD0)(TestName, ClassName, __VA_ARGS__)) +#define INTERNAL_CATCH_NTTP_REG_METHOD_GEN(TestName, ...) INTERNAL_CATCH_EXPAND_VARGS(INTERNAL_CATCH_VA_NARGS_IMPL("dummy", __VA_ARGS__, INTERNAL_CATCH_NTTP_REGISTER_METHOD, INTERNAL_CATCH_NTTP_REGISTER_METHOD, INTERNAL_CATCH_NTTP_REGISTER_METHOD, INTERNAL_CATCH_NTTP_REGISTER_METHOD, INTERNAL_CATCH_NTTP_REGISTER_METHOD, INTERNAL_CATCH_NTTP_REGISTER_METHOD, INTERNAL_CATCH_NTTP_REGISTER_METHOD, INTERNAL_CATCH_NTTP_REGISTER_METHOD, INTERNAL_CATCH_NTTP_REGISTER_METHOD, INTERNAL_CATCH_NTTP_REGISTER_METHOD0, INTERNAL_CATCH_NTTP_REGISTER_METHOD0)(TestName, __VA_ARGS__)) +#define INTERNAL_CATCH_NTTP_REG_GEN(TestFunc, ...) INTERNAL_CATCH_TYPES_REGISTER(TestFunc) INTERNAL_CATCH_EXPAND_VARGS(INTERNAL_CATCH_VA_NARGS_IMPL("dummy", __VA_ARGS__, INTERNAL_CATCH_NTTP_REGISTER, INTERNAL_CATCH_NTTP_REGISTER, INTERNAL_CATCH_NTTP_REGISTER, INTERNAL_CATCH_NTTP_REGISTER, INTERNAL_CATCH_NTTP_REGISTER, INTERNAL_CATCH_NTTP_REGISTER, INTERNAL_CATCH_NTTP_REGISTER, INTERNAL_CATCH_NTTP_REGISTER, INTERNAL_CATCH_NTTP_REGISTER, INTERNAL_CATCH_NTTP_REGISTER0, INTERNAL_CATCH_NTTP_REGISTER0)(TestFunc, __VA_ARGS__)) +#define INTERNAL_CATCH_DEFINE_SIG_TEST(TestName, ...) INTERNAL_CATCH_EXPAND_VARGS(INTERNAL_CATCH_VA_NARGS_IMPL("dummy", __VA_ARGS__, INTERNAL_CATCH_DEFINE_SIG_TEST_X, INTERNAL_CATCH_DEFINE_SIG_TEST_X, INTERNAL_CATCH_DEFINE_SIG_TEST_X, INTERNAL_CATCH_DEFINE_SIG_TEST_X, INTERNAL_CATCH_DEFINE_SIG_TEST_X, INTERNAL_CATCH_DEFINE_SIG_TEST_X, INTERNAL_CATCH_DEFINE_SIG_TEST_X, INTERNAL_CATCH_DEFINE_SIG_TEST_X, INTERNAL_CATCH_DEFINE_SIG_TEST_X, INTERNAL_CATCH_DEFINE_SIG_TEST1, INTERNAL_CATCH_DEFINE_SIG_TEST0)(TestName, __VA_ARGS__)) +#define INTERNAL_CATCH_DECLARE_SIG_TEST(TestName, ...) INTERNAL_CATCH_EXPAND_VARGS(INTERNAL_CATCH_VA_NARGS_IMPL("dummy", __VA_ARGS__, INTERNAL_CATCH_DECLARE_SIG_TEST_X, INTERNAL_CATCH_DECLARE_SIG_TEST_X, INTERNAL_CATCH_DECLARE_SIG_TEST_X, INTERNAL_CATCH_DECLARE_SIG_TEST_X, INTERNAL_CATCH_DECLARE_SIG_TEST_X, INTERNAL_CATCH_DECLARE_SIG_TEST_X, INTERNAL_CATCH_DEFINE_SIG_TEST_X, INTERNAL_CATCH_DECLARE_SIG_TEST_X, INTERNAL_CATCH_DECLARE_SIG_TEST_X, INTERNAL_CATCH_DECLARE_SIG_TEST1, INTERNAL_CATCH_DECLARE_SIG_TEST0)(TestName, __VA_ARGS__)) +#define INTERNAL_CATCH_REMOVE_PARENS_GEN(...) INTERNAL_CATCH_EXPAND_VARGS(INTERNAL_CATCH_VA_NARGS_IMPL(__VA_ARGS__, INTERNAL_CATCH_REMOVE_PARENS_11_ARG, INTERNAL_CATCH_REMOVE_PARENS_10_ARG, INTERNAL_CATCH_REMOVE_PARENS_9_ARG, INTERNAL_CATCH_REMOVE_PARENS_8_ARG, INTERNAL_CATCH_REMOVE_PARENS_7_ARG, INTERNAL_CATCH_REMOVE_PARENS_6_ARG, INTERNAL_CATCH_REMOVE_PARENS_5_ARG, INTERNAL_CATCH_REMOVE_PARENS_4_ARG, INTERNAL_CATCH_REMOVE_PARENS_3_ARG, INTERNAL_CATCH_REMOVE_PARENS_2_ARG, INTERNAL_CATCH_REMOVE_PARENS_1_ARG)(__VA_ARGS__)) +#endif + +#endif // CATCH_PREPROCESSOR_HPP_INCLUDED + +// GCC 5 and older do not properly handle disabling unused-variable warning +// with a _Pragma. This means that we have to leak the suppression to the +// user code as well :-( +#if defined(__GNUC__) && !defined(__clang__) && __GNUC__ <= 5 +#pragma GCC diagnostic ignored "-Wunused-variable" +#endif + +#if defined(CATCH_CONFIG_DISABLE) +#define INTERNAL_CATCH_TEMPLATE_TEST_CASE_NO_REGISTRATION_2(TestName, TestFunc, Name, Tags, Signature, ...) \ + INTERNAL_CATCH_DEFINE_SIG_TEST(TestFunc, INTERNAL_CATCH_REMOVE_PARENS(Signature)) +#define INTERNAL_CATCH_TEMPLATE_TEST_CASE_METHOD_NO_REGISTRATION_2(TestNameClass, TestName, ClassName, Name, Tags, Signature, ...) \ + namespace { \ + namespace INTERNAL_CATCH_MAKE_NAMESPACE(TestName) { \ + INTERNAL_CATCH_DECLARE_SIG_TEST_METHOD(TestName, ClassName, INTERNAL_CATCH_REMOVE_PARENS(Signature)); \ + } \ + } \ + INTERNAL_CATCH_DEFINE_SIG_TEST_METHOD(TestName, INTERNAL_CATCH_REMOVE_PARENS(Signature)) + +#ifndef CATCH_CONFIG_TRADITIONAL_MSVC_PREPROCESSOR +#define INTERNAL_CATCH_TEMPLATE_TEST_CASE_NO_REGISTRATION(Name, Tags, ...) \ + INTERNAL_CATCH_TEMPLATE_TEST_CASE_NO_REGISTRATION_2(INTERNAL_CATCH_UNIQUE_NAME(CATCH2_INTERNAL_TEMPLATE_TEST_), INTERNAL_CATCH_UNIQUE_NAME(CATCH2_INTERNAL_TEMPLATE_TEST_), Name, Tags, typename TestType, __VA_ARGS__) +#else +#define INTERNAL_CATCH_TEMPLATE_TEST_CASE_NO_REGISTRATION(Name, Tags, ...) \ + INTERNAL_CATCH_EXPAND_VARGS(INTERNAL_CATCH_TEMPLATE_TEST_CASE_NO_REGISTRATION_2(INTERNAL_CATCH_UNIQUE_NAME(CATCH2_INTERNAL_TEMPLATE_TEST_), INTERNAL_CATCH_UNIQUE_NAME(CATCH2_INTERNAL_TEMPLATE_TEST_), Name, Tags, typename TestType, __VA_ARGS__)) +#endif + +#ifndef CATCH_CONFIG_TRADITIONAL_MSVC_PREPROCESSOR +#define INTERNAL_CATCH_TEMPLATE_TEST_CASE_SIG_NO_REGISTRATION(Name, Tags, Signature, ...) \ + INTERNAL_CATCH_TEMPLATE_TEST_CASE_NO_REGISTRATION_2(INTERNAL_CATCH_UNIQUE_NAME(CATCH2_INTERNAL_TEMPLATE_TEST_), INTERNAL_CATCH_UNIQUE_NAME(CATCH2_INTERNAL_TEMPLATE_TEST_), Name, Tags, Signature, __VA_ARGS__) +#else +#define INTERNAL_CATCH_TEMPLATE_TEST_CASE_SIG_NO_REGISTRATION(Name, Tags, Signature, ...) \ + INTERNAL_CATCH_EXPAND_VARGS(INTERNAL_CATCH_TEMPLATE_TEST_CASE_NO_REGISTRATION_2(INTERNAL_CATCH_UNIQUE_NAME(CATCH2_INTERNAL_TEMPLATE_TEST_), INTERNAL_CATCH_UNIQUE_NAME(CATCH2_INTERNAL_TEMPLATE_TEST_), Name, Tags, Signature, __VA_ARGS__)) +#endif + +#ifndef CATCH_CONFIG_TRADITIONAL_MSVC_PREPROCESSOR +#define INTERNAL_CATCH_TEMPLATE_TEST_CASE_METHOD_NO_REGISTRATION(ClassName, Name, Tags, ...) \ + INTERNAL_CATCH_TEMPLATE_TEST_CASE_METHOD_NO_REGISTRATION_2(INTERNAL_CATCH_UNIQUE_NAME(CATCH2_INTERNAL_TEMPLATE_TEST_CLASS_), INTERNAL_CATCH_UNIQUE_NAME(CATCH2_INTERNAL_TEMPLATE_TEST_), ClassName, Name, Tags, typename T, __VA_ARGS__) +#else +#define INTERNAL_CATCH_TEMPLATE_TEST_CASE_METHOD_NO_REGISTRATION(ClassName, Name, Tags, ...) \ + INTERNAL_CATCH_EXPAND_VARGS(INTERNAL_CATCH_TEMPLATE_TEST_CASE_METHOD_NO_REGISTRATION_2(INTERNAL_CATCH_UNIQUE_NAME(CATCH2_INTERNAL_TEMPLATE_TEST_CLASS_), INTERNAL_CATCH_UNIQUE_NAME(CATCH2_INTERNAL_TEMPLATE_TEST_), ClassName, Name, Tags, typename T, __VA_ARGS__)) +#endif + +#ifndef CATCH_CONFIG_TRADITIONAL_MSVC_PREPROCESSOR +#define INTERNAL_CATCH_TEMPLATE_TEST_CASE_METHOD_SIG_NO_REGISTRATION(ClassName, Name, Tags, Signature, ...) \ + INTERNAL_CATCH_TEMPLATE_TEST_CASE_METHOD_NO_REGISTRATION_2(INTERNAL_CATCH_UNIQUE_NAME(CATCH2_INTERNAL_TEMPLATE_TEST_CLASS_), INTERNAL_CATCH_UNIQUE_NAME(CATCH2_INTERNAL_TEMPLATE_TEST_), ClassName, Name, Tags, Signature, __VA_ARGS__) +#else +#define INTERNAL_CATCH_TEMPLATE_TEST_CASE_METHOD_SIG_NO_REGISTRATION(ClassName, Name, Tags, Signature, ...) \ + INTERNAL_CATCH_EXPAND_VARGS(INTERNAL_CATCH_TEMPLATE_TEST_CASE_METHOD_NO_REGISTRATION_2(INTERNAL_CATCH_UNIQUE_NAME(CATCH2_INTERNAL_TEMPLATE_TEST_CLASS_), INTERNAL_CATCH_UNIQUE_NAME(CATCH2_INTERNAL_TEMPLATE_TEST_), ClassName, Name, Tags, Signature, __VA_ARGS__)) +#endif +#endif + +/////////////////////////////////////////////////////////////////////////////// +#define INTERNAL_CATCH_TEMPLATE_TEST_CASE_2(TestName, TestFunc, Name, Tags, Signature, ...) \ + CATCH_INTERNAL_START_WARNINGS_SUPPRESSION \ + CATCH_INTERNAL_SUPPRESS_GLOBALS_WARNINGS \ + CATCH_INTERNAL_SUPPRESS_ZERO_VARIADIC_WARNINGS \ + CATCH_INTERNAL_SUPPRESS_UNUSED_TEMPLATE_WARNINGS \ + CATCH_INTERNAL_SUPPRESS_UNUSED_VARIABLE_WARNINGS \ + CATCH_INTERNAL_SUPPRESS_COMMA_WARNINGS \ + INTERNAL_CATCH_DECLARE_SIG_TEST(TestFunc, INTERNAL_CATCH_REMOVE_PARENS(Signature)); \ + namespace { \ + namespace INTERNAL_CATCH_MAKE_NAMESPACE(TestName) { \ + INTERNAL_CATCH_TYPE_GEN \ + INTERNAL_CATCH_NTTP_GEN(INTERNAL_CATCH_REMOVE_PARENS(Signature)) \ + INTERNAL_CATCH_NTTP_REG_GEN(TestFunc, INTERNAL_CATCH_REMOVE_PARENS(Signature)) \ + template \ + struct TestName { \ + TestName() { \ + size_t index = 0; \ + constexpr char const *tmpl_types[] = {CATCH_REC_LIST(INTERNAL_CATCH_STRINGIZE_WITHOUT_PARENS, __VA_ARGS__)}; /* NOLINT(cppcoreguidelines-avoid-c-arrays,modernize-avoid-c-arrays,hicpp-avoid-c-arrays) */ \ + using expander = size_t[]; /* NOLINT(cppcoreguidelines-avoid-c-arrays,modernize-avoid-c-arrays,hicpp-avoid-c-arrays) */ \ + (void)expander{(reg_test(Types{}, Catch::NameAndTags{Name " - " + std::string(tmpl_types[index]), Tags}), index++)...}; /* NOLINT */ \ + } \ + }; \ + static const int INTERNAL_CATCH_UNIQUE_NAME(globalRegistrar) = []() { \ + TestName(); \ + return 0; \ + }(); \ + } \ + } \ + CATCH_INTERNAL_STOP_WARNINGS_SUPPRESSION \ + INTERNAL_CATCH_DEFINE_SIG_TEST(TestFunc, INTERNAL_CATCH_REMOVE_PARENS(Signature)) + +#ifndef CATCH_CONFIG_TRADITIONAL_MSVC_PREPROCESSOR +#define INTERNAL_CATCH_TEMPLATE_TEST_CASE(Name, Tags, ...) \ + INTERNAL_CATCH_TEMPLATE_TEST_CASE_2(INTERNAL_CATCH_UNIQUE_NAME(CATCH2_INTERNAL_TEMPLATE_TEST_), INTERNAL_CATCH_UNIQUE_NAME(CATCH2_INTERNAL_TEMPLATE_TEST_), Name, Tags, typename TestType, __VA_ARGS__) +#else +#define INTERNAL_CATCH_TEMPLATE_TEST_CASE(Name, Tags, ...) \ + INTERNAL_CATCH_EXPAND_VARGS(INTERNAL_CATCH_TEMPLATE_TEST_CASE_2(INTERNAL_CATCH_UNIQUE_NAME(CATCH2_INTERNAL_TEMPLATE_TEST_), INTERNAL_CATCH_UNIQUE_NAME(CATCH2_INTERNAL_TEMPLATE_TEST_), Name, Tags, typename TestType, __VA_ARGS__)) +#endif + +#ifndef CATCH_CONFIG_TRADITIONAL_MSVC_PREPROCESSOR +#define INTERNAL_CATCH_TEMPLATE_TEST_CASE_SIG(Name, Tags, Signature, ...) \ + INTERNAL_CATCH_TEMPLATE_TEST_CASE_2(INTERNAL_CATCH_UNIQUE_NAME(CATCH2_INTERNAL_TEMPLATE_TEST_), INTERNAL_CATCH_UNIQUE_NAME(CATCH2_INTERNAL_TEMPLATE_TEST_), Name, Tags, Signature, __VA_ARGS__) +#else +#define INTERNAL_CATCH_TEMPLATE_TEST_CASE_SIG(Name, Tags, Signature, ...) \ + INTERNAL_CATCH_EXPAND_VARGS(INTERNAL_CATCH_TEMPLATE_TEST_CASE_2(INTERNAL_CATCH_UNIQUE_NAME(CATCH2_INTERNAL_TEMPLATE_TEST_), INTERNAL_CATCH_UNIQUE_NAME(CATCH2_INTERNAL_TEMPLATE_TEST_), Name, Tags, Signature, __VA_ARGS__)) +#endif + +#define INTERNAL_CATCH_TEMPLATE_PRODUCT_TEST_CASE2(TestName, TestFuncName, Name, Tags, Signature, TmplTypes, TypesList) \ + CATCH_INTERNAL_START_WARNINGS_SUPPRESSION \ + CATCH_INTERNAL_SUPPRESS_GLOBALS_WARNINGS \ + CATCH_INTERNAL_SUPPRESS_ZERO_VARIADIC_WARNINGS \ + CATCH_INTERNAL_SUPPRESS_UNUSED_TEMPLATE_WARNINGS \ + CATCH_INTERNAL_SUPPRESS_UNUSED_VARIABLE_WARNINGS \ + CATCH_INTERNAL_SUPPRESS_COMMA_WARNINGS \ + template \ + static void TestFuncName(); \ + namespace { \ + namespace INTERNAL_CATCH_MAKE_NAMESPACE(TestName) { \ + INTERNAL_CATCH_TYPE_GEN \ + INTERNAL_CATCH_NTTP_GEN(INTERNAL_CATCH_REMOVE_PARENS(Signature)) \ + template \ + struct TestName { \ + void reg_tests() { \ + size_t index = 0; \ + using expander = size_t[]; \ + constexpr char const *tmpl_types[] = {CATCH_REC_LIST(INTERNAL_CATCH_STRINGIZE_WITHOUT_PARENS, INTERNAL_CATCH_REMOVE_PARENS(TmplTypes))}; \ + constexpr char const *types_list[] = {CATCH_REC_LIST(INTERNAL_CATCH_STRINGIZE_WITHOUT_PARENS, INTERNAL_CATCH_REMOVE_PARENS(TypesList))}; \ + constexpr auto num_types = sizeof(types_list) / sizeof(types_list[0]); \ + (void)expander{(Catch::AutoReg(Catch::makeTestInvoker(&TestFuncName), CATCH_INTERNAL_LINEINFO, Catch::StringRef(), Catch::NameAndTags{Name " - " + std::string(tmpl_types[index / num_types]) + '<' + types_list[index % num_types] + '>', Tags}), index++)...}; /* NOLINT */ \ + } \ + }; \ + static const int INTERNAL_CATCH_UNIQUE_NAME(globalRegistrar) = []() { \ + using TestInit = typename create(Catch::Detail::priority_tag<1>{})), TypeList>::type; \ + TestInit t; \ + t.reg_tests(); \ + return 0; \ + }(); \ + } \ + } \ + CATCH_INTERNAL_STOP_WARNINGS_SUPPRESSION \ + template \ + static void TestFuncName() + +#ifndef CATCH_CONFIG_TRADITIONAL_MSVC_PREPROCESSOR +#define INTERNAL_CATCH_TEMPLATE_PRODUCT_TEST_CASE(Name, Tags, ...) \ + INTERNAL_CATCH_TEMPLATE_PRODUCT_TEST_CASE2(INTERNAL_CATCH_UNIQUE_NAME(CATCH2_INTERNAL_TEMPLATE_TEST_), INTERNAL_CATCH_UNIQUE_NAME(CATCH2_INTERNAL_TEMPLATE_TEST_), Name, Tags, typename T, __VA_ARGS__) +#else +#define INTERNAL_CATCH_TEMPLATE_PRODUCT_TEST_CASE(Name, Tags, ...) \ + INTERNAL_CATCH_EXPAND_VARGS(INTERNAL_CATCH_TEMPLATE_PRODUCT_TEST_CASE2(INTERNAL_CATCH_UNIQUE_NAME(CATCH2_INTERNAL_TEMPLATE_TEST_), INTERNAL_CATCH_UNIQUE_NAME(CATCH2_INTERNAL_TEMPLATE_TEST_), Name, Tags, typename T, __VA_ARGS__)) +#endif + +#ifndef CATCH_CONFIG_TRADITIONAL_MSVC_PREPROCESSOR +#define INTERNAL_CATCH_TEMPLATE_PRODUCT_TEST_CASE_SIG(Name, Tags, Signature, ...) \ + INTERNAL_CATCH_TEMPLATE_PRODUCT_TEST_CASE2(INTERNAL_CATCH_UNIQUE_NAME(CATCH2_INTERNAL_TEMPLATE_TEST_), INTERNAL_CATCH_UNIQUE_NAME(CATCH2_INTERNAL_TEMPLATE_TEST_), Name, Tags, Signature, __VA_ARGS__) +#else +#define INTERNAL_CATCH_TEMPLATE_PRODUCT_TEST_CASE_SIG(Name, Tags, Signature, ...) \ + INTERNAL_CATCH_EXPAND_VARGS(INTERNAL_CATCH_TEMPLATE_PRODUCT_TEST_CASE2(INTERNAL_CATCH_UNIQUE_NAME(CATCH2_INTERNAL_TEMPLATE_TEST_), INTERNAL_CATCH_UNIQUE_NAME(CATCH2_INTERNAL_TEMPLATE_TEST_), Name, Tags, Signature, __VA_ARGS__)) +#endif + +#define INTERNAL_CATCH_TEMPLATE_LIST_TEST_CASE_2(TestName, TestFunc, Name, Tags, TmplList) \ + CATCH_INTERNAL_START_WARNINGS_SUPPRESSION \ + CATCH_INTERNAL_SUPPRESS_GLOBALS_WARNINGS \ + CATCH_INTERNAL_SUPPRESS_UNUSED_TEMPLATE_WARNINGS \ + CATCH_INTERNAL_SUPPRESS_UNUSED_VARIABLE_WARNINGS \ + CATCH_INTERNAL_SUPPRESS_COMMA_WARNINGS \ + template \ + static void TestFunc(); \ + namespace { \ + namespace INTERNAL_CATCH_MAKE_NAMESPACE(TestName) { \ + INTERNAL_CATCH_TYPE_GEN \ + template \ + struct TestName { \ + void reg_tests() { \ + size_t index = 0; \ + using expander = size_t[]; \ + (void)expander{(Catch::AutoReg(Catch::makeTestInvoker(&TestFunc), CATCH_INTERNAL_LINEINFO, Catch::StringRef(), Catch::NameAndTags{Name " - " INTERNAL_CATCH_STRINGIZE(TmplList) " - " + std::to_string(index), Tags}), index++)...}; /* NOLINT */ \ + } \ + }; \ + static const int INTERNAL_CATCH_UNIQUE_NAME(globalRegistrar) = []() { \ + using TestInit = typename convert::type; \ + TestInit t; \ + t.reg_tests(); \ + return 0; \ + }(); \ + } \ + } \ + CATCH_INTERNAL_STOP_WARNINGS_SUPPRESSION \ + template \ + static void TestFunc() + +#define INTERNAL_CATCH_TEMPLATE_LIST_TEST_CASE(Name, Tags, TmplList) \ + INTERNAL_CATCH_TEMPLATE_LIST_TEST_CASE_2(INTERNAL_CATCH_UNIQUE_NAME(CATCH2_INTERNAL_TEMPLATE_TEST_), INTERNAL_CATCH_UNIQUE_NAME(CATCH2_INTERNAL_TEMPLATE_TEST_), Name, Tags, TmplList) + +#define INTERNAL_CATCH_TEMPLATE_TEST_CASE_METHOD_2(TestNameClass, TestName, ClassName, Name, Tags, Signature, ...) \ + CATCH_INTERNAL_START_WARNINGS_SUPPRESSION \ + CATCH_INTERNAL_SUPPRESS_GLOBALS_WARNINGS \ + CATCH_INTERNAL_SUPPRESS_ZERO_VARIADIC_WARNINGS \ + CATCH_INTERNAL_SUPPRESS_UNUSED_TEMPLATE_WARNINGS \ + CATCH_INTERNAL_SUPPRESS_UNUSED_VARIABLE_WARNINGS \ + namespace { \ + namespace INTERNAL_CATCH_MAKE_NAMESPACE(TestName) { \ + INTERNAL_CATCH_TYPE_GEN \ + INTERNAL_CATCH_NTTP_GEN(INTERNAL_CATCH_REMOVE_PARENS(Signature)) \ + INTERNAL_CATCH_DECLARE_SIG_TEST_METHOD(TestName, ClassName, INTERNAL_CATCH_REMOVE_PARENS(Signature)); \ + INTERNAL_CATCH_NTTP_REG_METHOD_GEN(TestName, INTERNAL_CATCH_REMOVE_PARENS(Signature)) \ + template \ + struct TestNameClass { \ + TestNameClass() { \ + size_t index = 0; \ + constexpr char const *tmpl_types[] = {CATCH_REC_LIST(INTERNAL_CATCH_STRINGIZE_WITHOUT_PARENS, __VA_ARGS__)}; \ + using expander = size_t[]; \ + (void)expander{(reg_test(Types{}, #ClassName, Catch::NameAndTags{Name " - " + std::string(tmpl_types[index]), Tags}), index++)...}; /* NOLINT */ \ + } \ + }; \ + static const int INTERNAL_CATCH_UNIQUE_NAME(globalRegistrar) = []() { \ + TestNameClass(); \ + return 0; \ + }(); \ + } \ + } \ + CATCH_INTERNAL_STOP_WARNINGS_SUPPRESSION \ + INTERNAL_CATCH_DEFINE_SIG_TEST_METHOD(TestName, INTERNAL_CATCH_REMOVE_PARENS(Signature)) + +#ifndef CATCH_CONFIG_TRADITIONAL_MSVC_PREPROCESSOR +#define INTERNAL_CATCH_TEMPLATE_TEST_CASE_METHOD(ClassName, Name, Tags, ...) \ + INTERNAL_CATCH_TEMPLATE_TEST_CASE_METHOD_2(INTERNAL_CATCH_UNIQUE_NAME(CATCH2_INTERNAL_TEMPLATE_TEST_CLASS_), INTERNAL_CATCH_UNIQUE_NAME(CATCH2_INTERNAL_TEMPLATE_TEST_), ClassName, Name, Tags, typename T, __VA_ARGS__) +#else +#define INTERNAL_CATCH_TEMPLATE_TEST_CASE_METHOD(ClassName, Name, Tags, ...) \ + INTERNAL_CATCH_EXPAND_VARGS(INTERNAL_CATCH_TEMPLATE_TEST_CASE_METHOD_2(INTERNAL_CATCH_UNIQUE_NAME(CATCH2_INTERNAL_TEMPLATE_TEST_CLASS_), INTERNAL_CATCH_UNIQUE_NAME(CATCH2_INTERNAL_TEMPLATE_TEST_), ClassName, Name, Tags, typename T, __VA_ARGS__)) +#endif + +#ifndef CATCH_CONFIG_TRADITIONAL_MSVC_PREPROCESSOR +#define INTERNAL_CATCH_TEMPLATE_TEST_CASE_METHOD_SIG(ClassName, Name, Tags, Signature, ...) \ + INTERNAL_CATCH_TEMPLATE_TEST_CASE_METHOD_2(INTERNAL_CATCH_UNIQUE_NAME(CATCH2_INTERNAL_TEMPLATE_TEST_CLASS_), INTERNAL_CATCH_UNIQUE_NAME(CATCH2_INTERNAL_TEMPLATE_TEST_), ClassName, Name, Tags, Signature, __VA_ARGS__) +#else +#define INTERNAL_CATCH_TEMPLATE_TEST_CASE_METHOD_SIG(ClassName, Name, Tags, Signature, ...) \ + INTERNAL_CATCH_EXPAND_VARGS(INTERNAL_CATCH_TEMPLATE_TEST_CASE_METHOD_2(INTERNAL_CATCH_UNIQUE_NAME(CATCH2_INTERNAL_TEMPLATE_TEST_CLASS_), INTERNAL_CATCH_UNIQUE_NAME(CATCH2_INTERNAL_TEMPLATE_TEST_), ClassName, Name, Tags, Signature, __VA_ARGS__)) +#endif + +#define INTERNAL_CATCH_TEMPLATE_PRODUCT_TEST_CASE_METHOD_2(TestNameClass, TestName, ClassName, Name, Tags, Signature, TmplTypes, TypesList) \ + CATCH_INTERNAL_START_WARNINGS_SUPPRESSION \ + CATCH_INTERNAL_SUPPRESS_GLOBALS_WARNINGS \ + CATCH_INTERNAL_SUPPRESS_ZERO_VARIADIC_WARNINGS \ + CATCH_INTERNAL_SUPPRESS_UNUSED_TEMPLATE_WARNINGS \ + CATCH_INTERNAL_SUPPRESS_UNUSED_VARIABLE_WARNINGS \ + template \ + struct TestName : INTERNAL_CATCH_REMOVE_PARENS(ClassName) { \ + void test(); \ + }; \ + namespace { \ + namespace INTERNAL_CATCH_MAKE_NAMESPACE(TestNameClass) { \ + INTERNAL_CATCH_TYPE_GEN \ + INTERNAL_CATCH_NTTP_GEN(INTERNAL_CATCH_REMOVE_PARENS(Signature)) \ + template \ + struct TestNameClass { \ + void reg_tests() { \ + std::size_t index = 0; \ + using expander = std::size_t[]; \ + constexpr char const *tmpl_types[] = {CATCH_REC_LIST(INTERNAL_CATCH_STRINGIZE_WITHOUT_PARENS, INTERNAL_CATCH_REMOVE_PARENS(TmplTypes))}; \ + constexpr char const *types_list[] = {CATCH_REC_LIST(INTERNAL_CATCH_STRINGIZE_WITHOUT_PARENS, INTERNAL_CATCH_REMOVE_PARENS(TypesList))}; \ + constexpr auto num_types = sizeof(types_list) / sizeof(types_list[0]); \ + (void)expander{(Catch::AutoReg(Catch::makeTestInvoker(&TestName::test), CATCH_INTERNAL_LINEINFO, #ClassName, Catch::NameAndTags{Name " - " + std::string(tmpl_types[index / num_types]) + '<' + types_list[index % num_types] + '>', Tags}), index++)...}; /* NOLINT */ \ + } \ + }; \ + static const int INTERNAL_CATCH_UNIQUE_NAME(globalRegistrar) = []() { \ + using TestInit = typename create(Catch::Detail::priority_tag<1>{})), TypeList>::type; \ + TestInit t; \ + t.reg_tests(); \ + return 0; \ + }(); \ + } \ + } \ + CATCH_INTERNAL_STOP_WARNINGS_SUPPRESSION \ + template \ + void TestName::test() + +#ifndef CATCH_CONFIG_TRADITIONAL_MSVC_PREPROCESSOR +#define INTERNAL_CATCH_TEMPLATE_PRODUCT_TEST_CASE_METHOD(ClassName, Name, Tags, ...) \ + INTERNAL_CATCH_TEMPLATE_PRODUCT_TEST_CASE_METHOD_2(INTERNAL_CATCH_UNIQUE_NAME(CATCH2_INTERNAL_TEMPLATE_TEST_), INTERNAL_CATCH_UNIQUE_NAME(CATCH2_INTERNAL_TEMPLATE_TEST_), ClassName, Name, Tags, typename T, __VA_ARGS__) +#else +#define INTERNAL_CATCH_TEMPLATE_PRODUCT_TEST_CASE_METHOD(ClassName, Name, Tags, ...) \ + INTERNAL_CATCH_EXPAND_VARGS(INTERNAL_CATCH_TEMPLATE_PRODUCT_TEST_CASE_METHOD_2(INTERNAL_CATCH_UNIQUE_NAME(CATCH2_INTERNAL_TEMPLATE_TEST_), INTERNAL_CATCH_UNIQUE_NAME(CATCH2_INTERNAL_TEMPLATE_TEST_), ClassName, Name, Tags, typename T, __VA_ARGS__)) +#endif + +#ifndef CATCH_CONFIG_TRADITIONAL_MSVC_PREPROCESSOR +#define INTERNAL_CATCH_TEMPLATE_PRODUCT_TEST_CASE_METHOD_SIG(ClassName, Name, Tags, Signature, ...) \ + INTERNAL_CATCH_TEMPLATE_PRODUCT_TEST_CASE_METHOD_2(INTERNAL_CATCH_UNIQUE_NAME(CATCH2_INTERNAL_TEMPLATE_TEST_), INTERNAL_CATCH_UNIQUE_NAME(CATCH2_INTERNAL_TEMPLATE_TEST_), ClassName, Name, Tags, Signature, __VA_ARGS__) +#else +#define INTERNAL_CATCH_TEMPLATE_PRODUCT_TEST_CASE_METHOD_SIG(ClassName, Name, Tags, Signature, ...) \ + INTERNAL_CATCH_EXPAND_VARGS(INTERNAL_CATCH_TEMPLATE_PRODUCT_TEST_CASE_METHOD_2(INTERNAL_CATCH_UNIQUE_NAME(CATCH2_INTERNAL_TEMPLATE_TEST_), INTERNAL_CATCH_UNIQUE_NAME(CATCH2_INTERNAL_TEMPLATE_TEST_), ClassName, Name, Tags, Signature, __VA_ARGS__)) +#endif + +#define INTERNAL_CATCH_TEMPLATE_LIST_TEST_CASE_METHOD_2(TestNameClass, TestName, ClassName, Name, Tags, TmplList) \ + CATCH_INTERNAL_START_WARNINGS_SUPPRESSION \ + CATCH_INTERNAL_SUPPRESS_GLOBALS_WARNINGS \ + CATCH_INTERNAL_SUPPRESS_UNUSED_TEMPLATE_WARNINGS \ + CATCH_INTERNAL_SUPPRESS_UNUSED_VARIABLE_WARNINGS \ + CATCH_INTERNAL_SUPPRESS_COMMA_WARNINGS \ + template \ + struct TestName : INTERNAL_CATCH_REMOVE_PARENS(ClassName) { \ + void test(); \ + }; \ + namespace { \ + namespace INTERNAL_CATCH_MAKE_NAMESPACE(TestName) { \ + INTERNAL_CATCH_TYPE_GEN \ + template \ + struct TestNameClass { \ + void reg_tests() { \ + size_t index = 0; \ + using expander = size_t[]; \ + (void)expander{(Catch::AutoReg(Catch::makeTestInvoker(&TestName::test), CATCH_INTERNAL_LINEINFO, #ClassName##_catch_sr, Catch::NameAndTags{Name " - " INTERNAL_CATCH_STRINGIZE(TmplList) " - " + std::to_string(index), Tags}), index++)...}; /* NOLINT */ \ + } \ + }; \ + static const int INTERNAL_CATCH_UNIQUE_NAME(globalRegistrar) = []() { \ + using TestInit = typename convert::type; \ + TestInit t; \ + t.reg_tests(); \ + return 0; \ + }(); \ + } \ + } \ + CATCH_INTERNAL_STOP_WARNINGS_SUPPRESSION \ + template \ + void TestName::test() + +#define INTERNAL_CATCH_TEMPLATE_LIST_TEST_CASE_METHOD(ClassName, Name, Tags, TmplList) \ + INTERNAL_CATCH_TEMPLATE_LIST_TEST_CASE_METHOD_2(INTERNAL_CATCH_UNIQUE_NAME(CATCH2_INTERNAL_TEMPLATE_TEST_), INTERNAL_CATCH_UNIQUE_NAME(CATCH2_INTERNAL_TEMPLATE_TEST_), ClassName, Name, Tags, TmplList) + +#endif // CATCH_TEMPLATE_TEST_REGISTRY_HPP_INCLUDED + +#if defined(CATCH_CONFIG_PREFIX_ALL) && !defined(CATCH_CONFIG_DISABLE) + +#ifndef CATCH_CONFIG_TRADITIONAL_MSVC_PREPROCESSOR +#define CATCH_TEMPLATE_TEST_CASE(...) INTERNAL_CATCH_TEMPLATE_TEST_CASE(__VA_ARGS__) +#define CATCH_TEMPLATE_TEST_CASE_SIG(...) INTERNAL_CATCH_TEMPLATE_TEST_CASE_SIG(__VA_ARGS__) +#define CATCH_TEMPLATE_TEST_CASE_METHOD(className, ...) INTERNAL_CATCH_TEMPLATE_TEST_CASE_METHOD(className, __VA_ARGS__) +#define CATCH_TEMPLATE_TEST_CASE_METHOD_SIG(className, ...) INTERNAL_CATCH_TEMPLATE_TEST_CASE_METHOD_SIG(className, __VA_ARGS__) +#define CATCH_TEMPLATE_PRODUCT_TEST_CASE(...) INTERNAL_CATCH_TEMPLATE_PRODUCT_TEST_CASE(__VA_ARGS__) +#define CATCH_TEMPLATE_PRODUCT_TEST_CASE_SIG(...) INTERNAL_CATCH_TEMPLATE_PRODUCT_TEST_CASE_SIG(__VA_ARGS__) +#define CATCH_TEMPLATE_PRODUCT_TEST_CASE_METHOD(className, ...) INTERNAL_CATCH_TEMPLATE_PRODUCT_TEST_CASE_METHOD(className, __VA_ARGS__) +#define CATCH_TEMPLATE_PRODUCT_TEST_CASE_METHOD_SIG(className, ...) INTERNAL_CATCH_TEMPLATE_PRODUCT_TEST_CASE_METHOD_SIG(className, __VA_ARGS__) +#define CATCH_TEMPLATE_LIST_TEST_CASE(...) INTERNAL_CATCH_TEMPLATE_LIST_TEST_CASE(__VA_ARGS__) +#define CATCH_TEMPLATE_LIST_TEST_CASE_METHOD(className, ...) INTERNAL_CATCH_TEMPLATE_LIST_TEST_CASE_METHOD(className, __VA_ARGS__) +#else +#define CATCH_TEMPLATE_TEST_CASE(...) INTERNAL_CATCH_EXPAND_VARGS(INTERNAL_CATCH_TEMPLATE_TEST_CASE(__VA_ARGS__)) +#define CATCH_TEMPLATE_TEST_CASE_SIG(...) INTERNAL_CATCH_EXPAND_VARGS(INTERNAL_CATCH_TEMPLATE_TEST_CASE_SIG(__VA_ARGS__)) +#define CATCH_TEMPLATE_TEST_CASE_METHOD(className, ...) INTERNAL_CATCH_EXPAND_VARGS(INTERNAL_CATCH_TEMPLATE_TEST_CASE_METHOD(className, __VA_ARGS__)) +#define CATCH_TEMPLATE_TEST_CASE_METHOD_SIG(className, ...) INTERNAL_CATCH_EXPAND_VARGS(INTERNAL_CATCH_TEMPLATE_TEST_CASE_METHOD_SIG(className, __VA_ARGS__)) +#define CATCH_TEMPLATE_PRODUCT_TEST_CASE(...) INTERNAL_CATCH_EXPAND_VARGS(INTERNAL_CATCH_TEMPLATE_PRODUCT_TEST_CASE(__VA_ARGS__)) +#define CATCH_TEMPLATE_PRODUCT_TEST_CASE_SIG(...) INTERNAL_CATCH_EXPAND_VARGS(INTERNAL_CATCH_TEMPLATE_PRODUCT_TEST_CASE_SIG(__VA_ARGS__)) +#define CATCH_TEMPLATE_PRODUCT_TEST_CASE_METHOD(className, ...) INTERNAL_CATCH_EXPAND_VARGS(INTERNAL_CATCH_TEMPLATE_PRODUCT_TEST_CASE_METHOD(className, __VA_ARGS__)) +#define CATCH_TEMPLATE_PRODUCT_TEST_CASE_METHOD_SIG(className, ...) INTERNAL_CATCH_EXPAND_VARGS(INTERNAL_CATCH_TEMPLATE_PRODUCT_TEST_CASE_METHOD_SIG(className, __VA_ARGS__)) +#define CATCH_TEMPLATE_LIST_TEST_CASE(...) INTERNAL_CATCH_EXPAND_VARGS(INTERNAL_CATCH_TEMPLATE_LIST_TEST_CASE(__VA_ARGS__)) +#define CATCH_TEMPLATE_LIST_TEST_CASE_METHOD(className, ...) INTERNAL_CATCH_EXPAND_VARGS(INTERNAL_CATCH_TEMPLATE_LIST_TEST_CASE_METHOD(className, __VA_ARGS__)) +#endif + +#elif defined(CATCH_CONFIG_PREFIX_ALL) && defined(CATCH_CONFIG_DISABLE) + +#ifndef CATCH_CONFIG_TRADITIONAL_MSVC_PREPROCESSOR +#define CATCH_TEMPLATE_TEST_CASE(...) INTERNAL_CATCH_TEMPLATE_TEST_CASE_NO_REGISTRATION(__VA_ARGS__) +#define CATCH_TEMPLATE_TEST_CASE_SIG(...) INTERNAL_CATCH_TEMPLATE_TEST_CASE_SIG_NO_REGISTRATION(__VA_ARGS__) +#define CATCH_TEMPLATE_TEST_CASE_METHOD(className, ...) INTERNAL_CATCH_TEMPLATE_TEST_CASE_METHOD_NO_REGISTRATION(className, __VA_ARGS__) +#define CATCH_TEMPLATE_TEST_CASE_METHOD_SIG(className, ...) INTERNAL_CATCH_TEMPLATE_TEST_CASE_METHOD_SIG_NO_REGISTRATION(className, __VA_ARGS__) +#else +#define CATCH_TEMPLATE_TEST_CASE(...) INTERNAL_CATCH_EXPAND_VARGS(INTERNAL_CATCH_TEMPLATE_TEST_CASE_NO_REGISTRATION(__VA_ARGS__)) +#define CATCH_TEMPLATE_TEST_CASE_SIG(...) INTERNAL_CATCH_EXPAND_VARGS(INTERNAL_CATCH_TEMPLATE_TEST_CASE_SIG_NO_REGISTRATION(__VA_ARGS__)) +#define CATCH_TEMPLATE_TEST_CASE_METHOD(className, ...) INTERNAL_CATCH_EXPAND_VARGS(INTERNAL_CATCH_TEMPLATE_TEST_CASE_METHOD_NO_REGISTRATION(className, __VA_ARGS__)) +#define CATCH_TEMPLATE_TEST_CASE_METHOD_SIG(className, ...) INTERNAL_CATCH_EXPAND_VARGS(INTERNAL_CATCH_TEMPLATE_TEST_CASE_METHOD_SIG_NO_REGISTRATION(className, __VA_ARGS__)) +#endif + +// When disabled, these can be shared between proper preprocessor and MSVC preprocessor +#define CATCH_TEMPLATE_PRODUCT_TEST_CASE(...) CATCH_TEMPLATE_TEST_CASE(__VA_ARGS__) +#define CATCH_TEMPLATE_PRODUCT_TEST_CASE_SIG(...) CATCH_TEMPLATE_TEST_CASE(__VA_ARGS__) +#define CATCH_TEMPLATE_PRODUCT_TEST_CASE_METHOD(className, ...) CATCH_TEMPLATE_TEST_CASE_METHOD(className, __VA_ARGS__) +#define CATCH_TEMPLATE_PRODUCT_TEST_CASE_METHOD_SIG(className, ...) CATCH_TEMPLATE_TEST_CASE_METHOD(className, __VA_ARGS__) +#define CATCH_TEMPLATE_LIST_TEST_CASE(...) CATCH_TEMPLATE_TEST_CASE(__VA_ARGS__) +#define CATCH_TEMPLATE_LIST_TEST_CASE_METHOD(className, ...) CATCH_TEMPLATE_TEST_CASE_METHOD(className, __VA_ARGS__) + +#elif !defined(CATCH_CONFIG_PREFIX_ALL) && !defined(CATCH_CONFIG_DISABLE) + +#ifndef CATCH_CONFIG_TRADITIONAL_MSVC_PREPROCESSOR +#define TEMPLATE_TEST_CASE(...) INTERNAL_CATCH_TEMPLATE_TEST_CASE(__VA_ARGS__) +#define TEMPLATE_TEST_CASE_SIG(...) INTERNAL_CATCH_TEMPLATE_TEST_CASE_SIG(__VA_ARGS__) +#define TEMPLATE_TEST_CASE_METHOD(className, ...) INTERNAL_CATCH_TEMPLATE_TEST_CASE_METHOD(className, __VA_ARGS__) +#define TEMPLATE_TEST_CASE_METHOD_SIG(className, ...) INTERNAL_CATCH_TEMPLATE_TEST_CASE_METHOD_SIG(className, __VA_ARGS__) +#define TEMPLATE_PRODUCT_TEST_CASE(...) INTERNAL_CATCH_TEMPLATE_PRODUCT_TEST_CASE(__VA_ARGS__) +#define TEMPLATE_PRODUCT_TEST_CASE_SIG(...) INTERNAL_CATCH_TEMPLATE_PRODUCT_TEST_CASE_SIG(__VA_ARGS__) +#define TEMPLATE_PRODUCT_TEST_CASE_METHOD(className, ...) INTERNAL_CATCH_TEMPLATE_PRODUCT_TEST_CASE_METHOD(className, __VA_ARGS__) +#define TEMPLATE_PRODUCT_TEST_CASE_METHOD_SIG(className, ...) INTERNAL_CATCH_TEMPLATE_PRODUCT_TEST_CASE_METHOD_SIG(className, __VA_ARGS__) +#define TEMPLATE_LIST_TEST_CASE(...) INTERNAL_CATCH_TEMPLATE_LIST_TEST_CASE(__VA_ARGS__) +#define TEMPLATE_LIST_TEST_CASE_METHOD(className, ...) INTERNAL_CATCH_TEMPLATE_LIST_TEST_CASE_METHOD(className, __VA_ARGS__) +#else +#define TEMPLATE_TEST_CASE(...) INTERNAL_CATCH_EXPAND_VARGS(INTERNAL_CATCH_TEMPLATE_TEST_CASE(__VA_ARGS__)) +#define TEMPLATE_TEST_CASE_SIG(...) INTERNAL_CATCH_EXPAND_VARGS(INTERNAL_CATCH_TEMPLATE_TEST_CASE_SIG(__VA_ARGS__)) +#define TEMPLATE_TEST_CASE_METHOD(className, ...) INTERNAL_CATCH_EXPAND_VARGS(INTERNAL_CATCH_TEMPLATE_TEST_CASE_METHOD(className, __VA_ARGS__)) +#define TEMPLATE_TEST_CASE_METHOD_SIG(className, ...) INTERNAL_CATCH_EXPAND_VARGS(INTERNAL_CATCH_TEMPLATE_TEST_CASE_METHOD_SIG(className, __VA_ARGS__)) +#define TEMPLATE_PRODUCT_TEST_CASE(...) INTERNAL_CATCH_EXPAND_VARGS(INTERNAL_CATCH_TEMPLATE_PRODUCT_TEST_CASE(__VA_ARGS__)) +#define TEMPLATE_PRODUCT_TEST_CASE_SIG(...) INTERNAL_CATCH_EXPAND_VARGS(INTERNAL_CATCH_TEMPLATE_PRODUCT_TEST_CASE_SIG(__VA_ARGS__)) +#define TEMPLATE_PRODUCT_TEST_CASE_METHOD(className, ...) INTERNAL_CATCH_EXPAND_VARGS(INTERNAL_CATCH_TEMPLATE_PRODUCT_TEST_CASE_METHOD(className, __VA_ARGS__)) +#define TEMPLATE_PRODUCT_TEST_CASE_METHOD_SIG(className, ...) INTERNAL_CATCH_EXPAND_VARGS(INTERNAL_CATCH_TEMPLATE_PRODUCT_TEST_CASE_METHOD_SIG(className, __VA_ARGS__)) +#define TEMPLATE_LIST_TEST_CASE(...) INTERNAL_CATCH_EXPAND_VARGS(INTERNAL_CATCH_TEMPLATE_LIST_TEST_CASE(__VA_ARGS__)) +#define TEMPLATE_LIST_TEST_CASE_METHOD(className, ...) INTERNAL_CATCH_EXPAND_VARGS(INTERNAL_CATCH_TEMPLATE_LIST_TEST_CASE_METHOD(className, __VA_ARGS__)) +#endif + +#elif !defined(CATCH_CONFIG_PREFIX_ALL) && defined(CATCH_CONFIG_DISABLE) + +#ifndef CATCH_CONFIG_TRADITIONAL_MSVC_PREPROCESSOR +#define TEMPLATE_TEST_CASE(...) INTERNAL_CATCH_TEMPLATE_TEST_CASE_NO_REGISTRATION(__VA_ARGS__) +#define TEMPLATE_TEST_CASE_SIG(...) INTERNAL_CATCH_TEMPLATE_TEST_CASE_SIG_NO_REGISTRATION(__VA_ARGS__) +#define TEMPLATE_TEST_CASE_METHOD(className, ...) INTERNAL_CATCH_TEMPLATE_TEST_CASE_METHOD_NO_REGISTRATION(className, __VA_ARGS__) +#define TEMPLATE_TEST_CASE_METHOD_SIG(className, ...) INTERNAL_CATCH_TEMPLATE_TEST_CASE_METHOD_SIG_NO_REGISTRATION(className, __VA_ARGS__) +#else +#define TEMPLATE_TEST_CASE(...) INTERNAL_CATCH_EXPAND_VARGS(INTERNAL_CATCH_TEMPLATE_TEST_CASE_NO_REGISTRATION(__VA_ARGS__)) +#define TEMPLATE_TEST_CASE_SIG(...) INTERNAL_CATCH_EXPAND_VARGS(INTERNAL_CATCH_TEMPLATE_TEST_CASE_SIG_NO_REGISTRATION(__VA_ARGS__)) +#define TEMPLATE_TEST_CASE_METHOD(className, ...) INTERNAL_CATCH_EXPAND_VARGS(INTERNAL_CATCH_TEMPLATE_TEST_CASE_METHOD_NO_REGISTRATION(className, __VA_ARGS__)) +#define TEMPLATE_TEST_CASE_METHOD_SIG(className, ...) INTERNAL_CATCH_EXPAND_VARGS(INTERNAL_CATCH_TEMPLATE_TEST_CASE_METHOD_SIG_NO_REGISTRATION(className, __VA_ARGS__)) +#endif + +// When disabled, these can be shared between proper preprocessor and MSVC preprocessor +#define TEMPLATE_PRODUCT_TEST_CASE(...) TEMPLATE_TEST_CASE(__VA_ARGS__) +#define TEMPLATE_PRODUCT_TEST_CASE_SIG(...) TEMPLATE_TEST_CASE(__VA_ARGS__) +#define TEMPLATE_PRODUCT_TEST_CASE_METHOD(className, ...) TEMPLATE_TEST_CASE_METHOD(className, __VA_ARGS__) +#define TEMPLATE_PRODUCT_TEST_CASE_METHOD_SIG(className, ...) TEMPLATE_TEST_CASE_METHOD(className, __VA_ARGS__) +#define TEMPLATE_LIST_TEST_CASE(...) TEMPLATE_TEST_CASE(__VA_ARGS__) +#define TEMPLATE_LIST_TEST_CASE_METHOD(className, ...) TEMPLATE_TEST_CASE_METHOD(className, __VA_ARGS__) + +#endif // end of user facing macro declarations + +#endif // CATCH_TEMPLATE_TEST_MACROS_HPP_INCLUDED + +#ifndef CATCH_TEST_CASE_INFO_HPP_INCLUDED +#define CATCH_TEST_CASE_INFO_HPP_INCLUDED + +#include +#include +#include + +#ifdef __clang__ +#pragma clang diagnostic push +#pragma clang diagnostic ignored "-Wpadded" +#endif + +namespace Catch { + + /** + * A **view** of a tag string that provides case insensitive comparisons + * + * Note that in Catch2 internals, the square brackets around tags are + * not a part of tag's representation, so e.g. "[cool-tag]" is represented + * as "cool-tag" internally. + */ + struct Tag { + constexpr Tag(StringRef original_) + : original(original_) {} + StringRef original; + + friend bool operator<(Tag const &lhs, Tag const &rhs); + friend bool operator==(Tag const &lhs, Tag const &rhs); + }; + + class ITestInvoker; + struct NameAndTags; + + enum class TestCaseProperties : uint8_t { + None = 0, + IsHidden = 1 << 1, + ShouldFail = 1 << 2, + MayFail = 1 << 3, + Throws = 1 << 4, + NonPortable = 1 << 5, + Benchmark = 1 << 6 + }; + + /** + * Various metadata about the test case. + * + * A test case is uniquely identified by its (class)name and tags + * combination, with source location being ignored, and other properties + * being determined from tags. + * + * Tags are kept sorted. + */ + struct TestCaseInfo : Detail::NonCopyable { + TestCaseInfo(StringRef _className, + NameAndTags const &_nameAndTags, + SourceLineInfo const &_lineInfo); + + bool isHidden() const; + bool throws() const; + bool okToFail() const; + bool expectedToFail() const; + + // Adds the tag(s) with test's filename (for the -# flag) + void addFilenameTag(); + + //! Orders by name, classname and tags + friend bool operator<(TestCaseInfo const &lhs, + TestCaseInfo const &rhs); + + std::string tagsAsString() const; + + std::string name; + StringRef className; + + private: + std::string backingTags; + // Internally we copy tags to the backing storage and then add + // refs to this storage to the tags vector. + void internalAppendTag(StringRef tagString); + + public: + std::vector tags; + SourceLineInfo lineInfo; + TestCaseProperties properties = TestCaseProperties::None; + }; + + /** + * Wrapper over the test case information and the test case invoker + * + * Does not own either, and is specifically made to be cheap + * to copy around. + */ + class TestCaseHandle { + TestCaseInfo *m_info; + ITestInvoker *m_invoker; + + public: + constexpr TestCaseHandle(TestCaseInfo *info, ITestInvoker *invoker) + : m_info(info), m_invoker(invoker) {} + + void prepareTestCase() const { + m_invoker->prepareTestCase(); + } + + void tearDownTestCase() const { + m_invoker->tearDownTestCase(); + } + + void invoke() const { + m_invoker->invoke(); + } + + constexpr TestCaseInfo const &getTestCaseInfo() const { + return *m_info; + } + }; + + Detail::unique_ptr + makeTestCaseInfo(StringRef className, + NameAndTags const &nameAndTags, + SourceLineInfo const &lineInfo); +} // namespace Catch + +#ifdef __clang__ +#pragma clang diagnostic pop +#endif + +#endif // CATCH_TEST_CASE_INFO_HPP_INCLUDED + +#ifndef CATCH_TEST_RUN_INFO_HPP_INCLUDED +#define CATCH_TEST_RUN_INFO_HPP_INCLUDED + +namespace Catch { + + struct TestRunInfo { + constexpr TestRunInfo(StringRef _name) + : name(_name) {} + StringRef name; + }; + +} // end namespace Catch + +#endif // CATCH_TEST_RUN_INFO_HPP_INCLUDED + +#ifndef CATCH_TRANSLATE_EXCEPTION_HPP_INCLUDED +#define CATCH_TRANSLATE_EXCEPTION_HPP_INCLUDED + +#ifndef CATCH_INTERFACES_EXCEPTION_HPP_INCLUDED +#define CATCH_INTERFACES_EXCEPTION_HPP_INCLUDED + +#include +#include + +namespace Catch { + using exceptionTranslateFunction = std::string (*)(); + + class IExceptionTranslator; + using ExceptionTranslators = std::vector>; + + class IExceptionTranslator { + public: + virtual ~IExceptionTranslator(); // = default + virtual std::string translate(ExceptionTranslators::const_iterator it, ExceptionTranslators::const_iterator itEnd) const = 0; + }; + + class IExceptionTranslatorRegistry { + public: + virtual ~IExceptionTranslatorRegistry(); // = default + virtual std::string translateActiveException() const = 0; + }; + +} // namespace Catch + +#endif // CATCH_INTERFACES_EXCEPTION_HPP_INCLUDED + +#include + +namespace Catch { + namespace Detail { + void registerTranslatorImpl( + Detail::unique_ptr &&translator); + } + + class ExceptionTranslatorRegistrar { + template + class ExceptionTranslator : public IExceptionTranslator { + public: + constexpr ExceptionTranslator(std::string (*translateFunction)(T const &)) + : m_translateFunction(translateFunction) {} + + std::string translate(ExceptionTranslators::const_iterator it, ExceptionTranslators::const_iterator itEnd) const override { +#if !defined(CATCH_CONFIG_DISABLE_EXCEPTIONS) + try { + if (it == itEnd) + std::rethrow_exception(std::current_exception()); + else + return (*it)->translate(it + 1, itEnd); + } catch (T const &ex) { + return m_translateFunction(ex); + } +#else + return "You should never get here!"; +#endif + } + + protected: + std::string (*m_translateFunction)(T const &); + }; + + public: + template + ExceptionTranslatorRegistrar(std::string (*translateFunction)(T const &)) { + Detail::registerTranslatorImpl( + Detail::make_unique>( + translateFunction)); + } + }; + +} // namespace Catch + +/////////////////////////////////////////////////////////////////////////////// +#define INTERNAL_CATCH_TRANSLATE_EXCEPTION2(translatorName, signature) \ + static std::string translatorName(signature); \ + CATCH_INTERNAL_START_WARNINGS_SUPPRESSION \ + CATCH_INTERNAL_SUPPRESS_GLOBALS_WARNINGS \ + namespace { \ + const Catch::ExceptionTranslatorRegistrar INTERNAL_CATCH_UNIQUE_NAME(catch_internal_ExceptionRegistrar)(&translatorName); \ + } \ + CATCH_INTERNAL_STOP_WARNINGS_SUPPRESSION \ + static std::string translatorName(signature) + +#define INTERNAL_CATCH_TRANSLATE_EXCEPTION(signature) INTERNAL_CATCH_TRANSLATE_EXCEPTION2(INTERNAL_CATCH_UNIQUE_NAME(catch_internal_ExceptionTranslator), signature) + +#if defined(CATCH_CONFIG_DISABLE) +#define INTERNAL_CATCH_TRANSLATE_EXCEPTION_NO_REG(translatorName, signature) \ + static std::string translatorName(signature) +#endif + +// This macro is always prefixed +#if !defined(CATCH_CONFIG_DISABLE) +#define CATCH_TRANSLATE_EXCEPTION(signature) INTERNAL_CATCH_TRANSLATE_EXCEPTION(signature) +#else +#define CATCH_TRANSLATE_EXCEPTION(signature) INTERNAL_CATCH_TRANSLATE_EXCEPTION_NO_REG(INTERNAL_CATCH_UNIQUE_NAME(catch_internal_ExceptionTranslator), signature) +#endif + +#endif // CATCH_TRANSLATE_EXCEPTION_HPP_INCLUDED + +#ifndef CATCH_VERSION_HPP_INCLUDED +#define CATCH_VERSION_HPP_INCLUDED + +#include + +namespace Catch { + + // Versioning information + struct Version { + Version(Version const &) = delete; + Version &operator=(Version const &) = delete; + Version(unsigned int _majorVersion, + unsigned int _minorVersion, + unsigned int _patchNumber, + char const *const _branchName, + unsigned int _buildNumber); + + unsigned int const majorVersion; + unsigned int const minorVersion; + unsigned int const patchNumber; + + // buildNumber is only used if branchName is not null + char const *const branchName; + unsigned int const buildNumber; + + friend std::ostream &operator<<(std::ostream &os, Version const &version); + }; + + Version const &libraryVersion(); +} // namespace Catch + +#endif // CATCH_VERSION_HPP_INCLUDED + +#ifndef CATCH_VERSION_MACROS_HPP_INCLUDED +#define CATCH_VERSION_MACROS_HPP_INCLUDED + +#define CATCH_VERSION_MAJOR 3 +#define CATCH_VERSION_MINOR 14 +#define CATCH_VERSION_PATCH 0 + +#endif // CATCH_VERSION_MACROS_HPP_INCLUDED + +/** \file + * This is a convenience header for Catch2's Generator support. It includes + * **all** of Catch2 headers related to generators. + * + * Generally the Catch2 users should use specific includes they need, + * but this header can be used instead for ease-of-experimentation, or + * just plain convenience, at the cost of (significantly) increased + * compilation times. + * + * When a new header is added to either the `generators` folder, + * or to the corresponding internal subfolder, it should be added here. + */ + +#ifndef CATCH_GENERATORS_ALL_HPP_INCLUDED +#define CATCH_GENERATORS_ALL_HPP_INCLUDED + +#ifndef CATCH_GENERATOR_EXCEPTION_HPP_INCLUDED +#define CATCH_GENERATOR_EXCEPTION_HPP_INCLUDED + +#include + +namespace Catch { + + // Exception type to be thrown when a Generator runs into an error, + // e.g. it cannot initialize the first return value based on + // runtime information + class GeneratorException : public std::exception { + const char *const m_msg = ""; + + public: + GeneratorException(const char *msg) + : m_msg(msg) {} + + const char *what() const noexcept final; + }; + +} // end namespace Catch + +#endif // CATCH_GENERATOR_EXCEPTION_HPP_INCLUDED + +#ifndef CATCH_GENERATORS_HPP_INCLUDED +#define CATCH_GENERATORS_HPP_INCLUDED + +#ifndef CATCH_GENERATORS_THROW_HPP_INCLUDED +#define CATCH_GENERATORS_THROW_HPP_INCLUDED + +namespace Catch { + namespace Generators { + namespace Detail { + + //! Throws GeneratorException with the provided message + [[noreturn]] + void throw_generator_exception(char const *msg); + + } // namespace Detail + } // namespace Generators +} // namespace Catch + +#endif // CATCH_GENERATORS_THROW_HPP_INCLUDED + +#ifndef CATCH_INTERFACES_GENERATORTRACKER_HPP_INCLUDED +#define CATCH_INTERFACES_GENERATORTRACKER_HPP_INCLUDED + +#include + +namespace Catch { + + namespace Generators { + class GeneratorUntypedBase { + // Caches result from `toStringImpl`, assume that when it is an + // empty string, the cache is invalidated. + mutable std::string m_stringReprCache; + + // Counts based on `next` returning true + std::size_t m_currentElementIndex = 0; + + /** + * Attempts to move the generator to the next element + * + * Returns true iff the move succeeded (and a valid element + * can be retrieved). + */ + virtual bool next() = 0; + + //! Customization point for `currentElementAsString` + virtual std::string stringifyImpl() const = 0; + + /** + * Customization point for skipping to the n-th element + * + * Defaults to successively calling `countedNext`. If there + * are not enough elements to reach the nth one, will throw + * an error. + */ + virtual void skipToNthElementImpl(std::size_t n); + + public: + GeneratorUntypedBase() = default; + // Generation of copy ops is deprecated (and Clang will complain) + // if there is a user destructor defined + GeneratorUntypedBase(GeneratorUntypedBase const &) = default; + GeneratorUntypedBase &operator=(GeneratorUntypedBase const &) = default; + + virtual ~GeneratorUntypedBase(); // = default; + + /** + * Attempts to move the generator to the next element + * + * Serves as a non-virtual interface to `next`, so that the + * top level interface can provide sanity checking and shared + * features. + * + * As with `next`, returns true iff the move succeeded and + * the generator has new valid element to provide. + */ + bool countedNext(); + + std::size_t currentElementIndex() const { return m_currentElementIndex; } + + /** + * Moves the generator forward **to** the n-th element + * + * Cannot move backwards. Can stay in place. + */ + void skipToNthElement(std::size_t n); + + /** + * Returns generator's current element as user-friendly string. + * + * By default returns string equivalent to calling + * `Catch::Detail::stringify` on the current element, but generators + * can customize their implementation as needed. + * + * Not thread-safe due to internal caching. + * + * The returned ref is valid only until the generator instance + * is destructed, or it moves onto the next element, whichever + * comes first. + */ + StringRef currentElementAsString() const; + + /** + * Returns true if calls to `next` will eventually return false + * + * Note that for backwards compatibility this is currently defaulted + * to return `true`, but in the future all generators will have to + * provide their own implementation. + */ + virtual bool isFinite() const; + }; + using GeneratorBasePtr = Catch::Detail::unique_ptr; + + } // namespace Generators + + class IGeneratorTracker { + public: + virtual ~IGeneratorTracker(); // = default; + virtual auto getGenerator() const -> Generators::GeneratorBasePtr const & = 0; + }; + +} // namespace Catch + +#endif // CATCH_INTERFACES_GENERATORTRACKER_HPP_INCLUDED + +#include +#include + +namespace Catch { + + namespace Generators { + + template + class IGenerator : public GeneratorUntypedBase { + std::string stringifyImpl() const override { + return ::Catch::Detail::stringify(get()); + } + + public: + // Returns the current element of the generator + // + // \Precondition The generator is either freshly constructed, + // or the last call to `next()` returned true + virtual T const &get() const = 0; + using type = T; + }; + + template + using GeneratorPtr = Catch::Detail::unique_ptr>; + + template + class GeneratorWrapper final { + GeneratorPtr m_generator; + + public: + //! Takes ownership of the passed pointer. + GeneratorWrapper(IGenerator *generator) + : m_generator(generator) {} + GeneratorWrapper(GeneratorPtr generator) + : m_generator(CATCH_MOVE(generator)) {} + + T const &get() const { + return m_generator->get(); + } + bool next() { + return m_generator->countedNext(); + } + + bool isFinite() const { return m_generator->isFinite(); } + void skipToNthElement(size_t n) { m_generator->skipToNthElement(n); } + }; + + template + class SingleValueGenerator final : public IGenerator { + T m_value; + + public: + SingleValueGenerator(T const &value) + : m_value(value) {} + SingleValueGenerator(T &&value) + : m_value(CATCH_MOVE(value)) {} + + T const &get() const override { + return m_value; + } + bool next() override { + return false; + } + + bool isFinite() const override { return true; } + }; + + template + class FixedValuesGenerator final : public IGenerator { + static_assert(!std::is_same::value, + "FixedValuesGenerator does not support bools because of std::vector" + "specialization, use SingleValue Generator instead."); + std::vector m_values; + size_t m_idx = 0; + + void skipToNthElementImpl(std::size_t n) override { + if (n >= m_values.size()) { + Detail::throw_generator_exception( + "Coud not jump to Nth element: not enough elements"); + } + m_idx = n; + } + + public: + FixedValuesGenerator(std::initializer_list values) + : m_values(values) {} + + T const &get() const override { + return m_values[m_idx]; + } + bool next() override { + ++m_idx; + return m_idx < m_values.size(); + } + + bool isFinite() const override { return true; } + }; + + template> + GeneratorWrapper value(T &&value) { + return GeneratorWrapper( + Catch::Detail::make_unique>( + CATCH_FORWARD(value))); + } + template + GeneratorWrapper values(std::initializer_list values) { + return GeneratorWrapper(Catch::Detail::make_unique>(values)); + } + + template + class Generators : public IGenerator { + std::vector> m_generators; + size_t m_current = 0; + + void add_generator(GeneratorWrapper &&generator) { + m_generators.emplace_back(CATCH_MOVE(generator)); + } + void add_generator(T const &val) { + m_generators.emplace_back(value(val)); + } + void add_generator(T &&val) { + m_generators.emplace_back(value(CATCH_MOVE(val))); + } + template + std::enable_if_t, T>::value> + add_generator(U &&val) { + add_generator(T(CATCH_FORWARD(val))); + } + + template + void add_generators(U &&valueOrGenerator) { + add_generator(CATCH_FORWARD(valueOrGenerator)); + } + + template + void add_generators(U &&valueOrGenerator, Gs &&...moreGenerators) { + add_generator(CATCH_FORWARD(valueOrGenerator)); + add_generators(CATCH_FORWARD(moreGenerators)...); + } + + public: + template + Generators(Gs &&...moreGenerators) { + m_generators.reserve(sizeof...(Gs)); + add_generators(CATCH_FORWARD(moreGenerators)...); + } + + T const &get() const override { + return m_generators[m_current].get(); + } + + bool next() override { + if (m_current >= m_generators.size()) { + return false; + } + const bool current_status = m_generators[m_current].next(); + if (!current_status) { + ++m_current; + } + return m_current < m_generators.size(); + } + + bool isFinite() const override { + for (auto const &gen : m_generators) { + if (!gen.isFinite()) { + return false; + } + } + return true; + } + }; + + template + GeneratorWrapper...>> + table(std::initializer_list...>> tuples) { + return values>(tuples); + } + + // Tag type to signal that a generator sequence should convert arguments to a specific type + template + struct as {}; + + template + auto makeGenerators(GeneratorWrapper &&generator, Gs &&...moreGenerators) -> Generators { + return Generators(CATCH_MOVE(generator), CATCH_FORWARD(moreGenerators)...); + } + template + auto makeGenerators(GeneratorWrapper &&generator) -> Generators { + return Generators(CATCH_MOVE(generator)); + } + template + auto makeGenerators(T &&val, Gs &&...moreGenerators) -> Generators> { + return makeGenerators(value(CATCH_FORWARD(val)), CATCH_FORWARD(moreGenerators)...); + } + template + auto makeGenerators(as, U &&val, Gs &&...moreGenerators) -> Generators { + return makeGenerators(value(T(CATCH_FORWARD(val))), CATCH_FORWARD(moreGenerators)...); + } + + IGeneratorTracker *acquireGeneratorTracker(StringRef generatorName, + SourceLineInfo const &lineInfo); + IGeneratorTracker *createGeneratorTracker(StringRef generatorName, + SourceLineInfo lineInfo, + GeneratorBasePtr &&generator); + + template + auto generate(StringRef generatorName, SourceLineInfo const &lineInfo, L const &generatorExpression) -> typename decltype(generatorExpression())::type { + using UnderlyingType = typename decltype(generatorExpression())::type; + + IGeneratorTracker *tracker = acquireGeneratorTracker(generatorName, lineInfo); + // Creation of tracker is delayed after generator creation, so + // that constructing generator can fail without breaking everything. + if (!tracker) { + tracker = createGeneratorTracker( + generatorName, + lineInfo, + Catch::Detail::make_unique>( + generatorExpression())); + } + + auto const &generator = static_cast const &>(*tracker->getGenerator()); + return generator.get(); + } + + } // namespace Generators +} // namespace Catch + +#define CATCH_INTERNAL_GENERATOR_STRINGIZE_IMPL(...) #__VA_ARGS__##_catch_sr +#define CATCH_INTERNAL_GENERATOR_STRINGIZE(...) CATCH_INTERNAL_GENERATOR_STRINGIZE_IMPL(__VA_ARGS__) + +#define GENERATE(...) \ + Catch::Generators::generate(CATCH_INTERNAL_GENERATOR_STRINGIZE(INTERNAL_CATCH_UNIQUE_NAME(generator)), \ + CATCH_INTERNAL_LINEINFO, \ + [] { using namespace Catch::Generators; return makeGenerators( __VA_ARGS__ ); }) // NOLINT(google-build-using-namespace) +#define GENERATE_COPY(...) \ + Catch::Generators::generate(CATCH_INTERNAL_GENERATOR_STRINGIZE(INTERNAL_CATCH_UNIQUE_NAME(generator)), \ + CATCH_INTERNAL_LINEINFO, \ + [=] { using namespace Catch::Generators; return makeGenerators( __VA_ARGS__ ); }) // NOLINT(google-build-using-namespace) +#define GENERATE_REF(...) \ + Catch::Generators::generate(CATCH_INTERNAL_GENERATOR_STRINGIZE(INTERNAL_CATCH_UNIQUE_NAME(generator)), \ + CATCH_INTERNAL_LINEINFO, \ + [&] { using namespace Catch::Generators; return makeGenerators( __VA_ARGS__ ); }) // NOLINT(google-build-using-namespace) + +#endif // CATCH_GENERATORS_HPP_INCLUDED + +#ifndef CATCH_GENERATORS_ADAPTERS_HPP_INCLUDED +#define CATCH_GENERATORS_ADAPTERS_HPP_INCLUDED + +#include + +namespace Catch { + namespace Generators { + + template + class TakeGenerator final : public IGenerator { + GeneratorWrapper m_generator; + size_t m_returned = 0; + size_t m_target; + + void skipToNthElementImpl(std::size_t n) override { + if (n >= m_target) { + Detail::throw_generator_exception( + "Coud not jump to Nth element: not enough elements"); + } + + m_generator.skipToNthElement(n); + m_returned = n; + } + + public: + TakeGenerator(size_t target, GeneratorWrapper &&generator) + : m_generator(CATCH_MOVE(generator)), m_target(target) { + assert(target != 0 && "Empty generators are not allowed"); + } + T const &get() const override { + return m_generator.get(); + } + bool next() override { + ++m_returned; + if (m_returned >= m_target) { + return false; + } + + const auto success = m_generator.next(); + // If the underlying generator does not contain enough values + // then we cut short as well + if (!success) { + m_returned = m_target; + } + return success; + } + + bool isFinite() const override { return true; } + }; + + template + GeneratorWrapper take(size_t target, GeneratorWrapper &&generator) { + return GeneratorWrapper(Catch::Detail::make_unique>(target, CATCH_MOVE(generator))); + } + + template + class FilterGenerator final : public IGenerator { + GeneratorWrapper m_generator; + Predicate m_predicate; + static_assert(!std::is_reference::value, "This would most likely result in a dangling reference"); + + public: + template + FilterGenerator(P &&pred, GeneratorWrapper &&generator) + : m_generator(CATCH_MOVE(generator)), m_predicate(CATCH_FORWARD(pred)) { + if (!m_predicate(m_generator.get())) { + // It might happen that there are no values that pass the + // filter. In that case we throw an exception. + auto has_initial_value = next(); + if (!has_initial_value) { + Detail::throw_generator_exception("No valid value found in filtered generator"); + } + } + } + + T const &get() const override { + return m_generator.get(); + } + + bool next() override { + bool success = m_generator.next(); + if (!success) { + return false; + } + while (!m_predicate(m_generator.get()) && (success = m_generator.next()) == true); + return success; + } + + bool isFinite() const override { return m_generator.isFinite(); } + }; + + template + GeneratorWrapper filter(Predicate &&pred, GeneratorWrapper &&generator) { + return GeneratorWrapper(Catch::Detail::make_unique::type>>(CATCH_FORWARD(pred), CATCH_MOVE(generator))); + } + + template + class RepeatGenerator final : public IGenerator { + static_assert(!std::is_same::value, + "RepeatGenerator currently does not support bools" + "because of std::vector specialization"); + GeneratorWrapper m_generator; + mutable std::vector m_returned; + size_t m_target_repeats; + size_t m_current_repeat = 0; + size_t m_repeat_index = 0; + + public: + RepeatGenerator(size_t repeats, GeneratorWrapper &&generator) + : m_generator(CATCH_MOVE(generator)), m_target_repeats(repeats) { + assert(m_target_repeats > 0 && "Repeat generator must repeat at least once"); + if (!m_generator.isFinite()) { + Detail::throw_generator_exception("Cannot repeat infinite generator"); + } + } + + T const &get() const override { + if (m_current_repeat == 0) { + m_returned.push_back(m_generator.get()); + return m_returned.back(); + } + return m_returned[m_repeat_index]; + } + + bool next() override { + // There are 2 basic cases: + // 1) We are still reading the generator + // 2) We are reading our own cache + + // In the first case, we need to poke the underlying generator. + // If it happily moves, we are left in that state, otherwise it is time to start reading from our cache + if (m_current_repeat == 0) { + const auto success = m_generator.next(); + if (!success) { + ++m_current_repeat; + } + return m_current_repeat < m_target_repeats; + } + + // In the second case, we need to move indices forward and check that we haven't run up against the end + ++m_repeat_index; + if (m_repeat_index == m_returned.size()) { + m_repeat_index = 0; + ++m_current_repeat; + } + return m_current_repeat < m_target_repeats; + } + + bool isFinite() const override { return m_generator.isFinite(); } + }; + + template + GeneratorWrapper repeat(size_t repeats, GeneratorWrapper &&generator) { + return GeneratorWrapper(Catch::Detail::make_unique>(repeats, CATCH_MOVE(generator))); + } + + template + class MapGenerator final : public IGenerator { + // TBD: provide static assert for mapping function, for friendly error message + GeneratorWrapper m_generator; + Func m_function; + // To avoid returning dangling reference, we have to save the values + mutable Optional m_cache; + + void skipToNthElementImpl(std::size_t n) override { + m_generator.skipToNthElement(n); + m_cache.reset(); + } + + public: + template + MapGenerator(F2 &&function, GeneratorWrapper &&generator) + : m_generator(CATCH_MOVE(generator)), m_function(CATCH_FORWARD(function)) {} + + T const &get() const override { + if (!m_cache) { m_cache = m_function(m_generator.get()); } + return *m_cache; + } + bool next() override { + m_cache.reset(); + return m_generator.next(); + } + + bool isFinite() const override { return m_generator.isFinite(); } + }; + + template> + GeneratorWrapper map(Func &&function, GeneratorWrapper &&generator) { + return GeneratorWrapper( + Catch::Detail::make_unique>(CATCH_FORWARD(function), CATCH_MOVE(generator))); + } + + template + GeneratorWrapper map(Func &&function, GeneratorWrapper &&generator) { + return GeneratorWrapper( + Catch::Detail::make_unique>(CATCH_FORWARD(function), CATCH_MOVE(generator))); + } + + template + class ChunkGenerator final : public IGenerator> { + std::vector m_chunk; + size_t m_chunk_size; + GeneratorWrapper m_generator; + + public: + ChunkGenerator(size_t size, GeneratorWrapper generator) + : m_chunk_size(size), m_generator(CATCH_MOVE(generator)) { + m_chunk.reserve(m_chunk_size); + if (m_chunk_size != 0) { + m_chunk.push_back(m_generator.get()); + for (size_t i = 1; i < m_chunk_size; ++i) { + if (!m_generator.next()) { + Detail::throw_generator_exception("Not enough values to initialize the first chunk"); + } + m_chunk.push_back(m_generator.get()); + } + } + } + std::vector const &get() const override { + return m_chunk; + } + bool next() override { + m_chunk.clear(); + for (size_t idx = 0; idx < m_chunk_size; ++idx) { + if (!m_generator.next()) { + return false; + } + m_chunk.push_back(m_generator.get()); + } + return true; + } + + bool isFinite() const override { return m_generator.isFinite(); } + }; + + template + GeneratorWrapper> chunk(size_t size, GeneratorWrapper &&generator) { + return GeneratorWrapper>( + Catch::Detail::make_unique>(size, CATCH_MOVE(generator))); + } + + template + class ConcatGenerator final : public IGenerator { + std::vector> m_generators; + size_t m_current_generator = 0; + + void InsertGenerators(GeneratorWrapper &&gen) { + m_generators.push_back(CATCH_MOVE(gen)); + } + + template + void InsertGenerators(GeneratorWrapper &&gen, Generators &&...gens) { + m_generators.push_back(CATCH_MOVE(gen)); + InsertGenerators(CATCH_MOVE(gens)...); + } + + public: + template + ConcatGenerator(Generators &&...generators) { + InsertGenerators(CATCH_MOVE(generators)...); + } + + T const &get() const override { + return m_generators[m_current_generator].get(); + } + bool next() override { + const bool success = m_generators[m_current_generator].next(); + if (success) { return true; } + + // If current generator is used up, we have to move to the next one + ++m_current_generator; + return m_current_generator < m_generators.size(); + } + + bool isFinite() const override { + for (auto const &gen : m_generators) { + if (!gen.isFinite()) { return false; } + } + return true; + } + }; + + template + GeneratorWrapper cat(GeneratorWrapper &&generator, + Generators &&...generators) { + return GeneratorWrapper( + Catch::Detail::make_unique>( + CATCH_MOVE(generator), CATCH_MOVE(generators)...)); + } + + } // namespace Generators +} // namespace Catch + +#endif // CATCH_GENERATORS_ADAPTERS_HPP_INCLUDED + +#ifndef CATCH_GENERATORS_RANDOM_HPP_INCLUDED +#define CATCH_GENERATORS_RANDOM_HPP_INCLUDED + +#ifndef CATCH_RANDOM_NUMBER_GENERATOR_HPP_INCLUDED +#define CATCH_RANDOM_NUMBER_GENERATOR_HPP_INCLUDED + +#include + +namespace Catch { + + // This is a simple implementation of C++11 Uniform Random Number + // Generator. It does not provide all operators, because Catch2 + // does not use it, but it should behave as expected inside stdlib's + // distributions. + // The implementation is based on the PCG family (http://pcg-random.org) + class SimplePcg32 { + using state_type = std::uint64_t; + + public: + using result_type = std::uint32_t; + static constexpr result_type(min)() { + return 0; + } + static constexpr result_type(max)() { + return static_cast(-1); + } + + // Provide some default initial state for the default constructor + SimplePcg32() + : SimplePcg32(0xed743cc4U) {} + + explicit SimplePcg32(result_type seed_); + + void seed(result_type seed_); + void discard(uint64_t skip); + + result_type operator()(); + + private: + friend bool operator==(SimplePcg32 const &lhs, SimplePcg32 const &rhs); + friend bool operator!=(SimplePcg32 const &lhs, SimplePcg32 const &rhs); + + // In theory we also need operator<< and operator>> + // In practice we do not use them, so we will skip them for now + + std::uint64_t m_state; + // This part of the state determines which "stream" of the numbers + // is chosen -- we take it as a constant for Catch2, so we only + // need to deal with seeding the main state. + // Picked by reading 8 bytes from `/dev/random` :-) + static const std::uint64_t s_inc = (0x13ed0cc53f939476ULL << 1ULL) | 1ULL; + }; + +} // end namespace Catch + +#endif // CATCH_RANDOM_NUMBER_GENERATOR_HPP_INCLUDED + +#ifndef CATCH_UNIFORM_INTEGER_DISTRIBUTION_HPP_INCLUDED +#define CATCH_UNIFORM_INTEGER_DISTRIBUTION_HPP_INCLUDED + +#ifndef CATCH_RANDOM_INTEGER_HELPERS_HPP_INCLUDED +#define CATCH_RANDOM_INTEGER_HELPERS_HPP_INCLUDED + +#include +#include +#include +#include + +// Note: We use the usual enable-disable-autodetect dance here even though +// we do not support these in CMake configuration options (yet?). +// It is highly unlikely that we will need to make these actually +// user-configurable, but this will make it simpler if weend up needing +// it, and it provides an escape hatch to the users who need it. +#if defined(__SIZEOF_INT128__) +#define CATCH_CONFIG_INTERNAL_UINT128 +// Unlike GCC, MSVC does not polyfill umul as mulh + mul pair on ARM machines. +// Currently we do not bother doing this ourselves, but we could if it became +// important for perf. +#elif defined(_MSC_VER) && defined(_M_X64) +#define CATCH_CONFIG_INTERNAL_MSVC_UMUL128 +#endif + +#if defined(CATCH_CONFIG_INTERNAL_UINT128) && !defined(CATCH_CONFIG_NO_UINT128) && !defined(CATCH_CONFIG_UINT128) +#define CATCH_CONFIG_UINT128 +#endif + +#if defined(CATCH_CONFIG_INTERNAL_MSVC_UMUL128) && !defined(CATCH_CONFIG_NO_MSVC_UMUL128) && !defined(CATCH_CONFIG_MSVC_UMUL128) +#define CATCH_CONFIG_MSVC_UMUL128 +#include +#endif + +namespace Catch { + namespace Detail { + + template + struct SizedUnsignedType; +#define SizedUnsignedTypeHelper(TYPE) \ + template<> \ + struct SizedUnsignedType { \ + using type = TYPE; \ + } + + SizedUnsignedTypeHelper(std::uint8_t); + SizedUnsignedTypeHelper(std::uint16_t); + SizedUnsignedTypeHelper(std::uint32_t); + SizedUnsignedTypeHelper(std::uint64_t); +#undef SizedUnsignedTypeHelper + + template + using SizedUnsignedType_t = typename SizedUnsignedType::type; + + template + using DoubleWidthUnsignedType_t = SizedUnsignedType_t<2 * sizeof(T)>; + + template + struct ExtendedMultResult { + T upper; + T lower; + constexpr bool operator==(ExtendedMultResult const &rhs) const { + return upper == rhs.upper && lower == rhs.lower; + } + }; + + /** + * Returns 128 bit result of lhs * rhs using portable C++ code + * + * This implementation is almost twice as fast as naive long multiplication, + * and unlike intrinsic-based approach, it supports constexpr evaluation. + */ + constexpr ExtendedMultResult + extendedMultPortable(std::uint64_t lhs, std::uint64_t rhs) { +#define CarryBits(x) (x >> 32) +#define Digits(x) (x & 0xFF'FF'FF'FF) + std::uint64_t lhs_low = Digits(lhs); + std::uint64_t rhs_low = Digits(rhs); + std::uint64_t low_low = (lhs_low * rhs_low); + std::uint64_t high_high = CarryBits(lhs) * CarryBits(rhs); + + // We add in carry bits from low-low already + std::uint64_t high_low = (CarryBits(lhs) * rhs_low) + CarryBits(low_low); + // Note that we can add only low bits from high_low, to avoid + // overflow with large inputs + std::uint64_t low_high = (lhs_low * CarryBits(rhs)) + Digits(high_low); + + return {high_high + CarryBits(high_low) + CarryBits(low_high), + (low_high << 32) | Digits(low_low)}; +#undef CarryBits +#undef Digits + } + + //! Returns 128 bit result of lhs * rhs + inline ExtendedMultResult + extendedMult(std::uint64_t lhs, std::uint64_t rhs) { +#if defined(CATCH_CONFIG_UINT128) + auto result = __uint128_t(lhs) * __uint128_t(rhs); + return {static_cast(result >> 64), + static_cast(result)}; +#elif defined(CATCH_CONFIG_MSVC_UMUL128) + std::uint64_t high; + std::uint64_t low = _umul128(lhs, rhs, &high); + return {high, low}; +#else + return extendedMultPortable(lhs, rhs); +#endif + } + + template + constexpr ExtendedMultResult extendedMult(UInt lhs, UInt rhs) { + static_assert(std::is_unsigned::value, + "extendedMult can only handle unsigned integers"); + static_assert(sizeof(UInt) < sizeof(std::uint64_t), + "Generic extendedMult can only handle types smaller " + "than uint64_t"); + using WideType = DoubleWidthUnsignedType_t; + + auto result = WideType(lhs) * WideType(rhs); + return { + static_cast(result >> (CHAR_BIT * sizeof(UInt))), + static_cast(result & UInt(-1))}; + } + + template + std::enable_if_t= sizeof(TargetType), + TargetType> + fillBitsFrom(Generator &gen) { + using gresult_type = typename Generator::result_type; + static_assert(std::is_unsigned::value, "Only unsigned integers are supported"); + static_assert(Generator::min() == 0 && Generator::max() == static_cast(-1), + "Generator must be able to output all numbers in its result type (effectively it must be a random bit generator)"); + + // We want to return the top bits from a generator, as they are + // usually considered higher quality. + constexpr auto generated_bits = sizeof(gresult_type) * CHAR_BIT; + constexpr auto return_bits = sizeof(TargetType) * CHAR_BIT; + + return static_cast(gen() >> (generated_bits - return_bits)); + } + + template + std::enable_if_t + fillBitsFrom(Generator &gen) { + using gresult_type = typename Generator::result_type; + static_assert(std::is_unsigned::value, + "Only unsigned integers are supported"); + static_assert(Generator::min() == 0 && Generator::max() == static_cast(-1), + "Generator must be able to output all numbers in its result type (effectively it must be a random bit generator)"); + + constexpr auto generated_bits = sizeof(gresult_type) * CHAR_BIT; + constexpr auto return_bits = sizeof(TargetType) * CHAR_BIT; + std::size_t filled_bits = 0; + TargetType ret = 0; + do { + ret <<= generated_bits; + ret |= gen(); + filled_bits += generated_bits; + } while (filled_bits < return_bits); + + return ret; + } + + /* + * Transposes numbers into unsigned type while keeping their ordering + * + * This means that signed types are changed so that the ordering is + * [INT_MIN, ..., -1, 0, ..., INT_MAX], rather than order we would + * get by simple casting ([0, ..., INT_MAX, INT_MIN, ..., -1]) + */ + template + constexpr std::enable_if_t::value, UnsignedType> + transposeToNaturalOrder(UnsignedType in) { + static_assert( + sizeof(OriginalType) == sizeof(UnsignedType), + "reordering requires the same sized types on both sides"); + static_assert(std::is_unsigned::value, + "Input type must be unsigned"); + // Assuming 2s complement (standardized in current C++), the + // positive and negative numbers are already internally ordered, + // and their difference is in the top bit. Swapping it orders + // them the desired way. + constexpr auto highest_bit = UnsignedType(1) << (sizeof(UnsignedType) * CHAR_BIT - 1); + return static_cast(in ^ highest_bit); + } + + template + constexpr std::enable_if_t::value, UnsignedType> + transposeToNaturalOrder(UnsignedType in) { + static_assert( + sizeof(OriginalType) == sizeof(UnsignedType), + "reordering requires the same sized types on both sides"); + static_assert(std::is_unsigned::value, "Input type must be unsigned"); + // No reordering is needed for unsigned -> unsigned + return in; + } + } // namespace Detail +} // namespace Catch + +#endif // CATCH_RANDOM_INTEGER_HELPERS_HPP_INCLUDED + +namespace Catch { + + /** + * Implementation of uniform distribution on integers. + * + * Unlike `std::uniform_int_distribution`, this implementation supports + * various 1 byte integral types, including bool (but you should not + * actually use it for bools). + * + * The underlying algorithm is based on the one described in "Fast Random + * Integer Generation in an Interval" by Daniel Lemire, but has been + * optimized under the assumption of reuse of the same distribution object. + */ + template + class uniform_integer_distribution { + static_assert(std::is_integral::value, "..."); + + using UnsignedIntegerType = Detail::SizedUnsignedType_t; + + // Only the left bound is stored, and we store it converted to its + // unsigned image. This avoids having to do the conversions inside + // the operator(), at the cost of having to do the conversion in + // the a() getter. The right bound is only needed in the b() getter, + // so we recompute it there from other stored data. + UnsignedIntegerType m_a; + + // How many different values are there in [a, b]. a == b => 1, can be 0 for distribution over all values in the type. + UnsignedIntegerType m_ab_distance; + + // We hoisted this out of the main generation function. Technically, + // this means that using this distribution will be slower than Lemire's + // algorithm if this distribution instance will be used only few times, + // but it will be faster if it is used many times. Since Catch2 uses + // distributions only to implement random generators, we assume that each + // distribution will be reused many times and this is an optimization. + UnsignedIntegerType m_rejection_threshold = 0; + + static constexpr UnsignedIntegerType computeDistance(IntegerType a, IntegerType b) { + // This overflows and returns 0 if a == 0 and b == TYPE_MAX. + // We handle that later when generating the number. + return transposeTo(b) - transposeTo(a) + 1; + } + + static constexpr UnsignedIntegerType computeRejectionThreshold(UnsignedIntegerType ab_distance) { + // distance == 0 means that we will return all possible values from + // the type's range, and that we shouldn't reject anything. + if (ab_distance == 0) { return 0; } + return (~ab_distance + 1) % ab_distance; + } + + static constexpr UnsignedIntegerType transposeTo(IntegerType in) { + return Detail::transposeToNaturalOrder( + static_cast(in)); + } + static constexpr IntegerType transposeBack(UnsignedIntegerType in) { + return static_cast( + Detail::transposeToNaturalOrder(in)); + } + + public: + using result_type = IntegerType; + + constexpr uniform_integer_distribution(IntegerType a, IntegerType b) + : m_a(transposeTo(a)), m_ab_distance(computeDistance(a, b)), m_rejection_threshold(computeRejectionThreshold(m_ab_distance)) { + assert(a <= b); + } + + template + constexpr result_type operator()(Generator &g) { + // All possible values of result_type are valid. + if (m_ab_distance == 0) { + return transposeBack(Detail::fillBitsFrom(g)); + } + + auto random_number = Detail::fillBitsFrom(g); + auto emul = Detail::extendedMult(random_number, m_ab_distance); + // Unlike Lemire's algorithm we skip the ab_distance check, since + // we precomputed the rejection threshold, which is always tighter. + while (emul.lower < m_rejection_threshold) { + random_number = Detail::fillBitsFrom(g); + emul = Detail::extendedMult(random_number, m_ab_distance); + } + + return transposeBack(m_a + emul.upper); + } + + constexpr result_type a() const { return transposeBack(m_a); } + constexpr result_type b() const { return transposeBack(m_ab_distance + m_a - 1); } + }; + +} // end namespace Catch + +#endif // CATCH_UNIFORM_INTEGER_DISTRIBUTION_HPP_INCLUDED + +#ifndef CATCH_UNIFORM_FLOATING_POINT_DISTRIBUTION_HPP_INCLUDED +#define CATCH_UNIFORM_FLOATING_POINT_DISTRIBUTION_HPP_INCLUDED + +#ifndef CATCH_RANDOM_FLOATING_POINT_HELPERS_HPP_INCLUDED +#define CATCH_RANDOM_FLOATING_POINT_HELPERS_HPP_INCLUDED + +#ifndef CATCH_POLYFILLS_HPP_INCLUDED +#define CATCH_POLYFILLS_HPP_INCLUDED + +namespace Catch { + + bool isnan(float f); + bool isnan(double d); + + float nextafter(float x, float y); + double nextafter(double x, double y); + +} // namespace Catch + +#endif // CATCH_POLYFILLS_HPP_INCLUDED + +#include +#include +#include +#include +#include + +namespace Catch { + + namespace Detail { + /** + * Returns the largest magnitude of 1-ULP distance inside the [a, b] range. + * + * Assumes `a < b`. + */ + template + FloatType gamma(FloatType a, FloatType b) { + static_assert(std::is_floating_point::value, + "gamma returns the largest ULP magnitude within " + "floating point range [a, b]. This only makes sense " + "for floating point types"); + assert(a <= b); + + const auto gamma_up = Catch::nextafter(a, std::numeric_limits::infinity()) - a; + const auto gamma_down = b - Catch::nextafter(b, -std::numeric_limits::infinity()); + + return gamma_up < gamma_down ? gamma_down : gamma_up; + } + + template + struct DistanceTypePicker; + template<> + struct DistanceTypePicker { + using type = std::uint32_t; + }; + template<> + struct DistanceTypePicker { + using type = std::uint64_t; + }; + + template + using DistanceType = typename DistanceTypePicker::type; + +#if defined(__GNUC__) || defined(__clang__) +#pragma GCC diagnostic push +#pragma GCC diagnostic ignored "-Wfloat-equal" +#endif + /** + * Computes the number of equi-distant floats in [a, b] + * + * Since not every range can be split into equidistant floats + * exactly, we actually compute ceil(b/distance - a/distance), + * because in those cases we want to overcount. + * + * Uses modified Dekker's FastTwoSum algorithm to handle rounding. + */ + template + DistanceType + count_equidistant_floats(FloatType a, FloatType b, FloatType distance) { + assert(a <= b); + // We get distance as gamma for our uniform float distribution, + // so this will round perfectly. + const auto ag = a / distance; + const auto bg = b / distance; + + const auto s = bg - ag; + const auto err = (std::fabs(a) <= std::fabs(b)) + ? -ag - (s - bg) + : bg - (s + ag); + const auto ceil_s = static_cast>(std::ceil(s)); + + return (ceil_s != s) ? ceil_s : ceil_s + (err > 0); + } +#if defined(__GNUC__) || defined(__clang__) +#pragma GCC diagnostic pop +#endif + + } // namespace Detail + +} // end namespace Catch + +#endif // CATCH_RANDOM_FLOATING_POINT_HELPERS_HPP_INCLUDED + +#include +#include + +namespace Catch { + + namespace Detail { +#if defined(__GNUC__) || defined(__clang__) +#pragma GCC diagnostic push +#pragma GCC diagnostic ignored "-Wfloat-equal" +#endif + // The issue with overflow only happens with maximal ULP and HUGE + // distance, e.g. when generating numbers in [-inf, inf] for given + // type. So we only check for the largest possible ULP in the + // type, and return something that does not overflow to inf in 1 mult. + constexpr std::uint64_t calculate_max_steps_in_one_go(double gamma) { + if (gamma == 1.99584030953472e+292) { return 9007199254740991; } + return static_cast(-1); + } + constexpr std::uint32_t calculate_max_steps_in_one_go(float gamma) { + if (gamma == 2.028241e+31f) { return 16777215; } + return static_cast(-1); + } +#if defined(__GNUC__) || defined(__clang__) +#pragma GCC diagnostic pop +#endif + } // namespace Detail + + /** + * Implementation of uniform distribution on floating point numbers. + * + * Note that we support only `float` and `double` types, because these + * usually mean the same thing across different platform. `long double` + * varies wildly by platform and thus we cannot provide reproducible + * implementation. Also note that we don't implement all parts of + * distribution per standard: this distribution is not serializable, nor + * can the range be arbitrarily reset. + * + * The implementation also uses different approach than the one taken by + * `std::uniform_real_distribution`, where instead of generating a number + * between [0, 1) and then multiplying the range bounds with it, we first + * split the [a, b] range into a set of equidistributed floating point + * numbers, and then use uniform int distribution to pick which one to + * return. + * + * This has the advantage of guaranteeing uniformity (the multiplication + * method loses uniformity due to rounding when multiplying floats), except + * for small non-uniformity at one side of the interval, where we have + * to deal with the fact that not every interval is splittable into + * equidistributed floats. + * + * Based on "Drawing random floating-point numbers from an interval" by + * Frederic Goualard. + */ + template + class uniform_floating_point_distribution { + static_assert(std::is_floating_point::value, "..."); + static_assert(!std::is_same::value, + "We do not support long double due to inconsistent behaviour between platforms"); + + using WidthType = Detail::DistanceType; + + FloatType m_a, m_b; + FloatType m_ulp_magnitude; + WidthType m_floats_in_range; + uniform_integer_distribution m_int_dist; + + // In specific cases, we can overflow into `inf` when computing the + // `steps * g` offset. To avoid this, we don't offset by more than this + // in one multiply + addition. + WidthType m_max_steps_in_one_go; + // We don't want to do the magnitude check every call to `operator()` + bool m_a_has_leq_magnitude; + + public: + using result_type = FloatType; + + uniform_floating_point_distribution(FloatType a, FloatType b) + : m_a(a), m_b(b), m_ulp_magnitude(Detail::gamma(m_a, m_b)), m_floats_in_range(Detail::count_equidistant_floats(m_a, m_b, m_ulp_magnitude)), m_int_dist(0, m_floats_in_range), m_max_steps_in_one_go(Detail::calculate_max_steps_in_one_go(m_ulp_magnitude)), m_a_has_leq_magnitude(std::fabs(m_a) <= std::fabs(m_b)) { + assert(a <= b); + } + + template + result_type operator()(Generator &g) { + WidthType steps = m_int_dist(g); + if (m_a_has_leq_magnitude) { + if (steps == m_floats_in_range) { return m_a; } + auto b = m_b; + while (steps > m_max_steps_in_one_go) { + b -= m_max_steps_in_one_go * m_ulp_magnitude; + steps -= m_max_steps_in_one_go; + } + return b - steps * m_ulp_magnitude; + } else { + if (steps == m_floats_in_range) { return m_b; } + auto a = m_a; + while (steps > m_max_steps_in_one_go) { + a += m_max_steps_in_one_go * m_ulp_magnitude; + steps -= m_max_steps_in_one_go; + } + return a + steps * m_ulp_magnitude; + } + } + + result_type a() const { return m_a; } + result_type b() const { return m_b; } + }; + +} // end namespace Catch + +#endif // CATCH_UNIFORM_FLOATING_POINT_DISTRIBUTION_HPP_INCLUDED + +namespace Catch { + namespace Generators { + namespace Detail { + // Returns a suitable seed for a random floating generator based off + // the primary internal rng. It does so by taking current value from + // the rng and returning it as the seed. + std::uint32_t getSeed(); + } // namespace Detail + + template + class RandomFloatingGenerator final : public IGenerator { + Catch::SimplePcg32 m_rng; + Catch::uniform_floating_point_distribution m_dist; + Float m_current_number; + + public: + RandomFloatingGenerator(Float a, Float b, std::uint32_t seed) + : m_rng(seed), m_dist(a, b) { + static_cast(next()); + } + + Float const &get() const override { + return m_current_number; + } + bool next() override { + m_current_number = m_dist(m_rng); + return true; + } + bool isFinite() const override { return false; } + }; + + template<> + class RandomFloatingGenerator final : public IGenerator { + // We still rely on for this specialization, but we don't + // want to drag it into the header. + struct PImpl; + Catch::Detail::unique_ptr m_pimpl; + long double m_current_number; + + public: + RandomFloatingGenerator(long double a, long double b, std::uint32_t seed); + + long double const &get() const override { return m_current_number; } + bool next() override; + + ~RandomFloatingGenerator() override; // = default + bool isFinite() const override; + }; + + template + class RandomIntegerGenerator final : public IGenerator { + Catch::SimplePcg32 m_rng; + Catch::uniform_integer_distribution m_dist; + Integer m_current_number; + + public: + RandomIntegerGenerator(Integer a, Integer b, std::uint32_t seed) + : m_rng(seed), m_dist(a, b) { + static_cast(next()); + } + + Integer const &get() const override { + return m_current_number; + } + bool next() override { + m_current_number = m_dist(m_rng); + return true; + } + bool isFinite() const override { return false; } + }; + + template + std::enable_if_t::value, GeneratorWrapper> + random(T a, T b) { + return GeneratorWrapper( + Catch::Detail::make_unique>(a, b, Detail::getSeed())); + } + + template + std::enable_if_t::value, + GeneratorWrapper> + random(T a, T b) { + return GeneratorWrapper( + Catch::Detail::make_unique>(a, b, Detail::getSeed())); + } + + } // namespace Generators +} // namespace Catch + +#endif // CATCH_GENERATORS_RANDOM_HPP_INCLUDED + +#ifndef CATCH_GENERATORS_RANGE_HPP_INCLUDED +#define CATCH_GENERATORS_RANGE_HPP_INCLUDED + +#include +#include + +namespace Catch { + namespace Generators { + + template + class RangeGenerator final : public IGenerator { + T m_current; + T m_end; + T m_step; + bool m_positive; + + public: + RangeGenerator(T const &start, T const &end, T const &step) + : m_current(start), m_end(end), m_step(step), m_positive(m_step > T(0)) { + assert(m_current != m_end && "Range start and end cannot be equal"); + assert(m_step != T(0) && "Step size cannot be zero"); + assert(((m_positive && m_current <= m_end) || (!m_positive && m_current >= m_end)) && "Step moves away from end"); + } + + RangeGenerator(T const &start, T const &end) + : RangeGenerator(start, end, (start < end) ? T(1) : T(-1)) {} + + T const &get() const override { + return m_current; + } + + bool next() override { + m_current += m_step; + return (m_positive) ? (m_current < m_end) : (m_current > m_end); + } + + bool isFinite() const override { return true; } + }; + + template + GeneratorWrapper range(T const &start, T const &end, T const &step) { + static_assert(std::is_arithmetic::value && !std::is_same::value, "Type must be numeric"); + return GeneratorWrapper(Catch::Detail::make_unique>(start, end, step)); + } + + template + GeneratorWrapper range(T const &start, T const &end) { + static_assert(std::is_integral::value && !std::is_same::value, "Type must be an integer"); + return GeneratorWrapper(Catch::Detail::make_unique>(start, end)); + } + + template + class IteratorGenerator final : public IGenerator { + static_assert(!std::is_same::value, + "IteratorGenerator currently does not support bools" + "because of std::vector specialization"); + + std::vector m_elems; + size_t m_current = 0; + + public: + template + IteratorGenerator(InputIterator first, InputSentinel last) + : m_elems(first, last) { + if (m_elems.empty()) { + Detail::throw_generator_exception("IteratorGenerator received no valid values"); + } + } + + T const &get() const override { + return m_elems[m_current]; + } + + bool next() override { + ++m_current; + return m_current != m_elems.size(); + } + + bool isFinite() const override { return true; } + }; + + template::value_type>> + GeneratorWrapper from_range(InputIterator from, InputSentinel to) { + return GeneratorWrapper(Catch::Detail::make_unique>(from, to)); + } + + template + auto from_range(Container const &cnt) { + using std::begin; + using std::end; + return from_range(begin(cnt), end(cnt)); + } + + } // namespace Generators +} // namespace Catch + +#endif // CATCH_GENERATORS_RANGE_HPP_INCLUDED + +#endif // CATCH_GENERATORS_ALL_HPP_INCLUDED + +/** \file + * This is a convenience header for Catch2's interfaces. It includes + * **all** of Catch2 headers related to interfaces. + * + * Generally the Catch2 users should use specific includes they need, + * but this header can be used instead for ease-of-experimentation, or + * just plain convenience, at the cost of somewhat increased compilation + * times. + * + * When a new header is added to either the `interfaces` folder, or to + * the corresponding internal subfolder, it should be added here. + */ + +#ifndef CATCH_INTERFACES_ALL_HPP_INCLUDED +#define CATCH_INTERFACES_ALL_HPP_INCLUDED + +#ifndef CATCH_INTERFACES_REPORTER_HPP_INCLUDED +#define CATCH_INTERFACES_REPORTER_HPP_INCLUDED + +#include +#include +#include + +namespace Catch { + + struct ReporterDescription; + struct ListenerDescription; + struct TagInfo; + struct TestCaseInfo; + class TestCaseHandle; + class IConfig; + class IStream; + enum class ColourMode : std::uint8_t; + + struct ReporterConfig { + ReporterConfig(IConfig const *_fullConfig, + Detail::unique_ptr _stream, + ColourMode colourMode, + std::map customOptions); + + ReporterConfig(ReporterConfig &&) = default; + ReporterConfig &operator=(ReporterConfig &&) = default; + ~ReporterConfig(); // = default + + Detail::unique_ptr takeStream() &&; + IConfig const *fullConfig() const; + ColourMode colourMode() const; + std::map const &customOptions() const; + + private: + Detail::unique_ptr m_stream; + IConfig const *m_fullConfig; + ColourMode m_colourMode; + std::map m_customOptions; + }; + + struct AssertionStats { + AssertionStats(AssertionResult const &_assertionResult, + std::vector const &_infoMessages, + Totals const &_totals); + + AssertionStats(AssertionStats const &) = default; + AssertionStats(AssertionStats &&) = default; + AssertionStats &operator=(AssertionStats const &) = delete; + AssertionStats &operator=(AssertionStats &&) = delete; + + AssertionResult assertionResult; + std::vector infoMessages; + Totals totals; + }; + + struct SectionStats { + SectionStats(SectionInfo &&_sectionInfo, + Counts const &_assertions, + double _durationInSeconds, + bool _missingAssertions); + + SectionInfo sectionInfo; + Counts assertions; + double durationInSeconds; + bool missingAssertions; + }; + + struct TestCaseStats { + TestCaseStats(TestCaseInfo const &_testInfo, + Totals const &_totals, + std::string &&_stdOut, + std::string &&_stdErr, + bool _aborting); + + TestCaseInfo const *testInfo; + Totals totals; + std::string stdOut; + std::string stdErr; + bool aborting; + }; + + struct TestRunStats { + TestRunStats(TestRunInfo const &_runInfo, + Totals const &_totals, + bool _aborting); + + TestRunInfo runInfo; + Totals totals; + bool aborting; + }; + + //! By setting up its preferences, a reporter can modify Catch2's behaviour + //! in some regards, e.g. it can request Catch2 to capture writes to + //! stdout/stderr during test execution, and pass them to the reporter. + struct ReporterPreferences { + //! Catch2 should redirect writes to stdout and pass them to the + //! reporter + bool shouldRedirectStdOut = false; + //! Catch2 should call `Reporter::assertionEnded` even for passing + //! assertions + bool shouldReportAllAssertions = false; + //! Catch2 should call `Reporter::assertionStarting` for all assertions + // Defaults to true for backwards compatibility, but none of our current + // reporters actually want this, and it enables a fast path in assertion + // handling. + bool shouldReportAllAssertionStarts = true; + }; + + /** + * The common base for all reporters and event listeners + * + * Implementing classes must also implement: + * + * //! User-friendly description of the reporter/listener type + * static std::string getDescription() + * + * Generally shouldn't be derived from by users of Catch2 directly, + * instead they should derive from one of the utility bases that + * derive from this class. + */ + class IEventListener { + protected: + //! Derived classes can set up their preferences here + ReporterPreferences m_preferences; + //! The test run's config as filled in from CLI and defaults + IConfig const *m_config; + + public: + IEventListener(IConfig const *config) + : m_config(config) {} + + virtual ~IEventListener(); // = default; + + // Implementing class must also provide the following static methods: + // static std::string getDescription(); + + ReporterPreferences const &getPreferences() const { + return m_preferences; + } + + //! Called when no test cases match provided test spec + virtual void noMatchingTestCases(StringRef unmatchedSpec) = 0; + //! Called for all invalid test specs from the cli + virtual void reportInvalidTestSpec(StringRef invalidArgument) = 0; + + /** + * Called once in a testing run before tests are started + * + * Not called if tests won't be run (e.g. only listing will happen) + */ + virtual void testRunStarting(TestRunInfo const &testRunInfo) = 0; + + //! Called _once_ for each TEST_CASE, no matter how many times it is entered + virtual void testCaseStarting(TestCaseInfo const &testInfo) = 0; + //! Called _every time_ a TEST_CASE is entered, including repeats (due to sections) + virtual void testCasePartialStarting(TestCaseInfo const &testInfo, uint64_t partNumber) = 0; + //! Called when a `SECTION` is being entered. Not called for skipped sections + virtual void sectionStarting(SectionInfo const §ionInfo) = 0; + + //! Called when user-code is being probed before the actual benchmark runs + virtual void benchmarkPreparing(StringRef benchmarkName) = 0; + //! Called after probe but before the user-code is being benchmarked + virtual void benchmarkStarting(BenchmarkInfo const &benchmarkInfo) = 0; + //! Called with the benchmark results if benchmark successfully finishes + virtual void benchmarkEnded(BenchmarkStats<> const &benchmarkStats) = 0; + //! Called if running the benchmarks fails for any reason + virtual void benchmarkFailed(StringRef benchmarkName) = 0; + + //! Called before assertion success/failure is evaluated + virtual void assertionStarting(AssertionInfo const &assertionInfo) = 0; + + //! Called after assertion was fully evaluated + virtual void assertionEnded(AssertionStats const &assertionStats) = 0; + + //! Called after a `SECTION` has finished running + virtual void sectionEnded(SectionStats const §ionStats) = 0; + //! Called _every time_ a TEST_CASE is entered, including repeats (due to sections) + virtual void testCasePartialEnded(TestCaseStats const &testCaseStats, uint64_t partNumber) = 0; + //! Called _once_ for each TEST_CASE, no matter how many times it is entered + virtual void testCaseEnded(TestCaseStats const &testCaseStats) = 0; + /** + * Called once after all tests in a testing run are finished + * + * Not called if tests weren't run (e.g. only listings happened) + */ + virtual void testRunEnded(TestRunStats const &testRunStats) = 0; + + /** + * Called with test cases that are skipped due to the test run aborting. + * NOT called for test cases that are explicitly skipped using the `SKIP` macro. + * + * Deprecated - will be removed in the next major release. + */ + virtual void skipTest(TestCaseInfo const &testInfo) = 0; + + //! Called if a fatal error (signal/structured exception) occurred + virtual void fatalErrorEncountered(StringRef error) = 0; + + //! Writes out information about provided reporters using reporter-specific format + virtual void listReporters(std::vector const &descriptions) = 0; + //! Writes out the provided listeners descriptions using reporter-specific format + virtual void listListeners(std::vector const &descriptions) = 0; + //! Writes out information about provided tests using reporter-specific format + virtual void listTests(std::vector const &tests) = 0; + //! Writes out information about the provided tags using reporter-specific format + virtual void listTags(std::vector const &tags) = 0; + }; + using IEventListenerPtr = Detail::unique_ptr; + +} // end namespace Catch + +#endif // CATCH_INTERFACES_REPORTER_HPP_INCLUDED + +#ifndef CATCH_INTERFACES_REPORTER_FACTORY_HPP_INCLUDED +#define CATCH_INTERFACES_REPORTER_FACTORY_HPP_INCLUDED + +#include + +namespace Catch { + + struct ReporterConfig; + class IConfig; + class IEventListener; + using IEventListenerPtr = Detail::unique_ptr; + + class IReporterFactory { + public: + virtual ~IReporterFactory(); // = default + + virtual IEventListenerPtr + create(ReporterConfig &&config) const + = 0; + virtual std::string getDescription() const = 0; + }; + using IReporterFactoryPtr = Detail::unique_ptr; + + class EventListenerFactory { + public: + virtual ~EventListenerFactory(); // = default + virtual IEventListenerPtr create(IConfig const *config) const = 0; + //! Return a meaningful name for the listener, e.g. its type name + virtual StringRef getName() const = 0; + //! Return listener's description if available + virtual std::string getDescription() const = 0; + }; +} // namespace Catch + +#endif // CATCH_INTERFACES_REPORTER_FACTORY_HPP_INCLUDED + +#ifndef CATCH_INTERFACES_TAG_ALIAS_REGISTRY_HPP_INCLUDED +#define CATCH_INTERFACES_TAG_ALIAS_REGISTRY_HPP_INCLUDED + +#include + +namespace Catch { + + struct TagAlias; + + class ITagAliasRegistry { + public: + virtual ~ITagAliasRegistry(); // = default + // Nullptr if not present + virtual TagAlias const *find(std::string const &alias) const = 0; + virtual std::string expandAliases(std::string const &unexpandedTestSpec) const = 0; + + static ITagAliasRegistry const &get(); + }; + +} // end namespace Catch + +#endif // CATCH_INTERFACES_TAG_ALIAS_REGISTRY_HPP_INCLUDED + +#ifndef CATCH_INTERFACES_TESTCASE_HPP_INCLUDED +#define CATCH_INTERFACES_TESTCASE_HPP_INCLUDED + +#include + +namespace Catch { + + struct TestCaseInfo; + class TestCaseHandle; + class IConfig; + + class ITestCaseRegistry { + public: + virtual ~ITestCaseRegistry(); // = default + // TODO: this exists only for adding filenames to test cases -- let's expose this in a saner way later + virtual std::vector const &getAllInfos() const = 0; + virtual std::vector const &getAllTests() const = 0; + virtual std::vector const &getAllTestsSorted(IConfig const &config) const = 0; + }; + +} // namespace Catch + +#endif // CATCH_INTERFACES_TESTCASE_HPP_INCLUDED + +#endif // CATCH_INTERFACES_ALL_HPP_INCLUDED + +#ifndef CATCH_CASE_INSENSITIVE_COMPARISONS_HPP_INCLUDED +#define CATCH_CASE_INSENSITIVE_COMPARISONS_HPP_INCLUDED + +namespace Catch { + namespace Detail { + //! Provides case-insensitive `op<` semantics when called + struct CaseInsensitiveLess { + bool operator()(StringRef lhs, + StringRef rhs) const; + }; + + //! Provides case-insensitive `op==` semantics when called + struct CaseInsensitiveEqualTo { + bool operator()(StringRef lhs, + StringRef rhs) const; + }; + + } // namespace Detail +} // namespace Catch + +#endif // CATCH_CASE_INSENSITIVE_COMPARISONS_HPP_INCLUDED + +/** \file + * Wrapper for ANDROID_LOGWRITE configuration option + * + * We want to default to enabling it when compiled for android, but + * users of the library should also be able to disable it if they want + * to. + */ + +#ifndef CATCH_CONFIG_ANDROID_LOGWRITE_HPP_INCLUDED +#define CATCH_CONFIG_ANDROID_LOGWRITE_HPP_INCLUDED + +#if defined(__ANDROID__) +#define CATCH_INTERNAL_CONFIG_ANDROID_LOGWRITE +#endif + +#if defined(CATCH_INTERNAL_CONFIG_ANDROID_LOGWRITE) && !defined(CATCH_CONFIG_NO_ANDROID_LOGWRITE) && !defined(CATCH_CONFIG_ANDROID_LOGWRITE) +#define CATCH_CONFIG_ANDROID_LOGWRITE +#endif + +#endif // CATCH_CONFIG_ANDROID_LOGWRITE_HPP_INCLUDED + +/** \file + * Wrapper for UNCAUGHT_EXCEPTIONS configuration option + * + * For some functionality, Catch2 requires to know whether there is + * an active exception. Because `std::uncaught_exception` is deprecated + * in C++17, we want to use `std::uncaught_exceptions` if possible. + */ + +#ifndef CATCH_CONFIG_UNCAUGHT_EXCEPTIONS_HPP_INCLUDED +#define CATCH_CONFIG_UNCAUGHT_EXCEPTIONS_HPP_INCLUDED + +#if defined(_MSC_VER) +#if _MSC_VER >= 1900 // Visual Studio 2015 or newer +#define CATCH_INTERNAL_CONFIG_CPP17_UNCAUGHT_EXCEPTIONS +#endif +#endif + +#include + +#if defined(__cpp_lib_uncaught_exceptions) \ + && !defined(CATCH_INTERNAL_CONFIG_CPP17_UNCAUGHT_EXCEPTIONS) + +#define CATCH_INTERNAL_CONFIG_CPP17_UNCAUGHT_EXCEPTIONS +#endif // __cpp_lib_uncaught_exceptions + +#if defined(CATCH_INTERNAL_CONFIG_CPP17_UNCAUGHT_EXCEPTIONS) \ + && !defined(CATCH_CONFIG_NO_CPP17_UNCAUGHT_EXCEPTIONS) \ + && !defined(CATCH_CONFIG_CPP17_UNCAUGHT_EXCEPTIONS) + +#define CATCH_CONFIG_CPP17_UNCAUGHT_EXCEPTIONS +#endif + +#endif // CATCH_CONFIG_UNCAUGHT_EXCEPTIONS_HPP_INCLUDED + +#ifndef CATCH_CONSOLE_COLOUR_HPP_INCLUDED +#define CATCH_CONSOLE_COLOUR_HPP_INCLUDED + +#include +#include + +namespace Catch { + + enum class ColourMode : std::uint8_t; + class IStream; + + struct Colour { + enum Code { + None = 0, + + White, + Red, + Green, + Blue, + Cyan, + Yellow, + Grey, + + Bright = 0x10, + + BrightRed = Bright | Red, + BrightGreen = Bright | Green, + LightGrey = Bright | Grey, + BrightWhite = Bright | White, + BrightYellow = Bright | Yellow, + + // By intention + FileName = LightGrey, + Warning = BrightYellow, + ResultError = BrightRed, + ResultSuccess = BrightGreen, + ResultExpectedFailure = Warning, + + Error = BrightRed, + Success = Green, + Skip = LightGrey, + + OriginalExpression = Cyan, + ReconstructedExpression = BrightYellow, + + SecondaryText = LightGrey, + Headers = White + }; + }; + + class ColourImpl { + protected: + //! The associated stream of this ColourImpl instance + IStream *m_stream; + + public: + ColourImpl(IStream *stream) + : m_stream(stream) {} + + //! RAII wrapper around writing specific colour of text using specific + //! colour impl into a stream. + class ColourGuard { + ColourImpl const *m_colourImpl; + Colour::Code m_code; + bool m_engaged = false; + + public: + //! Does **not** engage the guard/start the colour + ColourGuard(Colour::Code code, + ColourImpl const *colour); + + ColourGuard(ColourGuard const &rhs) = delete; + ColourGuard &operator=(ColourGuard const &rhs) = delete; + + ColourGuard(ColourGuard &&rhs) noexcept; + ColourGuard &operator=(ColourGuard &&rhs) noexcept; + + //! Removes colour _if_ the guard was engaged + ~ColourGuard(); + + /** + * Explicitly engages colour for given stream. + * + * The API based on operator<< should be preferred. + */ + ColourGuard &engage(std::ostream &stream) &; + /** + * Explicitly engages colour for given stream. + * + * The API based on operator<< should be preferred. + */ + ColourGuard &&engage(std::ostream &stream) &&; + + private: + //! Engages the guard and starts using colour + friend std::ostream &operator<<(std::ostream &lhs, + ColourGuard &guard) { + guard.engageImpl(lhs); + return lhs; + } + //! Engages the guard and starts using colour + friend std::ostream &operator<<(std::ostream &lhs, + ColourGuard &&guard) { + guard.engageImpl(lhs); + return lhs; + } + + void engageImpl(std::ostream &stream); + }; + + virtual ~ColourImpl(); // = default + /** + * Creates a guard object for given colour and this colour impl + * + * **Important:** + * the guard starts disengaged, and has to be engaged explicitly. + */ + ColourGuard guardColour(Colour::Code colourCode); + + private: + virtual void use(Colour::Code colourCode) const = 0; + }; + + //! Provides ColourImpl based on global config and target compilation platform + Detail::unique_ptr makeColourImpl(ColourMode colourSelection, + IStream *stream); + + //! Checks if specific colour impl has been compiled into the binary + bool isColourImplAvailable(ColourMode colourSelection); + +} // end namespace Catch + +#endif // CATCH_CONSOLE_COLOUR_HPP_INCLUDED + +#ifndef CATCH_CONSOLE_WIDTH_HPP_INCLUDED +#define CATCH_CONSOLE_WIDTH_HPP_INCLUDED + +// This include must be kept so that user's configured value for CONSOLE_WIDTH +// is used before we attempt to provide a default value + +#ifndef CATCH_CONFIG_CONSOLE_WIDTH +#define CATCH_CONFIG_CONSOLE_WIDTH 80 +#endif + +#endif // CATCH_CONSOLE_WIDTH_HPP_INCLUDED + +#ifndef CATCH_CONTAINER_NONMEMBERS_HPP_INCLUDED +#define CATCH_CONTAINER_NONMEMBERS_HPP_INCLUDED + +#include +#include + +// We want a simple polyfill over `std::empty`, `std::size` and so on +// for C++14 or C++ libraries with incomplete support. +// We also have to handle that MSVC std lib will happily provide these +// under older standards. +#if defined(CATCH_CPP17_OR_GREATER) || defined(_MSC_VER) + +// We are already using this header either way, so there shouldn't +// be much additional overhead in including it to get the feature +// test macros +#include + +#if !defined(__cpp_lib_nonmember_container_access) +#define CATCH_CONFIG_POLYFILL_NONMEMBER_CONTAINER_ACCESS +#endif + +#else +#define CATCH_CONFIG_POLYFILL_NONMEMBER_CONTAINER_ACCESS +#endif + +namespace Catch { + namespace Detail { + +#if defined(CATCH_CONFIG_POLYFILL_NONMEMBER_CONTAINER_ACCESS) + template + constexpr auto empty(Container const &cont) -> decltype(cont.empty()) { + return cont.empty(); + } + template + constexpr bool empty(const T (&)[N]) noexcept { + // GCC < 7 does not support the const T(&)[] parameter syntax + // so we have to ignore the length explicitly + (void)N; + return false; + } + template + constexpr bool empty(std::initializer_list list) noexcept { + return list.size() > 0; + } + + template + constexpr auto size(Container const &cont) -> decltype(cont.size()) { + return cont.size(); + } + template + constexpr std::size_t size(const T (&)[N]) noexcept { + return N; + } +#endif // CATCH_CONFIG_POLYFILL_NONMEMBER_CONTAINER_ACCESS + + } // end namespace Detail +} // end namespace Catch + +#endif // CATCH_CONTAINER_NONMEMBERS_HPP_INCLUDED + +#ifndef CATCH_DEBUG_CONSOLE_HPP_INCLUDED +#define CATCH_DEBUG_CONSOLE_HPP_INCLUDED + +#include + +namespace Catch { + void writeToDebugConsole(std::string const &text); +} + +#endif // CATCH_DEBUG_CONSOLE_HPP_INCLUDED + +#ifndef CATCH_DEBUGGER_HPP_INCLUDED +#define CATCH_DEBUGGER_HPP_INCLUDED + +namespace Catch { + bool isDebuggerActive(); +} + +#if !defined(CATCH_TRAP) && defined(__clang__) && defined(__has_builtin) +#if __has_builtin(__builtin_debugtrap) +#define CATCH_TRAP() __builtin_debugtrap() +#endif +#endif + +#if !defined(CATCH_TRAP) && defined(_MSC_VER) +#define CATCH_TRAP() __debugbreak() +#endif + +#if !defined(CATCH_TRAP) // If we couldn't use compiler-specific impl from above, we get into platform-specific options +#ifdef CATCH_PLATFORM_MAC + +#if defined(__i386__) || defined(__x86_64__) +#define CATCH_TRAP() __asm__("int $3\n" : :) /* NOLINT */ +#elif defined(__aarch64__) +#define CATCH_TRAP() __asm__(".inst 0xd43e0000") +#elif defined(__POWERPC__) +#define CATCH_TRAP() __asm__("li r0, 20\nsc\nnop\nli r0, 37\nli r4, 2\nsc\nnop\n" \ + : : : "memory", "r0", "r3", "r4") /* NOLINT */ +#endif + +#elif defined(CATCH_PLATFORM_IPHONE) + +// use inline assembler +#if defined(__i386__) || defined(__x86_64__) +#define CATCH_TRAP() __asm__("int $3") +#elif defined(__aarch64__) +#define CATCH_TRAP() __asm__(".inst 0xd4200000") +#elif defined(__arm__) && !defined(__thumb__) +#define CATCH_TRAP() __asm__(".inst 0xe7f001f0") +#elif defined(__arm__) && defined(__thumb__) +#define CATCH_TRAP() __asm__(".inst 0xde01") +#endif + +#elif defined(CATCH_PLATFORM_LINUX) || defined(CATCH_PLATFORM_QNX) +// If we can use inline assembler, do it because this allows us to break +// directly at the location of the failing check instead of breaking inside +// raise() called from it, i.e. one stack frame below. +#if defined(__GNUC__) && (defined(__i386) || defined(__x86_64)) +#define CATCH_TRAP() asm volatile("int $3") /* NOLINT */ +#else // Fall back to the generic way. +#include + +#define CATCH_TRAP() raise(SIGTRAP) +#endif +#elif defined(__MINGW32__) +extern "C" __declspec(dllimport) void __stdcall DebugBreak(); +#define CATCH_TRAP() DebugBreak() +#endif +#endif // ^^ CATCH_TRAP is not defined yet, so we define it + +#if !defined(CATCH_BREAK_INTO_DEBUGGER) +#if defined(CATCH_TRAP) +#define CATCH_BREAK_INTO_DEBUGGER() [] { if( Catch::isDebuggerActive() ) { CATCH_TRAP(); } }() +#else +#define CATCH_BREAK_INTO_DEBUGGER() [] {}() +#endif +#endif + +#endif // CATCH_DEBUGGER_HPP_INCLUDED + +#ifndef CATCH_ENFORCE_HPP_INCLUDED +#define CATCH_ENFORCE_HPP_INCLUDED + +#include // for `std::exception` in no-exception configuration + +namespace Catch { +#if !defined(CATCH_CONFIG_DISABLE_EXCEPTIONS) + template + [[noreturn]] + void throw_exception(Ex const &e) { + throw e; + } +#else // ^^ Exceptions are enabled // Exceptions are disabled vv + [[noreturn]] + void throw_exception(std::exception const &e); +#endif + + [[noreturn]] + void throw_logic_error(std::string const &msg); + [[noreturn]] + void throw_domain_error(std::string const &msg); + [[noreturn]] + void throw_runtime_error(std::string const &msg); + +} // namespace Catch + +#define CATCH_MAKE_MSG(...) \ + (Catch::ReusableStringStream() << __VA_ARGS__).str() + +#define CATCH_INTERNAL_ERROR(...) \ + Catch::throw_logic_error(CATCH_MAKE_MSG(CATCH_INTERNAL_LINEINFO << ": Internal Catch2 error: " << __VA_ARGS__)) + +#define CATCH_ERROR(...) \ + Catch::throw_domain_error(CATCH_MAKE_MSG(__VA_ARGS__)) + +#define CATCH_RUNTIME_ERROR(...) \ + Catch::throw_runtime_error(CATCH_MAKE_MSG(__VA_ARGS__)) + +#define CATCH_ENFORCE(condition, ...) \ + do { \ + if (!(condition)) CATCH_ERROR(__VA_ARGS__); \ + } while (false) + +#endif // CATCH_ENFORCE_HPP_INCLUDED + +#ifndef CATCH_ENUM_VALUES_REGISTRY_HPP_INCLUDED +#define CATCH_ENUM_VALUES_REGISTRY_HPP_INCLUDED + +#include + +namespace Catch { + + namespace Detail { + + Catch::Detail::unique_ptr makeEnumInfo(StringRef enumName, StringRef allValueNames, std::vector const &values); + + class EnumValuesRegistry : public IMutableEnumValuesRegistry { + std::vector> m_enumInfos; + + EnumInfo const ®isterEnum(StringRef enumName, StringRef allValueNames, std::vector const &values) override; + }; + + std::vector parseEnums(StringRef enums); + + } // namespace Detail + +} // namespace Catch + +#endif // CATCH_ENUM_VALUES_REGISTRY_HPP_INCLUDED + +#ifndef CATCH_ERRNO_GUARD_HPP_INCLUDED +#define CATCH_ERRNO_GUARD_HPP_INCLUDED + +namespace Catch { + + //! Simple RAII class that stores the value of `errno` + //! at construction and restores it at destruction. + class ErrnoGuard { + public: + // Keep these outlined to avoid dragging in macros from + + ErrnoGuard(); + ~ErrnoGuard(); + + private: + int m_oldErrno; + }; + +} // namespace Catch + +#endif // CATCH_ERRNO_GUARD_HPP_INCLUDED + +#ifndef CATCH_EXCEPTION_TRANSLATOR_REGISTRY_HPP_INCLUDED +#define CATCH_EXCEPTION_TRANSLATOR_REGISTRY_HPP_INCLUDED + +#include + +namespace Catch { + + class ExceptionTranslatorRegistry : public IExceptionTranslatorRegistry { + public: + ~ExceptionTranslatorRegistry() override; + void registerTranslator(Detail::unique_ptr &&translator); + std::string translateActiveException() const override; + + private: + ExceptionTranslators m_translators; + }; +} // namespace Catch + +#endif // CATCH_EXCEPTION_TRANSLATOR_REGISTRY_HPP_INCLUDED + +#ifndef CATCH_FATAL_CONDITION_HANDLER_HPP_INCLUDED +#define CATCH_FATAL_CONDITION_HANDLER_HPP_INCLUDED + +#include + +namespace Catch { + + /** + * Wrapper for platform-specific fatal error (signals/SEH) handlers + * + * Tries to be cooperative with other handlers, and not step over + * other handlers. This means that unknown structured exceptions + * are passed on, previous signal handlers are called, and so on. + * + * Can only be instantiated once, and assumes that once a signal + * is caught, the binary will end up terminating. Thus, there + */ + class FatalConditionHandler { + bool m_started = false; + + // Install/disengage implementation for specific platform. + // Should be if-defed to work on current platform, can assume + // engage-disengage 1:1 pairing. + void engage_platform(); + void disengage_platform() noexcept; + + public: + // Should also have platform-specific implementations as needed + FatalConditionHandler(); + ~FatalConditionHandler(); + + void engage() { + assert(!m_started && "Handler cannot be installed twice."); + m_started = true; + engage_platform(); + } + + void disengage() noexcept { + assert(m_started && "Handler cannot be uninstalled without being installed first"); + m_started = false; + disengage_platform(); + } + }; + + //! Simple RAII guard for (dis)engaging the FatalConditionHandler + class FatalConditionHandlerGuard { + FatalConditionHandler *m_handler; + + public: + FatalConditionHandlerGuard(FatalConditionHandler *handler) + : m_handler(handler) { + m_handler->engage(); + } + ~FatalConditionHandlerGuard() { + m_handler->disengage(); + } + }; + +} // end namespace Catch + +#endif // CATCH_FATAL_CONDITION_HANDLER_HPP_INCLUDED + +#ifndef CATCH_FLOATING_POINT_HELPERS_HPP_INCLUDED +#define CATCH_FLOATING_POINT_HELPERS_HPP_INCLUDED + +#include +#include +#include +#include +#include + +namespace Catch { + namespace Detail { + + uint32_t convertToBits(float f); + uint64_t convertToBits(double d); + + // Used when we know we want == comparison of two doubles + // to centralize warning suppression + bool directCompare(float lhs, float rhs); + bool directCompare(double lhs, double rhs); + + } // end namespace Detail + +#if defined(__GNUC__) || defined(__clang__) +#pragma GCC diagnostic push + // We do a bunch of direct compensations of floating point numbers, + // because we know what we are doing and actually do want the direct + // comparison behaviour. +#pragma GCC diagnostic ignored "-Wfloat-equal" +#endif + + /** + * Calculates the ULP distance between two floating point numbers + * + * The ULP distance of two floating point numbers is the count of + * valid floating point numbers representable between them. + * + * There are some exceptions between how this function counts the + * distance, and the interpretation of the standard as implemented. + * by e.g. `nextafter`. For this function it always holds that: + * * `(x == y) => ulpDistance(x, y) == 0` (so `ulpDistance(-0, 0) == 0`) + * * `ulpDistance(maxFinite, INF) == 1` + * * `ulpDistance(x, -x) == 2 * ulpDistance(x, 0)` + * + * \pre `!isnan( lhs )` + * \pre `!isnan( rhs )` + * \pre floating point numbers are represented in IEEE-754 format + */ + template + uint64_t ulpDistance(FP lhs, FP rhs) { + assert(std::numeric_limits::is_iec559 && "ulpDistance assumes IEEE-754 format for floating point types"); + assert(!Catch::isnan(lhs) && "Distance between NaN and number is not meaningful"); + assert(!Catch::isnan(rhs) && "Distance between NaN and number is not meaningful"); + + // We want X == Y to imply 0 ULP distance even if X and Y aren't + // bit-equal (-0 and 0), or X - Y != 0 (same sign infinities). + if (lhs == rhs) { return 0; } + + // We need a properly typed positive zero for type inference. + static constexpr FP positive_zero{}; + + // We want to ensure that +/- 0 is always represented as positive zero + if (lhs == positive_zero) { lhs = positive_zero; } + if (rhs == positive_zero) { rhs = positive_zero; } + + // If arguments have different signs, we can handle them by summing + // how far are they from 0 each. + if (std::signbit(lhs) != std::signbit(rhs)) { + return ulpDistance(std::abs(lhs), positive_zero) + ulpDistance(std::abs(rhs), positive_zero); + } + + // When both lhs and rhs are of the same sign, we can just + // read the numbers bitwise as integers, and then subtract them + // (assuming IEEE). + uint64_t lc = Detail::convertToBits(lhs); + uint64_t rc = Detail::convertToBits(rhs); + + // The ulp distance between two numbers is symmetric, so to avoid + // dealing with overflows we want the bigger converted number on the lhs + if (lc < rc) { + std::swap(lc, rc); + } + + return lc - rc; + } + +#if defined(__GNUC__) || defined(__clang__) +#pragma GCC diagnostic pop +#endif + +} // end namespace Catch + +#endif // CATCH_FLOATING_POINT_HELPERS_HPP_INCLUDED + +#ifndef CATCH_GETENV_HPP_INCLUDED +#define CATCH_GETENV_HPP_INCLUDED + +namespace Catch { + namespace Detail { + + //! Wrapper over `std::getenv` that compiles on UWP (and always returns nullptr there) + char const *getEnv(char const *varName); + + } // namespace Detail +} // namespace Catch + +#endif // CATCH_GETENV_HPP_INCLUDED + +#ifndef CATCH_IS_PERMUTATION_HPP_INCLUDED +#define CATCH_IS_PERMUTATION_HPP_INCLUDED + +#include +#include + +namespace Catch { + namespace Detail { + + template + constexpr ForwardIter find_sentinel(ForwardIter start, + Sentinel sentinel, + T const &value, + Comparator cmp) { + while (start != sentinel) { + if (cmp(*start, value)) { break; } + ++start; + } + return start; + } + + template + constexpr std::ptrdiff_t count_sentinel(ForwardIter start, + Sentinel sentinel, + T const &value, + Comparator cmp) { + std::ptrdiff_t count = 0; + while (start != sentinel) { + if (cmp(*start, value)) { ++count; } + ++start; + } + return count; + } + + template + constexpr std::enable_if_t::value, + std::ptrdiff_t> + sentinel_distance(ForwardIter iter, const Sentinel sentinel) { + std::ptrdiff_t dist = 0; + while (iter != sentinel) { + ++iter; + ++dist; + } + return dist; + } + + template + constexpr std::ptrdiff_t sentinel_distance(ForwardIter first, + ForwardIter last) { + return std::distance(first, last); + } + + template + constexpr bool check_element_counts(ForwardIter1 first_1, + const Sentinel1 end_1, + ForwardIter2 first_2, + const Sentinel2 end_2, + Comparator cmp) { + auto cursor = first_1; + while (cursor != end_1) { + if (find_sentinel(first_1, cursor, *cursor, cmp) == cursor) { + // we haven't checked this element yet + const auto count_in_range_2 = count_sentinel(first_2, end_2, *cursor, cmp); + // Not a single instance in 2nd range, so it cannot be a + // permutation of 1st range + if (count_in_range_2 == 0) { return false; } + + const auto count_in_range_1 = count_sentinel(cursor, end_1, *cursor, cmp); + if (count_in_range_1 != count_in_range_2) { + return false; + } + } + + ++cursor; + } + + return true; + } + + template + constexpr bool is_permutation(ForwardIter1 first_1, + const Sentinel1 end_1, + ForwardIter2 first_2, + const Sentinel2 end_2, + Comparator cmp) { + // TODO: no optimization for stronger iterators, because we would also have to constrain on sentinel vs not sentinel types + // TODO: Comparator has to be "both sides", e.g. a == b => b == a + // This skips shared prefix of the two ranges + while (first_1 != end_1 && first_2 != end_2 && cmp(*first_1, *first_2)) { + ++first_1; + ++first_2; + } + + // We need to handle case where at least one of the ranges has no more elements + if (first_1 == end_1 || first_2 == end_2) { + return first_1 == end_1 && first_2 == end_2; + } + + // pair counting is n**2, so we pay linear walk to compare the sizes first + auto dist_1 = sentinel_distance(first_1, end_1); + auto dist_2 = sentinel_distance(first_2, end_2); + + if (dist_1 != dist_2) { return false; } + + // Since we do not try to handle stronger iterators pair (e.g. + // bidir) optimally, the only thing left to do is to check counts in + // the remaining ranges. + return check_element_counts(first_1, end_1, first_2, end_2, cmp); + } + + } // namespace Detail +} // namespace Catch + +#endif // CATCH_IS_PERMUTATION_HPP_INCLUDED + +#ifndef CATCH_ISTREAM_HPP_INCLUDED +#define CATCH_ISTREAM_HPP_INCLUDED + +#include +#include + +namespace Catch { + + class IStream { + public: + virtual ~IStream(); // = default + virtual std::ostream &stream() = 0; + /** + * Best guess on whether the instance is writing to a console (e.g. via stdout/stderr) + * + * This is useful for e.g. Win32 colour support, because the Win32 + * API manipulates console directly, unlike POSIX escape codes, + * that can be written anywhere. + * + * Due to variety of ways to change where the stdout/stderr is + * _actually_ being written, users should always assume that + * the answer might be wrong. + */ + virtual bool isConsole() const { return false; } + }; + + /** + * Creates a stream wrapper that writes to specific file. + * + * Also recognizes 4 special filenames + * * `-` for stdout + * * `%stdout` for stdout + * * `%stderr` for stderr + * * `%debug` for platform specific debugging output + * + * \throws if passed an unrecognized %-prefixed stream + */ + auto makeStream(std::string const &filename) -> Detail::unique_ptr; + +} // namespace Catch + +#endif // CATCH_STREAM_HPP_INCLUDED + +#ifndef CATCH_JSONWRITER_HPP_INCLUDED +#define CATCH_JSONWRITER_HPP_INCLUDED + +#include +#include + +namespace Catch { + class JsonObjectWriter; + class JsonArrayWriter; + + struct JsonUtils { + static void indent(std::ostream &os, std::uint64_t level); + static void appendCommaNewline(std::ostream &os, + bool &should_comma, + std::uint64_t level); + }; + + class JsonValueWriter { + public: + JsonValueWriter(std::ostream &os CATCH_ATTR_LIFETIMEBOUND); + JsonValueWriter(std::ostream &os CATCH_ATTR_LIFETIMEBOUND, std::uint64_t indent_level); + + JsonObjectWriter writeObject() &&; + JsonArrayWriter writeArray() &&; + + template + void write(T const &value) && { + writeImpl(value, !std::is_arithmetic::value); + } + void write(StringRef value) &&; + void write(bool value) &&; + + private: + void writeImpl(StringRef value, bool quote); + + // Without this SFINAE, this overload is a better match + // for `std::string`, `char const*`, `char const[N]` args. + // While it would still work, it would cause code bloat + // and multiple iteration over the strings + template::value>> + void writeImpl(T const &value, bool quote_value) { + m_sstream << value; + writeImpl(m_sstream.str(), quote_value); + } + + std::ostream &m_os; + std::stringstream m_sstream; + std::uint64_t m_indent_level; + }; + + class JsonObjectWriter { + public: + JsonObjectWriter(std::ostream &os CATCH_ATTR_LIFETIMEBOUND); + JsonObjectWriter(std::ostream &os CATCH_ATTR_LIFETIMEBOUND, std::uint64_t indent_level); + + JsonObjectWriter(JsonObjectWriter &&source) noexcept; + JsonObjectWriter &operator=(JsonObjectWriter &&source) = delete; + + ~JsonObjectWriter(); + + JsonValueWriter write(StringRef key); + + private: + std::ostream &m_os; + std::uint64_t m_indent_level; + bool m_should_comma = false; + bool m_active = true; + }; + + class JsonArrayWriter { + public: + JsonArrayWriter(std::ostream &os CATCH_ATTR_LIFETIMEBOUND); + JsonArrayWriter(std::ostream &os CATCH_ATTR_LIFETIMEBOUND, std::uint64_t indent_level); + + JsonArrayWriter(JsonArrayWriter &&source) noexcept; + JsonArrayWriter &operator=(JsonArrayWriter &&source) = delete; + + ~JsonArrayWriter(); + + JsonObjectWriter writeObject(); + JsonArrayWriter writeArray(); + + template + JsonArrayWriter &write(T const &value) { + return writeImpl(value); + } + + JsonArrayWriter &write(bool value); + + private: + template + JsonArrayWriter &writeImpl(T const &value) { + JsonUtils::appendCommaNewline( + m_os, m_should_comma, m_indent_level + 1); + JsonValueWriter{m_os}.write(value); + + return *this; + } + + std::ostream &m_os; + std::uint64_t m_indent_level; + bool m_should_comma = false; + bool m_active = true; + }; + +} // namespace Catch + +#endif // CATCH_JSONWRITER_HPP_INCLUDED + +#ifndef CATCH_LEAK_DETECTOR_HPP_INCLUDED +#define CATCH_LEAK_DETECTOR_HPP_INCLUDED + +namespace Catch { + + struct LeakDetector { + LeakDetector(); + ~LeakDetector(); + }; + +} // namespace Catch +#endif // CATCH_LEAK_DETECTOR_HPP_INCLUDED + +#ifndef CATCH_LIST_HPP_INCLUDED +#define CATCH_LIST_HPP_INCLUDED + +#include +#include + +namespace Catch { + + class IEventListener; + class Config; + + struct ReporterDescription { + std::string name, description; + }; + struct ListenerDescription { + StringRef name; + std::string description; + }; + + struct TagInfo { + void add(StringRef spelling); + std::string all() const; + + std::set spellings; + std::size_t count = 0; + }; + + bool list(IEventListener &reporter, Config const &config); + +} // end namespace Catch + +#endif // CATCH_LIST_HPP_INCLUDED + +#ifndef CATCH_OUTPUT_REDIRECT_HPP_INCLUDED +#define CATCH_OUTPUT_REDIRECT_HPP_INCLUDED + +#include +#include + +namespace Catch { + + class OutputRedirect { + bool m_redirectActive = false; + virtual void activateImpl() = 0; + virtual void deactivateImpl() = 0; + + public: + enum Kind { + //! No redirect (noop implementation) + None, + //! Redirect std::cout/std::cerr/std::clog streams internally + Streams, + //! Redirect the stdout/stderr file descriptors into files + FileDescriptors, + }; + + virtual ~OutputRedirect(); // = default; + + // TODO: Do we want to check that redirect is not active before retrieving the output? + virtual std::string getStdout() = 0; + virtual std::string getStderr() = 0; + virtual void clearBuffers() = 0; + bool isActive() const { return m_redirectActive; } + void activate() { + assert(!m_redirectActive && "redirect is already active"); + activateImpl(); + m_redirectActive = true; + } + void deactivate() { + assert(m_redirectActive && "redirect is not active"); + deactivateImpl(); + m_redirectActive = false; + } + }; + + bool isRedirectAvailable(OutputRedirect::Kind kind); + Detail::unique_ptr makeOutputRedirect(bool actual); + + class RedirectGuard { + OutputRedirect *m_redirect; + bool m_activate; + bool m_previouslyActive; + bool m_moved = false; + + public: + RedirectGuard(bool activate, OutputRedirect &redirectImpl); + ~RedirectGuard() noexcept(false); + + RedirectGuard(RedirectGuard const &) = delete; + RedirectGuard &operator=(RedirectGuard const &) = delete; + + // C++14 needs move-able guards to return them from functions + RedirectGuard(RedirectGuard &&rhs) noexcept; + RedirectGuard &operator=(RedirectGuard &&rhs) noexcept; + }; + + RedirectGuard scopedActivate(OutputRedirect &redirectImpl); + RedirectGuard scopedDeactivate(OutputRedirect &redirectImpl); + +} // end namespace Catch + +#endif // CATCH_OUTPUT_REDIRECT_HPP_INCLUDED + +#ifndef CATCH_PARSE_NUMBERS_HPP_INCLUDED +#define CATCH_PARSE_NUMBERS_HPP_INCLUDED + +#include + +namespace Catch { + + /** + * Parses unsigned int from the input, using provided base + * + * Effectively a wrapper around std::stoul but with better error checking + * e.g. "-1" is rejected, instead of being parsed as UINT_MAX. + */ + Optional parseUInt(std::string const &input, int base = 10); +} // namespace Catch + +#endif // CATCH_PARSE_NUMBERS_HPP_INCLUDED + +#ifndef CATCH_REPORTER_REGISTRY_HPP_INCLUDED +#define CATCH_REPORTER_REGISTRY_HPP_INCLUDED + +#include +#include +#include + +namespace Catch { + + class IEventListener; + using IEventListenerPtr = Detail::unique_ptr; + class IReporterFactory; + using IReporterFactoryPtr = Detail::unique_ptr; + struct ReporterConfig; + class EventListenerFactory; + + class ReporterRegistry { + struct ReporterRegistryImpl; + Detail::unique_ptr m_impl; + + public: + ReporterRegistry(); + ~ReporterRegistry(); // = default; + + IEventListenerPtr create(std::string const &name, + ReporterConfig &&config) const; + + void registerReporter(std::string const &name, + IReporterFactoryPtr factory); + + void + registerListener(Detail::unique_ptr factory); + + std::map const & + getFactories() const; + + std::vector> const & + getListeners() const; + }; + +} // end namespace Catch + +#endif // CATCH_REPORTER_REGISTRY_HPP_INCLUDED + +#ifndef CATCH_RUN_CONTEXT_HPP_INCLUDED +#define CATCH_RUN_CONTEXT_HPP_INCLUDED + +#ifndef CATCH_TEST_CASE_TRACKER_HPP_INCLUDED +#define CATCH_TEST_CASE_TRACKER_HPP_INCLUDED + +#include +#include + +namespace Catch { + + struct PathFilter; + + namespace TestCaseTracking { + + struct NameAndLocation { + std::string name; + SourceLineInfo location; + + NameAndLocation(std::string &&_name, SourceLineInfo const &_location); + friend bool operator==(NameAndLocation const &lhs, NameAndLocation const &rhs) { + // This is a very cheap check that should have a very high hit rate. + // If we get to SourceLineInfo::operator==, we will redo it, but the + // cost of repeating is trivial at that point (we will be paying + // multiple strcmp/memcmps at that point). + if (lhs.location.line != rhs.location.line) { return false; } + return lhs.name == rhs.name && lhs.location == rhs.location; + } + friend bool operator!=(NameAndLocation const &lhs, + NameAndLocation const &rhs) { + return !(lhs == rhs); + } + }; + + /** + * This is a variant of `NameAndLocation` that does not own the name string + * + * This avoids extra allocations when trying to locate a tracker by its + * name and location, as long as we make sure that trackers only keep + * around the owning variant. + */ + struct NameAndLocationRef { + StringRef name; + SourceLineInfo location; + + constexpr NameAndLocationRef(StringRef name_ CATCH_ATTR_LIFETIMEBOUND, + SourceLineInfo location_) + : name(name_), location(location_) {} + + friend bool operator==(NameAndLocation const &lhs, + NameAndLocationRef const &rhs) { + // This is a very cheap check that should have a very high hit rate. + // If we get to SourceLineInfo::operator==, we will redo it, but the + // cost of repeating is trivial at that point (we will be paying + // multiple strcmp/memcmps at that point). + if (lhs.location.line != rhs.location.line) { return false; } + return StringRef(lhs.name) == rhs.name && lhs.location == rhs.location; + } + friend bool operator==(NameAndLocationRef const &lhs, + NameAndLocation const &rhs) { + return rhs == lhs; + } + }; + + class ITracker; + + using ITrackerPtr = Catch::Detail::unique_ptr; + + class ITracker { + NameAndLocation m_nameAndLocation; + + using Children = std::vector; + + protected: + enum CycleState { + NotStarted, + Executing, + ExecutingChildren, + NeedsAnotherRun, + CompletedSuccessfully, + Failed + }; + + ITracker *m_parent = nullptr; + Children m_children; + CycleState m_runState = NotStarted; + + // Members for path filtering + std::vector const *m_filterRef = nullptr; + + // Note: There are 2 dummy section trackers (root, test-case) before + // the first "real" section tracker can be encountered. We start + // the default tracker at -2, so that the first "real" section + // tracker overflows to index 0. + // Nesting depth of this tracker, used to decide which new-style filter applies. + size_t m_allTrackerDepth = static_cast(-2); + // Nesting depth of sections (inc. this tracker), used for old-style filters. + // Must be updated by the section tracker on its own. + size_t m_sectionOnlyDepth = static_cast(-2); + // Transitory: Remove once we remove backwards compatibility with old-style (v3.x) filters + bool m_newStyleFilters = false; + + public: + ITracker(NameAndLocation &&nameAndLoc, ITracker *parent); + + // static queries + NameAndLocation const &nameAndLocation() const { + return m_nameAndLocation; + } + ITracker *parent() const { + return m_parent; + } + + virtual ~ITracker(); // = default + + // dynamic queries + + //! Returns true if tracker run to completion (successfully or not) + virtual bool isComplete() const = 0; + //! Returns true if tracker run to completion successfully + bool isSuccessfullyCompleted() const { + return m_runState == CompletedSuccessfully; + } + //! Returns true if tracker has started but hasn't been completed + bool isOpen() const; + //! Returns true iff tracker has started + bool hasStarted() const; + + void setFilters(std::vector const *filters, bool newStyleFilters) { + m_filterRef = filters; + m_newStyleFilters = newStyleFilters; + } + + // actions + virtual void close() = 0; // Successfully complete + virtual void fail() = 0; + void markAsNeedingAnotherRun(); + + //! Register a nested ITracker + void addChild(ITrackerPtr &&child); + /** + * Returns ptr to specific child if register with this tracker. + * + * Returns nullptr if not found. + */ + ITracker *findChild(NameAndLocationRef const &nameAndLocation); + //! Have any children been added? + bool hasChildren() const { + return !m_children.empty(); + } + + //! Marks tracker as executing a child, doing se recursively up the tree + void openChild(); + + /** + * Returns true if the instance is a section tracker + * + * Subclasses should override to true if they are, replaces RTTI + * for internal debug checks. + */ + virtual bool isSectionTracker() const; + /** + * Returns true if the instance is a generator tracker + * + * Subclasses should override to true if they are, replaces RTTI + * for internal debug checks. + */ + virtual bool isGeneratorTracker() const; + }; + + class TrackerContext { + enum RunState { + NotStarted, + Executing, + CompletedCycle + }; + + ITrackerPtr m_rootTracker; + ITracker *m_currentTracker = nullptr; + RunState m_runState = NotStarted; + + public: + ITracker &startRun(); + + void startCycle() { + m_currentTracker = m_rootTracker.get(); + m_runState = Executing; + } + void completeCycle(); + + bool completedCycle() const; + ITracker ¤tTracker() { return *m_currentTracker; } + void setCurrentTracker(ITracker *tracker); + }; + + class TrackerBase : public ITracker { + protected: + TrackerContext &m_ctx; + + public: + TrackerBase(NameAndLocation &&nameAndLocation, TrackerContext &ctx, ITracker *parent); + + bool isComplete() const override; + + void open(); + + void close() override; + void fail() override; + + private: + void moveToParent(); + void moveToThis(); + }; + + class SectionTracker final : public TrackerBase { + // Note that lifetime-wise we piggy back off the name stored in the `ITracker` parent`. + // Currently it allocates owns the name, so this is safe. If it is later refactored + // to not own the name, the name still has to outlive the `ITracker` parent, so + // this should still be safe. + StringRef m_trimmed_name; + + public: + SectionTracker(NameAndLocation &&nameAndLocation, TrackerContext &ctx, ITracker *parent); + + bool isSectionTracker() const override; + + bool isComplete() const override; + + static SectionTracker &acquire(TrackerContext &ctx, NameAndLocationRef const &nameAndLocation); + + void tryOpen(); + + //! Returns whitespace-trimmed name of the tracked section + StringRef trimmedName() const; + }; + + } // namespace TestCaseTracking + + using TestCaseTracking::ITracker; + using TestCaseTracking::SectionTracker; + using TestCaseTracking::TrackerContext; + +} // namespace Catch + +#endif // CATCH_TEST_CASE_TRACKER_HPP_INCLUDED + +#ifndef CATCH_THREAD_SUPPORT_HPP_INCLUDED +#define CATCH_THREAD_SUPPORT_HPP_INCLUDED + +#if defined(CATCH_CONFIG_THREAD_SAFE_ASSERTIONS) +#include +#include +#endif + +namespace Catch { + namespace Detail { +#if defined(CATCH_CONFIG_THREAD_SAFE_ASSERTIONS) + using Mutex = std::mutex; + using LockGuard = std::lock_guard; + struct AtomicCounts { + std::atomic passed{0}; + std::atomic failed{0}; + std::atomic failedButOk{0}; + std::atomic skipped{0}; + }; +#else // ^^ Use actual mutex, lock and atomics + // vv Dummy implementations for single-thread performance + + struct Mutex { + void lock() {} + void unlock() {} + }; + + struct LockGuard { + LockGuard(Mutex) {} + }; + + using AtomicCounts = Counts; +#endif + + } // namespace Detail +} // namespace Catch + +#endif // CATCH_THREAD_SUPPORT_HPP_INCLUDED + +#include + +namespace Catch { + + class IGeneratorTracker; + class IConfig; + class IEventListener; + using IEventListenerPtr = Detail::unique_ptr; + class OutputRedirect; + + /////////////////////////////////////////////////////////////////////////// + + class RunContext final : public IResultCapture { + public: + RunContext(RunContext const &) = delete; + RunContext &operator=(RunContext const &) = delete; + + explicit RunContext(IConfig const *_config, IEventListenerPtr &&reporter); + + ~RunContext() override; + + Totals runTest(TestCaseHandle const &testCase); + + public: // IResultCapture + // Assertion handlers + void handleExpr(AssertionInfo const &info, + ITransientExpression const &expr, + AssertionReaction &reaction) override; + void handleMessage(AssertionInfo const &info, + ResultWas::OfType resultType, + std::string &&message, + AssertionReaction &reaction) override; + void handleUnexpectedExceptionNotThrown(AssertionInfo const &info, + AssertionReaction &reaction) override; + void handleUnexpectedInflightException(AssertionInfo const &info, + std::string &&message, + AssertionReaction &reaction) override; + void handleIncomplete(AssertionInfo const &info) override; + void handleNonExpr(AssertionInfo const &info, + ResultWas::OfType resultType, + AssertionReaction &reaction) override; + + void notifyAssertionStarted(AssertionInfo const &info) override; + bool sectionStarted(StringRef sectionName, + SourceLineInfo const §ionLineInfo, + Counts &assertions) override; + + void sectionEnded(SectionEndInfo &&endInfo) override; + void sectionEndedEarly(SectionEndInfo &&endInfo) override; + + IGeneratorTracker * + acquireGeneratorTracker(StringRef generatorName, + SourceLineInfo const &lineInfo) override; + IGeneratorTracker *createGeneratorTracker( + StringRef generatorName, + SourceLineInfo lineInfo, + Generators::GeneratorBasePtr &&generator) override; + + void benchmarkPreparing(StringRef name) override; + void benchmarkStarting(BenchmarkInfo const &info) override; + void benchmarkEnded(BenchmarkStats<> const &stats) override; + void benchmarkFailed(StringRef error) override; + + std::string getCurrentTestName() const override; + + const AssertionResult *getLastResult() const override; + + void exceptionEarlyReported() override; + + void handleFatalErrorCondition(StringRef message) override; + + bool lastAssertionPassed() override; + + public: + // !TBD We need to do this another way! + bool aborting() const; + + private: + void assertionPassedFastPath(SourceLineInfo lineInfo); + // Update the non-thread-safe m_totals from the atomic assertion counts. + void updateTotalsFromAtomics(); + + void runCurrentTest(); + void invokeActiveTestCase(); + + bool testForMissingAssertions(Counts &assertions); + + void assertionEnded(AssertionResult &&result); + void reportExpr(AssertionInfo const &info, + ResultWas::OfType resultType, + ITransientExpression const *expr, + bool negated); + + void populateReaction(AssertionReaction &reaction, bool has_normal_disposition); + + // Creates dummy info for unexpected exceptions/fatal errors, + // where we do not have the access to one, but we still need + // to send one to the reporters. + AssertionInfo makeDummyAssertionInfo(); + + private: + void handleUnfinishedSections(); + mutable Detail::Mutex m_assertionMutex; + TestRunInfo m_runInfo; + TestCaseHandle const *m_activeTestCase = nullptr; + ITracker *m_testCaseTracker = nullptr; + Optional m_lastResult; + IConfig const *m_config; + Totals m_totals; + Detail::AtomicCounts m_atomicAssertionCount; + IEventListenerPtr m_reporter; + std::vector m_unfinishedSections; + std::vector m_activeSections; + TrackerContext m_trackerContext; + Detail::unique_ptr m_outputRedirect; + FatalConditionHandler m_fatalConditionhandler; + // Caches m_config->abortAfter() to avoid vptr calls/allow inlining + size_t m_abortAfterXFailedAssertions; + bool m_shouldReportUnexpected = true; + // Caches whether `assertionStarting` events should be sent to the reporter. + bool m_reportAssertionStarting; + // Caches whether `assertionEnded` events for successful assertions should be sent to the reporter + bool m_includeSuccessfulResults; + // Caches m_config->shouldDebugBreak() to avoid vptr calls/allow inlining + bool m_shouldDebugBreak; + }; + + void seedRng(IConfig const &config); + unsigned int rngSeed(); +} // end namespace Catch + +#endif // CATCH_RUN_CONTEXT_HPP_INCLUDED + +#ifndef CATCH_SHARDING_HPP_INCLUDED +#define CATCH_SHARDING_HPP_INCLUDED + +#include +#include + +namespace Catch { + + template + Container createShard(Container const &container, std::size_t const shardCount, std::size_t const shardIndex) { + assert(shardCount > shardIndex); + + if (shardCount == 1) { + return container; + } + + const std::size_t totalTestCount = container.size(); + + const std::size_t shardSize = totalTestCount / shardCount; + const std::size_t leftoverTests = totalTestCount % shardCount; + + const std::size_t startIndex = shardIndex * shardSize + (std::min)(shardIndex, leftoverTests); + const std::size_t endIndex = (shardIndex + 1) * shardSize + (std::min)(shardIndex + 1, leftoverTests); + + auto startIterator = std::next(container.begin(), static_cast(startIndex)); + auto endIterator = std::next(container.begin(), static_cast(endIndex)); + + return Container(startIterator, endIterator); + } + +} // namespace Catch + +#endif // CATCH_SHARDING_HPP_INCLUDED + +#ifndef CATCH_SINGLETONS_HPP_INCLUDED +#define CATCH_SINGLETONS_HPP_INCLUDED + +namespace Catch { + + struct ISingleton { + virtual ~ISingleton(); // = default + }; + + void addSingleton(ISingleton *singleton); + void cleanupSingletons(); + + template + class Singleton : SingletonImplT, public ISingleton { + static auto getInternal() -> Singleton * { + static Singleton *s_instance = nullptr; + if (!s_instance) { + s_instance = new Singleton; + addSingleton(s_instance); + } + return s_instance; + } + + public: + static auto get() -> InterfaceT const & { + return *getInternal(); + } + static auto getMutable() -> MutableInterfaceT & { + return *getInternal(); + } + }; + +} // namespace Catch + +#endif // CATCH_SINGLETONS_HPP_INCLUDED + +#ifndef CATCH_STARTUP_EXCEPTION_REGISTRY_HPP_INCLUDED +#define CATCH_STARTUP_EXCEPTION_REGISTRY_HPP_INCLUDED + +#include +#include + +namespace Catch { + + class StartupExceptionRegistry { +#if !defined(CATCH_CONFIG_DISABLE_EXCEPTIONS) + public: + void add(std::exception_ptr const &exception) noexcept; + std::vector const &getExceptions() const noexcept; + + private: + std::vector m_exceptions; +#endif + }; + +} // end namespace Catch + +#endif // CATCH_STARTUP_EXCEPTION_REGISTRY_HPP_INCLUDED + +#ifndef CATCH_STDSTREAMS_HPP_INCLUDED +#define CATCH_STDSTREAMS_HPP_INCLUDED + +#include + +namespace Catch { + + std::ostream &cout(); + std::ostream &cerr(); + std::ostream &clog(); + +} // namespace Catch + +#endif + +#ifndef CATCH_STRING_MANIP_HPP_INCLUDED +#define CATCH_STRING_MANIP_HPP_INCLUDED + +#include +#include +#include +#include + +namespace Catch { + + bool startsWith(std::string const &s, std::string const &prefix); + bool startsWith(StringRef s, char prefix); + bool endsWith(std::string const &s, std::string const &suffix); + bool endsWith(std::string const &s, char suffix); + bool contains(std::string const &s, std::string const &infix); + void toLowerInPlace(std::string &s); + std::string toLower(std::string const &s); + char toLower(char c); + //! Returns a new string without whitespace at the start/end + std::string trim(std::string const &str); + //! Returns a substring of the original ref without whitespace. Beware lifetimes! + StringRef trim(StringRef ref CATCH_ATTR_LIFETIMEBOUND); + + // !!! Be aware, returns refs into original string - make sure original string outlives them + std::vector splitStringRef(StringRef str CATCH_ATTR_LIFETIMEBOUND, char delimiter); + bool replaceInPlace(std::string &str, std::string const &replaceThis, std::string const &withThis); + + /** + * Helper for streaming a "count [maybe-plural-of-label]" human-friendly string + * + * Usage example: + * ```cpp + * std::cout << "Found " << pluralise(count, "error") << '\n'; + * ``` + * + * **Important:** The provided string must outlive the instance + */ + class pluralise { + std::uint64_t m_count; + StringRef m_label; + + public: + constexpr pluralise(std::uint64_t count, StringRef label CATCH_ATTR_LIFETIMEBOUND) + : m_count(count), m_label(label) {} + + friend std::ostream &operator<<(std::ostream &os, pluralise const &pluraliser); + }; +} // namespace Catch + +#endif // CATCH_STRING_MANIP_HPP_INCLUDED + +#ifndef CATCH_TAG_ALIAS_REGISTRY_HPP_INCLUDED +#define CATCH_TAG_ALIAS_REGISTRY_HPP_INCLUDED + +#include +#include + +namespace Catch { + struct SourceLineInfo; + + class TagAliasRegistry : public ITagAliasRegistry { + public: + ~TagAliasRegistry() override; + TagAlias const *find(std::string const &alias) const override; + std::string expandAliases(std::string const &unexpandedTestSpec) const override; + void add(std::string const &alias, std::string const &tag, SourceLineInfo const &lineInfo); + + private: + std::map m_registry; + }; + +} // end namespace Catch + +#endif // CATCH_TAG_ALIAS_REGISTRY_HPP_INCLUDED + +#ifndef CATCH_TEST_CASE_INFO_HASHER_HPP_INCLUDED +#define CATCH_TEST_CASE_INFO_HASHER_HPP_INCLUDED + +#include + +namespace Catch { + + struct TestCaseInfo; + + class TestCaseInfoHasher { + public: + using hash_t = std::uint64_t; + TestCaseInfoHasher(hash_t seed); + uint32_t operator()(TestCaseInfo const &t) const; + + private: + hash_t m_seed; + }; + +} // namespace Catch + +#endif /* CATCH_TEST_CASE_INFO_HASHER_HPP_INCLUDED */ + +#ifndef CATCH_TEST_CASE_REGISTRY_IMPL_HPP_INCLUDED +#define CATCH_TEST_CASE_REGISTRY_IMPL_HPP_INCLUDED + +#include + +namespace Catch { + + class IConfig; + class ITestInvoker; + class TestCaseHandle; + class TestSpec; + + std::vector sortTests(IConfig const &config, std::vector const &unsortedTestCases); + + bool isThrowSafe(TestCaseHandle const &testCase, IConfig const &config); + + std::vector filterTests(std::vector const &testCases, TestSpec const &testSpec, IConfig const &config); + std::vector const &getAllTestCasesSorted(IConfig const &config); + + class TestRegistry : public ITestCaseRegistry { + public: + void registerTest(Detail::unique_ptr testInfo, Detail::unique_ptr testInvoker); + + std::vector const &getAllInfos() const override; + std::vector const &getAllTests() const override; + std::vector const &getAllTestsSorted(IConfig const &config) const override; + + ~TestRegistry() override; // = default + + private: + std::vector> m_owned_test_infos; + // Keeps a materialized vector for `getAllInfos`. + // We should get rid of that eventually (see interface note) + std::vector m_viewed_test_infos; + + std::vector> m_invokers; + std::vector m_handles; + mutable TestRunOrder m_currentSortOrder = TestRunOrder::Declared; + mutable std::vector m_sortedFunctions; + }; + + /////////////////////////////////////////////////////////////////////////// + +} // end namespace Catch + +#endif // CATCH_TEST_CASE_REGISTRY_IMPL_HPP_INCLUDED + +#ifndef CATCH_TEST_SPEC_PARSER_HPP_INCLUDED +#define CATCH_TEST_SPEC_PARSER_HPP_INCLUDED + +#ifdef __clang__ +#pragma clang diagnostic push +#pragma clang diagnostic ignored "-Wpadded" +#endif + +#include +#include + +namespace Catch { + + class ITagAliasRegistry; + + class TestSpecParser { + enum Mode { None, + Name, + QuotedName, + Tag, + EscapedName }; + Mode m_mode = None; + Mode lastMode = None; + bool m_exclusion = false; + std::size_t m_pos = 0; + std::size_t m_realPatternPos = 0; + std::string m_arg; + std::string m_substring; + std::string m_patternName; + std::vector m_escapeChars; + TestSpec::Filter m_currentFilter; + TestSpec m_testSpec; + ITagAliasRegistry const *m_tagAliases = nullptr; + + public: + TestSpecParser(ITagAliasRegistry const &tagAliases); + + TestSpecParser &parse(std::string const &arg); + TestSpec testSpec(); + + private: + bool visitChar(char c); + void startNewMode(Mode mode); + bool processNoneChar(char c); + void processNameChar(char c); + bool processOtherChar(char c); + void endMode(); + void escape(); + bool isControlChar(char c) const; + void saveLastMode(); + void revertBackToLastMode(); + void addFilter(); + bool separate(); + + // Handles common preprocessing of the pattern for name/tag patterns + std::string preprocessPattern(); + // Adds the current pattern as a test name + void addNamePattern(); + // Adds the current pattern as a tag + void addTagPattern(); + + inline void addCharToPattern(char c) { + m_substring += c; + m_patternName += c; + m_realPatternPos++; + } + }; + +} // namespace Catch + +#ifdef __clang__ +#pragma clang diagnostic pop +#endif + +#endif // CATCH_TEST_SPEC_PARSER_HPP_INCLUDED + +#ifndef CATCH_TEXTFLOW_HPP_INCLUDED +#define CATCH_TEXTFLOW_HPP_INCLUDED + +#include +#include +#include + +namespace Catch { + namespace TextFlow { + + class Columns; + + /** + * Abstraction for a string with ansi escape sequences that + * automatically skips over escapes when iterating. Only graphical + * escape sequences are considered. + * + * Internal representation: + * An escape sequence looks like \033[39;49m + * We need bidirectional iteration and the unbound length of escape + * sequences poses a problem for operator-- To make this work we'll + * replace the last `m` with a 0xff (this is a codepoint that won't have + * any utf-8 meaning). + */ + class AnsiSkippingString { + std::string m_string; + std::size_t m_size = 0; + + // perform 0xff replacement and calculate m_size + void preprocessString(); + + public: + class const_iterator; + using iterator = const_iterator; + // note: must be u-suffixed or this will cause a "truncation of + // constant value" warning on MSVC + static constexpr char sentinel = static_cast(0xffu); + + explicit AnsiSkippingString(std::string const &text); + explicit AnsiSkippingString(std::string &&text); + + const_iterator begin() const; + const_iterator end() const; + + size_t size() const { return m_size; } + + std::string substring(const_iterator begin, + const_iterator end) const; + }; + + class AnsiSkippingString::const_iterator { + friend AnsiSkippingString; + struct EndTag {}; + + const std::string *m_string; + std::string::const_iterator m_it; + + explicit const_iterator(const std::string &string, EndTag) + : m_string(&string), m_it(string.end()) {} + + void tryParseAnsiEscapes(); + void advance(); + void unadvance(); + + public: + using difference_type = std::ptrdiff_t; + using value_type = char; + using pointer = value_type *; + using reference = value_type &; + using iterator_category = std::bidirectional_iterator_tag; + + explicit const_iterator(const std::string &string) + : m_string(&string), m_it(string.begin()) { + tryParseAnsiEscapes(); + } + + char operator*() const { return *m_it; } + + const_iterator &operator++() { + advance(); + return *this; + } + const_iterator operator++(int) { + iterator prev(*this); + operator++(); + return prev; + } + const_iterator &operator--() { + unadvance(); + return *this; + } + const_iterator operator--(int) { + iterator prev(*this); + operator--(); + return prev; + } + + bool operator==(const_iterator const &other) const { + return m_it == other.m_it; + } + bool operator!=(const_iterator const &other) const { + return !operator==(other); + } + bool operator<=(const_iterator const &other) const { + return m_it <= other.m_it; + } + + const_iterator oneBefore() const { + auto it = *this; + return --it; + } + }; + + /** + * Represents a column of text with specific width and indentation + * + * When written out to a stream, it will perform linebreaking + * of the provided text so that the written lines fit within + * target width. + */ + class Column { + // String to be written out + AnsiSkippingString m_string; + // Width of the column for linebreaking + size_t m_width = CATCH_CONFIG_CONSOLE_WIDTH - 1; + // Indentation of other lines (including first if initial indent is + // unset) + size_t m_indent = 0; + // Indentation of the first line + size_t m_initialIndent = std::string::npos; + + public: + /** + * Iterates "lines" in `Column` and returns them + */ + class const_iterator { + friend Column; + struct EndTag {}; + + Column const &m_column; + // Where does the current line start? + AnsiSkippingString::const_iterator m_lineStart; + // How long should the current line be? + AnsiSkippingString::const_iterator m_lineEnd; + // How far have we checked the string to iterate? + AnsiSkippingString::const_iterator m_parsedTo; + // Should a '-' be appended to the line? + bool m_addHyphen = false; + + const_iterator(Column const &column, EndTag) + : m_column(column), m_lineStart(m_column.m_string.end()), m_lineEnd(column.m_string.end()), m_parsedTo(column.m_string.end()) {} + + // Calculates the length of the current line + void calcLength(); + + // Returns current indentation width + size_t indentSize() const; + + // Creates an indented and (optionally) suffixed string from + // current iterator position, indentation and length. + std::string addIndentAndSuffix( + AnsiSkippingString::const_iterator start, + AnsiSkippingString::const_iterator end) const; + + public: + using difference_type = std::ptrdiff_t; + using value_type = std::string; + using pointer = value_type *; + using reference = value_type &; + using iterator_category = std::forward_iterator_tag; + + explicit const_iterator(Column const &column); + + std::string operator*() const; + + const_iterator &operator++(); + const_iterator operator++(int); + + bool operator==(const_iterator const &other) const { + return m_lineStart == other.m_lineStart && &m_column == &other.m_column; + } + bool operator!=(const_iterator const &other) const { + return !operator==(other); + } + }; + using iterator = const_iterator; + + explicit Column(std::string const &text) + : m_string(text) {} + explicit Column(std::string &&text) + : m_string(CATCH_MOVE(text)) {} + + Column &width(size_t newWidth) & { + assert(newWidth > 0); + m_width = newWidth; + return *this; + } + Column &&width(size_t newWidth) && { + assert(newWidth > 0); + m_width = newWidth; + return CATCH_MOVE(*this); + } + Column &indent(size_t newIndent) & { + m_indent = newIndent; + return *this; + } + Column &&indent(size_t newIndent) && { + m_indent = newIndent; + return CATCH_MOVE(*this); + } + Column &initialIndent(size_t newIndent) & { + m_initialIndent = newIndent; + return *this; + } + Column &&initialIndent(size_t newIndent) && { + m_initialIndent = newIndent; + return CATCH_MOVE(*this); + } + + size_t width() const { return m_width; } + const_iterator begin() const { return const_iterator(*this); } + const_iterator end() const { + return {*this, const_iterator::EndTag{}}; + } + + friend std::ostream &operator<<(std::ostream &os, + Column const &col); + + friend Columns operator+(Column const &lhs, Column const &rhs); + friend Columns operator+(Column &&lhs, Column &&rhs); + }; + + //! Creates a column that serves as an empty space of specific width + Column Spacer(size_t spaceWidth); + + class Columns { + std::vector m_columns; + + public: + class iterator { + friend Columns; + struct EndTag {}; + + std::vector const &m_columns; + std::vector m_iterators; + size_t m_activeIterators; + + iterator(Columns const &columns, EndTag); + + public: + using difference_type = std::ptrdiff_t; + using value_type = std::string; + using pointer = value_type *; + using reference = value_type &; + using iterator_category = std::forward_iterator_tag; + + explicit iterator(Columns const &columns); + + auto operator==(iterator const &other) const -> bool { + return m_iterators == other.m_iterators; + } + auto operator!=(iterator const &other) const -> bool { + return m_iterators != other.m_iterators; + } + std::string operator*() const; + iterator &operator++(); + iterator operator++(int); + }; + using const_iterator = iterator; + + iterator begin() const { return iterator(*this); } + iterator end() const { return {*this, iterator::EndTag()}; } + + friend Columns &operator+=(Columns &lhs, Column const &rhs); + friend Columns &operator+=(Columns &lhs, Column &&rhs); + friend Columns operator+(Columns const &lhs, Column const &rhs); + friend Columns operator+(Columns &&lhs, Column &&rhs); + + friend std::ostream &operator<<(std::ostream &os, + Columns const &cols); + }; + + } // namespace TextFlow +} // namespace Catch +#endif // CATCH_TEXTFLOW_HPP_INCLUDED + +#ifndef CATCH_THREAD_LOCAL_HPP_INCLUDED +#define CATCH_THREAD_LOCAL_HPP_INCLUDED + +#if defined(CATCH_CONFIG_THREAD_SAFE_ASSERTIONS) +#define CATCH_INTERNAL_THREAD_LOCAL thread_local +#else +#define CATCH_INTERNAL_THREAD_LOCAL +#endif + +#endif // CATCH_THREAD_LOCAL_HPP_INCLUDED + +#ifndef CATCH_TO_STRING_HPP_INCLUDED +#define CATCH_TO_STRING_HPP_INCLUDED + +#include + +namespace Catch { + template + std::string to_string(T const &t) { +#if defined(CATCH_CONFIG_CPP11_TO_STRING) + return std::to_string(t); +#else + ReusableStringStream rss; + rss << t; + return rss.str(); +#endif + } +} // end namespace Catch + +#endif // CATCH_TO_STRING_HPP_INCLUDED + +#ifndef CATCH_UNCAUGHT_EXCEPTIONS_HPP_INCLUDED +#define CATCH_UNCAUGHT_EXCEPTIONS_HPP_INCLUDED + +namespace Catch { + bool uncaught_exceptions(); +} // end namespace Catch + +#endif // CATCH_UNCAUGHT_EXCEPTIONS_HPP_INCLUDED + +#ifndef CATCH_XMLWRITER_HPP_INCLUDED +#define CATCH_XMLWRITER_HPP_INCLUDED + +#include +#include +#include + +namespace Catch { + enum class XmlFormatting : std::uint8_t { + None = 0x00, + Indent = 0x01, + Newline = 0x02, + }; + + constexpr XmlFormatting operator|(XmlFormatting lhs, XmlFormatting rhs) { + return static_cast(static_cast(lhs) | static_cast(rhs)); + } + + constexpr XmlFormatting operator&(XmlFormatting lhs, XmlFormatting rhs) { + return static_cast(static_cast(lhs) & static_cast(rhs)); + } + + /** + * Helper for XML-encoding text (escaping angle brackets, quotes, etc) + * + * Note: doesn't take ownership of passed strings, and thus the + * encoded string must outlive the encoding instance. + */ + class XmlEncode { + public: + enum ForWhat { ForTextNodes, + ForAttributes }; + + constexpr XmlEncode(StringRef str CATCH_ATTR_LIFETIMEBOUND, ForWhat forWhat = ForTextNodes) + : m_str(str), m_forWhat(forWhat) {} + + void encodeTo(std::ostream &os) const; + + friend std::ostream &operator<<(std::ostream &os, XmlEncode const &xmlEncode); + + private: + StringRef m_str; + ForWhat m_forWhat; + }; + + class XmlWriter { + public: + class ScopedElement { + public: + ScopedElement(XmlWriter *writer CATCH_ATTR_LIFETIMEBOUND, XmlFormatting fmt); + + ScopedElement(ScopedElement &&other) noexcept; + ScopedElement &operator=(ScopedElement &&other) noexcept; + + ~ScopedElement(); + + ScopedElement & + writeText(StringRef text, + XmlFormatting fmt = XmlFormatting::Newline | XmlFormatting::Indent); + + ScopedElement &writeAttribute(StringRef name, + StringRef attribute); + template::value>> + ScopedElement &writeAttribute(StringRef name, + T const &attribute) { + m_writer->writeAttribute(name, attribute); + return *this; + } + + private: + XmlWriter *m_writer = nullptr; + XmlFormatting m_fmt; + }; + + XmlWriter(std::ostream &os CATCH_ATTR_LIFETIMEBOUND); + ~XmlWriter(); + + XmlWriter(XmlWriter const &) = delete; + XmlWriter &operator=(XmlWriter const &) = delete; + + XmlWriter &startElement(std::string const &name, XmlFormatting fmt = XmlFormatting::Newline | XmlFormatting::Indent); + + ScopedElement scopedElement(std::string const &name, XmlFormatting fmt = XmlFormatting::Newline | XmlFormatting::Indent); + + XmlWriter &endElement(XmlFormatting fmt = XmlFormatting::Newline | XmlFormatting::Indent); + + //! The attribute content is XML-encoded + XmlWriter &writeAttribute(StringRef name, StringRef attribute); + + //! Writes the attribute as "true/false" + XmlWriter &writeAttribute(StringRef name, bool attribute); + + //! The attribute content is XML-encoded + XmlWriter &writeAttribute(StringRef name, char const *attribute); + + //! The attribute value must provide op<<(ostream&, T). The resulting + //! serialization is XML-encoded + template::value>> + XmlWriter &writeAttribute(StringRef name, T const &attribute) { + ReusableStringStream rss; + rss << attribute; + return writeAttribute(name, rss.str()); + } + + //! Writes escaped `text` in a element + XmlWriter &writeText(StringRef text, + XmlFormatting fmt = XmlFormatting::Newline | XmlFormatting::Indent); + + //! Writes XML comment as "" + XmlWriter &writeComment(StringRef text, + XmlFormatting fmt = XmlFormatting::Newline | XmlFormatting::Indent); + + void writeStylesheetRef(StringRef url); + + void ensureTagClosed(); + + private: + void applyFormatting(XmlFormatting fmt); + + void writeDeclaration(); + + void newlineIfNecessary(); + + bool m_tagIsOpen = false; + bool m_needsNewline = false; + std::vector m_tags; + std::string m_indent; + std::ostream &m_os; + }; + +} // namespace Catch + +#endif // CATCH_XMLWRITER_HPP_INCLUDED + +/** \file + * This is a convenience header for Catch2's Matcher support. It includes + * **all** of Catch2 headers related to matchers. + * + * Generally the Catch2 users should use specific includes they need, + * but this header can be used instead for ease-of-experimentation, or + * just plain convenience, at the cost of increased compilation times. + * + * When a new header is added to either the `matchers` folder, or to + * the corresponding internal subfolder, it should be added here. + */ + +#ifndef CATCH_MATCHERS_ALL_HPP_INCLUDED +#define CATCH_MATCHERS_ALL_HPP_INCLUDED + +#ifndef CATCH_MATCHERS_HPP_INCLUDED +#define CATCH_MATCHERS_HPP_INCLUDED + +#ifndef CATCH_MATCHERS_IMPL_HPP_INCLUDED +#define CATCH_MATCHERS_IMPL_HPP_INCLUDED + +#include + +namespace Catch { + +#ifdef __clang__ +#pragma clang diagnostic push +#pragma clang diagnostic ignored "-Wsign-compare" +#pragma clang diagnostic ignored "-Wnon-virtual-dtor" +#elif defined __GNUC__ +#pragma GCC diagnostic push +#pragma GCC diagnostic ignored "-Wsign-compare" +#pragma GCC diagnostic ignored "-Wnon-virtual-dtor" +#endif + + template + class MatchExpr : public ITransientExpression { + ArgT &&m_arg; + MatcherT const &m_matcher; + + public: + constexpr MatchExpr(ArgT &&arg, MatcherT const &matcher) + : ITransientExpression{true, matcher.match(arg)}, // not forwarding arg here on purpose + m_arg(CATCH_FORWARD(arg)) + , m_matcher(matcher) {} + + void streamReconstructedExpression(std::ostream &os) const override { + os << Catch::Detail::stringify(m_arg) + << ' ' + << m_matcher.toString(); + } + }; + +#ifdef __clang__ +#pragma clang diagnostic pop +#elif defined __GNUC__ +#pragma GCC diagnostic pop +#endif + + namespace Matchers { + template + class MatcherBase; + } + + using StringMatcher = Matchers::MatcherBase; + + void handleExceptionMatchExpr(AssertionHandler &handler, StringMatcher const &matcher); + + template + constexpr MatchExpr + makeMatchExpr(ArgT &&arg, MatcherT const &matcher) { + return MatchExpr(CATCH_FORWARD(arg), matcher); + } + +} // namespace Catch + +/////////////////////////////////////////////////////////////////////////////// +#define INTERNAL_CHECK_THAT(macroName, matcher, resultDisposition, arg) \ + do { \ + Catch::AssertionHandler catchAssertionHandler(macroName##_catch_sr, CATCH_INTERNAL_LINEINFO, CATCH_INTERNAL_STRINGIFY(arg) ", " CATCH_INTERNAL_STRINGIFY(matcher), resultDisposition); \ + INTERNAL_CATCH_TRY { \ + catchAssertionHandler.handleExpr(Catch::makeMatchExpr(arg, matcher)); \ + } \ + INTERNAL_CATCH_CATCH(catchAssertionHandler) \ + catchAssertionHandler.complete(); \ + } while (false) + +/////////////////////////////////////////////////////////////////////////////// +#define INTERNAL_CATCH_THROWS_MATCHES(macroName, exceptionType, resultDisposition, matcher, ...) \ + do { \ + Catch::AssertionHandler catchAssertionHandler(macroName##_catch_sr, CATCH_INTERNAL_LINEINFO, CATCH_INTERNAL_STRINGIFY(__VA_ARGS__) ", " CATCH_INTERNAL_STRINGIFY(exceptionType) ", " CATCH_INTERNAL_STRINGIFY(matcher), resultDisposition); \ + if (catchAssertionHandler.allowThrows()) \ + try { \ + CATCH_INTERNAL_START_WARNINGS_SUPPRESSION \ + CATCH_INTERNAL_SUPPRESS_USELESS_CAST_WARNINGS \ + static_cast(__VA_ARGS__); \ + CATCH_INTERNAL_STOP_WARNINGS_SUPPRESSION \ + catchAssertionHandler.handleUnexpectedExceptionNotThrown(); \ + } catch (exceptionType const &ex) { \ + catchAssertionHandler.handleExpr(Catch::makeMatchExpr(ex, matcher)); \ + } catch (...) { \ + catchAssertionHandler.handleUnexpectedInflightException(); \ + } \ + else \ + catchAssertionHandler.handleThrowingCallSkipped(); \ + catchAssertionHandler.complete(); \ + } while (false) + +#endif // CATCH_MATCHERS_IMPL_HPP_INCLUDED + +#include +#include + +namespace Catch { + namespace Matchers { + + class MatcherUntypedBase { + public: + MatcherUntypedBase() = default; + + MatcherUntypedBase(MatcherUntypedBase const &) = default; + MatcherUntypedBase(MatcherUntypedBase &&) = default; + + MatcherUntypedBase &operator=(MatcherUntypedBase const &) = delete; + MatcherUntypedBase &operator=(MatcherUntypedBase &&) = delete; + + std::string toString() const; + + protected: + virtual ~MatcherUntypedBase(); // = default; + virtual std::string describe() const = 0; + mutable std::string m_cachedToString; + }; + + template + class MatcherBase : public MatcherUntypedBase { + public: + virtual bool match(T const &arg) const = 0; + }; + + namespace Detail { + + template + class MatchAllOf final : public MatcherBase { + std::vector const *> m_matchers; + + public: + MatchAllOf() = default; + MatchAllOf(MatchAllOf const &) = delete; + MatchAllOf &operator=(MatchAllOf const &) = delete; + MatchAllOf(MatchAllOf &&) = default; + MatchAllOf &operator=(MatchAllOf &&) = default; + + bool match(ArgT const &arg) const override { + for (auto matcher : m_matchers) { + if (!matcher->match(arg)) + return false; + } + return true; + } + std::string describe() const override { + std::string description; + description.reserve(4 + m_matchers.size() * 32); + description += "( "; + bool first = true; + for (auto matcher : m_matchers) { + if (first) + first = false; + else + description += " and "; + description += matcher->toString(); + } + description += " )"; + return description; + } + + friend MatchAllOf operator&&(MatchAllOf &&lhs, + MatcherBase const &rhs + CATCH_ATTR_LIFETIMEBOUND) { + lhs.m_matchers.push_back(&rhs); + return CATCH_MOVE(lhs); + } + friend MatchAllOf + operator&&(MatcherBase const &lhs CATCH_ATTR_LIFETIMEBOUND, + MatchAllOf &&rhs) { + rhs.m_matchers.insert(rhs.m_matchers.begin(), &lhs); + return CATCH_MOVE(rhs); + } + }; + + //! lvalue overload is intentionally deleted, users should + //! not be trying to compose stored composition matchers + template + MatchAllOf operator&&(MatchAllOf const &lhs, MatcherBase const &rhs) = delete; + //! lvalue overload is intentionally deleted, users should + //! not be trying to compose stored composition matchers + template + MatchAllOf operator&&(MatcherBase const &lhs, MatchAllOf const &rhs) = delete; + + template + class MatchAnyOf final : public MatcherBase { + std::vector const *> m_matchers; + + public: + MatchAnyOf() = default; + MatchAnyOf(MatchAnyOf const &) = delete; + MatchAnyOf &operator=(MatchAnyOf const &) = delete; + MatchAnyOf(MatchAnyOf &&) = default; + MatchAnyOf &operator=(MatchAnyOf &&) = default; + + bool match(ArgT const &arg) const override { + for (auto matcher : m_matchers) { + if (matcher->match(arg)) + return true; + } + return false; + } + std::string describe() const override { + std::string description; + description.reserve(4 + m_matchers.size() * 32); + description += "( "; + bool first = true; + for (auto matcher : m_matchers) { + if (first) + first = false; + else + description += " or "; + description += matcher->toString(); + } + description += " )"; + return description; + } + + friend MatchAnyOf operator||(MatchAnyOf &&lhs, + MatcherBase const &rhs + CATCH_ATTR_LIFETIMEBOUND) { + lhs.m_matchers.push_back(&rhs); + return CATCH_MOVE(lhs); + } + friend MatchAnyOf + operator||(MatcherBase const &lhs CATCH_ATTR_LIFETIMEBOUND, + MatchAnyOf &&rhs) { + rhs.m_matchers.insert(rhs.m_matchers.begin(), &lhs); + return CATCH_MOVE(rhs); + } + }; + + //! lvalue overload is intentionally deleted, users should + //! not be trying to compose stored composition matchers + template + MatchAnyOf operator||(MatchAnyOf const &lhs, MatcherBase const &rhs) = delete; + //! lvalue overload is intentionally deleted, users should + //! not be trying to compose stored composition matchers + template + MatchAnyOf operator||(MatcherBase const &lhs, MatchAnyOf const &rhs) = delete; + + template + class MatchNotOf final : public MatcherBase { + MatcherBase const &m_underlyingMatcher; + + public: + explicit MatchNotOf(MatcherBase const &underlyingMatcher + CATCH_ATTR_LIFETIMEBOUND) + : m_underlyingMatcher(underlyingMatcher) {} + + bool match(ArgT const &arg) const override { + return !m_underlyingMatcher.match(arg); + } + + std::string describe() const override { + return "not " + m_underlyingMatcher.toString(); + } + }; + + } // namespace Detail + + template + Detail::MatchAllOf + operator&&(MatcherBase const &lhs CATCH_ATTR_LIFETIMEBOUND, + MatcherBase const &rhs CATCH_ATTR_LIFETIMEBOUND) { + return Detail::MatchAllOf{} && lhs && rhs; + } + + template + Detail::MatchAnyOf + operator||(MatcherBase const &lhs CATCH_ATTR_LIFETIMEBOUND, + MatcherBase const &rhs CATCH_ATTR_LIFETIMEBOUND) { + return Detail::MatchAnyOf{} || lhs || rhs; + } + + template + Detail::MatchNotOf + operator!(MatcherBase const &matcher CATCH_ATTR_LIFETIMEBOUND) { + return Detail::MatchNotOf{matcher}; + } + + } // namespace Matchers +} // namespace Catch + +#if defined(CATCH_CONFIG_PREFIX_ALL) && !defined(CATCH_CONFIG_DISABLE) +#define CATCH_REQUIRE_THROWS_WITH(expr, matcher) INTERNAL_CATCH_THROWS_STR_MATCHES("CATCH_REQUIRE_THROWS_WITH", Catch::ResultDisposition::Normal, matcher, expr) +#define CATCH_REQUIRE_THROWS_MATCHES(expr, exceptionType, matcher) INTERNAL_CATCH_THROWS_MATCHES("CATCH_REQUIRE_THROWS_MATCHES", exceptionType, Catch::ResultDisposition::Normal, matcher, expr) + +#define CATCH_CHECK_THROWS_WITH(expr, matcher) INTERNAL_CATCH_THROWS_STR_MATCHES("CATCH_CHECK_THROWS_WITH", Catch::ResultDisposition::ContinueOnFailure, matcher, expr) +#define CATCH_CHECK_THROWS_MATCHES(expr, exceptionType, matcher) INTERNAL_CATCH_THROWS_MATCHES("CATCH_CHECK_THROWS_MATCHES", exceptionType, Catch::ResultDisposition::ContinueOnFailure, matcher, expr) + +#define CATCH_CHECK_THAT(arg, matcher) INTERNAL_CHECK_THAT("CATCH_CHECK_THAT", matcher, Catch::ResultDisposition::ContinueOnFailure, arg) +#define CATCH_REQUIRE_THAT(arg, matcher) INTERNAL_CHECK_THAT("CATCH_REQUIRE_THAT", matcher, Catch::ResultDisposition::Normal, arg) + +#elif defined(CATCH_CONFIG_PREFIX_ALL) && defined(CATCH_CONFIG_DISABLE) + +#define CATCH_REQUIRE_THROWS_WITH(expr, matcher) (void)(0) +#define CATCH_REQUIRE_THROWS_MATCHES(expr, exceptionType, matcher) (void)(0) + +#define CATCH_CHECK_THROWS_WITH(expr, matcher) (void)(0) +#define CATCH_CHECK_THROWS_MATCHES(expr, exceptionType, matcher) (void)(0) + +#define CATCH_CHECK_THAT(arg, matcher) (void)(0) +#define CATCH_REQUIRE_THAT(arg, matcher) (void)(0) + +#elif !defined(CATCH_CONFIG_PREFIX_ALL) && !defined(CATCH_CONFIG_DISABLE) + +#define REQUIRE_THROWS_WITH(expr, matcher) INTERNAL_CATCH_THROWS_STR_MATCHES("REQUIRE_THROWS_WITH", Catch::ResultDisposition::Normal, matcher, expr) +#define REQUIRE_THROWS_MATCHES(expr, exceptionType, matcher) INTERNAL_CATCH_THROWS_MATCHES("REQUIRE_THROWS_MATCHES", exceptionType, Catch::ResultDisposition::Normal, matcher, expr) + +#define CHECK_THROWS_WITH(expr, matcher) INTERNAL_CATCH_THROWS_STR_MATCHES("CHECK_THROWS_WITH", Catch::ResultDisposition::ContinueOnFailure, matcher, expr) +#define CHECK_THROWS_MATCHES(expr, exceptionType, matcher) INTERNAL_CATCH_THROWS_MATCHES("CHECK_THROWS_MATCHES", exceptionType, Catch::ResultDisposition::ContinueOnFailure, matcher, expr) + +#define CHECK_THAT(arg, matcher) INTERNAL_CHECK_THAT("CHECK_THAT", matcher, Catch::ResultDisposition::ContinueOnFailure, arg) +#define REQUIRE_THAT(arg, matcher) INTERNAL_CHECK_THAT("REQUIRE_THAT", matcher, Catch::ResultDisposition::Normal, arg) + +#elif !defined(CATCH_CONFIG_PREFIX_ALL) && defined(CATCH_CONFIG_DISABLE) + +#define REQUIRE_THROWS_WITH(expr, matcher) (void)(0) +#define REQUIRE_THROWS_MATCHES(expr, exceptionType, matcher) (void)(0) + +#define CHECK_THROWS_WITH(expr, matcher) (void)(0) +#define CHECK_THROWS_MATCHES(expr, exceptionType, matcher) (void)(0) + +#define CHECK_THAT(arg, matcher) (void)(0) +#define REQUIRE_THAT(arg, matcher) (void)(0) + +#endif // end of user facing macro declarations + +#endif // CATCH_MATCHERS_HPP_INCLUDED + +#ifndef CATCH_MATCHERS_CONTAINER_PROPERTIES_HPP_INCLUDED +#define CATCH_MATCHERS_CONTAINER_PROPERTIES_HPP_INCLUDED + +#ifndef CATCH_MATCHERS_TEMPLATED_HPP_INCLUDED +#define CATCH_MATCHERS_TEMPLATED_HPP_INCLUDED + +#include +#include +#include +#include + +namespace Catch { + namespace Matchers { + class MatcherGenericBase : public MatcherUntypedBase { + public: + MatcherGenericBase() = default; + ~MatcherGenericBase() override; // = default; + + MatcherGenericBase(MatcherGenericBase const &) = default; + MatcherGenericBase(MatcherGenericBase &&) = default; + + MatcherGenericBase &operator=(MatcherGenericBase const &) = delete; + MatcherGenericBase &operator=(MatcherGenericBase &&) = delete; + }; + + namespace Detail { + template + std::array array_cat(std::array &&lhs, std::array &&rhs) { + std::array arr{}; + std::copy_n(lhs.begin(), N, arr.begin()); + std::copy_n(rhs.begin(), M, arr.begin() + N); + return arr; + } + + template + std::array array_cat(std::array &&lhs, void const *rhs) { + std::array arr{}; + std::copy_n(lhs.begin(), N, arr.begin()); + arr[N] = rhs; + return arr; + } + + template + std::array array_cat(void const *lhs, std::array &&rhs) { + std::array arr{{lhs}}; + std::copy_n(rhs.begin(), N, arr.begin() + 1); + return arr; + } + + template + static constexpr bool is_generic_matcher_v = std::is_base_of< + Catch::Matchers::MatcherGenericBase, + std::remove_cv_t>>::value; + + template + static constexpr bool are_generic_matchers_v = Catch::Detail::conjunction>...>::value; + + template + static constexpr bool is_matcher_v = std::is_base_of< + Catch::Matchers::MatcherUntypedBase, + std::remove_cv_t>>::value; + + template + bool match_all_of(Arg &&, std::array const &, std::index_sequence<>) { + return true; + } + + template + bool match_all_of(Arg &&arg, std::array const &matchers, std::index_sequence) { + return static_cast(matchers[Idx])->match(arg) && match_all_of(arg, matchers, std::index_sequence{}); + } + + template + bool match_any_of(Arg &&, std::array const &, std::index_sequence<>) { + return false; + } + + template + bool match_any_of(Arg &&arg, std::array const &matchers, std::index_sequence) { + return static_cast(matchers[Idx])->match(arg) || match_any_of(arg, matchers, std::index_sequence{}); + } + + std::string describe_multi_matcher(StringRef combine, std::string const *descriptions_begin, std::string const *descriptions_end); + + template + std::string describe_multi_matcher(StringRef combine, std::array const &matchers, std::index_sequence) { + std::array descriptions{{static_cast(matchers[Idx])->toString()...}}; + + return describe_multi_matcher(combine, descriptions.data(), descriptions.data() + descriptions.size()); + } + + template + class MatchAllOfGeneric final : public MatcherGenericBase { + public: + MatchAllOfGeneric(MatchAllOfGeneric const &) = delete; + MatchAllOfGeneric &operator=(MatchAllOfGeneric const &) = delete; + MatchAllOfGeneric(MatchAllOfGeneric &&) = default; + MatchAllOfGeneric &operator=(MatchAllOfGeneric &&) = default; + + MatchAllOfGeneric(MatcherTs const &...matchers CATCH_ATTR_LIFETIMEBOUND) + : m_matchers{{std::addressof(matchers)...}} {} + explicit MatchAllOfGeneric(std::array matchers) + : m_matchers{matchers} {} + + template + bool match(Arg &&arg) const { + return match_all_of(arg, m_matchers, std::index_sequence_for{}); + } + + std::string describe() const override { + return describe_multi_matcher(" and "_sr, m_matchers, std::index_sequence_for{}); + } + + // Has to be public to enable the concatenating operators + // below, because they are not friend of the RHS, only LHS, + // and thus cannot access private fields of RHS + std::array m_matchers; + + //! Avoids type nesting for `GenericAllOf && GenericAllOf` case + template + friend MatchAllOfGeneric operator&&( + MatchAllOfGeneric &&lhs CATCH_ATTR_LIFETIMEBOUND, + MatchAllOfGeneric &&rhs CATCH_ATTR_LIFETIMEBOUND) { + return MatchAllOfGeneric{array_cat(CATCH_MOVE(lhs.m_matchers), CATCH_MOVE(rhs.m_matchers))}; + } + + //! Avoids type nesting for `GenericAllOf && some matcher` case + template + friend std::enable_if_t, + MatchAllOfGeneric> + operator&&( + MatchAllOfGeneric &&lhs CATCH_ATTR_LIFETIMEBOUND, + MatcherRHS const &rhs CATCH_ATTR_LIFETIMEBOUND) { + return MatchAllOfGeneric{array_cat(CATCH_MOVE(lhs.m_matchers), static_cast(&rhs))}; + } + + //! Avoids type nesting for `some matcher && GenericAllOf` case + template + friend std::enable_if_t, + MatchAllOfGeneric> + operator&&( + MatcherLHS const &lhs CATCH_ATTR_LIFETIMEBOUND, + MatchAllOfGeneric &&rhs CATCH_ATTR_LIFETIMEBOUND) { + return MatchAllOfGeneric{array_cat(static_cast(std::addressof(lhs)), CATCH_MOVE(rhs.m_matchers))}; + } + }; + + template + class MatchAnyOfGeneric final : public MatcherGenericBase { + public: + MatchAnyOfGeneric(MatchAnyOfGeneric const &) = delete; + MatchAnyOfGeneric &operator=(MatchAnyOfGeneric const &) = delete; + MatchAnyOfGeneric(MatchAnyOfGeneric &&) = default; + MatchAnyOfGeneric &operator=(MatchAnyOfGeneric &&) = default; + + MatchAnyOfGeneric(MatcherTs const &...matchers CATCH_ATTR_LIFETIMEBOUND) + : m_matchers{{std::addressof(matchers)...}} {} + explicit MatchAnyOfGeneric(std::array matchers) + : m_matchers{matchers} {} + + template + bool match(Arg &&arg) const { + return match_any_of(arg, m_matchers, std::index_sequence_for{}); + } + + std::string describe() const override { + return describe_multi_matcher(" or "_sr, m_matchers, std::index_sequence_for{}); + } + + // Has to be public to enable the concatenating operators + // below, because they are not friend of the RHS, only LHS, + // and thus cannot access private fields of RHS + std::array m_matchers; + + //! Avoids type nesting for `GenericAnyOf || GenericAnyOf` case + template + friend MatchAnyOfGeneric operator||( + MatchAnyOfGeneric &&lhs CATCH_ATTR_LIFETIMEBOUND, + MatchAnyOfGeneric &&rhs CATCH_ATTR_LIFETIMEBOUND) { + return MatchAnyOfGeneric{array_cat(CATCH_MOVE(lhs.m_matchers), CATCH_MOVE(rhs.m_matchers))}; + } + + //! Avoids type nesting for `GenericAnyOf || some matcher` case + template + friend std::enable_if_t, + MatchAnyOfGeneric> + operator||( + MatchAnyOfGeneric &&lhs CATCH_ATTR_LIFETIMEBOUND, + MatcherRHS const &rhs CATCH_ATTR_LIFETIMEBOUND) { + return MatchAnyOfGeneric{array_cat(CATCH_MOVE(lhs.m_matchers), static_cast(std::addressof(rhs)))}; + } + + //! Avoids type nesting for `some matcher || GenericAnyOf` case + template + friend std::enable_if_t, + MatchAnyOfGeneric> + operator||( + MatcherLHS const &lhs CATCH_ATTR_LIFETIMEBOUND, + MatchAnyOfGeneric &&rhs CATCH_ATTR_LIFETIMEBOUND) { + return MatchAnyOfGeneric{array_cat(static_cast(std::addressof(lhs)), CATCH_MOVE(rhs.m_matchers))}; + } + }; + + template + class MatchNotOfGeneric final : public MatcherGenericBase { + MatcherT const &m_matcher; + + public: + MatchNotOfGeneric(MatchNotOfGeneric const &) = delete; + MatchNotOfGeneric &operator=(MatchNotOfGeneric const &) = delete; + MatchNotOfGeneric(MatchNotOfGeneric &&) = default; + MatchNotOfGeneric &operator=(MatchNotOfGeneric &&) = default; + + explicit MatchNotOfGeneric(MatcherT const &matcher CATCH_ATTR_LIFETIMEBOUND) + : m_matcher{matcher} {} + + template + bool match(Arg &&arg) const { + return !m_matcher.match(arg); + } + + std::string describe() const override { + return "not " + m_matcher.toString(); + } + + //! Negating negation can just unwrap and return underlying matcher + friend MatcherT const & + operator!(MatchNotOfGeneric const &matcher + CATCH_ATTR_LIFETIMEBOUND) { + return matcher.m_matcher; + } + }; + } // namespace Detail + + // compose only generic matchers + template + std::enable_if_t, Detail::MatchAllOfGeneric> + operator&&(MatcherLHS const &lhs CATCH_ATTR_LIFETIMEBOUND, + MatcherRHS const &rhs CATCH_ATTR_LIFETIMEBOUND) { + return {lhs, rhs}; + } + + template + std::enable_if_t, Detail::MatchAnyOfGeneric> + operator||(MatcherLHS const &lhs CATCH_ATTR_LIFETIMEBOUND, + MatcherRHS const &rhs CATCH_ATTR_LIFETIMEBOUND) { + return {lhs, rhs}; + } + + //! Wrap provided generic matcher in generic negator + template + std::enable_if_t, Detail::MatchNotOfGeneric> + operator!(MatcherT const &matcher CATCH_ATTR_LIFETIMEBOUND) { + return Detail::MatchNotOfGeneric{matcher}; + } + + // compose mixed generic and non-generic matchers + template + std::enable_if_t, Detail::MatchAllOfGeneric>> + operator&&(MatcherLHS const &lhs CATCH_ATTR_LIFETIMEBOUND, + MatcherBase const &rhs CATCH_ATTR_LIFETIMEBOUND) { + return {lhs, rhs}; + } + + template + std::enable_if_t, Detail::MatchAllOfGeneric, MatcherRHS>> + operator&&(MatcherBase const &lhs CATCH_ATTR_LIFETIMEBOUND, + MatcherRHS const &rhs CATCH_ATTR_LIFETIMEBOUND) { + return {lhs, rhs}; + } + + template + std::enable_if_t, Detail::MatchAnyOfGeneric>> + operator||(MatcherLHS const &lhs CATCH_ATTR_LIFETIMEBOUND, + MatcherBase const &rhs CATCH_ATTR_LIFETIMEBOUND) { + return {lhs, rhs}; + } + + template + std::enable_if_t, Detail::MatchAnyOfGeneric, MatcherRHS>> + operator||(MatcherBase const &lhs CATCH_ATTR_LIFETIMEBOUND, + MatcherRHS const &rhs CATCH_ATTR_LIFETIMEBOUND) { + return {lhs, rhs}; + } + + } // namespace Matchers +} // namespace Catch + +#endif // CATCH_MATCHERS_TEMPLATED_HPP_INCLUDED + +namespace Catch { + namespace Matchers { + + class IsEmptyMatcher final : public MatcherGenericBase { + public: + template + bool match(RangeLike &&rng) const { +#if defined(CATCH_CONFIG_POLYFILL_NONMEMBER_CONTAINER_ACCESS) + using Catch::Detail::empty; +#else + using std::empty; +#endif + return empty(rng); + } + + std::string describe() const override; + }; + + class HasSizeMatcher final : public MatcherGenericBase { + std::size_t m_target_size; + + public: + explicit HasSizeMatcher(std::size_t target_size) + : m_target_size(target_size) {} + + template + bool match(RangeLike &&rng) const { +#if defined(CATCH_CONFIG_POLYFILL_NONMEMBER_CONTAINER_ACCESS) + using Catch::Detail::size; +#else + using std::size; +#endif + return size(rng) == m_target_size; + } + + std::string describe() const override; + }; + + template + class SizeMatchesMatcher final : public MatcherGenericBase { + Matcher m_matcher; + + public: + explicit SizeMatchesMatcher(Matcher m) + : m_matcher(CATCH_MOVE(m)) {} + + template + bool match(RangeLike &&rng) const { +#if defined(CATCH_CONFIG_POLYFILL_NONMEMBER_CONTAINER_ACCESS) + using Catch::Detail::size; +#else + using std::size; +#endif + return m_matcher.match(size(rng)); + } + + std::string describe() const override { + return "size matches " + m_matcher.describe(); + } + }; + + //! Creates a matcher that accepts empty ranges/containers + IsEmptyMatcher IsEmpty(); + //! Creates a matcher that accepts ranges/containers with specific size + HasSizeMatcher SizeIs(std::size_t sz); + template + std::enable_if_t, + SizeMatchesMatcher> + SizeIs(Matcher &&m) { + return SizeMatchesMatcher{CATCH_FORWARD(m)}; + } + + } // end namespace Matchers +} // end namespace Catch + +#endif // CATCH_MATCHERS_CONTAINER_PROPERTIES_HPP_INCLUDED + +#ifndef CATCH_MATCHERS_CONTAINS_HPP_INCLUDED +#define CATCH_MATCHERS_CONTAINS_HPP_INCLUDED + +#include +#include + +namespace Catch { + namespace Matchers { + //! Matcher for checking that an element in range is equal to specific element + template + class ContainsElementMatcher final : public MatcherGenericBase { + T m_desired; + Equality m_eq; + + public: + template + ContainsElementMatcher(T2 &&target, Equality2 &&predicate) + : m_desired(CATCH_FORWARD(target)), m_eq(CATCH_FORWARD(predicate)) {} + + std::string describe() const override { + return "contains element " + Catch::Detail::stringify(m_desired); + } + + template + bool match(RangeLike &&rng) const { + for (auto &&elem : rng) { + if (m_eq(elem, m_desired)) { return true; } + } + return false; + } + }; + + //! Meta-matcher for checking that an element in a range matches a specific matcher + template + class ContainsMatcherMatcher final : public MatcherGenericBase { + Matcher m_matcher; + + public: + // Note that we do a copy+move to avoid having to SFINAE this + // constructor (and also avoid some perfect forwarding failure + // cases) + ContainsMatcherMatcher(Matcher matcher) + : m_matcher(CATCH_MOVE(matcher)) {} + + template + bool match(RangeLike &&rng) const { + for (auto &&elem : rng) { + if (m_matcher.match(elem)) { + return true; + } + } + return false; + } + + std::string describe() const override { + return "contains element matching " + m_matcher.describe(); + } + }; + + /** + * Creates a matcher that checks whether a range contains a specific element. + * + * Uses `std::equal_to` to do the comparison + */ + template + std::enable_if_t, + ContainsElementMatcher>> + Contains(T &&elem) { + return {CATCH_FORWARD(elem), std::equal_to<>{}}; + } + + //! Creates a matcher that checks whether a range contains element matching a matcher + template + std::enable_if_t, + ContainsMatcherMatcher> + Contains(Matcher &&matcher) { + return {CATCH_FORWARD(matcher)}; + } + + /** + * Creates a matcher that checks whether a range contains a specific element. + * + * Uses `eq` to do the comparisons, the element is provided on the rhs + */ + template + ContainsElementMatcher Contains(T &&elem, Equality &&eq) { + return {CATCH_FORWARD(elem), CATCH_FORWARD(eq)}; + } + + } // namespace Matchers +} // namespace Catch + +#endif // CATCH_MATCHERS_CONTAINS_HPP_INCLUDED + +#ifndef CATCH_MATCHERS_EXCEPTION_HPP_INCLUDED +#define CATCH_MATCHERS_EXCEPTION_HPP_INCLUDED + +namespace Catch { + namespace Matchers { + + class ExceptionMessageMatcher final : public MatcherBase { + std::string m_message; + + public: + ExceptionMessageMatcher(std::string const &message) + : m_message(message) {} + + bool match(std::exception const &ex) const override; + + std::string describe() const override; + }; + + //! Creates a matcher that checks whether a std derived exception has the provided message + ExceptionMessageMatcher Message(std::string const &message); + + template + class ExceptionMessageMatchesMatcher final + : public MatcherBase { + StringMatcherType m_matcher; + + public: + ExceptionMessageMatchesMatcher(StringMatcherType matcher) + : m_matcher(CATCH_MOVE(matcher)) {} + + bool match(std::exception const &ex) const override { + return m_matcher.match(ex.what()); + } + + std::string describe() const override { + return " matches \"" + m_matcher.describe() + '"'; + } + }; + + //! Creates a matcher that checks whether a message from an std derived + //! exception matches a provided matcher + template + ExceptionMessageMatchesMatcher + MessageMatches(StringMatcherType &&matcher) { + return {CATCH_FORWARD(matcher)}; + } + + } // namespace Matchers +} // namespace Catch + +#endif // CATCH_MATCHERS_EXCEPTION_HPP_INCLUDED + +#ifndef CATCH_MATCHERS_FLOATING_POINT_HPP_INCLUDED +#define CATCH_MATCHERS_FLOATING_POINT_HPP_INCLUDED + +namespace Catch { + namespace Matchers { + + namespace Detail { + enum class FloatingPointKind : uint8_t; + } + + class WithinAbsMatcher final : public MatcherBase { + public: + WithinAbsMatcher(double target, double margin); + bool match(double const &matchee) const override; + std::string describe() const override; + + private: + double m_target; + double m_margin; + }; + + //! Creates a matcher that accepts numbers within certain range of target + WithinAbsMatcher WithinAbs(double target, double margin); + + class WithinUlpsMatcher final : public MatcherBase { + public: + WithinUlpsMatcher(double target, + uint64_t ulps, + Detail::FloatingPointKind baseType); + bool match(double const &matchee) const override; + std::string describe() const override; + + private: + double m_target; + uint64_t m_ulps; + Detail::FloatingPointKind m_type; + }; + + //! Creates a matcher that accepts doubles within certain ULP range of target + WithinUlpsMatcher WithinULP(double target, uint64_t maxUlpDiff); + //! Creates a matcher that accepts floats within certain ULP range of target + WithinUlpsMatcher WithinULP(float target, uint64_t maxUlpDiff); + + // Given IEEE-754 format for floats and doubles, we can assume + // that float -> double promotion is lossless. Given this, we can + // assume that if we do the standard relative comparison of + // |lhs - rhs| <= epsilon * max(fabs(lhs), fabs(rhs)), then we get + // the same result if we do this for floats, as if we do this for + // doubles that were promoted from floats. + class WithinRelMatcher final : public MatcherBase { + public: + WithinRelMatcher(double target, double epsilon); + bool match(double const &matchee) const override; + std::string describe() const override; + + private: + double m_target; + double m_epsilon; + }; + + //! Creates a matcher that accepts doubles within certain relative range of target + WithinRelMatcher WithinRel(double target, double eps); + //! Creates a matcher that accepts doubles within 100*DBL_EPS relative range of target + WithinRelMatcher WithinRel(double target); + //! Creates a matcher that accepts doubles within certain relative range of target + WithinRelMatcher WithinRel(float target, float eps); + //! Creates a matcher that accepts floats within 100*FLT_EPS relative range of target + WithinRelMatcher WithinRel(float target); + + class IsNaNMatcher final : public MatcherBase { + public: + IsNaNMatcher() = default; + bool match(double const &matchee) const override; + std::string describe() const override; + }; + + IsNaNMatcher IsNaN(); + + } // namespace Matchers +} // namespace Catch + +#endif // CATCH_MATCHERS_FLOATING_POINT_HPP_INCLUDED + +#ifndef CATCH_MATCHERS_PREDICATE_HPP_INCLUDED +#define CATCH_MATCHERS_PREDICATE_HPP_INCLUDED + +#include + +namespace Catch { + namespace Matchers { + + namespace Detail { + std::string finalizeDescription(const std::string &desc); + } // namespace Detail + + template + class PredicateMatcher final : public MatcherBase { + Predicate m_predicate; + std::string m_description; + + public: + PredicateMatcher(Predicate &&elem, std::string const &descr) + : m_predicate(CATCH_FORWARD(elem)), m_description(Detail::finalizeDescription(descr)) {} + + bool match(T const &item) const override { + return m_predicate(item); + } + + std::string describe() const override { + return m_description; + } + }; + + /** + * Creates a matcher that calls delegates `match` to the provided predicate. + * + * The user has to explicitly specify the argument type to the matcher + */ + template + PredicateMatcher Predicate(Pred &&predicate, std::string const &description = "") { + static_assert(is_callable::value, "Predicate not callable with argument T"); + static_assert(std::is_same>::value, "Predicate does not return bool"); + return PredicateMatcher(CATCH_FORWARD(predicate), description); + } + + } // namespace Matchers +} // namespace Catch + +#endif // CATCH_MATCHERS_PREDICATE_HPP_INCLUDED + +#ifndef CATCH_MATCHERS_QUANTIFIERS_HPP_INCLUDED +#define CATCH_MATCHERS_QUANTIFIERS_HPP_INCLUDED + +namespace Catch { + namespace Matchers { + // Matcher for checking that all elements in range matches a given matcher. + template + class AllMatchMatcher final : public MatcherGenericBase { + Matcher m_matcher; + + public: + AllMatchMatcher(Matcher matcher) + : m_matcher(CATCH_MOVE(matcher)) {} + + std::string describe() const override { + return "all match " + m_matcher.describe(); + } + + template + bool match(RangeLike &&rng) const { + for (auto &&elem : rng) { + if (!m_matcher.match(elem)) { + return false; + } + } + return true; + } + }; + + // Matcher for checking that no element in range matches a given matcher. + template + class NoneMatchMatcher final : public MatcherGenericBase { + Matcher m_matcher; + + public: + NoneMatchMatcher(Matcher matcher) + : m_matcher(CATCH_MOVE(matcher)) {} + + std::string describe() const override { + return "none match " + m_matcher.describe(); + } + + template + bool match(RangeLike &&rng) const { + for (auto &&elem : rng) { + if (m_matcher.match(elem)) { + return false; + } + } + return true; + } + }; + + // Matcher for checking that at least one element in range matches a given matcher. + template + class AnyMatchMatcher final : public MatcherGenericBase { + Matcher m_matcher; + + public: + AnyMatchMatcher(Matcher matcher) + : m_matcher(CATCH_MOVE(matcher)) {} + + std::string describe() const override { + return "any match " + m_matcher.describe(); + } + + template + bool match(RangeLike &&rng) const { + for (auto &&elem : rng) { + if (m_matcher.match(elem)) { + return true; + } + } + return false; + } + }; + + // Matcher for checking that all elements in range are true. + class AllTrueMatcher final : public MatcherGenericBase { + public: + std::string describe() const override; + + template + bool match(RangeLike &&rng) const { + for (auto &&elem : rng) { + if (!elem) { + return false; + } + } + return true; + } + }; + + // Matcher for checking that no element in range is true. + class NoneTrueMatcher final : public MatcherGenericBase { + public: + std::string describe() const override; + + template + bool match(RangeLike &&rng) const { + for (auto &&elem : rng) { + if (elem) { + return false; + } + } + return true; + } + }; + + // Matcher for checking that any element in range is true. + class AnyTrueMatcher final : public MatcherGenericBase { + public: + std::string describe() const override; + + template + bool match(RangeLike &&rng) const { + for (auto &&elem : rng) { + if (elem) { + return true; + } + } + return false; + } + }; + + // Creates a matcher that checks whether all elements in a range match a matcher + template + AllMatchMatcher AllMatch(Matcher &&matcher) { + return {CATCH_FORWARD(matcher)}; + } + + // Creates a matcher that checks whether no element in a range matches a matcher. + template + NoneMatchMatcher NoneMatch(Matcher &&matcher) { + return {CATCH_FORWARD(matcher)}; + } + + // Creates a matcher that checks whether any element in a range matches a matcher. + template + AnyMatchMatcher AnyMatch(Matcher &&matcher) { + return {CATCH_FORWARD(matcher)}; + } + + // Creates a matcher that checks whether all elements in a range are true + AllTrueMatcher AllTrue(); + + // Creates a matcher that checks whether no element in a range is true + NoneTrueMatcher NoneTrue(); + + // Creates a matcher that checks whether any element in a range is true + AnyTrueMatcher AnyTrue(); + } // namespace Matchers +} // namespace Catch + +#endif // CATCH_MATCHERS_QUANTIFIERS_HPP_INCLUDED + +#ifndef CATCH_MATCHERS_RANGE_EQUALS_HPP_INCLUDED +#define CATCH_MATCHERS_RANGE_EQUALS_HPP_INCLUDED + +#include + +namespace Catch { + namespace Matchers { + + /** + * Matcher for checking that an element contains the same + * elements in the same order + */ + template + class RangeEqualsMatcher final : public MatcherGenericBase { + TargetRangeLike m_desired; + Equality m_predicate; + + public: + template + constexpr RangeEqualsMatcher(TargetRangeLike2 &&range, + Equality2 &&predicate) + : m_desired(CATCH_FORWARD(range)), m_predicate(CATCH_FORWARD(predicate)) {} + + template + constexpr bool match(RangeLike &&rng) const { + auto rng_start = begin(rng); + const auto rng_end = end(rng); + auto target_start = begin(m_desired); + const auto target_end = end(m_desired); + + while (rng_start != rng_end && target_start != target_end) { + if (!m_predicate(*rng_start, *target_start)) { + return false; + } + ++rng_start; + ++target_start; + } + return rng_start == rng_end && target_start == target_end; + } + + std::string describe() const override { + return "elements are " + Catch::Detail::stringify(m_desired); + } + }; + + /** + * Matcher for checking that an element contains the same + * elements (but not necessarily in the same order) + */ + template + class UnorderedRangeEqualsMatcher final : public MatcherGenericBase { + TargetRangeLike m_desired; + Equality m_predicate; + + public: + template + constexpr UnorderedRangeEqualsMatcher(TargetRangeLike2 &&range, + Equality2 &&predicate) + : m_desired(CATCH_FORWARD(range)), m_predicate(CATCH_FORWARD(predicate)) {} + + template + constexpr bool match(RangeLike &&rng) const { + using std::begin; + using std::end; + return Catch::Detail::is_permutation(begin(m_desired), + end(m_desired), + begin(rng), + end(rng), + m_predicate); + } + + std::string describe() const override { + return "unordered elements are " + ::Catch::Detail::stringify(m_desired); + } + }; + + /** + * Creates a matcher that checks if all elements in a range are equal + * to all elements in another range. + * + * Uses the provided predicate `predicate` to do the comparisons + * (defaulting to `std::equal_to`) + */ + template{})> + constexpr RangeEqualsMatcher + RangeEquals(RangeLike &&range, + Equality &&predicate = std::equal_to<>{}) { + return {CATCH_FORWARD(range), CATCH_FORWARD(predicate)}; + } + + /** + * Creates a matcher that checks if all elements in a range are equal + * to all elements in an initializer list. + * + * Uses the provided predicate `predicate` to do the comparisons + * (defaulting to `std::equal_to`) + */ + template{})> + constexpr RangeEqualsMatcher, Equality> + RangeEquals(std::initializer_list range, + Equality &&predicate = std::equal_to<>{}) { + return {range, CATCH_FORWARD(predicate)}; + } + + /** + * Creates a matcher that checks if all elements in a range are equal + * to all elements in another range, in some permutation. + * + * Uses the provided predicate `predicate` to do the comparisons + * (defaulting to `std::equal_to`) + */ + template{})> + constexpr UnorderedRangeEqualsMatcher + UnorderedRangeEquals(RangeLike &&range, + Equality &&predicate = std::equal_to<>{}) { + return {CATCH_FORWARD(range), CATCH_FORWARD(predicate)}; + } + + /** + * Creates a matcher that checks if all elements in a range are equal + * to all elements in an initializer list, in some permutation. + * + * Uses the provided predicate `predicate` to do the comparisons + * (defaulting to `std::equal_to`) + */ + template{})> + constexpr UnorderedRangeEqualsMatcher, Equality> + UnorderedRangeEquals(std::initializer_list range, + Equality &&predicate = std::equal_to<>{}) { + return {range, CATCH_FORWARD(predicate)}; + } + } // namespace Matchers +} // namespace Catch + +#endif // CATCH_MATCHERS_RANGE_EQUALS_HPP_INCLUDED + +#ifndef CATCH_MATCHERS_STRING_HPP_INCLUDED +#define CATCH_MATCHERS_STRING_HPP_INCLUDED + +#include + +namespace Catch { + namespace Matchers { + + struct CasedString { + CasedString(std::string const &str, CaseSensitive caseSensitivity); + std::string adjustString(std::string const &str) const; + StringRef caseSensitivitySuffix() const; + + CaseSensitive m_caseSensitivity; + std::string m_str; + }; + + class StringMatcherBase : public MatcherBase { + protected: + CasedString m_comparator; + StringRef m_operation; + + public: + StringMatcherBase(StringRef operation, + CasedString const &comparator); + std::string describe() const override; + }; + + class StringEqualsMatcher final : public StringMatcherBase { + public: + StringEqualsMatcher(CasedString const &comparator); + bool match(std::string const &source) const override; + }; + class StringContainsMatcher final : public StringMatcherBase { + public: + StringContainsMatcher(CasedString const &comparator); + bool match(std::string const &source) const override; + }; + class StartsWithMatcher final : public StringMatcherBase { + public: + StartsWithMatcher(CasedString const &comparator); + bool match(std::string const &source) const override; + }; + class EndsWithMatcher final : public StringMatcherBase { + public: + EndsWithMatcher(CasedString const &comparator); + bool match(std::string const &source) const override; + }; + + class RegexMatcher final : public MatcherBase { + std::string m_regex; + CaseSensitive m_caseSensitivity; + + public: + RegexMatcher(std::string regex, CaseSensitive caseSensitivity); + bool match(std::string const &matchee) const override; + std::string describe() const override; + }; + + //! Creates matcher that accepts strings that are exactly equal to `str` + StringEqualsMatcher Equals(std::string const &str, CaseSensitive caseSensitivity = CaseSensitive::Yes); + //! Creates matcher that accepts strings that contain `str` + StringContainsMatcher ContainsSubstring(std::string const &str, CaseSensitive caseSensitivity = CaseSensitive::Yes); + //! Creates matcher that accepts strings that _end_ with `str` + EndsWithMatcher EndsWith(std::string const &str, CaseSensitive caseSensitivity = CaseSensitive::Yes); + //! Creates matcher that accepts strings that _start_ with `str` + StartsWithMatcher StartsWith(std::string const &str, CaseSensitive caseSensitivity = CaseSensitive::Yes); + //! Creates matcher that accepts strings matching `regex` + RegexMatcher Matches(std::string const ®ex, CaseSensitive caseSensitivity = CaseSensitive::Yes); + + } // namespace Matchers +} // namespace Catch + +#endif // CATCH_MATCHERS_STRING_HPP_INCLUDED + +#ifndef CATCH_MATCHERS_VECTOR_HPP_INCLUDED +#define CATCH_MATCHERS_VECTOR_HPP_INCLUDED + +#include + +namespace Catch { + namespace Matchers { + + template + class VectorContainsElementMatcher final : public MatcherBase> { + T const &m_comparator; + + public: + VectorContainsElementMatcher(T const &comparator) + : m_comparator(comparator) {} + + bool match(std::vector const &v) const override { + for (auto const &el : v) { + if (el == m_comparator) { + return true; + } + } + return false; + } + + std::string describe() const override { + return "Contains: " + ::Catch::Detail::stringify(m_comparator); + } + }; + + template + class ContainsMatcher final : public MatcherBase> { + std::vector const &m_comparator; + + public: + ContainsMatcher(std::vector const &comparator) + : m_comparator(comparator) {} + + bool match(std::vector const &v) const override { + // !TBD: see note in EqualsMatcher + if (m_comparator.size() > v.size()) + return false; + for (auto const &comparator : m_comparator) { + auto present = false; + for (const auto &el : v) { + if (el == comparator) { + present = true; + break; + } + } + if (!present) { + return false; + } + } + return true; + } + std::string describe() const override { + return "Contains: " + ::Catch::Detail::stringify(m_comparator); + } + }; + + template + class EqualsMatcher final : public MatcherBase> { + std::vector const &m_comparator; + + public: + EqualsMatcher(std::vector const &comparator) + : m_comparator(comparator) {} + + bool match(std::vector const &v) const override { + // !TBD: This currently works if all elements can be compared using != + // - a more general approach would be via a compare template that defaults + // to using !=. but could be specialised for, e.g. std::vector etc + // - then just call that directly + if (m_comparator.size() != v.size()) { return false; } + for (std::size_t i = 0; i < v.size(); ++i) { + if (!(m_comparator[i] == v[i])) { return false; } + } + return true; + } + std::string describe() const override { + return "Equals: " + ::Catch::Detail::stringify(m_comparator); + } + }; + + template + class ApproxMatcher final : public MatcherBase> { + std::vector const &m_comparator; + mutable Catch::Approx approx = Catch::Approx::custom(); + + public: + ApproxMatcher(std::vector const &comparator) + : m_comparator(comparator) {} + + bool match(std::vector const &v) const override { + if (m_comparator.size() != v.size()) + return false; + for (std::size_t i = 0; i < v.size(); ++i) + if (m_comparator[i] != approx(v[i])) + return false; + return true; + } + std::string describe() const override { + return "is approx: " + ::Catch::Detail::stringify(m_comparator); + } + template::value>> + ApproxMatcher &epsilon(T const &newEpsilon) { + approx.epsilon(static_cast(newEpsilon)); + return *this; + } + template::value>> + ApproxMatcher &margin(T const &newMargin) { + approx.margin(static_cast(newMargin)); + return *this; + } + template::value>> + ApproxMatcher &scale(T const &newScale) { + approx.scale(static_cast(newScale)); + return *this; + } + }; + + template + class UnorderedEqualsMatcher final : public MatcherBase> { + std::vector const &m_target; + + public: + UnorderedEqualsMatcher(std::vector const &target) + : m_target(target) {} + bool match(std::vector const &vec) const override { + if (m_target.size() != vec.size()) { + return false; + } + return std::is_permutation(m_target.begin(), m_target.end(), vec.begin()); + } + + std::string describe() const override { + return "UnorderedEquals: " + ::Catch::Detail::stringify(m_target); + } + }; + + // The following functions create the actual matcher objects. + // This allows the types to be inferred + + //! Creates a matcher that matches vectors that contain all elements in `comparator` + template, typename AllocMatch = AllocComp> + ContainsMatcher Contains(std::vector const &comparator) { + return ContainsMatcher(comparator); + } + + //! Creates a matcher that matches vectors that contain `comparator` as an element + template> + VectorContainsElementMatcher VectorContains(T const &comparator) { + return VectorContainsElementMatcher(comparator); + } + + //! Creates a matcher that matches vectors that are exactly equal to `comparator` + template, typename AllocMatch = AllocComp> + EqualsMatcher Equals(std::vector const &comparator) { + return EqualsMatcher(comparator); + } + + //! Creates a matcher that matches vectors that `comparator` as an element + template, typename AllocMatch = AllocComp> + ApproxMatcher Approx(std::vector const &comparator) { + return ApproxMatcher(comparator); + } + + //! Creates a matcher that matches vectors that is equal to `target` modulo permutation + template, typename AllocMatch = AllocComp> + UnorderedEqualsMatcher UnorderedEquals(std::vector const &target) { + return UnorderedEqualsMatcher(target); + } + + } // namespace Matchers +} // namespace Catch + +#endif // CATCH_MATCHERS_VECTOR_HPP_INCLUDED + +#endif // CATCH_MATCHERS_ALL_HPP_INCLUDED + +/** \file + * This is a convenience header for Catch2's Reporter support. It includes + * **all** of Catch2 headers related to reporters, including all reporters. + * + * Generally the Catch2 users should use specific includes they need, + * but this header can be used instead for ease-of-experimentation, or + * just plain convenience, at the cost of (significantly) increased + * compilation times. + * + * When a new header (reporter) is added to either the `reporter` folder, + * or to the corresponding internal subfolder, it should be added here. + */ + +#ifndef CATCH_REPORTERS_ALL_HPP_INCLUDED +#define CATCH_REPORTERS_ALL_HPP_INCLUDED + +#ifndef CATCH_REPORTER_AUTOMAKE_HPP_INCLUDED +#define CATCH_REPORTER_AUTOMAKE_HPP_INCLUDED + +#ifndef CATCH_REPORTER_STREAMING_BASE_HPP_INCLUDED +#define CATCH_REPORTER_STREAMING_BASE_HPP_INCLUDED + +#ifndef CATCH_REPORTER_COMMON_BASE_HPP_INCLUDED +#define CATCH_REPORTER_COMMON_BASE_HPP_INCLUDED + +#include +#include + +namespace Catch { + class ColourImpl; + + /** + * This is the base class for all reporters. + * + * If are writing a reporter, you must derive from this type, or one + * of the helper reporter bases that are derived from this type. + * + * ReporterBase centralizes handling of various common tasks in reporters, + * like storing the right stream for the reporters to write to, and + * providing the default implementation of the different listing events. + */ + class ReporterBase : public IEventListener { + protected: + //! The stream wrapper as passed to us by outside code + Detail::unique_ptr m_wrapped_stream; + //! Cached output stream from `m_wrapped_stream` to reduce + //! number of indirect calls needed to write output. + std::ostream &m_stream; + //! Colour implementation this reporter was configured for + Detail::unique_ptr m_colour; + //! The custom reporter options user passed down to the reporter + std::map m_customOptions; + + public: + ReporterBase(ReporterConfig &&config); + ~ReporterBase() override; // = default; + + /** + * Provides a simple default listing of reporters. + * + * Should look roughly like the reporter listing in v2 and earlier + * versions of Catch2. + */ + void listReporters( + std::vector const &descriptions) override; + /** + * Provides a simple default listing of listeners + * + * Looks similarly to listing of reporters, but with listener type + * instead of reporter name. + */ + void listListeners( + std::vector const &descriptions) override; + /** + * Provides a simple default listing of tests. + * + * Should look roughly like the test listing in v2 and earlier versions + * of Catch2. Especially supports low-verbosity listing that mimics the + * old `--list-test-names-only` output. + */ + void listTests(std::vector const &tests) override; + /** + * Provides a simple default listing of tags. + * + * Should look roughly like the tag listing in v2 and earlier versions + * of Catch2. + */ + void listTags(std::vector const &tags) override; + }; +} // namespace Catch + +#endif // CATCH_REPORTER_COMMON_BASE_HPP_INCLUDED + +#include + +namespace Catch { + + class StreamingReporterBase : public ReporterBase { + public: + // GCC5 compat: we cannot use inherited constructor, because it + // doesn't implement backport of P0136 + StreamingReporterBase(ReporterConfig &&_config) + : ReporterBase(CATCH_MOVE(_config)) {} + ~StreamingReporterBase() override; + + void benchmarkPreparing(StringRef) override {} + void benchmarkStarting(BenchmarkInfo const &) override {} + void benchmarkEnded(BenchmarkStats<> const &) override {} + void benchmarkFailed(StringRef) override {} + + void fatalErrorEncountered(StringRef /*error*/) override {} + void noMatchingTestCases(StringRef /*unmatchedSpec*/) override {} + void reportInvalidTestSpec(StringRef /*invalidArgument*/) override {} + + void testRunStarting(TestRunInfo const &_testRunInfo) override; + + void testCaseStarting(TestCaseInfo const &_testInfo) override { + currentTestCaseInfo = &_testInfo; + } + void testCasePartialStarting(TestCaseInfo const &, uint64_t) override {} + void sectionStarting(SectionInfo const &_sectionInfo) override { + m_sectionStack.push_back(_sectionInfo); + } + + void assertionStarting(AssertionInfo const &) override {} + void assertionEnded(AssertionStats const &) override {} + + void sectionEnded(SectionStats const & /* _sectionStats */) override { + m_sectionStack.pop_back(); + } + void testCasePartialEnded(TestCaseStats const &, uint64_t) override {} + void testCaseEnded(TestCaseStats const & /* _testCaseStats */) override { + currentTestCaseInfo = nullptr; + } + void testRunEnded(TestRunStats const & /* _testRunStats */) override; + + void skipTest(TestCaseInfo const &) override { + // Don't do anything with this by default. + // It can optionally be overridden in the derived class. + } + + protected: + TestRunInfo currentTestRunInfo{"test run has not started yet"_sr}; + TestCaseInfo const *currentTestCaseInfo = nullptr; + + //! Stack of all _active_ sections in the _current_ test case + std::vector m_sectionStack; + }; + +} // end namespace Catch + +#endif // CATCH_REPORTER_STREAMING_BASE_HPP_INCLUDED + +#include + +namespace Catch { + + class AutomakeReporter final : public StreamingReporterBase { + public: + // GCC5 compat: we cannot use inherited constructor, because it + // doesn't implement backport of P0136 + AutomakeReporter(ReporterConfig &&_config) + : StreamingReporterBase(CATCH_MOVE(_config)) { + m_preferences.shouldReportAllAssertionStarts = false; + } + + ~AutomakeReporter() override; + + static std::string getDescription() { + using namespace std::string_literals; + return "Reports test results in the format of Automake .trs files"s; + } + + void testCaseEnded(TestCaseStats const &_testCaseStats) override; + void skipTest(TestCaseInfo const &testInfo) override; + }; + +} // end namespace Catch + +#endif // CATCH_REPORTER_AUTOMAKE_HPP_INCLUDED + +#ifndef CATCH_REPORTER_COMPACT_HPP_INCLUDED +#define CATCH_REPORTER_COMPACT_HPP_INCLUDED + +namespace Catch { + + class CompactReporter final : public StreamingReporterBase { + public: + CompactReporter(ReporterConfig &&_config) + : StreamingReporterBase(CATCH_MOVE(_config)) { + m_preferences.shouldReportAllAssertionStarts = false; + } + + ~CompactReporter() override; + + static std::string getDescription(); + + void noMatchingTestCases(StringRef unmatchedSpec) override; + + void testRunStarting(TestRunInfo const &_testInfo) override; + + void assertionEnded(AssertionStats const &_assertionStats) override; + + void sectionEnded(SectionStats const &_sectionStats) override; + + void testRunEnded(TestRunStats const &_testRunStats) override; + }; + +} // end namespace Catch + +#endif // CATCH_REPORTER_COMPACT_HPP_INCLUDED + +#ifndef CATCH_REPORTER_CONSOLE_HPP_INCLUDED +#define CATCH_REPORTER_CONSOLE_HPP_INCLUDED + +namespace Catch { + // Fwd decls + class TablePrinter; + + class ConsoleReporter final : public StreamingReporterBase { + Detail::unique_ptr m_tablePrinter; + + public: + ConsoleReporter(ReporterConfig &&config); + ~ConsoleReporter() override; + static std::string getDescription(); + + void noMatchingTestCases(StringRef unmatchedSpec) override; + void reportInvalidTestSpec(StringRef arg) override; + + void assertionEnded(AssertionStats const &_assertionStats) override; + + void sectionStarting(SectionInfo const &_sectionInfo) override; + void sectionEnded(SectionStats const &_sectionStats) override; + + void benchmarkPreparing(StringRef name) override; + void benchmarkStarting(BenchmarkInfo const &info) override; + void benchmarkEnded(BenchmarkStats<> const &stats) override; + void benchmarkFailed(StringRef error) override; + + void testCaseEnded(TestCaseStats const &_testCaseStats) override; + void testRunEnded(TestRunStats const &_testRunStats) override; + void testRunStarting(TestRunInfo const &_testRunInfo) override; + + private: + void lazyPrint(); + + void lazyPrintWithoutClosingBenchmarkTable(); + void lazyPrintRunInfo(); + void printTestCaseAndSectionHeader(); + + void printClosedHeader(std::string const &_name); + void printOpenHeader(std::string const &_name); + + // if string has a : in first line will set indent to follow it on + // subsequent lines + void printHeaderString(std::string const &_string, std::size_t indent = 0); + + void printTotalsDivider(Totals const &totals); + + bool m_headerPrinted = false; + bool m_testRunInfoPrinted = false; + }; + +} // end namespace Catch + +#endif // CATCH_REPORTER_CONSOLE_HPP_INCLUDED + +#ifndef CATCH_REPORTER_CUMULATIVE_BASE_HPP_INCLUDED +#define CATCH_REPORTER_CUMULATIVE_BASE_HPP_INCLUDED + +#include +#include + +namespace Catch { + + namespace Detail { + + //! Represents either an assertion or a benchmark result to be handled by cumulative reporter later + class AssertionOrBenchmarkResult { + // This should really be a variant, but this is much faster + // to write and the data layout here is already terrible + // enough that we do not have to care about the object size. + Optional m_assertion; + Optional> m_benchmark; + + public: + AssertionOrBenchmarkResult(AssertionStats const &assertion); + AssertionOrBenchmarkResult(BenchmarkStats<> const &benchmark); + + bool isAssertion() const; + bool isBenchmark() const; + + AssertionStats const &asAssertion() const; + BenchmarkStats<> const &asBenchmark() const; + }; + } // namespace Detail + + /** + * Utility base for reporters that need to handle all results at once + * + * It stores tree of all test cases, sections and assertions, and after the + * test run is finished, calls into `testRunEndedCumulative` to pass the + * control to the deriving class. + * + * If you are deriving from this class and override any testing related + * member functions, you should first call into the base's implementation to + * avoid breaking the tree construction. + * + * Due to the way this base functions, it has to expand assertions up-front, + * even if they are later unused (e.g. because the deriving reporter does + * not report successful assertions, or because the deriving reporter does + * not use assertion expansion at all). Derived classes can use two + * customization points, `m_shouldStoreSuccesfulAssertions` and + * `m_shouldStoreFailedAssertions`, to disable the expansion and gain extra + * performance. **Accessing the assertion expansions if it wasn't stored is + * UB.** + */ + class CumulativeReporterBase : public ReporterBase { + public: + template + struct Node { + explicit Node(T const &_value) + : value(_value) {} + + using ChildNodes = std::vector>; + T value; + ChildNodes children; + }; + struct SectionNode { + explicit SectionNode(SectionStats const &_stats) + : stats(_stats) {} + + bool operator==(SectionNode const &other) const { + return stats.sectionInfo.lineInfo == other.stats.sectionInfo.lineInfo; + } + + bool hasAnyAssertions() const; + + SectionStats stats; + std::vector> childSections; + std::vector assertionsAndBenchmarks; + std::string stdOut; + std::string stdErr; + }; + + using TestCaseNode = Node; + using TestRunNode = Node; + + // GCC5 compat: we cannot use inherited constructor, because it + // doesn't implement backport of P0136 + CumulativeReporterBase(ReporterConfig &&_config) + : ReporterBase(CATCH_MOVE(_config)) {} + ~CumulativeReporterBase() override; + + void benchmarkPreparing(StringRef) override {} + void benchmarkStarting(BenchmarkInfo const &) override {} + void benchmarkEnded(BenchmarkStats<> const &benchmarkStats) override; + void benchmarkFailed(StringRef) override {} + + void noMatchingTestCases(StringRef) override {} + void reportInvalidTestSpec(StringRef) override {} + void fatalErrorEncountered(StringRef /*error*/) override {} + + void testRunStarting(TestRunInfo const &) override {} + + void testCaseStarting(TestCaseInfo const &) override {} + void testCasePartialStarting(TestCaseInfo const &, uint64_t) override {} + void sectionStarting(SectionInfo const §ionInfo) override; + + void assertionStarting(AssertionInfo const &) override {} + + void assertionEnded(AssertionStats const &assertionStats) override; + void sectionEnded(SectionStats const §ionStats) override; + void testCasePartialEnded(TestCaseStats const &, uint64_t) override {} + void testCaseEnded(TestCaseStats const &testCaseStats) override; + void testRunEnded(TestRunStats const &testRunStats) override; + //! Customization point: called after last test finishes (testRunEnded has been handled) + virtual void testRunEndedCumulative() = 0; + + void skipTest(TestCaseInfo const &) override {} + + protected: + //! Should the cumulative base store the assertion expansion for successful assertions? + bool m_shouldStoreSuccesfulAssertions = true; + //! Should the cumulative base store the assertion expansion for failed assertions? + bool m_shouldStoreFailedAssertions = true; + + // We need lazy construction here. We should probably refactor it + // later, after the events are redone. + //! The root node of the test run tree. + Detail::unique_ptr m_testRun; + + private: + // Note: We rely on pointer identity being stable, which is why + // we store pointers to the nodes rather than the values. + std::vector> m_testCases; + // Root section of the _current_ test case + Detail::unique_ptr m_rootSection; + // Deepest section of the _current_ test case + SectionNode *m_deepestSection = nullptr; + // Stack of _active_ sections in the _current_ test case + std::vector m_sectionStack; + }; + +} // end namespace Catch + +#endif // CATCH_REPORTER_CUMULATIVE_BASE_HPP_INCLUDED + +#ifndef CATCH_REPORTER_EVENT_LISTENER_HPP_INCLUDED +#define CATCH_REPORTER_EVENT_LISTENER_HPP_INCLUDED + +namespace Catch { + + /** + * Base class to simplify implementing listeners. + * + * Provides empty default implementation for all IEventListener member + * functions, so that a listener implementation can pick which + * member functions it actually cares about. + */ + class EventListenerBase : public IEventListener { + public: + using IEventListener::IEventListener; + + void reportInvalidTestSpec(StringRef unmatchedSpec) override; + void fatalErrorEncountered(StringRef error) override; + + void benchmarkPreparing(StringRef name) override; + void benchmarkStarting(BenchmarkInfo const &benchmarkInfo) override; + void benchmarkEnded(BenchmarkStats<> const &benchmarkStats) override; + void benchmarkFailed(StringRef error) override; + + void assertionStarting(AssertionInfo const &assertionInfo) override; + void assertionEnded(AssertionStats const &assertionStats) override; + + void listReporters( + std::vector const &descriptions) override; + void listListeners( + std::vector const &descriptions) override; + void listTests(std::vector const &tests) override; + void listTags(std::vector const &tagInfos) override; + + void noMatchingTestCases(StringRef unmatchedSpec) override; + void testRunStarting(TestRunInfo const &testRunInfo) override; + void testCaseStarting(TestCaseInfo const &testInfo) override; + void testCasePartialStarting(TestCaseInfo const &testInfo, + uint64_t partNumber) override; + void sectionStarting(SectionInfo const §ionInfo) override; + void sectionEnded(SectionStats const §ionStats) override; + void testCasePartialEnded(TestCaseStats const &testCaseStats, + uint64_t partNumber) override; + void testCaseEnded(TestCaseStats const &testCaseStats) override; + void testRunEnded(TestRunStats const &testRunStats) override; + void skipTest(TestCaseInfo const &testInfo) override; + }; + +} // end namespace Catch + +#endif // CATCH_REPORTER_EVENT_LISTENER_HPP_INCLUDED + +#ifndef CATCH_REPORTER_HELPERS_HPP_INCLUDED +#define CATCH_REPORTER_HELPERS_HPP_INCLUDED + +#include +#include +#include + +namespace Catch { + + class IConfig; + class TestCaseHandle; + class ColourImpl; + + // Returns double formatted as %.3f (format expected on output) + std::string getFormattedDuration(double duration); + + //! Should the reporter show duration of test given current configuration? + bool shouldShowDuration(IConfig const &config, double duration); + + std::string serializeFilters(std::vector const &filters); + + struct lineOfChars { + char c; + constexpr lineOfChars(char c_) + : c(c_) {} + + friend std::ostream &operator<<(std::ostream &out, lineOfChars value); + }; + + /** + * Lists reporter descriptions to the provided stream in user-friendly + * format + * + * Used as the default listing implementation by the first party reporter + * bases. The output should be backwards compatible with the output of + * Catch2 v2 binaries. + */ + void + defaultListReporters(std::ostream &out, + std::vector const &descriptions, + Verbosity verbosity); + + /** + * Lists listeners descriptions to the provided stream in user-friendly + * format + */ + void defaultListListeners(std::ostream &out, + std::vector const &descriptions, + Verbosity verbosity); + + /** + * Lists tag information to the provided stream in user-friendly format + * + * Used as the default listing implementation by the first party reporter + * bases. The output should be backwards compatible with the output of + * Catch2 v2 binaries. + */ + void defaultListTags(std::ostream &out, + std::vector const &tags, + bool isFiltered, + Verbosity verbosity); + + /** + * Lists test case information to the provided stream in user-friendly + * format + * + * Used as the default listing implementation by the first party reporter + * bases. The output is backwards compatible with the output of Catch2 + * v2 binaries, and also supports the format specific to the old + * `--list-test-names-only` option, for people who used it in integrations. + */ + void defaultListTests(std::ostream &out, + ColourImpl *streamColour, + std::vector const &tests, + bool isFiltered, + Verbosity verbosity); + + /** + * Prints test run totals to the provided stream in user-friendly format + * + * Used by the console and compact reporters. + */ + void printTestRunTotals(std::ostream &stream, + ColourImpl &streamColour, + Totals const &totals); + +} // end namespace Catch + +#endif // CATCH_REPORTER_HELPERS_HPP_INCLUDED + +#ifndef CATCH_REPORTER_JSON_HPP_INCLUDED +#define CATCH_REPORTER_JSON_HPP_INCLUDED + +#include + +namespace Catch { + class JsonReporter : public StreamingReporterBase { + public: + JsonReporter(ReporterConfig &&config); + + ~JsonReporter() override; + + static std::string getDescription(); + + public: // StreamingReporterBase + void testRunStarting(TestRunInfo const &runInfo) override; + void testRunEnded(TestRunStats const &runStats) override; + + void testCaseStarting(TestCaseInfo const &tcInfo) override; + void testCaseEnded(TestCaseStats const &tcStats) override; + + void testCasePartialStarting(TestCaseInfo const &tcInfo, + uint64_t index) override; + void testCasePartialEnded(TestCaseStats const &tcStats, + uint64_t index) override; + + void sectionStarting(SectionInfo const §ionInfo) override; + void sectionEnded(SectionStats const §ionStats) override; + + void assertionEnded(AssertionStats const &assertionStats) override; + + // void testRunEndedCumulative() override; + + void benchmarkPreparing(StringRef name) override; + void benchmarkStarting(BenchmarkInfo const &) override; + void benchmarkEnded(BenchmarkStats<> const &) override; + void benchmarkFailed(StringRef error) override; + + void listReporters( + std::vector const &descriptions) override; + void listListeners( + std::vector const &descriptions) override; + void listTests(std::vector const &tests) override; + void listTags(std::vector const &tags) override; + + private: + Timer m_testCaseTimer; + enum class Writer { + Object, + Array + }; + + JsonArrayWriter &startArray(); + JsonArrayWriter &startArray(StringRef key); + + JsonObjectWriter &startObject(); + JsonObjectWriter &startObject(StringRef key); + + void endObject(); + void endArray(); + + bool isInside(Writer writer); + + void startListing(); + void endListing(); + + // Invariant: + // When m_writers is not empty and its top element is + // - Writer::Object, then m_objectWriters is not be empty + // - Writer::Array, then m_arrayWriters shall not be empty + std::stack m_objectWriters{}; + std::stack m_arrayWriters{}; + std::stack m_writers{}; + + bool m_startedListing = false; + + // std::size_t m_sectionDepth = 0; + // std::size_t m_sectionStarted = 0; + }; +} // namespace Catch + +#endif // CATCH_REPORTER_JSON_HPP_INCLUDED + +#ifndef CATCH_REPORTER_JUNIT_HPP_INCLUDED +#define CATCH_REPORTER_JUNIT_HPP_INCLUDED + +namespace Catch { + + class JunitReporter final : public CumulativeReporterBase { + public: + JunitReporter(ReporterConfig &&_config); + + static std::string getDescription(); + + void testRunStarting(TestRunInfo const &runInfo) override; + + void testCaseStarting(TestCaseInfo const &testCaseInfo) override; + void assertionEnded(AssertionStats const &assertionStats) override; + + void testCaseEnded(TestCaseStats const &testCaseStats) override; + + void testRunEndedCumulative() override; + + private: + void writeRun(TestRunNode const &testRunNode, double suiteTime); + + void writeTestCase(TestCaseNode const &testCaseNode); + + void writeSection(std::string const &className, + std::string const &rootName, + SectionNode const §ionNode, + bool testOkToFail); + + void writeAssertions(SectionNode const §ionNode); + bool writeAssertion(AssertionStats const &stats); + + XmlWriter xml; + Timer suiteTimer; + std::string stdOutForSuite; + std::string stdErrForSuite; + unsigned int unexpectedExceptions = 0; + bool m_okToFail = false; + }; + +} // end namespace Catch + +#endif // CATCH_REPORTER_JUNIT_HPP_INCLUDED + +#ifndef CATCH_REPORTER_MULTI_HPP_INCLUDED +#define CATCH_REPORTER_MULTI_HPP_INCLUDED + +namespace Catch { + + class MultiReporter final : public IEventListener { + /* + * Stores all added reporters and listeners + * + * All Listeners are stored before all reporters, and individual + * listeners/reporters are stored in order of insertion. + */ + std::vector m_reporterLikes; + bool m_haveNoncapturingReporters = false; + + // Keep track of how many listeners we have already inserted, + // so that we can insert them into the main vector at the right place + size_t m_insertedListeners = 0; + + void updatePreferences(IEventListener const &reporterish); + + public: + MultiReporter(IConfig const *config) + : IEventListener(config) { + m_preferences.shouldReportAllAssertionStarts = false; + } + + using IEventListener::IEventListener; + + void addListener(IEventListenerPtr &&listener); + void addReporter(IEventListenerPtr &&reporter); + + public: // IEventListener + void noMatchingTestCases(StringRef unmatchedSpec) override; + void fatalErrorEncountered(StringRef error) override; + void reportInvalidTestSpec(StringRef arg) override; + + void benchmarkPreparing(StringRef name) override; + void benchmarkStarting(BenchmarkInfo const &benchmarkInfo) override; + void benchmarkEnded(BenchmarkStats<> const &benchmarkStats) override; + void benchmarkFailed(StringRef error) override; + + void testRunStarting(TestRunInfo const &testRunInfo) override; + void testCaseStarting(TestCaseInfo const &testInfo) override; + void testCasePartialStarting(TestCaseInfo const &testInfo, uint64_t partNumber) override; + void sectionStarting(SectionInfo const §ionInfo) override; + void assertionStarting(AssertionInfo const &assertionInfo) override; + + void assertionEnded(AssertionStats const &assertionStats) override; + void sectionEnded(SectionStats const §ionStats) override; + void testCasePartialEnded(TestCaseStats const &testStats, uint64_t partNumber) override; + void testCaseEnded(TestCaseStats const &testCaseStats) override; + void testRunEnded(TestRunStats const &testRunStats) override; + + void skipTest(TestCaseInfo const &testInfo) override; + + void listReporters(std::vector const &descriptions) override; + void listListeners(std::vector const &descriptions) override; + void listTests(std::vector const &tests) override; + void listTags(std::vector const &tags) override; + }; + +} // end namespace Catch + +#endif // CATCH_REPORTER_MULTI_HPP_INCLUDED + +#ifndef CATCH_REPORTER_REGISTRARS_HPP_INCLUDED +#define CATCH_REPORTER_REGISTRARS_HPP_INCLUDED + +#include + +namespace Catch { + + namespace Detail { + + template + struct has_description : std::false_type {}; + + template + struct has_description< + T, + void_t> + : std::true_type {}; + + //! Indirection for reporter registration, so that the error handling is + //! independent on the reporter's concrete type + void registerReporterImpl(std::string const &name, + IReporterFactoryPtr reporterPtr); + //! Actually registers the factory, independent on listener's concrete type + void registerListenerImpl(Detail::unique_ptr listenerFactory); + } // namespace Detail + + class IEventListener; + using IEventListenerPtr = Detail::unique_ptr; + + template + class ReporterFactory : public IReporterFactory { + IEventListenerPtr create(ReporterConfig &&config) const override { + return Detail::make_unique(CATCH_MOVE(config)); + } + + std::string getDescription() const override { + return T::getDescription(); + } + }; + + template + class ReporterRegistrar { + public: + explicit ReporterRegistrar(std::string const &name) { + registerReporterImpl(name, + Detail::make_unique>()); + } + }; + + template + class ListenerRegistrar { + class TypedListenerFactory : public EventListenerFactory { + StringRef m_listenerName; + + std::string getDescriptionImpl(std::true_type) const { + return T::getDescription(); + } + + std::string getDescriptionImpl(std::false_type) const { + return "(No description provided)"; + } + + public: + TypedListenerFactory(StringRef listenerName) + : m_listenerName(listenerName) {} + + IEventListenerPtr create(IConfig const *config) const override { + return Detail::make_unique(config); + } + + StringRef getName() const override { + return m_listenerName; + } + + std::string getDescription() const override { + return getDescriptionImpl(Detail::has_description{}); + } + }; + + public: + ListenerRegistrar(StringRef listenerName) { + registerListenerImpl(Detail::make_unique(listenerName)); + } + }; +} // namespace Catch + +#if !defined(CATCH_CONFIG_DISABLE) + +#define CATCH_REGISTER_REPORTER(name, reporterType) \ + CATCH_INTERNAL_START_WARNINGS_SUPPRESSION \ + CATCH_INTERNAL_SUPPRESS_GLOBALS_WARNINGS \ + namespace { \ + const Catch::ReporterRegistrar \ + INTERNAL_CATCH_UNIQUE_NAME(catch_internal_RegistrarFor)( \ + name); \ + } \ + CATCH_INTERNAL_STOP_WARNINGS_SUPPRESSION + +#define CATCH_REGISTER_LISTENER(listenerType) \ + CATCH_INTERNAL_START_WARNINGS_SUPPRESSION \ + CATCH_INTERNAL_SUPPRESS_GLOBALS_WARNINGS \ + namespace { \ + const Catch::ListenerRegistrar \ + INTERNAL_CATCH_UNIQUE_NAME(catch_internal_RegistrarFor)( \ + #listenerType##_catch_sr); \ + } \ + CATCH_INTERNAL_STOP_WARNINGS_SUPPRESSION + +#else // CATCH_CONFIG_DISABLE + +#define CATCH_REGISTER_REPORTER(name, reporterType) +#define CATCH_REGISTER_LISTENER(listenerType) + +#endif // CATCH_CONFIG_DISABLE + +#endif // CATCH_REPORTER_REGISTRARS_HPP_INCLUDED + +#ifndef CATCH_REPORTER_SONARQUBE_HPP_INCLUDED +#define CATCH_REPORTER_SONARQUBE_HPP_INCLUDED + +namespace Catch { + + class SonarQubeReporter final : public CumulativeReporterBase { + public: + SonarQubeReporter(ReporterConfig &&config) + : CumulativeReporterBase(CATCH_MOVE(config)) + , xml(m_stream) { + m_preferences.shouldRedirectStdOut = true; + m_preferences.shouldReportAllAssertions = false; + m_preferences.shouldReportAllAssertionStarts = false; + m_shouldStoreSuccesfulAssertions = false; + } + + static std::string getDescription() { + using namespace std::string_literals; + return "Reports test results in the Generic Test Data SonarQube XML format"s; + } + + void testRunStarting(TestRunInfo const &testRunInfo) override; + + void testRunEndedCumulative() override { + writeRun(*m_testRun); + xml.endElement(); + } + + void writeRun(TestRunNode const &runNode); + + void writeTestFile(StringRef filename, std::vector const &testCaseNodes); + + void writeTestCase(TestCaseNode const &testCaseNode); + + void writeSection(std::string const &rootName, SectionNode const §ionNode, bool okToFail); + + void writeAssertions(SectionNode const §ionNode, bool okToFail); + + void writeAssertion(AssertionStats const &stats, bool okToFail); + + private: + XmlWriter xml; + }; + +} // end namespace Catch + +#endif // CATCH_REPORTER_SONARQUBE_HPP_INCLUDED + +#ifndef CATCH_REPORTER_TAP_HPP_INCLUDED +#define CATCH_REPORTER_TAP_HPP_INCLUDED + +namespace Catch { + + class TAPReporter final : public StreamingReporterBase { + public: + TAPReporter(ReporterConfig &&config) + : StreamingReporterBase(CATCH_MOVE(config)) { + m_preferences.shouldReportAllAssertions = true; + m_preferences.shouldReportAllAssertionStarts = false; + } + + static std::string getDescription() { + using namespace std::string_literals; + return "Reports test results in TAP format, suitable for test harnesses"s; + } + + void testRunStarting(TestRunInfo const &testInfo) override; + + void noMatchingTestCases(StringRef unmatchedSpec) override; + + void assertionEnded(AssertionStats const &_assertionStats) override; + + void testRunEnded(TestRunStats const &_testRunStats) override; + + private: + std::size_t counter = 0; + }; + +} // end namespace Catch + +#endif // CATCH_REPORTER_TAP_HPP_INCLUDED + +#ifndef CATCH_REPORTER_TEAMCITY_HPP_INCLUDED +#define CATCH_REPORTER_TEAMCITY_HPP_INCLUDED + +#include + +#ifdef __clang__ +#pragma clang diagnostic push +#pragma clang diagnostic ignored "-Wpadded" +#endif + +namespace Catch { + + class TeamCityReporter final : public StreamingReporterBase { + public: + TeamCityReporter(ReporterConfig &&_config) + : StreamingReporterBase(CATCH_MOVE(_config)) { + m_preferences.shouldRedirectStdOut = true; + m_preferences.shouldReportAllAssertionStarts = false; + } + + ~TeamCityReporter() override; + + static std::string getDescription() { + using namespace std::string_literals; + return "Reports test results as TeamCity service messages"s; + } + + void testRunStarting(TestRunInfo const &runInfo) override; + void testRunEnded(TestRunStats const &runStats) override; + + void assertionEnded(AssertionStats const &assertionStats) override; + + void sectionStarting(SectionInfo const §ionInfo) override { + m_headerPrintedForThisSection = false; + StreamingReporterBase::sectionStarting(sectionInfo); + } + + void testCaseStarting(TestCaseInfo const &testInfo) override; + + void testCaseEnded(TestCaseStats const &testCaseStats) override; + + private: + void printSectionHeader(std::ostream &os); + + bool m_headerPrintedForThisSection = false; + Timer m_testTimer; + }; + +} // end namespace Catch + +#ifdef __clang__ +#pragma clang diagnostic pop +#endif + +#endif // CATCH_REPORTER_TEAMCITY_HPP_INCLUDED + +#ifndef CATCH_REPORTER_XML_HPP_INCLUDED +#define CATCH_REPORTER_XML_HPP_INCLUDED + +namespace Catch { + class XmlReporter : public StreamingReporterBase { + public: + XmlReporter(ReporterConfig &&_config); + + ~XmlReporter() override; + + static std::string getDescription(); + + virtual std::string getStylesheetRef() const; + + void writeSourceInfo(SourceLineInfo const &sourceInfo); + + public: // StreamingReporterBase + void testRunStarting(TestRunInfo const &testInfo) override; + + void testCaseStarting(TestCaseInfo const &testInfo) override; + + void sectionStarting(SectionInfo const §ionInfo) override; + + void assertionEnded(AssertionStats const &assertionStats) override; + + void sectionEnded(SectionStats const §ionStats) override; + + void testCaseEnded(TestCaseStats const &testCaseStats) override; + + void testRunEnded(TestRunStats const &testRunStats) override; + + void benchmarkPreparing(StringRef name) override; + void benchmarkStarting(BenchmarkInfo const &) override; + void benchmarkEnded(BenchmarkStats<> const &) override; + void benchmarkFailed(StringRef error) override; + + void listReporters(std::vector const &descriptions) override; + void listListeners(std::vector const &descriptions) override; + void listTests(std::vector const &tests) override; + void listTags(std::vector const &tags) override; + + private: + Timer m_testCaseTimer; + XmlWriter m_xml; + int m_sectionDepth = 0; + }; + +} // end namespace Catch + +#endif // CATCH_REPORTER_XML_HPP_INCLUDED + +#endif // CATCH_REPORTERS_ALL_HPP_INCLUDED + +#endif // CATCH_ALL_HPP_INCLUDED +#endif // CATCH_AMALGAMATED_HPP_INCLUDED diff --git a/unittests/class_cheatsheet.chai b/unittests/class_cheatsheet.chai new file mode 100644 index 000000000..476f1c491 --- /dev/null +++ b/unittests/class_cheatsheet.chai @@ -0,0 +1,103 @@ +// Test for class features documented in the cheatsheet + +// --- Class definition (preferred block syntax) --- +class Rectangle { + var width + var height + def Rectangle(w, h) { this.width = w; this.height = h; } + def Rectangle() { this.width = 0; this.height = 0; } + def area() { this.width * this.height; } +} + +auto r = Rectangle(3, 4) +assert_equal(12, r.area()) + +// --- Default constructor --- +auto r2 = Rectangle() +assert_equal(0, r2.area()) + +// --- Alternative open syntax --- +attr Circle::radius +def Circle::Circle(r) { this.radius = r; } +def Circle::circumference() { 2.0 * 3.14159 * this.radius; } + +auto c = Circle(5.0) +assert_equal(5.0, c.radius) + +// --- attr, auto, var all work for attributes --- +class AttrTest { + attr a + auto b + var c + def AttrTest() { this.a = 1; this.b = 2; this.c = 3; } +} + +auto at = AttrTest() +assert_equal(1, at.a) +assert_equal(2, at.b) +assert_equal(3, at.c) + +// --- Constructor guards --- +class Clamped { + var value + def Clamped(x) : x >= 0 { this.value = x; } + def Clamped(x) { this.value = 0; } +} + +assert_equal(5, Clamped(5).value) +assert_equal(0, Clamped(-3).value) + +// --- Method guards --- +class Abs { + var x + def Abs(v) { this.x = v; } + def get() : this.x >= 0 { this.x; } + def get() { -this.x; } +} + +assert_equal(5, Abs(5).get()) +assert_equal(3, Abs(-3).get()) + +// --- Operator overloading --- +class Vec2 { + var x + var y + def Vec2(x, y) { this.x = x; this.y = y; } + def `+`(other) { Vec2(this.x + other.x, this.y + other.y); } +} + +auto v = Vec2(1, 2) + Vec2(3, 4) +assert_equal(4, v.x) +assert_equal(6, v.y) + +// --- Cloning objects --- +auto r3 = Rectangle(10, 20) +auto r4 = clone(r3) +r4.width = 99 +assert_equal(10, r3.width) +assert_equal(99, r4.width) + +// --- Dynamic attributes --- +auto r5 = Rectangle(1, 1) +r5.color = "red" +assert_equal("red", r5.color) + +// --- Explicit mode --- +class Strict { + var x + def Strict() { + this.x = 0 + this.set_explicit(true) + } +} + +auto s = Strict() +assert_equal(0, s.x) +assert_equal(true, s.is_explicit()) + +try { + s.y = 10 + assert_equal(true, false) // should not reach here +} catch(e) { + // expected: cannot add dynamic attribute in explicit mode +} diff --git a/unittests/class_inheritance.chai b/unittests/class_inheritance.chai new file mode 100644 index 000000000..cd27fa8e1 --- /dev/null +++ b/unittests/class_inheritance.chai @@ -0,0 +1,137 @@ + +class Base +{ + attr x + + def Base() + { + this.x = 10 + } + + def do_something() + { + return this.x * 2 + } +} + +class Derived : Base +{ + attr y + + def Derived() + { + this.x = 10 + this.y = 20 + } + + def do_other() + { + return this.y * 3 + } +} + +// Test basic inheritance - derived can call base methods +auto d = Derived() +assert_equal(10, d.x) +assert_equal(20, d.y) +assert_equal(20, d.do_something()) +assert_equal(60, d.do_other()) + +// Test base class still works independently +auto b = Base() +assert_equal(10, b.x) +assert_equal(20, b.do_something()) + +// Test method override +class Derived2 : Base +{ + def Derived2() + { + this.x = 5 + } + + def do_something() + { + return this.x * 100 + } +} + +auto d2 = Derived2() +assert_equal(500, d2.do_something()) + +// Test passing a derived object to an untyped free function +def call_do_something_untyped(obj) { + return obj.do_something() +} + +auto d3 = Derived() +assert_equal(20, call_do_something_untyped(d3)) +assert_equal(20, call_do_something_untyped(Base())) + +// Test typed functions: parameter declared as Base, accepts derived objects +def call_do_something(Base obj) { + return obj.do_something() +} + +assert_equal(20, call_do_something(Base())) +assert_equal(20, call_do_something(d3)) + +// Test typed function accessing base attributes on a derived object +def get_x(Base obj) { + return obj.x +} + +assert_equal(10, get_x(d3)) +assert_equal(10, get_x(Base())) + +// Test polymorphic dispatch through typed function: derived override is called +auto d4 = Derived2() +assert_equal(500, call_do_something(d4)) + +// Test mixing base and derived in a container, calling base methods +var objects = [Base(), Derived(), Derived2()] +assert_equal(20, objects[0].do_something()) +assert_equal(20, objects[1].do_something()) +assert_equal(500, objects[2].do_something()) + +// Test that derived objects still report correct type +auto d5 = Derived() +assert_true(d5.is_type("Derived")) + +// Test multi-level inheritance +class GrandChild : Derived +{ + attr z + + def GrandChild() + { + this.x = 1 + this.y = 2 + this.z = 3 + } + + def do_grandchild() + { + return this.z * 4 + } +} + +auto gc = GrandChild() +assert_equal(1, gc.x) +assert_equal(2, gc.y) +assert_equal(3, gc.z) +assert_equal(2, gc.do_something()) // Base method +assert_equal(6, gc.do_other()) // Derived method +assert_equal(12, gc.do_grandchild()) // Own method + +// Test passing grandchild to typed Base function (multi-level inheritance) +assert_equal(2, call_do_something(gc)) +assert_equal(1, get_x(gc)) + +// Test typed function expecting mid-level type +def call_do_other(Derived obj) { + return obj.do_other() +} + +assert_equal(6, call_do_other(gc)) +assert_equal(60, call_do_other(Derived())) diff --git a/unittests/class_missing_line_separator.chai b/unittests/class_missing_line_separator.chai new file mode 100644 index 000000000..3b7c107ff --- /dev/null +++ b/unittests/class_missing_line_separator.chai @@ -0,0 +1,23 @@ + +// Test that two var declarations on the same line in a class body +// are rejected with a "missing line separator" error (issue #592) + +try { + eval("class Foo { var x var y }") + assert_true(false) +} catch (eval_error) { +} + +// Also verify that properly separated declarations still work +class Bar { + var x + var y + def Bar() { + this.x = 1 + this.y = 2 + } +} + +auto b = Bar() +assert_equal(1, b.x) +assert_equal(2, b.y) diff --git a/unittests/compiled_tests.cpp b/unittests/compiled_tests.cpp index f8873df8f..c8a7da40a 100644 --- a/unittests/compiled_tests.cpp +++ b/unittests/compiled_tests.cpp @@ -22,12 +22,10 @@ #include "../static_libs/chaiscript_parser.hpp" #include "../static_libs/chaiscript_stdlib.hpp" -#define CATCH_CONFIG_MAIN +#include "catch_amalgamated.hpp" #include -#include "catch.hpp" - // lambda_tests TEST_CASE("C++11 Lambdas Can Be Registered") { // We cannot deduce the type of a lambda expression, you must either wrap it @@ -190,7 +188,7 @@ TEST_CASE("Throw int or double") { chai.eval("throw(1.0)", chaiscript::exception_specification()); REQUIRE(false); } catch (const double e) { - CHECK(e == Approx(1.0)); + CHECK(e == Catch::Approx(1.0)); } } @@ -356,6 +354,33 @@ TEST_CASE("Functor cast") { CHECK(d == 3 * 6); } +namespace { + int shared_ptr_callback_observed_value = 0; + + void shared_ptr_callback_accept(const std::shared_ptr &ptr) { + shared_ptr_callback_observed_value = ptr ? *ptr : 0; + } + + void shared_ptr_callback_call(const std::function &)> &func) { + func(std::make_shared(42)); + } +} // namespace + +// Regression for https://github.com/ChaiScript/ChaiScript/issues/493 - shared_ptr +// arguments could not be round-tripped from C++ through a std::function-wrapped +// ChaiScript callback back into a C++ function expecting a shared_ptr. +TEST_CASE("shared_ptr passed through std::function callback") { + chaiscript::ChaiScript_Basic chai(create_chaiscript_stdlib(), create_chaiscript_parser()); + + shared_ptr_callback_observed_value = 0; + + chai.add(chaiscript::fun(&shared_ptr_callback_accept), "accept"); + chai.add(chaiscript::fun(&shared_ptr_callback_call), "call"); + + CHECK_NOTHROW(chai.eval("call(accept)")); + CHECK(shared_ptr_callback_observed_value == 42); +} + TEST_CASE("Non-ASCII characters in the middle of string") { chaiscript::ChaiScript_Basic chai(create_chaiscript_stdlib(), create_chaiscript_parser()); CHECK_THROWS_AS(chai.eval("prin\xeft \"Hello World\""), chaiscript::exception::eval_error); @@ -412,6 +437,44 @@ TEST_CASE("Set and restore chai state") { CHECK_THROWS_AS(chai.eval("i"), chaiscript::exception::eval_error); } +TEST_CASE("Get function objects from public API") { + chaiscript::ChaiScript_Basic chai(create_chaiscript_stdlib(), create_chaiscript_parser()); + + // Add a custom function + chai.add(chaiscript::fun(&set_state_test_myfun), "myfun"); + + // get_function_objects should be accessible from the public API + auto funcs = chai.get_function_objects(); + + // Our custom function should be in the map + CHECK(funcs.count("myfun") == 1); + + // Built-in functions should also be present + CHECK(funcs.count("to_string") == 1); + + // The function should be callable + CHECK(chai.eval("myfun()") == 2); +} + +TEST_CASE("Get scripting objects from public API") { + chaiscript::ChaiScript_Basic chai(create_chaiscript_stdlib(), create_chaiscript_parser()); + + // Define some variables in script + chai.eval("var x = 42"); + chai.eval("var name = \"hello\""); + + // get_scripting_objects should be accessible from the public API + auto objects = chai.get_scripting_objects(); + + // Our scripting variables should be in the map + CHECK(objects.count("x") == 1); + CHECK(objects.count("name") == 1); + + // Verify the values are correct + CHECK(chaiscript::boxed_cast(objects["x"]) == 42); + CHECK(chaiscript::boxed_cast(objects["name"]) == "hello"); +} + //// Short comparisons class Short_Comparison_Test { @@ -518,6 +581,80 @@ TEST_CASE("Utility_Test utility class wrapper") { chai.eval("t = Utility_Test();"); } +///// Issue 612: fun(&overloaded) should work for free and member function overloads + +namespace issue_612 { + std::string free_overload(int) { + return "int"; + } + std::string free_overload(double) { + return "double"; + } + std::string free_overload(const std::string &, bool) { + return "string,bool"; + } + + class Issue_612_Class { + public: + std::string member_overload(int) { return "int"; } + std::string member_overload(double) { return "double"; } + std::string const_member_overload(int) const { return "const_int"; } + std::string const_member_overload(double) const { return "const_double"; } + }; +} // namespace issue_612 + +TEST_CASE("Issue 612: fun for overloaded free functions") { + chaiscript::ChaiScript_Basic chai(create_chaiscript_stdlib(), create_chaiscript_parser()); + + chai.add(chaiscript::fun(&issue_612::free_overload), "free_overload"); + chai.add(chaiscript::fun(&issue_612::free_overload), "free_overload"); + chai.add(chaiscript::fun(&issue_612::free_overload), "free_overload"); + + CHECK(chai.eval("free_overload(1)") == "int"); + CHECK(chai.eval("free_overload(1.5)") == "double"); + CHECK(chai.eval("free_overload(\"hi\", true)") == "string,bool"); +} + +TEST_CASE("Issue 612: fun for overloaded member functions (pointer-to-member form)") { + chaiscript::ChaiScript_Basic chai(create_chaiscript_stdlib(), create_chaiscript_parser()); + + chai.add(chaiscript::user_type(), "Issue_612_Class"); + chai.add(chaiscript::constructor(), "Issue_612_Class"); + + chai.add(chaiscript::fun(&issue_612::Issue_612_Class::member_overload), + "member_overload"); + chai.add(chaiscript::fun(&issue_612::Issue_612_Class::member_overload), + "member_overload"); + chai.add(chaiscript::fun(&issue_612::Issue_612_Class::const_member_overload), + "const_member_overload"); + chai.add(chaiscript::fun(&issue_612::Issue_612_Class::const_member_overload), + "const_member_overload"); + + chai.eval("var t = Issue_612_Class();"); + CHECK(chai.eval("t.member_overload(1)") == "int"); + CHECK(chai.eval("t.member_overload(1.5)") == "double"); + CHECK(chai.eval("t.const_member_overload(1)") == "const_int"); + CHECK(chai.eval("t.const_member_overload(1.5)") == "const_double"); +} + +TEST_CASE("Issue 612: fun for overloaded member functions (function-type form)") { + chaiscript::ChaiScript_Basic chai(create_chaiscript_stdlib(), create_chaiscript_parser()); + + chai.add(chaiscript::user_type(), "Issue_612_Class"); + chai.add(chaiscript::constructor(), "Issue_612_Class"); + + chai.add(chaiscript::fun(&issue_612::Issue_612_Class::member_overload), "member_overload"); + chai.add(chaiscript::fun(&issue_612::Issue_612_Class::member_overload), "member_overload"); + chai.add(chaiscript::fun(&issue_612::Issue_612_Class::const_member_overload), "const_member_overload"); + chai.add(chaiscript::fun(&issue_612::Issue_612_Class::const_member_overload), "const_member_overload"); + + chai.eval("var t = Issue_612_Class();"); + CHECK(chai.eval("t.member_overload(1)") == "int"); + CHECK(chai.eval("t.member_overload(1.5)") == "double"); + CHECK(chai.eval("t.const_member_overload(1)") == "const_int"); + CHECK(chai.eval("t.const_member_overload(1.5)") == "const_double"); +} + enum Utility_Test_Numbers { ONE, TWO, @@ -566,6 +703,49 @@ TEST_CASE("Utility_Test utility class wrapper for enum") { CHECK_NOTHROW(chai.eval("var o = ONE; o = TWO")); } +// Issue #601: add_class for enums should work directly with ChaiScript reference +enum class Issue601_EnumClass { Apple, + Banana, + Pear }; + +TEST_CASE("Issue 601: add_class enum with ChaiScript reference directly") { + chaiscript::ChaiScript_Basic chai(create_chaiscript_stdlib(), create_chaiscript_parser()); + + // This should compile and work — previously it failed because the operator + // functions in chaiscript::bootstrap::operators hardcoded Module& as their + // first parameter instead of using a template parameter. + chaiscript::utility::add_class(chai, + "Issue601_EnumClass", + {{Issue601_EnumClass::Apple, "Apple"}, + {Issue601_EnumClass::Banana, "Banana"}, + {Issue601_EnumClass::Pear, "Pear"}}); + + CHECK(chai.eval("Apple == Apple")); + CHECK(chai.eval("Apple != Banana")); + CHECK_NOTHROW(chai.eval("var e = Apple; e = Pear")); + CHECK(chai.eval("Banana") == Issue601_EnumClass::Banana); +} + +// Also test non-scoped enum directly with ChaiScript reference +enum Issue601_PlainEnum { Issue601_Red = 0, + Issue601_Green = 1, + Issue601_Blue = 2 }; + +TEST_CASE("Issue 601: add_class plain enum with ChaiScript reference directly") { + chaiscript::ChaiScript_Basic chai(create_chaiscript_stdlib(), create_chaiscript_parser()); + + chaiscript::utility::add_class(chai, + "Issue601_PlainEnum", + {{Issue601_Red, "Red"}, + {Issue601_Green, "Green"}, + {Issue601_Blue, "Blue"}}); + + CHECK(chai.eval("Red == Red")); + CHECK(chai.eval("Red == 0")); + CHECK(chai.eval("Red != Green")); + CHECK_NOTHROW(chai.eval("var c = Red; c = Blue")); +} + ////// Object copy count test class Object_Copy_Count_Test { @@ -866,6 +1046,27 @@ TEST_CASE("Map conversions") { CHECK(c == 42); } +TEST_CASE("Pair conversions") { + chaiscript::ChaiScript_Basic chai(create_chaiscript_stdlib(), create_chaiscript_parser()); + chai.add(chaiscript::pair_conversion()); + chai.add(chaiscript::pair_conversion()); + + { + const auto p = chai.eval>(R"cs( + Pair("chai", "script"); + )cs"); + CHECK(p.first == std::string{"chai"}); + CHECK(p.second == "script"); + } + { + const auto p = chai.eval>(R"cs( + Pair(5, 3.14); + )cs"); + CHECK(p.first == 5); + CHECK(p.second == Catch::Approx(3.14)); + } +} + TEST_CASE("Parse floats with non-posix locale") { #ifdef CHAISCRIPT_MSVC std::setlocale(LC_ALL, "en-ZA"); @@ -874,7 +1075,7 @@ TEST_CASE("Parse floats with non-posix locale") { #endif chaiscript::ChaiScript_Basic chai(create_chaiscript_stdlib(), create_chaiscript_parser()); const double parsed = chai.eval("print(1.3); 1.3"); - CHECK(parsed == Approx(1.3)); + CHECK(parsed == Catch::Approx(1.3)); const std::string str = chai.eval("to_string(1.3)"); CHECK(str == "1.3"); } @@ -1032,7 +1233,7 @@ TEST_CASE("Use unique_ptr") { chaiscript::ChaiScript_Basic chai(create_chaiscript_stdlib(), create_chaiscript_parser()); chai.add(chaiscript::fun([](int &i) { ++i; }), "inci"); - chai.add(chaiscript::fun([](int i) { ++i; }), "copyi"); + chai.add(chaiscript::fun([]([[maybe_unused]] int i) { ++i; }), "copyi"); chai.add(chaiscript::fun([](int *i) { ++(*i); }), "derefi"); chai.add(chaiscript::fun([](const std::unique_ptr &i) { ++(*i); }), "constrefuniqptri"); chai.add(chaiscript::fun([](std::unique_ptr &i) { ++(*i); }), "refuniqptri"); @@ -1168,7 +1369,7 @@ TEST_CASE("Test reference member being registered") { double d; chai.add(chaiscript::var(Reference_MyClass(d)), "ref"); chai.eval("ref.x = 2.3"); - CHECK(d == Approx(2.3)); + CHECK(d == Catch::Approx(2.3)); } // starting with C++20 u8"" strings cannot be compared with std::string @@ -1283,3 +1484,894 @@ TEST_CASE("Test if non copyable/movable types can be registered") { chai.add(chaiscript::user_type(), "Nothing"); chai.add(chaiscript::constructor(), "Nothing"); } + +// Tests for issue #146: configuration to bypass registering built-in functions +// Tests through ChaiScript_Basic (library options passed explicitly to Std_Lib::library) + +TEST_CASE("ChaiScript_Basic No_Stdlib option disables all standard library functions") { + chaiscript::ChaiScript_Basic chai(chaiscript::Std_Lib::library({chaiscript::Library_Options::No_Stdlib}), + create_chaiscript_parser(), + {}, + {}, + {chaiscript::Options::No_Load_Modules, chaiscript::Options::No_External_Scripts}); + + CHECK_NOTHROW(chai.eval("var x = 5")); + CHECK_THROWS(chai.eval("print(\"hello\")")); + CHECK_THROWS(chai.eval("var v = Vector()")); + CHECK_THROWS(chai.eval("\"hello\".trim()")); + CHECK_THROWS(chai.eval("from_json(\"[1,2,3]\")")); +} + +TEST_CASE("ChaiScript_Basic No_IO option uses null handler by default") { + chaiscript::ChaiScript_Basic chai(chaiscript::Std_Lib::library({chaiscript::Library_Options::No_IO}), + create_chaiscript_parser(), + {}, + {}, + {chaiscript::Options::No_Load_Modules, chaiscript::Options::No_External_Scripts}, + true); + + // print_string and println_string should still be available via the handler mechanism + // but the default handler is a no-op (no stdout output) + CHECK_NOTHROW(chai.eval("print_string(\"hello\")")); + CHECK_NOTHROW(chai.eval("println_string(\"hello\")")); + CHECK(chai.eval("5 + 3") == 8); + CHECK_NOTHROW(chai.eval("var v = Vector()")); + + // Users can set their own print handler even with No_IO + std::string captured; + chai.set_print_handler([&captured](const std::string &s) { captured += s; }); + chai.eval("print_string(\"redirected\")"); + CHECK(captured == "redirected"); +} + +TEST_CASE("ChaiScript_Basic No_Prelude option disables prelude functions") { + chaiscript::ChaiScript_Basic chai(chaiscript::Std_Lib::library({chaiscript::Library_Options::No_Prelude}), + create_chaiscript_parser(), + {}, + {}, + {chaiscript::Options::No_Load_Modules, chaiscript::Options::No_External_Scripts}); + + CHECK_THROWS(chai.eval("print(\"hello\")")); + CHECK_NOTHROW(chai.eval("print_string(\"hello\")")); + CHECK_THROWS(chai.eval("filter([1,2,3], fun(x) { x > 1 })")); + CHECK(chai.eval("5 + 3") == 8); +} + +TEST_CASE("ChaiScript_Basic No_JSON option disables JSON support") { + chaiscript::ChaiScript_Basic chai(chaiscript::Std_Lib::library({chaiscript::Library_Options::No_JSON}), + create_chaiscript_parser(), + {}, + {}, + {chaiscript::Options::No_Load_Modules, chaiscript::Options::No_External_Scripts}); + + CHECK_THROWS(chai.eval("from_json(\"[1,2,3]\")")); + CHECK(chai.eval("5 + 3") == 8); + CHECK_NOTHROW(chai.eval("print(\"hello\")")); +} + +TEST_CASE("ChaiScript_Basic default library has all functions") { + chaiscript::ChaiScript_Basic chai(chaiscript::Std_Lib::library(), + create_chaiscript_parser(), + {}, + {}, + {chaiscript::Options::No_Load_Modules, chaiscript::Options::No_External_Scripts}); + + CHECK_NOTHROW(chai.eval("print(\"hello\")")); + CHECK_NOTHROW(chai.eval("print_string(\"hello\")")); + CHECK_NOTHROW(chai.eval("var v = Vector()")); + CHECK(chai.eval("5 + 3") == 8); +} + +// Tests through ChaiScript (library options passed as constructor parameter) + +TEST_CASE("ChaiScript No_Stdlib option via library options parameter") { + chaiscript::ChaiScript chai({}, + {}, + {chaiscript::Options::No_Load_Modules, chaiscript::Options::No_External_Scripts}, + {chaiscript::Library_Options::No_Stdlib}); + + CHECK_NOTHROW(chai.eval("var x = 5")); + CHECK_THROWS(chai.eval("print(\"hello\")")); + CHECK_THROWS(chai.eval("var v = Vector()")); + CHECK_THROWS(chai.eval("from_json(\"[1,2,3]\")")); +} + +TEST_CASE("ChaiScript No_IO option via library options parameter") { + chaiscript::ChaiScript chai({}, + {}, + {chaiscript::Options::No_Load_Modules, chaiscript::Options::No_External_Scripts}, + {chaiscript::Library_Options::No_IO}); + + // print_string and println_string remain available via the handler mechanism + // but the default handler is a no-op (no stdout output) + CHECK_NOTHROW(chai.eval("print_string(\"hello\")")); + CHECK_NOTHROW(chai.eval("println_string(\"hello\")")); + CHECK(chai.eval("5 + 3") == 8); + CHECK_NOTHROW(chai.eval("var v = Vector()")); + + // Users can override the null handler with their own + std::string captured; + chai.set_print_handler([&captured](const std::string &s) { captured += s; }); + chai.eval("print_string(\"redirected\")"); + CHECK(captured == "redirected"); +} + +TEST_CASE("ChaiScript No_Prelude option via library options parameter") { + chaiscript::ChaiScript chai({}, + {}, + {chaiscript::Options::No_Load_Modules, chaiscript::Options::No_External_Scripts}, + {chaiscript::Library_Options::No_Prelude}); + + CHECK_THROWS(chai.eval("print(\"hello\")")); + CHECK_NOTHROW(chai.eval("print_string(\"hello\")")); + CHECK_THROWS(chai.eval("filter([1,2,3], fun(x) { x > 1 })")); + CHECK(chai.eval("5 + 3") == 8); +} + +TEST_CASE("ChaiScript No_JSON option via library options parameter") { + chaiscript::ChaiScript chai({}, + {}, + {chaiscript::Options::No_Load_Modules, chaiscript::Options::No_External_Scripts}, + {chaiscript::Library_Options::No_JSON}); + + CHECK_THROWS(chai.eval("from_json(\"[1,2,3]\")")); + CHECK(chai.eval("5 + 3") == 8); + CHECK_NOTHROW(chai.eval("print(\"hello\")")); +} + +TEST_CASE("ChaiScript default has all functions") { + chaiscript::ChaiScript chai; + + CHECK_NOTHROW(chai.eval("print(\"hello\")")); + CHECK_NOTHROW(chai.eval("print_string(\"hello\")")); + CHECK_NOTHROW(chai.eval("var v = Vector()")); + CHECK(chai.eval("5 + 3") == 8); +} + +// Issue #421: Class with type_conversion from int and "==" operator +// causes switch statement to compare destroyed objects. +// The switch case comparison must use Function_Push_Pop to properly +// manage the lifetime of temporaries created by type conversions. +TEST_CASE("Issue #421 - Switch with type_conversion does not compare destroyed objects") { + struct MyType { + int value; + explicit MyType(int v) + : value(v) {} + }; + + chaiscript::ChaiScript_Basic chai(create_chaiscript_stdlib(), create_chaiscript_parser()); + chai.add(chaiscript::user_type(), "MyType"); + chai.add(chaiscript::constructor(), "MyType"); + chai.add(chaiscript::fun(&MyType::value), "value"); + chai.add(chaiscript::fun([](const MyType &a, const MyType &b) { return a.value == b.value; }), "=="); + chai.add(chaiscript::type_conversion([](const int &i) { return MyType(i); })); + + // Test switch with type conversion - the case integer literals must be + // converted to MyType via the registered conversion before comparison. + // Without Function_Push_Pop, the converted temporaries may be destroyed + // before the == operator can compare them. + CHECK(chai.eval(R"({ + var result = 0 + var obj = MyType(2) + switch(obj) { + case (1) { + result = 1 + break + } + case (2) { + result = 2 + break + } + case (3) { + result = 3 + break + } + } + result + })") == 2); + + // Test fall-through with type conversion + CHECK(chai.eval(R"({ + var total = 0 + var obj = MyType(2) + switch(obj) { + case (1) { + total += 1 + } + case (2) { + total += 2 + } + case (3) { + total += 4 + } + } + total + })") == 6); + + // Test matching the first case + CHECK(chai.eval(R"({ + var result = 0 + var obj = MyType(1) + switch(obj) { + case (1) { + result = 10 + break + } + case (2) { + result = 20 + break + } + } + result + })") == 10); + + // Test no match + CHECK(chai.eval(R"({ + var result = 0 + var obj = MyType(5) + switch(obj) { + case (1) { + result = 1 + break + } + case (2) { + result = 2 + break + } + } + result + })") == 0); +} + +// Issue #524: A std::vector of std::unique_ptrs can't be added +// vector_type should compile with non-copyable value types by +// skipping copy-dependent operations via if constexpr. +struct Issue524_Foo { + int value = 42; +}; + +TEST_CASE("Issue #524 - vector of unique_ptr can be registered") { + using VecType = std::vector>; + + chaiscript::ChaiScript_Basic chai(create_chaiscript_stdlib(), create_chaiscript_parser()); + + // This should compile and not throw - previously failed to compile + // because vector_type tried to instantiate copy constructor, assignment, + // push_back(const T&), insert_at(const T&), and resize(n, const T&) + // for the non-copyable std::unique_ptr. + chaiscript::ModulePtr m = std::make_shared(); + chaiscript::bootstrap::standard_library::vector_type("UniqueVec", *m); + CHECK_NOTHROW(chai.add(m)); + + // Verify basic operations still work + CHECK(chai.eval("var v = UniqueVec(); v.size()") == 0); + CHECK(chai.eval("var v2 = UniqueVec(); v2.empty()") == true); +} + +// Issue #625: function_less_than comparator must satisfy strict-weak ordering. +// Registering overloaded functions with different arities triggered a +// std::stable_sort assertion on macOS 15.2 (hardened libc++) because the +// comparator violated transitivity of equivalence. +TEST_CASE("Issue 625: function_less_than strict-weak ordering with different arities") { + chaiscript::ChaiScript_Basic chai(create_chaiscript_stdlib(), create_chaiscript_parser()); + + // Register overloaded functions with varying arities under the same name. + // If the comparator doesn't order by arity when overlapping params match, + // std::stable_sort may exhibit undefined behavior. + CHECK_NOTHROW(chai.add(chaiscript::fun([](int x, const chaiscript::Boxed_Value &) { return x; }), "overloaded")); + CHECK_NOTHROW(chai.add(chaiscript::fun([](int x, double y) { return x + static_cast(y); }), "overloaded")); + CHECK_NOTHROW(chai.add(chaiscript::fun([](int x) { return x; }), "overloaded")); + + // Verify dispatch still works correctly + CHECK(chai.eval("overloaded(5)") == 5); + CHECK(chai.eval("overloaded(3, 2.0)") == 5); +} + +TEST_CASE("IO redirection with set_print_handler") { + chaiscript::ChaiScript_Basic chai(create_chaiscript_stdlib(), create_chaiscript_parser()); + + std::string captured_output; + + // Set custom print handler — both print_string and println_string dispatch through it + chai.set_print_handler([&captured_output](const std::string &s) { + captured_output += s; + }); + + // Test that puts() uses the custom handler + captured_output.clear(); + chai.eval("puts(\"hello\")"); + CHECK(captured_output == "hello"); + + // Test that print() uses the custom handler (println_string appends newline before calling handler) + captured_output.clear(); + chai.eval("print(\"world\")"); + CHECK(captured_output == "world\n"); + + // Test that print_string() directly uses the custom handler + captured_output.clear(); + chai.eval("print_string(\"direct\")"); + CHECK(captured_output == "direct"); + + // Test that println_string() directly uses the custom handler with newline + captured_output.clear(); + chai.eval("println_string(\"direct_ln\")"); + CHECK(captured_output == "direct_ln\n"); +} + +TEST_CASE("IO redirection captures numeric output") { + chaiscript::ChaiScript_Basic chai(create_chaiscript_stdlib(), create_chaiscript_parser()); + + std::string captured_output; + chai.set_print_handler([&captured_output](const std::string &s) { + captured_output += s; + }); + + chai.eval("print(42)"); + CHECK(captured_output == "42\n"); +} + +TEST_CASE("IO redirection different instances are independent") { + chaiscript::ChaiScript_Basic chai1(create_chaiscript_stdlib(), create_chaiscript_parser()); + chaiscript::ChaiScript_Basic chai2(create_chaiscript_stdlib(), create_chaiscript_parser()); + + std::string output1; + std::string output2; + + chai1.set_print_handler([&output1](const std::string &s) { output1 += s; }); + chai2.set_print_handler([&output2](const std::string &s) { output2 += s; }); + + chai1.eval("print(\"from1\")"); + chai2.eval("print(\"from2\")"); + + CHECK(output1 == "from1\n"); + CHECK(output2 == "from2\n"); +} + +TEST_CASE("set_print_handler accessible from ChaiScript") { + chaiscript::ChaiScript_Basic chai(create_chaiscript_stdlib(), create_chaiscript_parser()); + + auto captured = std::make_shared(); + chai.add(chaiscript::fun([captured](const std::string &s) { *captured += s; }), "test_output_sink"); + + // Set the print handler from within ChaiScript + chai.eval("set_print_handler(fun(s) { test_output_sink(s) })"); + + chai.eval("print(\"from_script\")"); + CHECK(*captured == "from_script\n"); + + captured->clear(); + chai.eval("puts(\"no_newline\")"); + CHECK(*captured == "no_newline"); +} + +// Regression test: push_back() on script-created vector has no effect when +// vector_conversion is in effect. The bug occurs because dispatch selects +// the C++ push_back for the converted type over the built-in one, operating +// on a temporary copy of the vector. +TEST_CASE("push_back on script vector with vector_conversion") { + chaiscript::ChaiScript_Basic chai(create_chaiscript_stdlib(), create_chaiscript_parser()); + + auto m = std::make_shared(); + chaiscript::bootstrap::standard_library::vector_type>("VectorString", *m); + m->add(chaiscript::vector_conversion>()); + chai.add(m); + + // Register a C++ function that accepts the converted type, so we can + // verify that vector_conversion actually works for passing vectors + chai.add(chaiscript::fun([](const std::vector &v) -> std::string { + std::string result; + for (const auto &s : v) { + if (!result.empty()) { result += ","; } + result += s; + } + return result; + }), + "join_strings"); + + // push_back on an empty script-created vector must be visible + CHECK(chai.eval( + "auto x = [];" + "x.push_back(\"Hello\");" + "x.size() == 1")); + + // push_back on a vector with initial elements must grow correctly + CHECK(chai.eval( + "auto y = [\"a\", \"b\"];" + "y.push_back(\"c\");" + "y.push_back(\"d\");" + "y.size() == 4")); + + // Verify the actual content is preserved after push_back + CHECK(chai.eval( + "auto z = [];" + "z.push_back(\"World\");" + "z[0]") + == "World"); + + // Round-trip: build a vector in script, push_back elements, then pass it + // to a C++ function via vector_conversion and verify the contents + CHECK(chai.eval( + "auto v = [\"one\", \"two\"];" + "v.push_back(\"three\");" + "join_strings(v)") + == "one,two,three"); + + // Verify conversion works on a freshly created vector too + CHECK(chai.eval( + "auto w = [];" + "w.push_back(\"hello\");" + "w.push_back(\"world\");" + "join_strings(w)") + == "hello,world"); +} + +TEST_CASE("vector of vectors conversion (issue #374)") { + chaiscript::ChaiScript_Basic chai(create_chaiscript_stdlib(), create_chaiscript_parser()); + + auto m = std::make_shared(); + chaiscript::bootstrap::standard_library::vector_type>("VectorDouble", *m); + chaiscript::bootstrap::standard_library::vector_type>>("VectorVectorDouble", *m); + m->add(chaiscript::vector_conversion>()); + m->add(chaiscript::vector_conversion>>()); + chai.add(m); + + chai.add(chaiscript::fun([](const std::vector> &v) -> double { + double sum = 0; + for (const auto &inner : v) { + for (const auto d : inner) { + sum += d; + } + } + return sum; + }), + "sum_nested"); + + CHECK(chai.eval("sum_nested([[1.0, 2.0], [3.0, 4.0]])") == Catch::Approx(10.0)); + + CHECK(chai.eval( + "auto v = VectorVectorDouble();" + "v = [[1.0, 2.0], [3.0, 4.0]];" + "v.size() == 2")); +} + +// Regression test for issue #607: AST_Node_Trace must be a complete type +// when used in eval_error's std::vector call_stack member. +// This failed to compile with C++20 on clang/libc++ when AST_Node_Trace +// was only forward-declared before eval_error's definition. +TEST_CASE("eval_error with AST_Node_Trace call stack compiles in C++20") { + chaiscript::ChaiScript_Basic chai(create_chaiscript_stdlib(), create_chaiscript_parser()); + + // Trigger an eval_error by calling a non-existent function + try { + chai.eval("nonexistent_function()"); + REQUIRE(false); + } catch (const chaiscript::exception::eval_error &e) { + // Verify that eval_error's call_stack member (std::vector) + // is usable - this would fail to compile if AST_Node_Trace were incomplete + const auto &stack = e.call_stack; + CHECK(e.pretty_print().size() > 0); + (void)stack; + } +} + +TEST_CASE("Test set_file_reader from C++ land") { + chaiscript::ChaiScript chai; + chai.set_file_reader([](const std::string &) { + return std::string("var file_reader_test_val = 42"); + }); + chai.eval_file("nonexistent_file.chai"); + CHECK(chai.eval("file_reader_test_val") == 42); +} + +TEST_CASE("Test set_file_reader from ChaiScript land") { + chaiscript::ChaiScript chai; + chai.set_file_reader([](const std::string &) { + return std::string("var from_custom_reader = true"); + }); + chai.eval("set_file_reader(fun(filename) { return \"var from_chai_reader = true\"; })"); + chai.eval_file("any_file.chai"); + CHECK(chai.eval("from_chai_reader") == true); +} + +TEST_CASE("Test set_file_reader receives correct filename") { + chaiscript::ChaiScript chai; + std::string captured_filename; + chai.set_file_reader([&captured_filename](const std::string &t_filename) { + captured_filename = t_filename; + return std::string("var dummy = 1"); + }); + chai.eval_file("my_special_file.chai"); + CHECK(captured_filename == "my_special_file.chai"); +} + +TEST_CASE("Test use with set_file_reader") { + chaiscript::ChaiScript chai; + chai.set_file_reader([](const std::string &) { + return std::string("var use_reader_val = 99"); + }); + chai.use("virtual_file.chai"); + CHECK(chai.eval("use_reader_val") == 99); +} +TEST_CASE("Nested namespaces via register_namespace with :: separator") { + chaiscript::ChaiScript_Basic chai(create_chaiscript_stdlib(), create_chaiscript_parser()); + + chai.register_namespace( + [](chaiscript::Namespace &si) { + si["mu_B"] = chaiscript::const_var(9.274); + }, + "constants::si"); + + chai.register_namespace( + [](chaiscript::Namespace &mm) { + mm["mu_B"] = chaiscript::const_var(0.05788); + }, + "constants::mm"); + + chai.import("constants"); + + CHECK(chai.eval("constants.si.mu_B") == Catch::Approx(9.274)); + CHECK(chai.eval("constants.mm.mu_B") == Catch::Approx(0.05788)); + + // Scope resolution via :: works the same as . for access + CHECK(chai.eval("constants::si::mu_B") == Catch::Approx(9.274)); + CHECK(chai.eval("constants::mm::mu_B") == Catch::Approx(0.05788)); +} + +TEST_CASE("Deeply nested namespaces via register_namespace") { + chaiscript::ChaiScript_Basic chai(create_chaiscript_stdlib(), create_chaiscript_parser()); + + chai.register_namespace( + [](chaiscript::Namespace &leaf) { + leaf["val"] = chaiscript::const_var(42); + }, + "a::b::c"); + + chai.import("a"); + + CHECK(chai.eval("a.b.c.val") == 42); + CHECK(chai.eval("a::b::c::val") == 42); +} + +TEST_CASE("Block namespace declaration with ::") { + chaiscript::ChaiScript_Basic chai(create_chaiscript_stdlib(), create_chaiscript_parser()); + + chai.eval(R"( + namespace math { + def square(x) { x * x } + } + )"); + + CHECK(chai.eval("math::square(5)") == 25); + CHECK(chai.eval("math.square(5)") == 25); +} + +TEST_CASE("Nested block namespace declaration") { + chaiscript::ChaiScript_Basic chai(create_chaiscript_stdlib(), create_chaiscript_parser()); + + chai.eval(R"( + namespace physics::constants { + def speed_of_light() { return 299792458 } + } + )"); + + CHECK(chai.eval("physics::constants::speed_of_light()") == 299792458); +} + +TEST_CASE("Namespace block reopening") { + chaiscript::ChaiScript_Basic chai(create_chaiscript_stdlib(), create_chaiscript_parser()); + + chai.eval(R"( + namespace ns { + def foo() { return 1 } + } + namespace ns { + def bar() { return 2 } + } + )"); + + CHECK(chai.eval("ns::foo()") == 1); + CHECK(chai.eval("ns::bar()") == 2); +} + +TEST_CASE("Namespace block with var declarations") { + chaiscript::ChaiScript_Basic chai(create_chaiscript_stdlib(), create_chaiscript_parser()); + + chai.eval(R"( + namespace config { + var pi = 3.14 + var name = "hello" + } + )"); + + CHECK(chai.eval("config::pi") == Catch::Approx(3.14)); + CHECK(chai.eval("config::name") == "hello"); +} + +TEST_CASE("Namespace block rejects non-declaration statements") { + chaiscript::ChaiScript_Basic chai(create_chaiscript_stdlib(), create_chaiscript_parser()); + + CHECK_THROWS_AS(chai.eval(R"( + namespace bad { + 1 + 2 + } + )"), + chaiscript::exception::eval_error); + + CHECK_THROWS_AS(chai.eval(R"( + namespace bad { + print("hello") + } + )"), + chaiscript::exception::eval_error); + + CHECK_THROWS_AS(chai.eval(R"( + var x = 5 + namespace bad { + x = 10 + } + )"), + chaiscript::exception::eval_error); +} + +TEST_CASE("C++ runtime_error thrown from registered function is catchable in ChaiScript") { + chaiscript::ChaiScript_Basic chai(create_chaiscript_stdlib(), create_chaiscript_parser()); + + chai.add(chaiscript::fun([]() -> int { throw std::runtime_error("cpp_runtime_error"); }), "cpp_throw_runtime"); + + CHECK(chai.eval(R"( + var caught = false + try { + cpp_throw_runtime() + } + catch(e) { + caught = true + } + caught + )") == true); +} + +TEST_CASE("C++ out_of_range thrown from registered function is catchable in ChaiScript") { + chaiscript::ChaiScript_Basic chai(create_chaiscript_stdlib(), create_chaiscript_parser()); + + chai.add(chaiscript::fun([]() -> int { throw std::out_of_range("cpp_out_of_range"); }), "cpp_throw_oor"); + + CHECK(chai.eval(R"( + var caught = false + try { + cpp_throw_oor() + } + catch(e) { + caught = true + } + caught + )") == true); +} + +TEST_CASE("C++ logic_error thrown from registered function is catchable in ChaiScript") { + chaiscript::ChaiScript_Basic chai(create_chaiscript_stdlib(), create_chaiscript_parser()); + + chai.add(chaiscript::fun([]() -> int { throw std::logic_error("cpp_logic_error"); }), "cpp_throw_logic"); + + CHECK(chai.eval(R"( + var caught = false + try { + cpp_throw_logic() + } + catch(e) { + caught = true + } + caught + )") == true); +} + +TEST_CASE("ChaiScript throw(int) propagates as Boxed_Value to C++") { + chaiscript::ChaiScript_Basic chai(create_chaiscript_stdlib(), create_chaiscript_parser()); + + try { + chai.eval("throw(42)"); + REQUIRE(false); + } catch (chaiscript::Boxed_Value &bv) { + CHECK(chaiscript::boxed_cast(bv) == 42); + } +} + +TEST_CASE("ChaiScript throw(string) propagates as Boxed_Value to C++") { + chaiscript::ChaiScript_Basic chai(create_chaiscript_stdlib(), create_chaiscript_parser()); + + try { + chai.eval(R"(throw("error msg"))"); + REQUIRE(false); + } catch (chaiscript::Boxed_Value &bv) { + CHECK(chaiscript::boxed_cast(bv) == "error msg"); + } +} + +TEST_CASE("Typed catch with no match propagates exception") { + chaiscript::ChaiScript_Basic chai(create_chaiscript_stdlib(), create_chaiscript_parser()); + + CHECK_THROWS_AS(chai.eval(R"( + try { + throw(42) + } + catch(string e) { + // wrong type, should not match + } + )"), + chaiscript::Boxed_Value); +} + +TEST_CASE("Typed catch with no match still runs finally block") { + chaiscript::ChaiScript_Basic chai(create_chaiscript_stdlib(), create_chaiscript_parser()); + + CHECK_THROWS_AS(chai.eval(R"( + var finally_ran = false + try { + throw(42) + } + catch(string e) { + // wrong type + } + finally { + finally_ran = true + } + )"), + chaiscript::Boxed_Value); + + CHECK(chai.eval("finally_ran") == true); +} + +TEST_CASE("Multiple C++ exception types from registered functions") { + chaiscript::ChaiScript_Basic chai(create_chaiscript_stdlib(), create_chaiscript_parser()); + + chai.add(chaiscript::fun([](int which) -> int { + switch (which) { + case 0: throw std::runtime_error("runtime"); + case 1: throw std::out_of_range("range"); + case 2: throw std::logic_error("logic"); + default: return which; + } + }), + "cpp_multi_throw"); + + CHECK(chai.eval(R"( + var catch_count = 0 + for (var i = 0; i < 3; ++i) { + try { + cpp_multi_throw(i) + } + catch(e) { + catch_count = catch_count + 1 + } + } + catch_count + )") == 3); + + CHECK(chai.eval("cpp_multi_throw(5)") == 5); +} + +TEST_CASE("Exception from C++ binary operator is catchable in ChaiScript") { + chaiscript::ChaiScript_Basic chai(create_chaiscript_stdlib(), create_chaiscript_parser()); + + struct ThrowingType { + int value; + }; + + chai.add(chaiscript::user_type(), "ThrowingType"); + chai.add(chaiscript::constructor(), "ThrowingType"); + chai.add(chaiscript::fun([](const ThrowingType &, const ThrowingType &) -> ThrowingType { + throw std::runtime_error("cpp operator+ threw"); + }), + "+"); + + CHECK(chai.eval(R"( + var caught = false + try { + var a = ThrowingType(1) + var b = ThrowingType(2) + var c = a + b + } + catch(e) { + caught = true + } + caught + )") == true); +} + +TEST_CASE("Exception from C++ [] operator is catchable in ChaiScript") { + chaiscript::ChaiScript_Basic chai(create_chaiscript_stdlib(), create_chaiscript_parser()); + + struct IndexableType { + int value; + }; + + chai.add(chaiscript::user_type(), "IndexableType"); + chai.add(chaiscript::constructor(), "IndexableType"); + chai.add(chaiscript::fun([](const IndexableType &, int idx) -> int { + if (idx < 0) { throw std::out_of_range("negative index"); } + return idx; + }), + "[]"); + + CHECK(chai.eval("var obj = IndexableType(0); obj[5]") == 5); + + CHECK(chai.eval(R"( + var caught = false + try { + var x = obj[-1] + } + catch(e) { + caught = true + } + caught + )") == true); +} + +// Issue #458: ChaiScript strings should be passable to C++ functions that +// take std::string_view, std::string_view should be a known type, and +// explicit conversion from std::string_view to std::string should work. +TEST_CASE("Issue #458: std::string_view interop with ChaiScript strings") { + chaiscript::ChaiScript_Basic chai(create_chaiscript_stdlib(), create_chaiscript_parser()); + + chai.add(chaiscript::fun([](const std::string_view sv) { return std::string(sv); }), "consume_string_view"); + + CHECK(chai.eval(R"(consume_string_view("Hi there"))") == "Hi there"); + CHECK(chai.eval(R"(var s = "from variable"; consume_string_view(s))") == "from variable"); + + CHECK(chai.eval(R"(type_name(string_view("hello")) == "string_view")")); + + CHECK(chai.eval(R"(string(string_view("round trip")))") == "round trip"); +} + +// Issue #458 follow-up: string_view should expose the same search/substring +// surface as string, and overload dispatch must keep myString.substr(...) on +// String::substr while mySV.substr(...) goes to StringView::substr. +TEST_CASE("Issue #458: string_view supports string-like search and substring") { + chaiscript::ChaiScript_Basic chai(create_chaiscript_stdlib(), create_chaiscript_parser()); + + // Receiver-type wins: string keeps String::substr, string_view picks StringView::substr. + CHECK(chai.eval(R"( + fun(){ + var s = "hello world"; + var sv = string_view(s); + type_name(sv.substr(6, 5)) == "string_view" && type_name(s.substr(6, 5)) == "string" + }() + )")); + + // Round-trip: substr through string_view yields the expected substring. + CHECK(chai.eval(R"( + fun(){ + var s = "hello world"; + var sv = string_view(s); + to_string(sv.substr(6, 5)) + }() + )") == "world"); + + // starts_with / ends_with on string_view. + CHECK(chai.eval(R"( + fun(){ + var s = "hello world"; + var sv = string_view(s); + sv.starts_with(string_view("hello")) && sv.ends_with(string_view("world")) + }() + )")); + + // find / rfind / find_first_of return the expected positions. + CHECK(chai.eval(R"( + fun(){ + var s = "hello world"; + var sv = string_view(s); + sv.find(string_view("world"), 0) == 6 && + sv.rfind(string_view("o"), sv.size()) == 7 && + sv.find_first_of(string_view("aeiou"), 0) == 1 + }() + )")); + + // [] indexed access reads the character at the given position. + CHECK(chai.eval(R"( + fun(){ + var s = "hello"; + var sv = string_view(s); + sv[1] == 'e' + }() + )")); +} diff --git a/unittests/const_var.chai b/unittests/const_var.chai new file mode 100644 index 000000000..e5a46c8cd --- /dev/null +++ b/unittests/const_var.chai @@ -0,0 +1,21 @@ +// Test const variable declarations +const var x = 5 +assert_equal(5, x) + +// Test that const variables cannot be reassigned +assert_throws("Error: \"Error, cannot assign to constant value.\"", fun() { const var y = 10; y = 20 }) + +// Test const with auto keyword +const auto z = "hello" +assert_equal("hello", z) + +// Test that const auto variables cannot be reassigned +assert_throws("Error: \"Error, cannot assign to constant value.\"", fun() { const auto w = 3; w = 4 }) + +// Test is_var_const on const var +const var c = 42 +assert_true(c.is_var_const()) + +// Test that non-const var is not const +var nc = 42 +assert_false(nc.is_var_const()) diff --git a/unittests/conversion_functions.chai b/unittests/conversion_functions.chai new file mode 100644 index 000000000..4f51e1010 --- /dev/null +++ b/unittests/conversion_functions.chai @@ -0,0 +1,33 @@ +// Test that all conversion functions work across char, int, and string types + +// char() constructor +assert_equal('A', char('A')) // char from char (identity via Boxed_Number) +assert_equal('A', char(65)) // char from int (via Boxed_Number) +assert_equal('A', char("A")) // char from string (new) + +// to_char() conversions +assert_equal('A', to_char('A')) // to_char from char (identity) +assert_equal('A', to_char(65)) // to_char from int (new, via Boxed_Number) +assert_equal('A', to_char("A")) // to_char from string (existing) + +// int() constructor +assert_equal(65, int('A')) // int from char (via Boxed_Number) +assert_equal(65, int(65)) // int from int (identity via Boxed_Number) +assert_equal(65, int("65")) // int from string (new) + +// to_int() conversions +assert_equal(65, to_int('A')) // to_int from char (new, via Boxed_Number) +assert_equal(65, to_int(65)) // to_int from int (identity) +assert_equal(65, to_int("65")) // to_int from string (existing) + +// to_string() conversions +assert_equal("A", to_string('A')) +assert_equal("65", to_string(65)) +assert_equal("A", to_string("A")) + +// double conversions +assert_equal(3.5, double("3.5")) // double from string (new) +assert_equal(3.5, to_double("3.5")) // to_double from string (existing) +assert_equal(65.0, to_double('A')) // to_double from char (new, via Boxed_Number) +assert_equal(65.0, to_double(65)) // to_double from int (new, via Boxed_Number) +assert_equal("3.5", to_string(3.5)) // to_string from double (existing) diff --git a/unittests/emscripten_eval_test.cpp b/unittests/emscripten_eval_test.cpp new file mode 100644 index 000000000..e13c8f541 --- /dev/null +++ b/unittests/emscripten_eval_test.cpp @@ -0,0 +1,72 @@ +// Test that validates the eval patterns used by the Emscripten wrapper. +// Based on work by Rob Loach (https://github.com/RobLoach/ChaiScript.js) + +#ifndef CHAISCRIPT_NO_THREADS +#define CHAISCRIPT_NO_THREADS +#endif + +#ifndef CHAISCRIPT_NO_DYNLOAD +#define CHAISCRIPT_NO_DYNLOAD +#endif + +#include "../emscripten/chaiscript_eval.hpp" +#include +#include +#include +#include + +int main() { + const int chai = chaiscript_create(); + assert(chai > 0 && "create must return a positive handle"); + + // Test eval (void return) - same as Emscripten eval() + chaiscript_eval(chai, "var x = 42"); + + // Test evalString - same as Emscripten evalString() + [[maybe_unused]] const std::string s = chaiscript_eval_string(chai, "to_string(x)"); + assert(s == "42"); + + // Test evalInt - same as Emscripten evalInt() + [[maybe_unused]] const int i = chaiscript_eval_int(chai, "1 + 2"); + assert(i == 3); + + // Test evalBool - same as Emscripten evalBool() + [[maybe_unused]] bool b = chaiscript_eval_bool(chai, "true"); + assert(b == true); + + b = chaiscript_eval_bool(chai, "false"); + assert(b == false); + + // Test evalFloat - same as Emscripten evalFloat() + [[maybe_unused]] const float f = chaiscript_eval_float(chai, "1.5f"); + assert(std::abs(f - 1.5f) < 0.001f); + + // Test evalDouble - same as Emscripten evalDouble() + [[maybe_unused]] const double d = chaiscript_eval_double(chai, "3.14"); + assert(std::abs(d - 3.14) < 0.001); + + // Test a more complex expression + chaiscript_eval(chai, "def square(n) { return n * n; }"); + [[maybe_unused]] const int sq = chaiscript_eval_int(chai, "square(7)"); + assert(sq == 49); + + // A second engine is fully independent of the first. + const int chai2 = chaiscript_create(); + assert(chai2 != chai && "create must mint a fresh handle each call"); + [[maybe_unused]] bool caught = false; + try { + chaiscript_eval(chai2, "x"); + } catch (const chaiscript::exception::eval_error &) { + caught = true; + } + assert(caught && "engines must not share globals"); + + chaiscript_destroy(chai2); + chaiscript_destroy(chai); + + // Destroying an unknown handle is a no-op. + chaiscript_destroy(chai); + chaiscript_destroy(99999); + + return 0; +} diff --git a/unittests/emscripten_exception_test.cpp b/unittests/emscripten_exception_test.cpp new file mode 100644 index 000000000..0b0d90c9f --- /dev/null +++ b/unittests/emscripten_exception_test.cpp @@ -0,0 +1,77 @@ +// Test that validates exception propagation through the Emscripten eval wrapper. +// Without proper exception support flags (-fwasm-exceptions) in the WASM build, +// C++ exceptions would cause an abort instead of being catchable. + +#ifndef CHAISCRIPT_NO_THREADS +#define CHAISCRIPT_NO_THREADS +#endif + +#ifndef CHAISCRIPT_NO_DYNLOAD +#define CHAISCRIPT_NO_DYNLOAD +#endif + +#include "../emscripten/chaiscript_eval.hpp" +#include +#include +#include +#include +#include + +int main() { + // Verify that ChaiScript evaluation errors propagate as exceptions + // through the eval wrapper functions. In WASM builds without exception + // support, these would abort instead of throwing. + + const int chai = chaiscript_create(); + + [[maybe_unused]] bool caught = false; + + // Test 1: eval with undefined variable should throw + caught = false; + try { + chaiscript_eval(chai, "this_variable_does_not_exist"); + } catch (const chaiscript::exception::eval_error &) { + caught = true; + } + assert(caught && "eval of undefined variable must throw eval_error"); + + // Test 2: evalString with a type mismatch should throw + caught = false; + try { + chaiscript_eval_string(chai, "1 + 2"); + } catch (const chaiscript::exception::bad_boxed_cast &) { + caught = true; + } + assert(caught && "evalString with non-string result must throw bad_boxed_cast"); + + // Test 3: evalInt with invalid syntax should throw + caught = false; + try { + chaiscript_eval_int(chai, "def {}"); + } catch (const chaiscript::exception::eval_error &) { + caught = true; + } + assert(caught && "evalInt with syntax error must throw eval_error"); + + // Test 4: eval with throw statement should propagate exception + caught = false; + try { + chaiscript_eval(chai, "throw(\"user exception\")"); + } catch (const chaiscript::Boxed_Value &) { + caught = true; + } catch (...) { + caught = true; + } + assert(caught && "ChaiScript throw must propagate as an exception"); + + // Test 5: Verify normal operation still works after caught exceptions + chaiscript_eval(chai, "var post_exception_test = 100"); + + [[maybe_unused]] const int result = chaiscript_eval_int(chai, "post_exception_test"); + assert(result == 100 && "normal eval must work after caught exceptions"); + + chaiscript_destroy(chai); + + std::cout << "All emscripten exception tests passed.\n"; + return 0; +} diff --git a/unittests/emscripten_state_test.cpp b/unittests/emscripten_state_test.cpp new file mode 100644 index 000000000..ed3747ea9 --- /dev/null +++ b/unittests/emscripten_state_test.cpp @@ -0,0 +1,76 @@ +// Test that validates the save/restore/release state helpers used by the +// Emscripten wrapper. A handle-based registry is exposed so the JS playground +// can capture a pristine engine once and restore it between runs without +// reloading the WASM module. + +#ifndef CHAISCRIPT_NO_THREADS +#define CHAISCRIPT_NO_THREADS +#endif + +#ifndef CHAISCRIPT_NO_DYNLOAD +#define CHAISCRIPT_NO_DYNLOAD +#endif + +#include "../emscripten/chaiscript_eval.hpp" +#include +#include +#include + +int main() { + const int chai = chaiscript_create(); + assert(chai > 0 && "create must return a positive handle"); + + // Capture a baseline before any user code has been evaluated. + const int baseline = chaiscript_save_state(chai); + assert(baseline > 0 && "saveState must return a positive handle"); + + // Distinct calls produce distinct handles. + const int second = chaiscript_save_state(chai); + assert(second != baseline && "saveState must mint a fresh handle each call"); + chaiscript_release_state(second); + + // Define a global that did not exist in the baseline. + chaiscript_eval(chai, "var post_baseline_marker = 123"); + assert(chaiscript_eval_int(chai, "post_baseline_marker") == 123); + + // Restoring the baseline must drop the post-baseline definition: evaluating + // it should now fail with eval_error because the variable is undefined. + chaiscript_restore_state(chai, baseline); + + [[maybe_unused]] bool caught = false; + try { + chaiscript_eval(chai, "post_baseline_marker"); + } catch (const chaiscript::exception::eval_error &) { + caught = true; + } + assert(caught && "restoreState must drop globals defined after the snapshot"); + + // The engine remains usable after a restore. + chaiscript_eval(chai, "var after_restore = 7"); + assert(chaiscript_eval_int(chai, "after_restore * 6") == 42); + + // Releasing the handle is required so the registry does not grow unbounded. + chaiscript_release_state(baseline); + + // Releasing a handle that was never minted (or has already been released) is + // a no-op rather than an error. + chaiscript_release_state(baseline); + chaiscript_release_state(99999); + + // Restoring an unknown handle is a no-op: state must be unchanged. + chaiscript_restore_state(chai, 99999); + assert(chaiscript_eval_int(chai, "after_restore") == 7); + + // A snapshot taken on one engine can be restored onto a different engine, + // confirming that state is decoupled from any particular instance. + const int snapshot_with_after_restore = chaiscript_save_state(chai); + const int chai2 = chaiscript_create(); + chaiscript_restore_state(chai2, snapshot_with_after_restore); + assert(chaiscript_eval_int(chai2, "after_restore") == 7); + + chaiscript_release_state(snapshot_with_after_restore); + chaiscript_destroy(chai2); + chaiscript_destroy(chai); + + return 0; +} diff --git a/unittests/enum.chai b/unittests/enum.chai new file mode 100644 index 000000000..1821af7a7 --- /dev/null +++ b/unittests/enum.chai @@ -0,0 +1,110 @@ +// Basic enum class definition (default underlying type: int) +enum class Color { Red, Green, Blue } + +// Access via :: syntax +auto r = Color::Red +auto g = Color::Green +auto b = Color::Blue + +// Equality and inequality +assert_true(Color::Red == Color::Red) +assert_false(Color::Red == Color::Green) +assert_true(Color::Red != Color::Green) +assert_false(Color::Red != Color::Red) + +// Constructor from valid underlying value +auto c = Color::Color(1) +assert_true(c == Color::Green) + +// Constructor from invalid value throws +try { + Color::Color(52) + assert_true(false) +} catch(e) { + // expected +} + +// Strong typing: function with typed parameter +def takes_color(Color val) { val } +takes_color(Color::Red) +takes_color(Color::Green) +takes_color(Color::Color(2)) + +// Cannot pass int where Color is expected +try { + takes_color(52) + assert_true(false) +} catch(e) { + // expected: dispatch error +} + +// to_underlying accessor +assert_equal(0, Color::Red.to_underlying()) +assert_equal(1, Color::Green.to_underlying()) +assert_equal(2, Color::Blue.to_underlying()) + +// Enum class with explicit values +enum class Priority { Low = 10, Medium = 20, High = 30 } +assert_equal(10, Priority::Low.to_underlying()) +assert_equal(20, Priority::Medium.to_underlying()) +assert_equal(30, Priority::High.to_underlying()) + +auto p = Priority::Priority(20) +assert_true(p == Priority::Medium) + +// Mixed auto and explicit values +enum class Status { Pending, Active = 5, Done } +assert_equal(0, Status::Pending.to_underlying()) +assert_equal(5, Status::Active.to_underlying()) +assert_equal(6, Status::Done.to_underlying()) + +// Switch on enum values +var result = "" +switch(Color::Green) { + case (Color::Red) { + result = "red" + break + } + case (Color::Green) { + result = "green" + break + } + case (Color::Blue) { + result = "blue" + break + } +} +assert_equal("green", result) + +// Switch on enum with explicit values +var prio_result = "" +switch(Priority::High) { + case (Priority::Low) { + prio_result = "low" + break + } + case (Priority::Medium) { + prio_result = "medium" + break + } + case (Priority::High) { + prio_result = "high" + break + } +} +assert_equal("high", prio_result) + +// Enum class with explicit underlying type +enum class Flags : char { Read = 1, Write = 2, Execute = 4 } +assert_equal(1, Flags::Read.to_underlying()) +assert_equal(2, Flags::Write.to_underlying()) +assert_equal(4, Flags::Execute.to_underlying()) + +auto f = Flags::Flags(2) +assert_true(f == Flags::Write) + +// enum struct syntax (equivalent to enum class, like C++) +enum struct Direction { North, East, South, West } +assert_equal(0, Direction::North.to_underlying()) +assert_equal(3, Direction::West.to_underlying()) +assert_true(Direction::East != Direction::South) diff --git a/unittests/exception_comprehensive.chai b/unittests/exception_comprehensive.chai new file mode 100644 index 000000000..6e39ceb08 --- /dev/null +++ b/unittests/exception_comprehensive.chai @@ -0,0 +1,862 @@ +// Comprehensive exception throwing and catching tests +// Tests throw/catch from various contexts: operators, functions, lambdas, +// methods, [] operator, nested scopes, and with various value types. + +// ============================================================ +// Section 1: Throwing and catching different value types +// ============================================================ + +// Throw int +try { + throw(42) +} +catch(e) { + assert_equal(42, e) +} + +// Throw string +try { + throw("error message") +} +catch(e) { + assert_equal("error message", e) +} + +// Throw double +try { + throw(3.14) +} +catch(e) { + assert_equal(3.14, e) +} + +// Throw bool +try { + throw(true) +} +catch(e) { + assert_equal(true, e) +} + +// Throw a Vector (via named variable to preserve mutability) +auto thrown_vec = [1, 2, 3] +try { + throw(thrown_vec) +} +catch(e) { + assert_equal(3, e.size()) + assert_equal(1, e[0]) + assert_equal(2, e[1]) + assert_equal(3, e[2]) +} + +// Throw a Map (via named variable to preserve mutability) +auto thrown_map = ["key": 42] +try { + throw(thrown_map) +} +catch(e) { + assert_equal(42, e["key"]) +} + +// ============================================================ +// Section 2: Typed catch blocks +// ============================================================ + +// Typed catch matching int +auto typed_result = 0 +try { + throw(10) +} +catch(int e) { + typed_result = e + 1 +} +assert_equal(11, typed_result) + +// Typed catch matching string +typed_result = 0 +try { + throw("hello") +} +catch(string e) { + typed_result = 1 +} +assert_equal(1, typed_result) + +// Typed catch mismatch falls through to untyped +typed_result = 0 +try { + throw(42) +} +catch(string e) { + typed_result = 1 +} +catch(e) { + typed_result = 2 +} +assert_equal(2, typed_result) + +// Multiple typed catches - first match wins +typed_result = 0 +try { + throw("test") +} +catch(int e) { + typed_result = 1 +} +catch(string e) { + typed_result = 2 +} +catch(e) { + typed_result = 3 +} +assert_equal(2, typed_result) + +// Typed catch with int, multiple typed blocks, none match except untyped +typed_result = 0 +try { + throw(3.14) +} +catch(int e) { + typed_result = 1 +} +catch(string e) { + typed_result = 2 +} +catch(e) { + typed_result = 3 +} +assert_equal(3, typed_result) + +// ============================================================ +// Section 3: Catch-all (no variable) catch block +// ============================================================ + +auto catch_all_reached = false +try { + throw(99) +} +catch { + catch_all_reached = true +} +assert_true(catch_all_reached) + +// ============================================================ +// Section 4: Finally block semantics +// ============================================================ + +// Finally runs after exception +auto finally_ran = false +try { + throw(1) +} +catch(e) { + // caught +} +finally { + finally_ran = true +} +assert_true(finally_ran) + +// Finally runs without exception +finally_ran = false +try { + auto x = 1 +} +catch(e) { + // not reached +} +finally { + finally_ran = true +} +assert_true(finally_ran) + +// Finally runs even with typed catch that matches +finally_ran = false +try { + throw(42) +} +catch(int e) { + assert_equal(42, e) +} +finally { + finally_ran = true +} +assert_true(finally_ran) + +// Finally runs when typed catch does NOT match (exception not caught) +finally_ran = false +auto outer_caught = false +try { + try { + throw(42) + } + catch(string e) { + // wrong type, won't match + } + finally { + finally_ran = true + } +} +catch(e) { + outer_caught = true +} +assert_true(finally_ran) +assert_true(outer_caught) + +// ============================================================ +// Section 5: Throwing from functions +// ============================================================ + +def throwing_function() { + throw("from function") +} + +try { + throwing_function() +} +catch(e) { + assert_equal("from function", e) +} + +// Throwing from nested function calls +def inner_throw() { + throw("inner") +} + +def outer_call() { + inner_throw() +} + +try { + outer_call() +} +catch(e) { + assert_equal("inner", e) +} + +// Function that throws conditionally +def conditional_throw(should_throw) { + if (should_throw) { + throw("conditional") + } + return "no throw" +} + +assert_equal("no throw", conditional_throw(false)) + +try { + conditional_throw(true) +} +catch(e) { + assert_equal("conditional", e) +} + +// ============================================================ +// Section 6: Throwing from lambdas +// ============================================================ + +auto throwing_lambda = fun() { throw("from lambda") } + +try { + throwing_lambda() +} +catch(e) { + assert_equal("from lambda", e) +} + +// Lambda that captures and throws +auto captured_val = "captured" +auto capture_lambda = fun[captured_val]() { throw(captured_val) } + +try { + capture_lambda() +} +catch(e) { + assert_equal("captured", e) +} + +// ============================================================ +// Section 7: Throwing from binary operators +// ============================================================ + +// Define a type and an operator that throws +attr ThrowOnAdd::val +def ThrowOnAdd::ThrowOnAdd(v) { this.val = v } + +def `+`(ThrowOnAdd x, ThrowOnAdd y) { + throw("add not supported") +} + +try { + auto a = ThrowOnAdd(1) + auto b = ThrowOnAdd(2) + auto c = a + b + assert_true(false) // should not reach here +} +catch(e) { + assert_equal("add not supported", e) +} + +// Operator that throws for specific values +def `-`(ThrowOnAdd x, ThrowOnAdd y) { + if (x.val == y.val) { + throw("cannot subtract equal values") + } + return ThrowOnAdd(x.val - y.val) +} + +try { + auto a = ThrowOnAdd(5) + auto b = ThrowOnAdd(5) + auto c = a - b + assert_true(false) +} +catch(e) { + assert_equal("cannot subtract equal values", e) +} + +// Multiplication operator that throws (not pre-defined for Dynamic_Object) +def `*`(ThrowOnAdd x, ThrowOnAdd y) { + throw("multiply not supported") +} + +try { + auto a = ThrowOnAdd(1) + auto b = ThrowOnAdd(2) + auto c = a * b + assert_true(false) +} +catch(e) { + assert_equal("multiply not supported", e) +} + +// Subtraction works for non-equal values +auto sub_result = ThrowOnAdd(10) - ThrowOnAdd(3) +assert_equal(7, sub_result.val) + +// ============================================================ +// Section 8: Throwing from unary/prefix operators +// ============================================================ + +def `++`(ThrowOnAdd x) { + throw("increment not supported") +} + +try { + auto a = ThrowOnAdd(1) + ++a + assert_true(false) +} +catch(e) { + assert_equal("increment not supported", e) +} + +// ============================================================ +// Section 9: Throwing from [] operator +// ============================================================ + +attr ThrowOnIndex::data +def ThrowOnIndex::ThrowOnIndex() { this.data = [1, 2, 3] } + +def `[]`(ThrowOnIndex obj, int idx) { + if (idx < 0) { + throw("negative index not allowed") + } + return obj.data[idx] +} + +auto toi = ThrowOnIndex() +assert_equal(1, toi[0]) +assert_equal(2, toi[1]) + +try { + auto val = toi[-1] + assert_true(false) +} +catch(e) { + assert_equal("negative index not allowed", e) +} + +// ============================================================ +// Section 10: Throwing from member functions +// ============================================================ + +attr Validatable::value +def Validatable::Validatable(v) { this.value = v } + +def Validatable::validate() { + if (this.value < 0) { + throw("validation failed: negative value") + } + return true +} + +auto valid_obj = Validatable(10) +assert_true(valid_obj.validate()) + +auto invalid_obj = Validatable(-1) +try { + invalid_obj.validate() + assert_true(false) +} +catch(e) { + assert_equal("validation failed: negative value", e) +} + +// ============================================================ +// Section 11: Nested try/catch +// ============================================================ + +auto inner_caught = false +auto outer_caught_val = 0 +try { + try { + throw(1) + } + catch(e) { + inner_caught = true + throw(e + 10) + } +} +catch(e) { + outer_caught_val = e +} +assert_true(inner_caught) +assert_equal(11, outer_caught_val) + +// Deeply nested try/catch +auto depth = 0 +try { + try { + try { + throw("deep") + } + catch(e) { + depth = 1 + throw(e + "er") + } + } + catch(e) { + depth = 2 + throw(e + "est") + } +} +catch(e) { + depth = 3 + assert_equal("deeperest", e) +} +assert_equal(3, depth) + +// ============================================================ +// Section 12: Rethrow from catch block +// ============================================================ + +auto rethrow_caught = false +try { + try { + throw("rethrown") + } + catch(e) { + throw(e) + } +} +catch(e) { + rethrow_caught = true + assert_equal("rethrown", e) +} +assert_true(rethrow_caught) + +// ============================================================ +// Section 13: Exception in for loop +// ============================================================ + +auto loop_exception_val = 0 +try { + for (auto i = 0; i < 10; ++i) { + if (i == 5) { + throw(i) + } + } +} +catch(e) { + loop_exception_val = e +} +assert_equal(5, loop_exception_val) + +// ============================================================ +// Section 14: Exception in while loop +// ============================================================ + +auto while_exc_val = 0 +auto counter = 0 +try { + while (true) { + ++counter + if (counter == 3) { + throw(counter) + } + } +} +catch(e) { + while_exc_val = e +} +assert_equal(3, while_exc_val) + +// ============================================================ +// Section 15: Exception preserves value through nested calls +// ============================================================ + +def deep_throw(val) { + throw(val) +} + +def middle_call(val) { + deep_throw(val) +} + +def top_call(val) { + middle_call(val) +} + +auto nested_map = ["key": "value"] +try { + top_call(nested_map) +} +catch(e) { + assert_equal("value", e["key"]) +} + +auto nested_vec = [10, 20, 30] +try { + top_call(nested_vec) +} +catch(e) { + assert_equal(3, e.size()) + assert_equal(20, e[1]) +} + +// ============================================================ +// Section 16: Code after throw is not executed +// ============================================================ + +auto after_throw = false +try { + throw(1) + after_throw = true +} +catch(e) { + // caught +} +assert_false(after_throw) + +// ============================================================ +// Section 17: Exception value is usable in catch block arithmetic +// ============================================================ + +auto catch_computed = 0 +try { + throw(1) +} +catch(e) { + catch_computed = e + 100 +} +assert_equal(101, catch_computed) + +// ============================================================ +// Section 18: No exception means catch is skipped +// ============================================================ + +auto catch_skipped = true +try { + auto x = 42 +} +catch(e) { + catch_skipped = false +} +assert_true(catch_skipped) + +// ============================================================ +// Section 19: Exception from dynamic object method chaining +// ============================================================ + +attr Chain::val +def Chain::Chain(v) { this.val = v } + +def Chain::add(n) { + if (this.val + n > 100) { + throw("overflow: " + to_string(this.val + n)) + } + this.val = this.val + n + return this +} + +auto chain = Chain(50) +try { + chain.add(30).add(30) + assert_true(false) +} +catch(e) { + assert_equal("overflow: 110", e) +} +assert_equal(80, chain.val) + +// ============================================================ +// Section 20: Exception thrown during map construction +// ============================================================ + +def exploding_value() { + throw("boom during construction") +} + +try { + auto m = ["ok": 1, "bad": exploding_value()] + assert_true(false) +} +catch(e) { + assert_equal("boom during construction", e) +} + +// ============================================================ +// Section 21: Exception thrown during vector construction +// ============================================================ + +try { + auto v = [1, 2, exploding_value(), 4] + assert_true(false) +} +catch(e) { + assert_equal("boom during construction", e) +} + +// ============================================================ +// Section 22: Exception in if-condition +// ============================================================ + +def exploding_condition() { + throw("condition exploded") +} + +try { + if (exploding_condition()) { + assert_true(false) + } +} +catch(e) { + assert_equal("condition exploded", e) +} + +// ============================================================ +// Section 23: Multiple catch blocks - only first matching runs +// ============================================================ + +auto catch_count = 0 +try { + throw(42) +} +catch(int e) { + ++catch_count +} +catch(e) { + ++catch_count +} +assert_equal(1, catch_count) + +// ============================================================ +// Section 24: Throwing from within catch, with finally +// ============================================================ + +auto s24_finally = false +auto s24_outer = false +try { + try { + throw("original") + } + catch(e) { + throw("replaced: " + e) + } + finally { + s24_finally = true + } +} +catch(e) { + s24_outer = true + assert_equal("replaced: original", e) +} +assert_true(s24_finally) +assert_true(s24_outer) + +// ============================================================ +// Section 25: Unhandled typed catch propagates exception +// ============================================================ + +auto s25_caught = false +try { + try { + throw(3.14) + } + catch(int e) { + assert_true(false) // should not match double + } + catch(string e) { + assert_true(false) // should not match double + } +} +catch(e) { + s25_caught = true + assert_equal(3.14, e) +} +assert_true(s25_caught) + +// ============================================================ +// Section 26: Throw from range-based for +// ============================================================ + +auto s26_val = 0 +try { + for (x : [10, 20, 30, 40]) { + if (x == 30) { + throw(x) + } + } +} +catch(e) { + s26_val = e +} +assert_equal(30, s26_val) + +// ============================================================ +// Section 27: Throw from eval +// ============================================================ + +try { + eval("throw(\"from eval\")") +} +catch(e) { + assert_equal("from eval", e) +} + +// ============================================================ +// Section 28: Exception from built-in operations (out of range) +// ============================================================ + +auto s28_caught = false +try { + auto v = [1, 2, 3] + auto x = v[10] +} +catch(e) { + s28_caught = true +} +assert_true(s28_caught) + +// ============================================================ +// Section 29: Throw zero and empty string (falsy values) +// ============================================================ + +try { + throw(0) +} +catch(e) { + assert_equal(0, e) +} + +try { + throw("") +} +catch(e) { + assert_equal("", e) +} + +// ============================================================ +// Section 30: Throw from ternary-style inline_if +// ============================================================ + +def maybe_throw(do_it) { + if (do_it) { throw("inline threw") } else { "ok" } +} + +try { + maybe_throw(true) +} +catch(e) { + assert_equal("inline threw", e) +} +assert_equal("ok", maybe_throw(false)) + +// ============================================================ +// Section 31: Verify catch variable scope isolation +// ============================================================ + +auto outer_e = "untouched" +try { + throw("caught_value") +} +catch(e) { + assert_equal("caught_value", e) +} +assert_equal("untouched", outer_e) + +// ============================================================ +// Section 32: Exception from recursive function +// ============================================================ + +def recursive_throw(n) { + if (n == 0) { + throw("bottom") + } + recursive_throw(n - 1) +} + +try { + recursive_throw(5) +} +catch(e) { + assert_equal("bottom", e) +} + +// ============================================================ +// Section 33: Try/catch in a function body +// ============================================================ + +def safe_divide(a, b) { + try { + if (b == 0) { + throw("division by zero") + } + return a / b + } + catch(e) { + return e + } +} + +assert_equal(5, safe_divide(10, 2)) +assert_equal("division by zero", safe_divide(10, 0)) + +// ============================================================ +// Section 34: Throw from [] on a Map with missing key +// ============================================================ + +auto s34_caught = false +try { + auto m = ["a": 1] + auto x = m["nonexistent"] +} +catch(e) { + s34_caught = true +} +assert_true(s34_caught) + +// ============================================================ +// Section 35: Arithmetic exception (divide by zero) +// ============================================================ + +auto s35_caught = false +try { + auto x = 1 / 0 +} +catch(e) { + s35_caught = true +} +assert_true(s35_caught) diff --git a/unittests/function_introspection.chai b/unittests/function_introspection.chai index 2c511a73f..d040ff6a2 100644 --- a/unittests/function_introspection.chai +++ b/unittests/function_introspection.chai @@ -39,14 +39,15 @@ assert_equal(true, test_fun_types[1].bare_equal(int_type)); assert_equal(2, `==`.get_arity()); -// < should be the merging of two functions bool <(PODObject, PODObject) and bool <(string, string) +// < should be the merging of three functions bool <(PODObject, PODObject), +// bool <(string, string), and bool <(string_view, string_view) // we want to peel it apart and make sure that's true auto types = `<`.get_param_types(); assert_equal(3, types.size()); assert_equal(true, types[0].bare_equal(bool_type)); assert_equal(true, types[1].bare_equal(Object_type)); assert_equal(true, types[2].bare_equal(Object_type)); -assert_equal(2, `<`.get_contained_functions().size()); +assert_equal(3, `<`.get_contained_functions().size()); // guard existence tests diff --git a/unittests/grammar_constructs.chai b/unittests/grammar_constructs.chai new file mode 100644 index 000000000..1a0db0c4a --- /dev/null +++ b/unittests/grammar_constructs.chai @@ -0,0 +1,219 @@ +// Regression test: exercises grammar constructs documented in grammar/chaiscript.ebnf + +// --- Variable declarations --- +var a = 1 +auto b = 2 +global c = 3 +const d = 42 + +assert_equal(1, a) +assert_equal(2, b) +assert_equal(3, c) +assert_equal(42, d) + +// --- Reference variables --- +var orig = 10 +var &ref = orig +ref = 20 +assert_equal(20, orig) + +// --- Numeric literals --- +assert_equal(255, 0xFF) +assert_equal(255, 0xff) +assert_equal(5, 0b101) +assert_equal(42, 42) +assert_equal(3.14, 3.14) + +// --- String interpolation --- +var name = "world" +assert_equal("hello world", "hello ${name}") + +// --- Escape sequences --- +assert_equal("\n", "\n") +assert_equal("\t", "\t") + +// --- Single-quoted char --- +assert_equal('A', 'A') + +// --- Operators and precedence --- +assert_equal(7, 1 + 2 * 3) +assert_equal(true, 5 > 3 && 2 < 4) +assert_equal(true, false || true) +assert_equal(6, 3 << 1) +assert_equal(1, 3 >> 1) +assert_equal(5, 7 & 5) +assert_equal(7, 5 | 3) +assert_equal(6, 5 ^ 3) +assert_equal(-1, ~0) + +// --- Ternary operator --- +assert_equal("yes", true ? "yes" : "no") +assert_equal("no", false ? "yes" : "no") + +// --- Prefix operators --- +var x = 5 +++x +assert_equal(6, x) +--x +assert_equal(5, x) +assert_equal(true, !false) + +// --- Assignment operators --- +var v = 10 +v += 5; assert_equal(15, v) +v -= 3; assert_equal(12, v) +v *= 2; assert_equal(24, v) +v /= 4; assert_equal(6, v) +v %= 4; assert_equal(2, v) +v <<= 2; assert_equal(8, v) +v >>= 1; assert_equal(4, v) +v |= 3; assert_equal(7, v) +v &= 5; assert_equal(5, v) +v ^= 3; assert_equal(6, v) + +// --- Lambda --- +var add = fun(a, b) { a + b } +assert_equal(5, add(2, 3)) + +// --- Lambda with capture --- +var captured = 100 +var get_captured = fun[captured]() { captured } +assert_equal(100, get_captured()) + +// --- Function definition --- +def multiply(a, b) { a * b } +assert_equal(12, multiply(3, 4)) + +// --- Guard condition on function --- +def abs_val(x) : x >= 0 { x } +def abs_val(x) : x < 0 { -x } +assert_equal(5, abs_val(5)) +assert_equal(5, abs_val(-5)) + +// --- Class definition --- +class Animal +{ + attr sound + def Animal(s) { this.sound = s } + def speak() { this.sound } +} + +var dog = Animal("woof") +assert_equal("woof", dog.speak()) + +// --- Class with inheritance --- +class Puppy : Animal +{ + attr name + def Puppy(n, s) { this.name = n; this.sound = s } + def greet() { to_string(this.name) + " says " + to_string(this.speak()) } +} + +var p = Puppy("Rex", "yip") +assert_equal("Rex says yip", p.greet()) + +// --- Control flow: if/else --- +var result = "" +if (true) { result = "yes" } else { result = "no" } +assert_equal("yes", result) + +// --- Control flow: while --- +var counter = 0 +while (counter < 3) { ++counter } +assert_equal(3, counter) + +// --- Control flow: for --- +var sum = 0 +for (var i = 0; i < 5; ++i) { sum += i } +assert_equal(10, sum) + +// --- Control flow: ranged for --- +var items = [10, 20, 30] +var total = 0 +for (item : items) { total += item } +assert_equal(60, total) + +// --- Switch/case --- +def classify(n) { + var label = "" + switch (n) { + case (1) { label = "one"; break } + case (2) { label = "two"; break } + default { label = "other" } + } + return label +} +assert_equal("one", classify(1)) +assert_equal("two", classify(2)) +assert_equal("other", classify(99)) + +// --- Try/catch/finally --- +var caught = false +var finalized = false +try { + throw("oops") +} catch (e) { + caught = true +} finally { + finalized = true +} +assert_true(caught) +assert_true(finalized) + +// --- Inline containers --- +var vec = [1, 2, 3] +assert_equal(3, vec.size()) + +var m = ["a": 1, "b": 2] +assert_equal(1, m["a"]) + +var r = [1, 2, 3, 4, 5] +assert_equal(5, r.size()) + +// --- Dot access chaining --- +assert_equal(3, [1, 2, 3].size()) + +// --- Array access --- +var arr = [10, 20, 30] +assert_equal(20, arr[1]) + +// --- Backtick identifier --- +var `my var` = 42 +assert_equal(42, `my var`) + +// --- Special identifiers --- +assert_equal(true, true) +assert_equal(false, false) + +// --- Nested block --- +var block_result = 0 +{ block_result = 42 } +assert_equal(42, block_result) + +// --- Break and continue --- +var break_sum = 0 +for (var i = 0; i < 10; ++i) { + if (i == 5) { break } + break_sum += i +} +assert_equal(10, break_sum) + +var cont_sum = 0 +for (var i = 0; i < 5; ++i) { + if (i == 2) { continue } + cont_sum += i +} +assert_equal(8, cont_sum) + +// --- Return from function --- +def early_return(n) { + if (n > 0) { return "positive" } + return "non-positive" +} +assert_equal("positive", early_return(1)) +assert_equal("non-positive", early_return(-1)) + +// --- Colon assignment --- +var ca = 0 +ca := 99 +assert_equal(99, ca) diff --git a/unittests/index_operator_override.chai b/unittests/index_operator_override.chai new file mode 100644 index 000000000..2a3d89c3d --- /dev/null +++ b/unittests/index_operator_override.chai @@ -0,0 +1,45 @@ + +// Test overriding [] operator with various index types (issue #398) + +class my_class +{ + def my_class() + { + } +}; + +def `[]`(my_class o, idx) +{ + return "Hello World!"; +} + +var o = my_class(); + +// Integer index should work +assert_equal("Hello World!", o[3]); + +// String index should work +assert_equal("Hello World!", o["3"]); + +// String variable as index should work +var s = "abc"; +assert_equal("Hello World!", o[s]); + +// Typed string parameter override +class my_class2 +{ + def my_class2() + { + } +}; + +def `[]`(my_class2 o, string key) +{ + return "key=" + key; +} + +var o2 = my_class2(); +assert_equal("key=foo", o2["foo"]); + +var key = "bar"; +assert_equal("key=bar", o2[key]); diff --git a/unittests/json_unicode_1.chai b/unittests/json_unicode_1.chai new file mode 100644 index 000000000..572ca6278 --- /dev/null +++ b/unittests/json_unicode_1.chai @@ -0,0 +1,2 @@ +// Test JSON \u escape: ASCII range (U+0041 = 'A') +assert_equal(from_json("\"\\u0041\""), "A") diff --git a/unittests/json_unicode_2.chai b/unittests/json_unicode_2.chai new file mode 100644 index 000000000..57ba4c54c --- /dev/null +++ b/unittests/json_unicode_2.chai @@ -0,0 +1,3 @@ +// Test JSON \u escape: 2-byte UTF-8 (U+00C4 = 'Ä') +// This is the example from issue #477 +assert_equal(from_json("\"\\u00c4\""), "\u00C4") diff --git a/unittests/json_unicode_3.chai b/unittests/json_unicode_3.chai new file mode 100644 index 000000000..f77d7a232 --- /dev/null +++ b/unittests/json_unicode_3.chai @@ -0,0 +1,2 @@ +// Test JSON \u escape: 3-byte UTF-8 (U+20AC = '€') +assert_equal(from_json("\"\\u20AC\""), "\u20AC") diff --git a/unittests/json_unicode_4.chai b/unittests/json_unicode_4.chai new file mode 100644 index 000000000..7b6f780fd --- /dev/null +++ b/unittests/json_unicode_4.chai @@ -0,0 +1,2 @@ +// Test JSON \u escape: mixed with regular text +assert_equal(from_json("\"Hello \\u0057orld\""), "Hello World") diff --git a/unittests/json_unicode_5.chai b/unittests/json_unicode_5.chai new file mode 100644 index 000000000..786f09e77 --- /dev/null +++ b/unittests/json_unicode_5.chai @@ -0,0 +1,2 @@ +// Test JSON \u escape: multiple unicode escapes in one string +assert_equal(from_json("\"\\u0048\\u0065\\u006C\\u006C\\u006F\""), "Hello") diff --git a/unittests/json_unicode_6.chai b/unittests/json_unicode_6.chai new file mode 100644 index 000000000..92f4a993a --- /dev/null +++ b/unittests/json_unicode_6.chai @@ -0,0 +1,2 @@ +// Test JSON \u escape: uppercase hex digits +assert_equal(from_json("\"\\u00C4\""), "\u00C4") diff --git a/unittests/json_unicode_7.chai b/unittests/json_unicode_7.chai new file mode 100644 index 000000000..dd34aaf3b --- /dev/null +++ b/unittests/json_unicode_7.chai @@ -0,0 +1,2 @@ +// Test JSON \u escape: null character (U+0000) - edge case +assert_equal(from_json("\"before\\u0041after\""), "beforeAafter") diff --git a/unittests/json_unicode_8.chai b/unittests/json_unicode_8.chai new file mode 100644 index 000000000..3796ea70d --- /dev/null +++ b/unittests/json_unicode_8.chai @@ -0,0 +1,3 @@ +// Test JSON \u escape inside an object value +var m = from_json("{\"key\": \"\\u00C4\\u00D6\\u00DC\"}") +assert_equal(m["key"], "\u00C4\u00D6\u00DC") diff --git a/unittests/map_find.chai b/unittests/map_find.chai new file mode 100644 index 000000000..d8d3030e3 --- /dev/null +++ b/unittests/map_find.chai @@ -0,0 +1,15 @@ + +auto m = ["k":"v"] + +// find existing key returns the value +assert_equal("v", m.find("k")) + +// find missing key returns undef +assert_true(m.find("missing").is_var_undef()) + +// find does not mutate the map +m.find("other") +assert_equal(1, m.size()) + +// find works with in-place map +assert_equal("v", ["k":"v"].find("k")) diff --git a/unittests/map_keys_vector_push_back.chai b/unittests/map_keys_vector_push_back.chai new file mode 100644 index 000000000..2310e8465 --- /dev/null +++ b/unittests/map_keys_vector_push_back.chai @@ -0,0 +1,29 @@ +// Regression test for issue #594 +// Map keys pushed into a vector via .first should remain valid +// after the map goes out of scope. + +def keys(Map map) +{ + var v = Vector(); + for( i : map ) + { + v.push_back(i.first); + } + return v; +} + +var k = Vector(); +if ( true ) +{ + var m = ["a":"x", "b":"y", "c":"z"]; + k = keys(m); +} + +// After the map is out of scope, the keys should still be valid strings +assert_equal(3, k.size()) + +// Verify each element is a non-empty string +for (elem : k) +{ + assert_true(elem.size() > 0) +} diff --git a/unittests/modulo_by_zero_protection.chai b/unittests/modulo_by_zero_protection.chai new file mode 100644 index 000000000..2b0995da9 --- /dev/null +++ b/unittests/modulo_by_zero_protection.chai @@ -0,0 +1,16 @@ +// modulo by zero should throw, not crash +try { + 3 % 0 + assert_true(false) +} catch (e) { + assert_equal("Arithmetic error: divide by zero", e.what()) +} + +// assign-modulo by zero should also throw +var x = 3; +try { + x %= 0 + assert_true(false) +} catch (e) { + assert_equal("Arithmetic error: divide by zero", e.what()) +} diff --git a/unittests/multifile_test_chai.cpp b/unittests/multifile_test_chai.cpp index f0a19fff6..ea1379d84 100644 --- a/unittests/multifile_test_chai.cpp +++ b/unittests/multifile_test_chai.cpp @@ -5,8 +5,8 @@ Multi_Test_Chai::Multi_Test_Chai() : m_chai(new chaiscript::ChaiScript_Basic( - chaiscript::Std_Lib::library(), - std::make_unique>())) { + chaiscript::Std_Lib::library(), + std::make_unique>())) { } std::shared_ptr Multi_Test_Chai::get_chai() { diff --git a/unittests/nested_namespaces.chai b/unittests/nested_namespaces.chai new file mode 100644 index 000000000..f1ca73398 --- /dev/null +++ b/unittests/nested_namespaces.chai @@ -0,0 +1,55 @@ +// Test C++-style block namespace declarations +namespace constants::si { + def mu_B() { return 1.0 } +} + +namespace constants::mm { + def mu_B() { return 2.0 } +} + +assert_equal(1.0, constants::si::mu_B()) +assert_equal(2.0, constants::mm::mu_B()) + +// Test deeper nesting with block syntax +namespace a::b::c { + def val() { return 42 } +} + +assert_equal(42, a::b::c::val()) + +// Test reopening a namespace to add more members +namespace math { + def square(x) { x * x } +} + +namespace math::trig { + def double_angle(x) { 2.0 * x } +} + +assert_equal(16, math::square(4)) +assert_equal(6.0, math::trig::double_angle(3.0)) + +// Test reopening a namespace (C++ allows this) +namespace math { + def cube(x) { x * x * x } +} + +assert_equal(27, math::cube(3)) + +// Test that :: scope resolution works the same as . for access +assert_equal(16, math.square(4)) +assert_equal(6.0, math.trig.double_angle(3.0)) + +// Test namespace with var declarations +namespace config { + var pi = 3.14159 + var name = "test" +} + +assert_equal(3.14159, config::pi) +assert_equal("test", config::name) + +// Test function-call style still works +namespace("compat") +compat.legacy = 99 +assert_equal(99, compat::legacy) diff --git a/unittests/now_function.chai b/unittests/now_function.chai new file mode 100644 index 000000000..5825cdec6 --- /dev/null +++ b/unittests/now_function.chai @@ -0,0 +1,3 @@ +// Regression test for issue #660: now() requires include +var t = now() +assert_true(t > 0) diff --git a/unittests/object_from_json_1.chai b/unittests/object_from_json_1.chai new file mode 100644 index 000000000..541d1ac90 --- /dev/null +++ b/unittests/object_from_json_1.chai @@ -0,0 +1,6 @@ +// object_from_json: returns Dynamic_Object with dot-access on JSON fields +var obj = object_from_json("{\"name\":\"ChaiScript\",\"version\":6,\"active\":true}") +assert_equal(obj.name, "ChaiScript") +assert_equal(obj.version, 6) +assert_equal(obj.active, true) +assert_equal(obj.get_type_name(), "JSON_Object") diff --git a/unittests/object_from_json_2.chai b/unittests/object_from_json_2.chai new file mode 100644 index 000000000..05467164f --- /dev/null +++ b/unittests/object_from_json_2.chai @@ -0,0 +1,3 @@ +// object_from_json: nested objects become nested Dynamic_Objects +var obj = object_from_json("{\"outer\":{\"inner\":42}}") +assert_equal(obj.outer.inner, 42) diff --git a/unittests/object_from_json_3.chai b/unittests/object_from_json_3.chai new file mode 100644 index 000000000..11a428da2 --- /dev/null +++ b/unittests/object_from_json_3.chai @@ -0,0 +1,3 @@ +// object_from_json: arrays remain as vectors +var obj = object_from_json("{\"items\":[1,2,3]}") +assert_equal(obj.items, [1,2,3]) diff --git a/unittests/object_from_json_4.chai b/unittests/object_from_json_4.chai new file mode 100644 index 000000000..626e1d631 --- /dev/null +++ b/unittests/object_from_json_4.chai @@ -0,0 +1,9 @@ +// object_to_map and map_to_object conversions +var m = ["a": 1, "b": "hello"] +var obj = map_to_object(m) +assert_equal(obj.a, 1) +assert_equal(obj.b, "hello") + +var m2 = object_to_map(obj) +assert_equal(m2["a"], 1) +assert_equal(m2["b"], "hello") diff --git a/unittests/object_from_json_5.chai b/unittests/object_from_json_5.chai new file mode 100644 index 000000000..678f9a9c5 --- /dev/null +++ b/unittests/object_from_json_5.chai @@ -0,0 +1,6 @@ +// object_from_json roundtrip through to_json +var json_str = "{\"key\":\"value\"}" +var obj = object_from_json(json_str) +var result = to_json(obj) +var obj2 = object_from_json(result) +assert_equal(obj2.key, "value") diff --git a/unittests/raw_string.chai b/unittests/raw_string.chai new file mode 100644 index 000000000..ecd80a138 --- /dev/null +++ b/unittests/raw_string.chai @@ -0,0 +1,23 @@ +// Basic raw string +assert_equal("hello", R"(hello)") + +// Raw string with no interpolation +assert_equal("\${5+5}", R"(${5+5})") + +// Raw string preserves backslashes +assert_equal("hello\\nworld", R"(hello\nworld)") + +// Raw string with custom delimiter +assert_equal("hello)world", R"foo(hello)world)foo") + +// Raw string with quotes inside +assert_equal("he said \"hi\"", R"(he said "hi")") + +// Raw string with dollar signs +assert_equal("cost is \$100", R"(cost is $100)") + +// Empty raw string +assert_equal("", R"()") + +// Raw string with backslash-dollar +assert_equal("\\\${foo}", R"(\${foo})") diff --git a/unittests/recursion_depth_protection.chai b/unittests/recursion_depth_protection.chai new file mode 100644 index 000000000..716edb9ad --- /dev/null +++ b/unittests/recursion_depth_protection.chai @@ -0,0 +1,41 @@ +// Regression test for issue #633: Stack-overflow due to infinite recursion +// in user-defined operator (string interpolation). +// +// Before the fix the recursive `/=` invocation triggered unbounded native +// recursion in the evaluator and crashed the host process with a SIGSEGV. +// The engine now bounds call_depth and throws a catchable exception +// instead of letting the native stack overflow. + +def string::`/=`(double d) { + this = "${this/= 2}/=${d}"; + return this; +} + +var s = "o World" +var caught = false +var message = "" + +try { + s /= 2 + // unreachable: the recursive operator must abort with an exception + assert_true(false) +} catch (e) { + caught = true + message = e.what() +} + +assert_true(caught) + +// The reported error must mention the call-stack overflow so users can +// distinguish it from an arbitrary script-level error. +assert_true(find(message, "call stack") != -1) + +// A bounded recursion that stays well below the limit must keep working. +// Use a small depth so the test passes on every platform default - notably +// the MSVC Debug build where the native stack budget forces a tighter cap. +def count_down(n) { + if (n <= 0) { return 0 } + return count_down(n - 1) + 1 +} + +assert_equal(10, count_down(10)) diff --git a/unittests/reflection_documentation.chai b/unittests/reflection_documentation.chai new file mode 100644 index 000000000..db89dc2c4 --- /dev/null +++ b/unittests/reflection_documentation.chai @@ -0,0 +1,123 @@ + +// Tests for ChaiScript reflection / introspection capabilities +// Ensures all documented reflection functions work correctly + +// --- Global reflection functions --- + +// type_name: returns the type name of a value +assert_equal("int", type_name(1)) +assert_equal("string", type_name("hello")) +assert_equal("bool", type_name(true)) +assert_equal("double", type_name(1.0)) + +// is_type: checks if a value is of the given type +assert_true(is_type(1, "int")) +assert_true(is_type("hello", "string")) +assert_false(is_type(1, "string")) + +// function_exists: checks if a named function is registered +assert_true(function_exists("print")) +assert_true(function_exists("type_name")) +assert_true(function_exists("function_exists")) +assert_false(function_exists("this_function_does_not_exist_xyz")) + +// get_functions: returns a Map of all registered functions +var funcs = get_functions() +assert_true(funcs.size() > 0) +assert_true(funcs.count("print") > 0) +assert_true(funcs.count("type_name") > 0) + +// get_objects: returns a Map of all scripting objects +var my_test_var = 42 +var objs = get_objects() +assert_true(objs.size() > 0) +assert_true(objs.count("my_test_var") > 0) + +// type: returns a Type_Info for a named type +var ti = type("int") +assert_equal("int", ti.name()) + +// call_exists: checks if a function call with given params exists +assert_true(call_exists(`+`, 1, 2)) +assert_true(call_exists(`+`, "a", "b")) + +// --- Object methods --- + +// get_type_info: returns Type_Info for a value +var s = "hello" +assert_equal("string", s.get_type_info().name()) +assert_equal("int", 1.get_type_info().name()) + +// is_type on objects +assert_true("hello".is_type("string")) +assert_true(1.is_type("int")) +assert_false(1.is_type("string")) + +// is_type with Type_Info +assert_true("hello".is_type(string_type)) +assert_true(1.is_type(int_type)) + +// is_var_* methods +var x = 5 +assert_false(x.is_var_const()) +assert_false(x.is_var_null()) +assert_false(x.is_var_undef()) + +// --- Type_Info methods --- +var int_ti = type("int") +assert_equal("int", int_ti.name()) +assert_false(int_ti.is_type_const()) +assert_false(int_ti.is_type_void()) +assert_false(int_ti.is_type_undef()) +assert_false(int_ti.is_type_reference()) +assert_false(int_ti.is_type_pointer()) + +// bare_equal: compares types ignoring const/pointer/reference +assert_true(int_ti.bare_equal(1.get_type_info())) + +// --- Function introspection --- + +def my_reflection_test_func(a, b) { return a + b; } + +// get_arity +assert_equal(2, my_reflection_test_func.get_arity()) + +// get_param_types +var param_types = my_reflection_test_func.get_param_types() +assert_true(param_types.size() > 0) + +// get_contained_functions +assert_equal(0, my_reflection_test_func.get_contained_functions().size()) + +// has_guard +assert_false(my_reflection_test_func.has_guard()) + +// Guarded function +def my_guarded_func(x) : x > 0 { return x; } +assert_true(my_guarded_func.has_guard()) +var g = my_guarded_func.get_guard() +assert_equal(1, g.get_arity()) + +// call: invoke a function with a vector of parameters +assert_equal(3, `+`.call([1, 2])) + +// --- Dynamic_Object reflection --- +var obj = Dynamic_Object() +obj.name = "test" +obj.value = 42 +var attrs = obj.get_attrs() +assert_true(attrs.count("name") > 0) +assert_true(attrs.count("value") > 0) + +// --- Class reflection --- +class ReflectionTestClass { + var x + def ReflectionTestClass() { this.x = 10; } + def get_x() { return this.x; } +} + +var rtc = ReflectionTestClass() +assert_equal("ReflectionTestClass", rtc.get_type_name()) +assert_true(rtc.is_type("ReflectionTestClass")) +assert_equal("Dynamic_Object", type_name(rtc)) +assert_equal("ReflectionTestClass", rtc.get_type_name()) diff --git a/unittests/return_assignment.chai b/unittests/return_assignment.chai new file mode 100644 index 000000000..02a008d4a --- /dev/null +++ b/unittests/return_assignment.chai @@ -0,0 +1,16 @@ +// Test that assignment expressions work inside return statements +// Issue #473: `return foo = 5` should work + +def return_assign() { + var x = 0 + return x = 5 +} + +assert_equal(5, return_assign()) + +def return_member_assign() { + var o = Dynamic_Object() + return o.value = 42 +} + +assert_equal(42, return_member_assign()) diff --git a/unittests/static_chaiscript.cpp b/unittests/static_chaiscript.cpp index 7819d9eb7..61aa3ac73 100644 --- a/unittests/static_chaiscript.cpp +++ b/unittests/static_chaiscript.cpp @@ -6,8 +6,14 @@ /// ChaiScript as a static is unsupported with thread support enabled /// +#if defined(__clang__) +#pragma clang diagnostic push +#pragma clang diagnostic ignored "-Wexit-time-destructors" +#pragma clang diagnostic ignored "-Wglobal-constructors" +#endif + #include -static chaiscript::ChaiScript chai; +static chaiscript::ChaiScript chai{}; int main() {} diff --git a/unittests/strong_typedef.chai b/unittests/strong_typedef.chai new file mode 100644 index 000000000..73aaa4a9b --- /dev/null +++ b/unittests/strong_typedef.chai @@ -0,0 +1,200 @@ +// Strong typedef: using Type = int creates a distinct type +using Meters = int + +def measure(Meters m) { + return m +} + +// Constructing a strong typedef value should work +var m = Meters(42) + +// Calling with the typedef'd value should succeed +measure(m) + +// Calling with a plain int should fail (strong typedef) +try { + measure(42) + assert_equal(true, false) +} catch(e) { + // Expected: type mismatch because int is not Meters +} + +// Multiple strong typedefs from the same base type should be distinct +using Seconds = int + +def wait(Seconds s) { + return s +} + +var s = Seconds(10) +wait(s) + +// Meters and Seconds should not be interchangeable +try { + wait(m) + assert_equal(true, false) +} catch(e) { + // Expected: Meters is not Seconds +} + +try { + measure(s) + assert_equal(true, false) +} catch(e) { + // Expected: Seconds is not Meters +} + +// to_underlying should return the base value +assert_equal(to_underlying(m), 42) +assert_equal(to_underlying(s), 10) + +// to_underlying result should be a plain value, not a strong typedef +def takes_int(int i) { + return i +} +assert_equal(takes_int(to_underlying(m)), 42) + +// --- Arithmetic operators: strongly typed --- +var m2 = Meters(8) +var m_sum = m + m2 +assert_equal(to_underlying(m_sum), 50) +measure(m_sum) + +var m_diff = m - m2 +assert_equal(to_underlying(m_diff), 34) + +var m_prod = Meters(3) * Meters(4) +assert_equal(to_underlying(m_prod), 12) + +var m_quot = Meters(20) / Meters(5) +assert_equal(to_underlying(m_quot), 4) + +var m_rem = Meters(17) % Meters(5) +assert_equal(to_underlying(m_rem), 2) + +// Arithmetic result is strongly typed, not plain int +try { + takes_int(m_sum) + assert_equal(true, false) +} catch(e) { + // Expected: m_sum is Meters, not int +} + +// --- Comparison operators --- +assert_equal(Meters(5) == Meters(5), true) +assert_equal(Meters(5) != Meters(3), true) +assert_equal(Meters(3) < Meters(5), true) +assert_equal(Meters(5) > Meters(3), true) +assert_equal(Meters(5) <= Meters(5), true) +assert_equal(Meters(3) >= Meters(3), true) +assert_equal(Meters(3) >= Meters(5), false) + +// --- Bitwise and shift operators --- +assert_equal(to_underlying(Meters(6) & Meters(3)), 2) +assert_equal(to_underlying(Meters(6) | Meters(3)), 7) +assert_equal(to_underlying(Meters(6) ^ Meters(3)), 5) +assert_equal(to_underlying(Meters(5) << Meters(2)), 20) +assert_equal(to_underlying(Meters(12) >> Meters(1)), 6) + +// Bitwise results are strongly typed +try { + takes_int(Meters(6) & Meters(3)) + assert_equal(true, false) +} catch(e) { + // Expected: result is Meters, not int +} + +// --- Strong typedef over string --- +using StrongString = string + +var ss1 = StrongString("hello") +var ss2 = StrongString(" world") +var ss_cat = ss1 + ss2 +assert_equal(to_underlying(ss_cat), "hello world") + +// StrongString + StrongString -> StrongString (strongly typed) +def takes_strong_string(StrongString ss) { + return ss +} +takes_strong_string(ss_cat) + +// Operators not supported by the underlying type error at call time +try { + var bad = ss1 * ss2 + assert_equal(true, false) +} catch(e) { + // Expected: underlying string has no * operator +} +try { + var bad = ss1 - ss2 + assert_equal(true, false) +} catch(e) { + // Expected: underlying string has no - operator +} +try { + var bad = ss1 / ss2 + assert_equal(true, false) +} catch(e) { + // Expected: underlying string has no / operator +} +try { + var bad = ss1 % ss2 + assert_equal(true, false) +} catch(e) { + // Expected: underlying string has no % operator +} + +// Comparison on StrongString +assert_equal(StrongString("abc") < StrongString("def"), true) +assert_equal(StrongString("abc") == StrongString("abc"), true) +assert_equal(StrongString("abc") != StrongString("def"), true) +assert_equal(StrongString("def") > StrongString("abc"), true) +assert_equal(StrongString("abc") <= StrongString("abc"), true) +assert_equal(StrongString("def") >= StrongString("abc"), true) + +// --- User-defined extensions on strong typedefs --- +def first_char(StrongString ss) { + return to_string(to_underlying(ss)[0]) +} +assert_equal(first_char(StrongString("hello")), "h") + +def double_meters(Meters m) { + return Meters(to_underlying(m) * 2) +} +assert_equal(to_underlying(double_meters(Meters(21))), 42) + +// User-defined operator extension +def `[]`(StrongString ss, int offset) { + return to_string(to_underlying(ss)[offset]) +} +assert_equal(StrongString("hello")[1], "e") + +// --- Compound assignment operators --- +var m3 = Meters(10) +m3 += Meters(5) +assert_equal(to_underlying(m3), 15) +measure(m3) + +m3 -= Meters(3) +assert_equal(to_underlying(m3), 12) + +m3 *= Meters(2) +assert_equal(to_underlying(m3), 24) + +m3 /= Meters(4) +assert_equal(to_underlying(m3), 6) + +m3 %= Meters(4) +assert_equal(to_underlying(m3), 2) + +// Compound assignment result is still the strong typedef +var m4 = Meters(10) +m4 += Meters(5) +assert_equal(to_underlying(m4), 15) +measure(m4) + +// Compound assignment on StrongString +var ss3 = StrongString("hello") +ss3 += StrongString(" world") +assert_equal(to_underlying(ss3), "hello world") +takes_strong_string(ss3) diff --git a/unittests/switch_type_conversion.chai b/unittests/switch_type_conversion.chai new file mode 100644 index 000000000..ff80737f2 --- /dev/null +++ b/unittests/switch_type_conversion.chai @@ -0,0 +1,50 @@ +// Test for issue #421: switch statement with custom == operator on +// dynamic objects must properly manage object lifetimes during +// case comparisons. + +class MyType { + var value + def MyType(v) { this.value = v } +} + +def `==`(a, b) : a.is_type("MyType") && b.is_type("MyType") { + return a.value == b.value +} + +var result = 0 +var obj = MyType(2) + +switch(obj) { + case (MyType(1)) { + result = 1 + break + } + case (MyType(2)) { + result = 2 + break + } + case (MyType(3)) { + result = 3 + break + } +} + +assert_equal(result, 2) + +// Also test fall-through with custom == operator +var total = 0 +var obj2 = MyType(2) + +switch(obj2) { + case (MyType(1)) { + total += 1 + } + case (MyType(2)) { + total += 2 + } + case (MyType(3)) { + total += 4 + } +} + +assert_equal(total, 6) diff --git a/unittests/threading_config_test.cpp b/unittests/threading_config_test.cpp new file mode 100644 index 000000000..7696d13a9 --- /dev/null +++ b/unittests/threading_config_test.cpp @@ -0,0 +1,26 @@ +#include + +int main() { +#ifndef CHAISCRIPT_NO_THREADS + // When threading is enabled, verify that async() is registered and functional + chaiscript::ChaiScript chai; + const auto result = chai.eval(R"( + var f = async(fun() { return 42; }); + f.get(); + )"); + if (result != 42) { + return EXIT_FAILURE; + } +#else + // When threading is disabled, verify that async() is NOT registered + chaiscript::ChaiScript chai; + try { + chai.eval("async(fun() { return 0; })"); + return EXIT_FAILURE; + } catch (const std::exception &) { + // Expected: async should not exist + } +#endif + + return EXIT_SUCCESS; +} diff --git a/unittests/type_info_test.cpp b/unittests/type_info_test.cpp index df984a23e..5b6f3b1f9 100644 --- a/unittests/type_info_test.cpp +++ b/unittests/type_info_test.cpp @@ -16,9 +16,7 @@ #include #include -#define CATCH_CONFIG_MAIN - -#include "catch.hpp" +#include "catch_amalgamated.hpp" TEST_CASE("Type_Info objects generate expected results") { const auto test_type