├── .clang-format ├── .github └── workflows │ ├── cmake.yml │ ├── meson.yml │ └── release.yml ├── .gitignore ├── CMakeLists.txt ├── CODE_OF_CONDUCT.md ├── CONTRIBUTING.md ├── LICENSE ├── README.md ├── cmake ├── Findsnitch.cmake └── snitch-config.cmake.in ├── codecov.yml ├── doc ├── coding_guidelines.md ├── comparison_catch2.md ├── logo-big.png ├── logo-big.svg ├── logo.png ├── logo.svg ├── testing_snitch.md └── vcpkg-example │ ├── CMakeLists.txt │ ├── README.md │ ├── main.cpp │ └── vcpkg.json ├── docker └── Dockerfile.conan-center ├── include └── snitch │ ├── snitch.hpp │ ├── snitch_any.hpp │ ├── snitch_append.hpp │ ├── snitch_capture.hpp │ ├── snitch_cli.hpp │ ├── snitch_concepts.hpp │ ├── snitch_config.hpp.config │ ├── snitch_console.hpp │ ├── snitch_error_handling.hpp │ ├── snitch_expression.hpp │ ├── snitch_file.hpp │ ├── snitch_fixed_point.hpp │ ├── snitch_function.hpp │ ├── snitch_macros_check.hpp │ ├── snitch_macros_check_base.hpp │ ├── snitch_macros_consteval.hpp │ ├── snitch_macros_constexpr.hpp │ ├── snitch_macros_exceptions.hpp │ ├── snitch_macros_misc.hpp │ ├── snitch_macros_reporter.hpp │ ├── snitch_macros_test_case.hpp │ ├── snitch_macros_utility.hpp │ ├── snitch_macros_warnings.hpp │ ├── snitch_main.hpp │ ├── snitch_matcher.hpp │ ├── snitch_registry.hpp │ ├── snitch_reporter_catch2_xml.hpp │ ├── snitch_reporter_console.hpp │ ├── snitch_reporter_teamcity.hpp │ ├── snitch_section.hpp │ ├── snitch_string.hpp │ ├── snitch_string_utility.hpp │ ├── snitch_test_data.hpp │ ├── snitch_time.hpp │ ├── snitch_type_id.hpp │ ├── snitch_type_name.hpp │ └── snitch_vector.hpp ├── make_snitch_all.py ├── meson.build ├── meson_options.txt ├── snitch.sublime-project ├── snitch └── meson.build ├── src ├── snitch.cpp ├── snitch_append.cpp ├── snitch_capture.cpp ├── snitch_cli.cpp ├── snitch_console.cpp ├── snitch_error_handling.cpp ├── snitch_file.cpp ├── snitch_main.cpp ├── snitch_matcher.cpp ├── snitch_registry.cpp ├── snitch_reporter_catch2_xml.cpp ├── snitch_reporter_console.cpp ├── snitch_reporter_teamcity.cpp ├── snitch_section.cpp ├── snitch_string_utility.cpp ├── snitch_test_data.cpp └── snitch_time.cpp └── tests ├── CMakeLists.txt ├── approval_tests ├── data │ ├── actual │ │ └── keepme │ ├── blanked │ │ └── keepme │ └── expected │ │ ├── reporter_catch2_xml_allfail │ │ ├── reporter_catch2_xml_allpass │ │ ├── reporter_catch2_xml_default │ │ ├── reporter_catch2_xml_full │ │ ├── reporter_catch2_xml_list_tests │ │ ├── reporter_catch2_xml_notest │ │ ├── reporter_console_allfail │ │ ├── reporter_console_allpass │ │ ├── reporter_console_default │ │ ├── reporter_console_full │ │ ├── reporter_console_list_tests │ │ ├── reporter_console_notest │ │ ├── reporter_console_withcolor │ │ ├── reporter_teamcity_allfail │ │ ├── reporter_teamcity_allpass │ │ ├── reporter_teamcity_default │ │ ├── reporter_teamcity_full │ │ ├── reporter_teamcity_list_tests │ │ └── reporter_teamcity_notest ├── reporter_catch2_xml.cpp ├── reporter_console.cpp └── reporter_teamcity.cpp ├── install_tests ├── CMakeLists.txt └── standalone.cpp ├── runtime_tests ├── any.cpp ├── capture.cpp ├── check.cpp ├── cli.cpp ├── function_ref.cpp ├── macros.cpp ├── matchers.cpp ├── registry.cpp ├── regressions.cpp ├── section.cpp ├── skip.cpp ├── small_string.cpp ├── small_vector.cpp ├── string_utility.cpp ├── type_id.cpp └── type_name.cpp ├── testing.cpp ├── testing.hpp ├── testing_assertions.hpp ├── testing_event.cpp ├── testing_event.hpp ├── testing_reporters.cpp └── testing_reporters.hpp /.clang-format: -------------------------------------------------------------------------------- 1 | --- 2 | BasedOnStyle: LLVM 3 | AccessModifierOffset: -4 4 | AlignAfterOpenBracket: AlwaysBreak 5 | AlignConsecutiveAssignments: 'true' 6 | AlignConsecutiveDeclarations: 'true' 7 | AlignEscapedNewlines: Right 8 | AlignOperands: 'true' 9 | AlignTrailingComments: 'false' 10 | AllowAllArgumentsOnNextLine: 'true' 11 | AllowAllConstructorInitializersOnNextLine: 'true' 12 | AllowAllParametersOfDeclarationOnNextLine: 'true' 13 | AllowShortBlocksOnASingleLine: 'false' 14 | AllowShortCaseLabelsOnASingleLine: 'true' 15 | AllowShortFunctionsOnASingleLine: Empty 16 | AllowShortIfStatementsOnASingleLine: Never 17 | AllowShortLambdasOnASingleLine: All 18 | AllowShortLoopsOnASingleLine: 'false' 19 | AlwaysBreakAfterReturnType: None 20 | AlwaysBreakBeforeMultilineStrings: 'false' 21 | AlwaysBreakTemplateDeclarations: 'Yes' 22 | BinPackArguments: 'true' 23 | BinPackParameters: 'false' 24 | BreakBeforeBinaryOperators: None 25 | BreakBeforeBraces: Attach 26 | BreakBeforeTernaryOperators: 'true' 27 | BreakBeforeConceptDeclarations: Always 28 | # Only in clang-format 16; but this is what we want 29 | #RequiresExpressionIndentation: OuterScope 30 | BreakConstructorInitializers: AfterColon 31 | BreakInheritanceList: AfterColon 32 | BreakStringLiterals: 'true' 33 | ColumnLimit: '100' 34 | CompactNamespaces: 'true' 35 | ConstructorInitializerAllOnOneLineOrOnePerLine: 'true' 36 | Cpp11BracedListStyle: 'true' 37 | DerivePointerAlignment: 'false' 38 | DisableFormat: 'false' 39 | FixNamespaceComments: 'true' 40 | IncludeBlocks: Regroup 41 | IndentPPDirectives: AfterHash 42 | IndentWidth: '4' 43 | IndentWrappedFunctionNames: 'false' 44 | KeepEmptyLinesAtTheStartOfBlocks: 'true' 45 | Language: Cpp 46 | MaxEmptyLinesToKeep: '1' 47 | PointerAlignment: Left 48 | ReflowComments: 'true' 49 | CommentPragmas: '(\\param |\\returns |\\warning |\\ingroup |\\author |\\date |\\related |\\relates |\\relatesalso |\\deprecated |\\image |\\return |\\brief |\\attention |\\copydoc |\\addtogroup |\\todo |\\tparam |\\see |\\note |\\skip |\\skipline |\\until |\\line |\\dontinclude |\\include|@function|@treturn|@tparam|@classmod|@module|@see)' 50 | SortIncludes: 'true' 51 | SortUsingDeclarations: 'true' 52 | SpaceAfterCStyleCast: 'false' 53 | SpaceAfterLogicalNot: 'false' 54 | SpaceAfterTemplateKeyword: 'false' 55 | SpaceBeforeAssignmentOperators: 'true' 56 | SpaceBeforeCpp11BracedList: 'false' 57 | SpaceBeforeCtorInitializerColon: 'true' 58 | SpaceBeforeInheritanceColon: 'true' 59 | SpaceBeforeParens: ControlStatementsExceptControlMacros 60 | SpaceBeforeRangeBasedForLoopColon: 'true' 61 | SpaceInEmptyParentheses: 'false' 62 | SpacesBeforeTrailingComments: '1' 63 | SpacesInAngles: 'false' 64 | SpacesInCStyleCastParentheses: 'false' 65 | SpacesInParentheses: 'false' 66 | SpacesInSquareBrackets: 'false' 67 | Standard: Cpp11 68 | TabWidth: '4' 69 | UseTab: Never 70 | IfMacros: ['SECTION', 'SNITCH_SECTION'] 71 | ... 72 | -------------------------------------------------------------------------------- /.github/workflows/meson.yml: -------------------------------------------------------------------------------- 1 | name: meson 2 | 3 | on: 4 | workflow_dispatch: 5 | pull_request: 6 | push: 7 | branches: 8 | - main 9 | 10 | jobs: 11 | meson-build: 12 | strategy: 13 | fail-fast: false 14 | matrix: 15 | platform: 16 | - { name: Linux GCC 12, os: ubuntu-latest, compiler: g++12, cxx: "g++-12", backend: "ninja", build: "linux-libstdc++", args: ""} 17 | - { name: Linux GCC 12 nounity, os: ubuntu-latest, compiler: g++12, cxx: "g++-12", backend: "ninja", build: "linux-libstdc++", args: "-Dunity_build=false"} 18 | - { name: Linux GCC 12 shared, os: ubuntu-latest, compiler: g++12, cxx: "g++-12", backend: "ninja", build: "linux-libstdc++", args: "--default-library shared"} 19 | - { name: Linux Clang 15, os: ubuntu-latest, compiler: clang-15, cxx: "clang++-15", backend: "ninja", build: "linux-libc++", args: "-Dcpp_args='-stdlib=libc++' -Dcpp_link_args='-stdlib=libc++'"} 20 | - { name: Windows 64, os: windows-latest, compiler: msvc, cxx: "cl", backend: "vs2022 --vsenv", build: "win64-vs2022", args: ""} 21 | - { name: MacOS, os: macos-latest, compiler: clang++, cxx: "clang++", backend: "ninja", build: "osx-libc++", args: ""} 22 | build-type: 23 | - release 24 | 25 | name: ${{matrix.platform.name}} ${{matrix.build-type}} ${{matrix.config.name}} 26 | runs-on: ${{matrix.platform.os}} 27 | defaults: 28 | run: 29 | shell: bash 30 | 31 | steps: 32 | - name: Checkout code 33 | uses: actions/checkout@v4 34 | 35 | - name: Setup Clang 36 | if: matrix.platform.compiler == 'clang-15' && matrix.platform.os == 'ubuntu-latest' 37 | run: sudo apt install clang-15 libc++-15-dev libc++abi-15-dev 38 | 39 | - uses: actions/setup-python@v4 40 | with: 41 | python-version: '3.x' 42 | 43 | - run: pip install meson ninja 44 | 45 | - name: meson setup 46 | run: CXX=${{matrix.platform.cxx}} meson setup ${{matrix.platform.build}} --backend=${{matrix.platform.backend}} -Dbuildtype=${{matrix.build-type}} -Dprefix=`pwd`/../install ${{matrix.platform.args}} 47 | 48 | - name: Build 49 | run: meson compile -C ${{matrix.platform.build}} 50 | 51 | - name: Install 52 | run: meson install -C ${{matrix.platform.build}} 53 | 54 | - name: Test single header 55 | run: meson test -C ${{matrix.platform.build}} 56 | -------------------------------------------------------------------------------- /.github/workflows/release.yml: -------------------------------------------------------------------------------- 1 | name: release artifacts 2 | on: 3 | release: 4 | types: [published] 5 | 6 | jobs: 7 | publish: 8 | permissions: 9 | contents: write 10 | name: Publish artifacts 11 | runs-on: ubuntu-latest 12 | steps: 13 | - name: Fetch artifacts 14 | uses: dawidd6/action-download-artifact@v9 15 | with: 16 | workflow: cmake.yml 17 | branch: main 18 | skip_unpack: true 19 | allow_forks: false 20 | 21 | - name: Upload artifacts 22 | uses: softprops/action-gh-release@v2 23 | with: 24 | files: '*.zip' 25 | -------------------------------------------------------------------------------- /.gitignore: -------------------------------------------------------------------------------- 1 | compile_commands.json 2 | build/ 3 | build_*/ 4 | doc/html/ 5 | snitch.sublime-workspace 6 | tests/approval_tests/data/actual 7 | tests/approval_tests/data/blanked 8 | install/ 9 | .sublime-tests/ 10 | .cache/ 11 | -------------------------------------------------------------------------------- /CODE_OF_CONDUCT.md: -------------------------------------------------------------------------------- 1 | # Contributor Covenant Code of Conduct 2 | 3 | ## Our Pledge 4 | 5 | We as maintainers, contributors, and mediators pledge to make participation in our 6 | community a harassment-free experience for everyone, regardless of age, body 7 | size, visible or invisible disability, ethnicity, sex characteristics, gender 8 | identity and expression, level of experience, education, socio-economic status, 9 | nationality, personal appearance, race, caste, color, religion, or sexual 10 | identity and orientation. 11 | 12 | We pledge to act and interact in ways that contribute to an open, welcoming, 13 | diverse, inclusive, and healthy community. 14 | 15 | ## Our Standards 16 | 17 | Examples of behavior that contributes to a positive environment for our 18 | community include: 19 | 20 | * Demonstrating empathy and kindness toward other people 21 | * Being respectful of differing opinions, viewpoints, and experiences 22 | * Giving and gracefully accepting constructive feedback 23 | * Accepting responsibility and apologizing to those affected by our mistakes, 24 | and learning from the experience 25 | * Focusing on what is best not just for us as individuals, but for the overall 26 | community 27 | 28 | Examples of unacceptable behavior include: 29 | 30 | * The use of sexualized language or imagery, and sexual attention or advances of 31 | any kind 32 | * Trolling, insulting or derogatory comments, and personal or political attacks 33 | * Public or private harassment 34 | * Publishing others' private information, such as a physical or email address, 35 | without their explicit permission 36 | * Other conduct which could reasonably be considered inappropriate in a 37 | professional setting 38 | 39 | ## Enforcement Responsibilities 40 | 41 | Mediators are responsible for clarifying and enforcing our standards of 42 | acceptable behavior and will take appropriate and fair corrective action in 43 | response to any behavior that they deem inappropriate, threatening, offensive, 44 | or harmful. 45 | 46 | Mediators have the right and responsibility to remove, edit, or reject 47 | comments, commits, code, wiki edits, issues, and other contributions that are 48 | not aligned to this Code of Conduct, and will communicate reasons for moderation 49 | decisions when appropriate. 50 | 51 | ## Scope 52 | 53 | This Code of Conduct applies within all community spaces, and also applies when 54 | an individual is officially representing the community in public spaces. 55 | Examples of representing our community include using an official e-mail address, 56 | posting via an official social media account, or acting as an appointed 57 | representative at an online or offline event. 58 | 59 | ## Enforcement 60 | 61 | Instances of abusive, harassing, or otherwise unacceptable behavior may be 62 | reported to the mediators at 63 | snitch-mediators@corentin.net. 64 | All complaints will be reviewed and investigated promptly and fairly. 65 | 66 | All mediators are obligated to respect the privacy and security of the 67 | reporter of any incident. 68 | 69 | ## Enforcement Guidelines 70 | 71 | Mediators will follow these Community Impact Guidelines in determining 72 | the consequences for any action they deem in violation of this Code of Conduct: 73 | 74 | ### 1. Correction 75 | 76 | **Community Impact**: Use of inappropriate language or other behavior deemed 77 | unprofessional or unwelcome in the community. 78 | 79 | **Consequence**: A private, written warning from mediators, providing 80 | clarity around the nature of the violation and an explanation of why the 81 | behavior was inappropriate. A public apology may be requested. 82 | 83 | ### 2. Warning 84 | 85 | **Community Impact**: A violation through a single incident or series of 86 | actions. 87 | 88 | **Consequence**: A warning with consequences for continued behavior. No 89 | interaction with the people involved, including unsolicited interaction with 90 | those enforcing the Code of Conduct, for a specified period of time. This 91 | includes avoiding interactions in community spaces as well as external channels 92 | like social media. Violating these terms may lead to a temporary or permanent 93 | ban. 94 | 95 | ### 3. Temporary Ban 96 | 97 | **Community Impact**: A serious violation of community standards, including 98 | sustained inappropriate behavior. 99 | 100 | **Consequence**: A temporary ban from any sort of interaction or public 101 | communication with the community for a specified period of time. No public or 102 | private interaction with the people involved, including unsolicited interaction 103 | with those enforcing the Code of Conduct, is allowed during this period. 104 | Violating these terms may lead to a permanent ban. 105 | 106 | ### 4. Permanent Ban 107 | 108 | **Community Impact**: Demonstrating a pattern of violation of community 109 | standards, including sustained inappropriate behavior, harassment of an 110 | individual, or aggression toward or disparagement of classes of individuals. 111 | 112 | **Consequence**: A permanent ban from any sort of public interaction within the 113 | community. 114 | 115 | ## Attribution 116 | 117 | This Code of Conduct is adapted from the [Contributor Covenant][homepage], 118 | version 2.1, available at 119 | [https://www.contributor-covenant.org/version/2/1/code_of_conduct.html][v2.1]. 120 | 121 | Community Impact Guidelines were inspired by 122 | [Mozilla's code of conduct enforcement ladder][Mozilla CoC]. 123 | 124 | For answers to common questions about this code of conduct, see the FAQ at 125 | [https://www.contributor-covenant.org/faq][FAQ]. Translations are available at 126 | [https://www.contributor-covenant.org/translations][translations]. 127 | 128 | [homepage]: https://www.contributor-covenant.org 129 | [v2.1]: https://www.contributor-covenant.org/version/2/1/code_of_conduct.html 130 | [Mozilla CoC]: https://github.com/mozilla/diversity 131 | [FAQ]: https://www.contributor-covenant.org/faq 132 | [translations]: https://www.contributor-covenant.org/translations 133 | 134 | -------------------------------------------------------------------------------- /LICENSE: -------------------------------------------------------------------------------- 1 | Boost Software License - Version 1.0 - August 17th, 2003 2 | 3 | Permission is hereby granted, free of charge, to any person or organization 4 | obtaining a copy of the software and accompanying documentation covered by 5 | this license (the "Software") to use, reproduce, display, distribute, 6 | execute, and transmit the Software, and to prepare derivative works of the 7 | Software, and to permit third-parties to whom the Software is furnished to 8 | do so, all subject to the following: 9 | 10 | The copyright notices in the Software and this entire statement, including 11 | the above license grant, this restriction and the following disclaimer, 12 | must be included in all copies of the Software, in whole or in part, and 13 | all derivative works of the Software, unless such copies or derivative 14 | works are solely in the form of machine-executable object code generated by 15 | a source language processor. 16 | 17 | THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR 18 | IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, 19 | FITNESS FOR A PARTICULAR PURPOSE, TITLE AND NON-INFRINGEMENT. IN NO EVENT 20 | SHALL THE COPYRIGHT HOLDERS OR ANYONE DISTRIBUTING THE SOFTWARE BE LIABLE 21 | FOR ANY DAMAGES OR OTHER LIABILITY, WHETHER IN CONTRACT, TORT OR OTHERWISE, 22 | ARISING FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER 23 | DEALINGS IN THE SOFTWARE. 24 | -------------------------------------------------------------------------------- /cmake/Findsnitch.cmake: -------------------------------------------------------------------------------- 1 | find_path(SNITCH_INCLUDE_DIR snitch.hpp 2 | HINTS ${SNITCH_DIR} 3 | PATH_SUFFIXES snitch include/snitch 4 | ) 5 | 6 | find_library(SNITCH_LIBRARY 7 | NAMES snitch snitch.lib 8 | HINTS ${SNITCH_DIR} 9 | PATH_SUFFIXES lib 10 | ) 11 | 12 | set(SNITCH_INCLUDE_DIRS ${SNITCH_INCLUDE_DIR}) 13 | set(SNITCH_LIBRARIES ${SNITCH_LIBRARY}) 14 | 15 | include(FindPackageHandleStandardArgs) 16 | 17 | FIND_PACKAGE_HANDLE_STANDARD_ARGS(snitch REQUIRED_VARS SNITCH_INCLUDE_DIRS SNITCH_LIBRARIES) 18 | 19 | mark_as_advanced(SNITCH_INCLUDE_DIR SNITCH_LIBRARY) 20 | 21 | if (SNITCH_FOUND) 22 | if(NOT TARGET snitch::snitch) 23 | add_library(snitch::snitch UNKNOWN IMPORTED) 24 | set_target_properties(snitch::snitch PROPERTIES INTERFACE_INCLUDE_DIRECTORIES "${SNITCH_INCLUDE_DIRS}") 25 | set_target_properties(snitch::snitch PROPERTIES INTERFACE_LINK_LIBRARIES "${SNITCH_LIBRARIES}") 26 | set_target_properties(snitch::snitch PROPERTIES IMPORTED_LOCATION "${SNITCH_LIBRARY}") 27 | endif() 28 | endif() 29 | -------------------------------------------------------------------------------- /cmake/snitch-config.cmake.in: -------------------------------------------------------------------------------- 1 | @PACKAGE_INIT@ 2 | 3 | file(GLOB CONFIG_FILES "${CMAKE_CURRENT_LIST_DIR}/snitch*-targets.cmake") 4 | foreach(f ${CONFIG_FILES}) 5 | include(${f}) 6 | 7 | string(REGEX MATCH "${CMAKE_CURRENT_LIST_DIR}/(snitch.*)-targets.cmake" match ${f}) 8 | set(target ${CMAKE_MATCH_1}) 9 | 10 | if (NOT TARGET snitch::${target}) 11 | add_library(snitch::${target} ALIAS ${target}) 12 | endif() 13 | endforeach() 14 | -------------------------------------------------------------------------------- /codecov.yml: -------------------------------------------------------------------------------- 1 | codecov: 2 | require_ci_to_pass: yes 3 | 4 | coverage: 5 | precision: 2 6 | round: down 7 | range: "80...95" 8 | status: 9 | project: 10 | default: 11 | target: auto 12 | # adjust accordingly based on how flaky your tests are 13 | # this allows a drop from the previous base commit coverage 14 | threshold: 1% 15 | patch: 16 | default: 17 | target: 0% 18 | 19 | parsers: 20 | gcov: 21 | branch_detection: 22 | conditional: yes 23 | loop: yes 24 | method: yes 25 | macro: no 26 | 27 | ignore: 28 | - "tests" 29 | 30 | comment: 31 | layout: "reach,diff,flags,files,footer" 32 | behavior: default 33 | require_changes: no 34 | -------------------------------------------------------------------------------- /doc/coding_guidelines.md: -------------------------------------------------------------------------------- 1 | # Guidelines for writing C++ code for *snitch* 2 | 3 | 4 | 5 | - [General](#general) 6 | - [`noexcept`](#noexcept) 7 | - [Heap allocations](#heap-allocations) 8 | - [Heavy headers and compilation time](#heavy-headers-and-compilation-time) 9 | 10 | 11 | 12 | 13 | ## General 14 | 15 | Unless otherwise stated, follow the [C++ Core Guidelines](https://isocpp.github.io/CppCoreGuidelines/CppCoreGuidelines). Below are exceptions to these guidelines, or more opinionated choices. 16 | 17 | 18 | ## `noexcept` 19 | 20 | Follow the [Lakos Rule](https://quuxplusone.github.io/blog/2018/04/25/the-lakos-rule/) (with some tweaks): 21 | - Functions with a wide contract (i.e., with no precondition) should be marked as unconditionally `noexcept`. 22 | - Functions with a narrow contract (i.e., with preconditions), should not be marked as `noexcept`. 23 | - If a template function is conditionally-wide, (e.g., its purpose is only to be a wrapper or adapter over another function), then it may be marked conditionally `noexcept`. 24 | 25 | In particular: 26 | - Do not mark a function `noexcept` just because it happens not to throw. The decision should be based on the *interface* of the function (which includes its pre-condition), and not its implementation. 27 | 28 | Rationale: 29 | - Easy transition to using contracts when they come to C++. 30 | - Enable testing for pre-condition violations by conditionally throwing. 31 | 32 | 33 | ## Heap allocations 34 | 35 | *snitch* code must not directly or indirectly allocate heap (or "free store") memory while running tests. This means that a number of common C++ STL classes cannot be used (at least not with the default allocator): 36 | - `std::string`: use `std::string_view` (for constant strings) or `snitch::small_string` (for variable strings) instead. 37 | - `std::vector`, `std::map`, `std::set`, and their variants: use `std::array` (for fixed size arrays) or `snitch::small_vector` (for variable size arrays) instead. 38 | - `std::function`: use `snitch::function_ref` instead. 39 | - `std::unique_ptr`, `std::shared_ptr`: use values on the stack, and raw pointers for non-owning references. 40 | 41 | Unfortunately, the standard does not generally specify if a function or class allocates heap memory or not. We can make reasonable guesses for simple cases; in particular the following are fine to use: 42 | - `std::string_view`, 43 | - `std::array`, 44 | - `std::span`, 45 | - `std::variant` with `std::get_if` and `std::visit`, 46 | - `std::optional`, 47 | - `std::tuple`, 48 | - Functions in `` ([except `std::stable_sort`, `std::stable_partition`, and `std::inplace_merge`](https://stackoverflow.com/a/46714875/1565581)). 49 | - Concepts and type traits. 50 | 51 | Any type or function not listed above *should* be assumed to use heap memory unless demonstrated otherwise. One way to monitor this on Linux is to use [valgrind/massif](https://valgrind.org/docs/manual/ms-manual.html). 52 | 53 | Note: If necessary, it is acceptable to allocate/de-allocate heap memory when a test is not running, i.e., either at the start or end of the program, or (if single-threaded) in between test cases. For example, as of writing this, with GCC 10 on Linux and the default reporter, *snitch* performs heap allocations on two occasions: 54 | - several allocations at program startup, generated by [`libstdc++` initialisation](https://gcc.gnu.org/bugzilla/show_bug.cgi?id=68606). 55 | - one at the first console output, generated by `glibc` for its internal I/O buffer. 56 | 57 | 58 | ## Heavy headers and compilation time 59 | 60 | One of the advantages of *snitch* over competing testing framework is fast compilation of tests. To preserve this advantage, ["heavy" STL headers](https://artificial-mind.net/projects/compile-health/) should not be included in *snitch* headers unless absolutely necessary. However, they can be included in the *snitch* implementation `*.cpp` files. 61 | 62 | Therefore: 63 | - Place as much code as possible in the `*.cpp` files rather than in headers. 64 | - When not possible (templates, constexpr, etc.), consider if you can use a short and clear handwritten alternative instead. For example, `std::max(a, b)` requires ``, but can also be written as `a > b ? a : b`. Some of the simplest algorithms in ``, like `std::copy`, can also be written with an explicit loop. 65 | - Finally, consider if you really need the full feature from the STL, or just a small subset. For example, if you need a metaprogramming type list and don't need to instanciate the types, don't use `std::tuple<...>`: use a custom `template struct type_list {}` instead. 66 | -------------------------------------------------------------------------------- /doc/logo-big.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/snitch-org/snitch/adbabb9a0c30652339b4a167774a7ec3f710c989/doc/logo-big.png -------------------------------------------------------------------------------- /doc/logo.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/snitch-org/snitch/adbabb9a0c30652339b4a167774a7ec3f710c989/doc/logo.png -------------------------------------------------------------------------------- /doc/vcpkg-example/CMakeLists.txt: -------------------------------------------------------------------------------- 1 | cmake_minimum_required(VERSION 3.14) 2 | 3 | project(vcpkg-example) 4 | 5 | add_executable(vcpkg-example main.cpp) 6 | 7 | find_package(snitch CONFIG REQUIRED) 8 | target_link_libraries(vcpkg-example PRIVATE snitch::snitch) 9 | -------------------------------------------------------------------------------- /doc/vcpkg-example/README.md: -------------------------------------------------------------------------------- 1 | # Example of using *snitch* with vcpkg 2 | 3 | This is an example of building a project with *snitch* as a dependency, using [vcpkg][vcpkg]. 4 | 5 | ## Set up vcpkg 6 | 7 | Clone vcpkg and run the bootstrap script. 8 | 9 | ```bash 10 | git clone https://github.com/microsoft/vcpkg 11 | cd vcpkg && ./bootstrap-vcpkg.bat 12 | ``` 13 | 14 | ## Build and run the project 15 | 16 | ### CMake 17 | 18 | The folder containing this README includes CMake files for an example empty project using *snitch*. You can build this example project with the following code. 19 | ```bash 20 | "In directory of vcpkg-example" 21 | mkdir build 22 | cd build 23 | cmake .. -DCMAKE_TOOLCHAIN_FILE=${VCPKG_ROOT}/scripts/buildsystems/vcpkg.cmake 24 | cmake --build . 25 | ``` 26 | After running the above code, you can find *snitch* installed by vcpkg in a directory named `vcpkg-installed`. 27 | 28 | ### MSbuild 29 | 30 | MSbuild project can be integrated directly with the `vcpkg integrate install` command. 31 | 32 | [vcpkg]: https://github.com/microsoft/vcpkg -------------------------------------------------------------------------------- /doc/vcpkg-example/main.cpp: -------------------------------------------------------------------------------- 1 | #include 2 | 3 | int main(int argc, char* argv[]) { 4 | // Parse the command-line arguments. 5 | std::optional args = snitch::cli::parse_arguments(argc, argv); 6 | if (!args) { 7 | return 1; 8 | } 9 | 10 | snitch::tests.configure(*args); 11 | return snitch::tests.run_tests(*args) ? 0 : 1; 12 | } 13 | -------------------------------------------------------------------------------- /doc/vcpkg-example/vcpkg.json: -------------------------------------------------------------------------------- 1 | { 2 | "name": "vcpkg-example", 3 | "version": "1.2.4", 4 | "dependencies": [ 5 | "snitch" 6 | ] 7 | } 8 | -------------------------------------------------------------------------------- /docker/Dockerfile.conan-center: -------------------------------------------------------------------------------- 1 | # NOTE: This Dockerfile is meant to test the integration of snitch in the Conan center. 2 | # You can use it to get a hint on how to fetch snitch with Conan (v1 or v2). 3 | 4 | # ------------------------------------------- 5 | # Base images 6 | # ------------------------------------------- 7 | # Base image with build tools 8 | FROM ubuntu:latest AS snitch-dev-base 9 | RUN apt update && apt install -y g++ python3 python3-pip python-is-python3 git wget rsync bash 10 | RUN wget -q https://github.com/Kitware/CMake/releases/download/v3.15.7/cmake-3.15.7-Linux-x86_64.tar.gz && \ 11 | tar -xvzf cmake-3.15.7-Linux-x86_64.tar.gz && \ 12 | rm cmake-3.15.7-Linux-x86_64.tar.gz 13 | ENV PATH=$PATH:/cmake-3.15.7-Linux-x86_64/bin 14 | 15 | # Image with cloned conan center repo 16 | FROM snitch-dev-base AS snitch-conan-repo 17 | ARG GIT_BRANCH 18 | RUN git clone https://github.com/cschreib/conan-center-index.git && cd conan-center-index && git checkout ${GIT_BRANCH} 19 | 20 | # ------------------------------------------- 21 | # Conan v1 22 | # ------------------------------------------- 23 | FROM snitch-dev-base AS snitch-conan-v1-base 24 | RUN pip install conan==1.59 25 | RUN conan config install https://github.com/conan-io/hooks.git -sf hooks -tf hooks && \ 26 | conan config set hooks.conan-center 27 | COPY --from=snitch-conan-repo /conan-center-index/recipes/snitch /conan-center-index/recipes/snitch 28 | 29 | # Static 30 | FROM snitch-conan-v1-base AS snitch-conan-v1-static 31 | ARG SNITCH_VERSION 32 | RUN cd /conan-center-index/recipes/snitch && \ 33 | conan create all/conanfile.py snitch/${SNITCH_VERSION}@ -pr:b=default -pr:h=default && \ 34 | conan test all/test_v1_package/conanfile.py snitch/${SNITCH_VERSION}@ -pr:b=default -pr:h=default 35 | 36 | # Header-only 37 | FROM snitch-conan-v1-base AS snitch-conan-v1-header-only 38 | ARG SNITCH_VERSION 39 | RUN cd /conan-center-index/recipes/snitch && \ 40 | conan create all/conanfile.py snitch/${SNITCH_VERSION}@ -pr:b=default -pr:h=default -o snitch:header_only=True && \ 41 | conan test all/test_v1_package/conanfile.py snitch/${SNITCH_VERSION}@ -pr:b=default -pr:h=default -o snitch:header_only=True 42 | 43 | # Shared 44 | FROM snitch-conan-v1-base AS snitch-conan-v1-shared 45 | ARG SNITCH_VERSION 46 | RUN cd /conan-center-index/recipes/snitch && \ 47 | conan create all/conanfile.py snitch/${SNITCH_VERSION}@ -pr:b=default -pr:h=default -o snitch:shared=True && \ 48 | conan test all/test_v1_package/conanfile.py snitch/${SNITCH_VERSION}@ -pr:b=default -pr:h=default -o snitch:shared=True 49 | 50 | # ------------------------------------------- 51 | # Conan v2 52 | # ------------------------------------------- 53 | FROM snitch-dev-base AS snitch-conan-v2-base 54 | RUN pip install conan==2.0 55 | RUN conan profile detect 56 | COPY --from=snitch-conan-repo /conan-center-index/recipes/snitch /conan-center-index/recipes/snitch 57 | 58 | # Static 59 | FROM snitch-conan-v2-base AS snitch-conan-v2-static 60 | ARG SNITCH_VERSION 61 | RUN cd /conan-center-index/recipes/snitch && \ 62 | conan create all/conanfile.py --version ${SNITCH_VERSION} -s compiler.cppstd=20 63 | 64 | # Header-only 65 | FROM snitch-conan-v2-base AS snitch-conan-v2-header-only 66 | ARG SNITCH_VERSION 67 | RUN cd /conan-center-index/recipes/snitch && \ 68 | conan create all/conanfile.py --version ${SNITCH_VERSION} -s compiler.cppstd=20 -o snitch/${SNITCH_VERSION}:header_only=True 69 | 70 | # Shared 71 | FROM snitch-conan-v2-base AS snitch-conan-v2-shared 72 | ARG SNITCH_VERSION 73 | RUN cd /conan-center-index/recipes/snitch && \ 74 | conan create all/conanfile.py --version ${SNITCH_VERSION} -s compiler.cppstd=20 -o snitch/${SNITCH_VERSION}:shared=True 75 | -------------------------------------------------------------------------------- /include/snitch/snitch.hpp: -------------------------------------------------------------------------------- 1 | #ifndef SNITCH_HPP 2 | #define SNITCH_HPP 3 | 4 | #include "snitch/snitch_any.hpp" 5 | #include "snitch/snitch_append.hpp" 6 | #include "snitch/snitch_capture.hpp" 7 | #include "snitch/snitch_cli.hpp" 8 | #include "snitch/snitch_concepts.hpp" 9 | #include "snitch/snitch_config.hpp" 10 | #include "snitch/snitch_console.hpp" 11 | #include "snitch/snitch_error_handling.hpp" 12 | #include "snitch/snitch_expression.hpp" 13 | #include "snitch/snitch_file.hpp" 14 | #include "snitch/snitch_fixed_point.hpp" 15 | #include "snitch/snitch_function.hpp" 16 | #include "snitch/snitch_macros_check.hpp" 17 | #include "snitch/snitch_macros_check_base.hpp" 18 | #include "snitch/snitch_macros_consteval.hpp" 19 | #include "snitch/snitch_macros_constexpr.hpp" 20 | #include "snitch/snitch_macros_exceptions.hpp" 21 | #include "snitch/snitch_macros_misc.hpp" 22 | #include "snitch/snitch_macros_reporter.hpp" 23 | #include "snitch/snitch_macros_test_case.hpp" 24 | #include "snitch/snitch_macros_utility.hpp" 25 | #include "snitch/snitch_macros_warnings.hpp" 26 | #include "snitch/snitch_main.hpp" 27 | #include "snitch/snitch_matcher.hpp" 28 | #include "snitch/snitch_registry.hpp" 29 | #include "snitch/snitch_reporter_catch2_xml.hpp" 30 | #include "snitch/snitch_reporter_console.hpp" 31 | #include "snitch/snitch_reporter_teamcity.hpp" 32 | #include "snitch/snitch_section.hpp" 33 | #include "snitch/snitch_string.hpp" 34 | #include "snitch/snitch_string_utility.hpp" 35 | #include "snitch/snitch_test_data.hpp" 36 | #include "snitch/snitch_type_id.hpp" 37 | #include "snitch/snitch_type_name.hpp" 38 | #include "snitch/snitch_vector.hpp" 39 | 40 | #endif 41 | -------------------------------------------------------------------------------- /include/snitch/snitch_any.hpp: -------------------------------------------------------------------------------- 1 | #ifndef SNITCH_ANY_HPP 2 | #define SNITCH_ANY_HPP 3 | 4 | #include "snitch/snitch_config.hpp" 5 | #include "snitch/snitch_error_handling.hpp" 6 | #include "snitch/snitch_function.hpp" 7 | #include "snitch/snitch_type_id.hpp" 8 | 9 | #include 10 | #include 11 | #include 12 | 13 | namespace snitch { 14 | namespace impl { 15 | template 16 | void delete_object(char* storage) noexcept { 17 | reinterpret_cast(storage)->~T(); 18 | } 19 | } // namespace impl 20 | 21 | template 22 | class inplace_any { 23 | std::array storage = {}; 24 | function_ref deleter = [](char*) noexcept {}; 25 | type_id_t id = type_id(); 26 | 27 | void release() noexcept { 28 | deleter = [](char*) noexcept {}; 29 | id = type_id(); 30 | } 31 | 32 | public: 33 | constexpr inplace_any() = default; 34 | 35 | inplace_any(const inplace_any&) = delete; 36 | 37 | constexpr inplace_any(inplace_any&& other) noexcept : 38 | storage(other.storage), deleter(other.deleter), id(other.id) { 39 | other.release(); 40 | } 41 | 42 | inplace_any& operator=(const inplace_any&) = delete; 43 | 44 | constexpr inplace_any& operator=(inplace_any&& other) noexcept { 45 | reset(); 46 | storage = other.storage; 47 | deleter = other.deleter; 48 | id = other.id; 49 | other.release(); 50 | return *this; 51 | } 52 | 53 | template 54 | explicit inplace_any(std::in_place_type_t, Args&&... args) { 55 | emplace(std::forward(args)...); 56 | } 57 | 58 | ~inplace_any() { 59 | reset(); 60 | } 61 | 62 | bool has_value() const noexcept { 63 | return id != type_id(); 64 | } 65 | 66 | type_id_t type() const noexcept { 67 | return id; 68 | } 69 | 70 | template 71 | void emplace(Args&&... args) { 72 | static_assert( 73 | sizeof(T) <= MaxSize, 74 | "This type is too large to fit in this inplace_any, increase storage size"); 75 | 76 | reset(); 77 | new (storage.data()) T(std::forward(args)...); 78 | deleter = &impl::delete_object; 79 | id = type_id(); 80 | } 81 | 82 | // Requires: not empty and stored type == T. 83 | template 84 | const T& get() const { 85 | if (!has_value()) { 86 | assertion_failed("inplace_any is empty"); 87 | } 88 | if (type() != type_id()) { 89 | assertion_failed("inplace_any holds an object of a different type"); 90 | } 91 | 92 | return *reinterpret_cast(storage.data()); 93 | } 94 | 95 | // Requires: not empty and stored type == T. 96 | template 97 | T& get() { 98 | return const_cast(const_cast(this)->get()); 99 | } 100 | 101 | void reset() noexcept { 102 | deleter(storage.data()); 103 | release(); 104 | } 105 | }; 106 | } // namespace snitch 107 | 108 | #endif 109 | -------------------------------------------------------------------------------- /include/snitch/snitch_capture.hpp: -------------------------------------------------------------------------------- 1 | #ifndef SNITCH_CAPTURE_HPP 2 | #define SNITCH_CAPTURE_HPP 3 | 4 | #include "snitch/snitch_config.hpp" 5 | #include "snitch/snitch_string.hpp" 6 | #include "snitch/snitch_string_utility.hpp" 7 | #include "snitch/snitch_test_data.hpp" 8 | 9 | #include 10 | #include 11 | 12 | namespace snitch::impl { 13 | struct scoped_capture { 14 | test_state& state; 15 | std::size_t count = 0; 16 | 17 | SNITCH_EXPORT ~scoped_capture(); 18 | }; 19 | 20 | SNITCH_EXPORT std::string_view extract_next_name(std::string_view& names) noexcept; 21 | 22 | struct test_state; 23 | 24 | // Requires: number of captures < max_captures. 25 | SNITCH_EXPORT small_string& add_capture(test_state& state); 26 | 27 | // Requires: number of captures < max_captures. 28 | template 29 | void add_capture(test_state& state, std::string_view& names, const T& arg) { 30 | auto& capture = add_capture(state); 31 | append_or_truncate(capture, extract_next_name(names), " := ", arg); 32 | } 33 | 34 | // Requires: number of captures < max_captures. 35 | template 36 | scoped_capture add_captures(test_state& state, std::string_view names, const Args&... args) { 37 | (add_capture(state, names, args), ...); 38 | return {state, sizeof...(args)}; 39 | } 40 | 41 | // Requires: number of captures < max_captures. 42 | template 43 | scoped_capture add_info(test_state& state, const Args&... args) { 44 | auto& capture = add_capture(state); 45 | append_or_truncate(capture, args...); 46 | return {state, 1}; 47 | } 48 | } // namespace snitch::impl 49 | 50 | #endif 51 | -------------------------------------------------------------------------------- /include/snitch/snitch_cli.hpp: -------------------------------------------------------------------------------- 1 | #ifndef SNITCH_CLI_HPP 2 | #define SNITCH_CLI_HPP 3 | 4 | #include "snitch/snitch_config.hpp" 5 | #include "snitch/snitch_function.hpp" 6 | #include "snitch/snitch_string.hpp" 7 | #include "snitch/snitch_string_utility.hpp" 8 | #include "snitch/snitch_vector.hpp" 9 | 10 | #include 11 | #include 12 | #include 13 | 14 | // Testing framework configuration. 15 | // -------------------------------- 16 | 17 | namespace snitch { 18 | // Maximum number of command line arguments. 19 | constexpr std::size_t max_command_line_args = SNITCH_MAX_COMMAND_LINE_ARGS; 20 | } // namespace snitch 21 | 22 | namespace snitch::cli { 23 | struct argument { 24 | std::string_view name = {}; 25 | std::optional value_name = {}; 26 | std::optional value = {}; 27 | }; 28 | 29 | struct input { 30 | std::string_view executable = {}; 31 | small_vector arguments = {}; 32 | }; 33 | 34 | SNITCH_EXPORT extern function_ref console_print; 35 | 36 | template 37 | void print(Args&&... args) noexcept { 38 | small_string message; 39 | append_or_truncate(message, std::forward(args)...); 40 | console_print(message); 41 | } 42 | 43 | struct print_help_settings { 44 | bool with_color = true; 45 | }; 46 | 47 | SNITCH_EXPORT void print_help( 48 | std::string_view program_name, 49 | const print_help_settings& settings = print_help_settings{}) noexcept; 50 | 51 | SNITCH_EXPORT std::optional parse_arguments(int argc, const char* const argv[]) noexcept; 52 | 53 | SNITCH_EXPORT std::optional 54 | get_option(const cli::input& args, std::string_view name) noexcept; 55 | 56 | SNITCH_EXPORT std::optional 57 | get_positional_argument(const cli::input& args, std::string_view name) noexcept; 58 | 59 | SNITCH_EXPORT void for_each_positional_argument( 60 | const cli::input& args, 61 | std::string_view name, 62 | const function_ref& callback) noexcept; 63 | } // namespace snitch::cli 64 | 65 | #endif 66 | -------------------------------------------------------------------------------- /include/snitch/snitch_concepts.hpp: -------------------------------------------------------------------------------- 1 | #ifndef SNITCH_CONCEPTS_HPP 2 | #define SNITCH_CONCEPTS_HPP 3 | 4 | #include "snitch/snitch_config.hpp" 5 | 6 | #include 7 | 8 | namespace snitch { 9 | 10 | template 11 | concept integral = std::is_integral_v; 12 | 13 | template 14 | concept signed_integral = integral && std::is_signed_v; 15 | 16 | template 17 | concept unsigned_integral = integral && std::is_unsigned_v; 18 | 19 | template 20 | concept floating_point = std::is_floating_point_v; 21 | 22 | template 23 | concept convertible_to = std::is_convertible_v; 24 | 25 | template 26 | concept same_as = std::is_same_v; 27 | 28 | template 29 | concept enumeration = std::is_enum_v; 30 | 31 | namespace impl { 32 | template 33 | using decay_object = std::remove_cv_t>; 34 | 35 | template 36 | struct is_function_pointer : std::false_type {}; 37 | template 38 | struct is_function_pointer : std::is_function {}; 39 | } // namespace impl 40 | 41 | template 42 | struct is_function_pointer : impl::is_function_pointer> {}; 43 | 44 | template 45 | constexpr bool is_function_pointer_v = is_function_pointer::value; 46 | 47 | template 48 | concept function_pointer = is_function_pointer_v>; 49 | 50 | template 51 | concept member_function_pointer = std::is_member_function_pointer_v>; 52 | 53 | template 54 | using char_array = char[N]; 55 | 56 | template 57 | struct is_raw_string : std::false_type {}; 58 | template 59 | struct is_raw_string> : std::true_type {}; 60 | 61 | template 62 | constexpr bool is_raw_string_v = is_raw_string::value; 63 | 64 | template 65 | concept raw_string = is_raw_string_v>; 66 | 67 | template 68 | concept pointer = std::is_pointer_v>; 69 | } // namespace snitch 70 | 71 | #endif 72 | -------------------------------------------------------------------------------- /include/snitch/snitch_config.hpp.config: -------------------------------------------------------------------------------- 1 | #ifndef SNITCH_CONFIG_HPP 2 | #define SNITCH_CONFIG_HPP 3 | 4 | #include // for C++ feature check macros 5 | 6 | // These are defined from build-time configuration. 7 | // clang-format off 8 | #define SNITCH_VERSION "${PROJECT_VERSION}" 9 | #define SNITCH_FULL_VERSION "${SNITCH_FULL_VERSION}" 10 | #define SNITCH_VERSION_MAJOR ${PROJECT_VERSION_MAJOR} 11 | #define SNITCH_VERSION_MINOR ${PROJECT_VERSION_MINOR} 12 | #define SNITCH_VERSION_PATCH ${PROJECT_VERSION_PATCH} 13 | 14 | #if !defined(SNITCH_MAX_TEST_CASES) 15 | # define SNITCH_MAX_TEST_CASES ${SNITCH_MAX_TEST_CASES} 16 | #endif 17 | #if !defined(SNITCH_MAX_NESTED_SECTIONS) 18 | # define SNITCH_MAX_NESTED_SECTIONS ${SNITCH_MAX_NESTED_SECTIONS} 19 | #endif 20 | #if !defined(SNITCH_MAX_EXPR_LENGTH) 21 | # define SNITCH_MAX_EXPR_LENGTH ${SNITCH_MAX_EXPR_LENGTH} 22 | #endif 23 | #if !defined(SNITCH_MAX_MESSAGE_LENGTH) 24 | # define SNITCH_MAX_MESSAGE_LENGTH ${SNITCH_MAX_MESSAGE_LENGTH} 25 | #endif 26 | #if !defined(SNITCH_MAX_TEST_NAME_LENGTH) 27 | # define SNITCH_MAX_TEST_NAME_LENGTH ${SNITCH_MAX_TEST_NAME_LENGTH} 28 | #endif 29 | #if !defined(SNITCH_MAX_TAG_LENGTH) 30 | # define SNITCH_MAX_TAG_LENGTH ${SNITCH_MAX_TAG_LENGTH} 31 | #endif 32 | #if !defined(SNITCH_MAX_CAPTURES) 33 | # define SNITCH_MAX_CAPTURES ${SNITCH_MAX_CAPTURES} 34 | #endif 35 | #if !defined(SNITCH_MAX_CAPTURE_LENGTH) 36 | # define SNITCH_MAX_CAPTURE_LENGTH ${SNITCH_MAX_CAPTURE_LENGTH} 37 | #endif 38 | #if !defined(SNITCH_MAX_UNIQUE_TAGS) 39 | # define SNITCH_MAX_UNIQUE_TAGS ${SNITCH_MAX_UNIQUE_TAGS} 40 | #endif 41 | #if !defined(SNITCH_MAX_COMMAND_LINE_ARGS) 42 | # define SNITCH_MAX_COMMAND_LINE_ARGS ${SNITCH_MAX_COMMAND_LINE_ARGS} 43 | #endif 44 | #if !defined(SNITCH_MAX_REGISTERED_REPORTERS) 45 | # define SNITCH_MAX_REGISTERED_REPORTERS ${SNITCH_MAX_REGISTERED_REPORTERS} 46 | #endif 47 | #if !defined(SNITCH_MAX_PATH_LENGTH) 48 | # define SNITCH_MAX_PATH_LENGTH ${SNITCH_MAX_PATH_LENGTH} 49 | #endif 50 | #if !defined(SNITCH_MAX_REPORTER_SIZE_BYTES) 51 | # define SNITCH_MAX_REPORTER_SIZE_BYTES ${SNITCH_MAX_REPORTER_SIZE_BYTES} 52 | #endif 53 | #if !defined(SNITCH_DEFINE_MAIN) 54 | #cmakedefine01 SNITCH_DEFINE_MAIN 55 | #endif 56 | #if !defined(SNITCH_WITH_EXCEPTIONS) 57 | #cmakedefine01 SNITCH_WITH_EXCEPTIONS 58 | #endif 59 | #if !defined(SNITCH_WITH_TIMINGS) 60 | #cmakedefine01 SNITCH_WITH_TIMINGS 61 | #endif 62 | #if !defined(SNITCH_WITH_SHORTHAND_MACROS) 63 | #cmakedefine01 SNITCH_WITH_SHORTHAND_MACROS 64 | #endif 65 | #if !defined(SNITCH_DEFAULT_WITH_COLOR) 66 | #cmakedefine01 SNITCH_DEFAULT_WITH_COLOR 67 | #endif 68 | #if !defined(SNITCH_CONSTEXPR_FLOAT_USE_BITCAST) 69 | #cmakedefine01 SNITCH_CONSTEXPR_FLOAT_USE_BITCAST 70 | #endif 71 | #if !defined(SNITCH_APPEND_TO_CHARS) 72 | #cmakedefine01 SNITCH_APPEND_TO_CHARS 73 | #endif 74 | #if !defined(SNITCH_DECOMPOSE_SUCCESSFUL_ASSERTIONS) 75 | #cmakedefine01 SNITCH_DECOMPOSE_SUCCESSFUL_ASSERTIONS 76 | #endif 77 | #if !defined(SNITCH_WITH_ALL_REPORTERS) 78 | #cmakedefine01 SNITCH_WITH_ALL_REPORTERS 79 | #endif 80 | #if !defined(SNITCH_WITH_TEAMCITY_REPORTER) 81 | #cmakedefine01 SNITCH_WITH_TEAMCITY_REPORTER 82 | #endif 83 | #if !defined(SNITCH_WITH_CATCH2_XML_REPORTER) 84 | #cmakedefine01 SNITCH_WITH_CATCH2_XML_REPORTER 85 | #endif 86 | #if !defined(SNITCH_WITH_MULTITHREADING) 87 | #cmakedefine01 SNITCH_WITH_MULTITHREADING 88 | #endif 89 | #if !defined(SNITCH_SHARED_LIBRARY) 90 | #cmakedefine01 SNITCH_SHARED_LIBRARY 91 | #endif 92 | #if !defined(SNITCH_ENABLE) 93 | #cmakedefine01 SNITCH_ENABLE 94 | #endif 95 | // clang-format on 96 | 97 | #if defined(_MSC_VER) 98 | # if defined(_KERNEL_MODE) || (defined(_HAS_EXCEPTIONS) && !_HAS_EXCEPTIONS) 99 | # define SNITCH_EXCEPTIONS_NOT_AVAILABLE 100 | # endif 101 | #elif defined(__clang__) || defined(__GNUC__) 102 | # if !defined(__EXCEPTIONS) 103 | # define SNITCH_EXCEPTIONS_NOT_AVAILABLE 104 | # endif 105 | #endif 106 | 107 | #if defined(SNITCH_EXCEPTIONS_NOT_AVAILABLE) 108 | # undef SNITCH_WITH_EXCEPTIONS 109 | # define SNITCH_WITH_EXCEPTIONS 0 110 | #endif 111 | 112 | #if SNITCH_WITH_MULTITHREADING 113 | # define SNITCH_THREAD_LOCAL thread_local 114 | #else 115 | # define SNITCH_THREAD_LOCAL 116 | #endif 117 | 118 | #if !defined(__cpp_lib_bit_cast) 119 | # undef SNITCH_CONSTEXPR_FLOAT_USE_BITCAST 120 | # define SNITCH_CONSTEXPR_FLOAT_USE_BITCAST 0 121 | #endif 122 | 123 | #if (!defined(__cpp_lib_to_chars)) || (defined(_GLIBCXX_RELEASE) && _GLIBCXX_RELEASE <= 11) || \ 124 | (defined(_LIBCPP_VERSION) && _LIBCPP_VERSION <= 14000) || \ 125 | (defined(_MSC_VER) && _MSC_VER <= 1924) 126 | # undef SNITCH_APPEND_TO_CHARS 127 | # define SNITCH_APPEND_TO_CHARS 0 128 | #endif 129 | 130 | #if SNITCH_SHARED_LIBRARY 131 | # if defined(_MSC_VER) 132 | # if defined(SNITCH_EXPORTS) 133 | # define SNITCH_EXPORT __declspec(dllexport) 134 | # else 135 | # define SNITCH_EXPORT __declspec(dllimport) 136 | # endif 137 | # elif defined(__clang__) || defined(__GNUC__) 138 | # define SNITCH_EXPORT [[gnu::visibility("default")]] 139 | # else 140 | # define SNITCH_EXPORT 141 | # endif 142 | #else 143 | # define SNITCH_EXPORT 144 | #endif 145 | 146 | #endif 147 | -------------------------------------------------------------------------------- /include/snitch/snitch_console.hpp: -------------------------------------------------------------------------------- 1 | #ifndef SNITCH_CONSOLE_HPP 2 | #define SNITCH_CONSOLE_HPP 3 | 4 | #include "snitch/snitch_string.hpp" 5 | #include "snitch/snitch_string_utility.hpp" 6 | 7 | #include 8 | 9 | namespace snitch::impl { 10 | SNITCH_EXPORT void stdout_print(std::string_view message) noexcept; 11 | 12 | using color_t = std::string_view; 13 | 14 | namespace color { 15 | constexpr color_t error [[maybe_unused]] = "\x1b[1;31m"; 16 | constexpr color_t warning [[maybe_unused]] = "\x1b[1;33m"; 17 | constexpr color_t status [[maybe_unused]] = "\x1b[1;36m"; 18 | constexpr color_t fail [[maybe_unused]] = "\x1b[1;31m"; 19 | constexpr color_t skipped [[maybe_unused]] = "\x1b[1;33m"; 20 | constexpr color_t pass [[maybe_unused]] = "\x1b[1;32m"; 21 | constexpr color_t highlight1 [[maybe_unused]] = "\x1b[1;35m"; 22 | constexpr color_t highlight2 [[maybe_unused]] = "\x1b[1;36m"; 23 | constexpr color_t reset [[maybe_unused]] = "\x1b[0m"; 24 | } // namespace color 25 | 26 | template 27 | struct colored { 28 | const T& value; 29 | color_t color_start; 30 | color_t color_end; 31 | }; 32 | 33 | template 34 | colored make_colored(const T& t, bool with_color, color_t start) noexcept { 35 | return {t, with_color ? start : "", with_color ? color::reset : ""}; 36 | } 37 | 38 | template 39 | bool append(small_string_span ss, const colored& colored_value) noexcept { 40 | if (ss.available() <= colored_value.color_start.size() + colored_value.color_end.size()) { 41 | return false; 42 | } 43 | 44 | bool could_fit = true; 45 | if (!append(ss, colored_value.color_start, colored_value.value)) { 46 | ss.resize(ss.capacity() - colored_value.color_end.size()); 47 | could_fit = false; 48 | } 49 | 50 | return append(ss, colored_value.color_end) && could_fit; 51 | } 52 | } // namespace snitch::impl 53 | 54 | #endif 55 | -------------------------------------------------------------------------------- /include/snitch/snitch_error_handling.hpp: -------------------------------------------------------------------------------- 1 | #ifndef SNITCH_ERROR_HANDLING_HPP 2 | #define SNITCH_ERROR_HANDLING_HPP 3 | 4 | #include "snitch/snitch_config.hpp" 5 | #include "snitch/snitch_function.hpp" 6 | 7 | #include 8 | 9 | namespace snitch { 10 | // Maximum length of error messages. 11 | constexpr std::size_t max_message_length = SNITCH_MAX_MESSAGE_LENGTH; 12 | 13 | [[noreturn]] SNITCH_EXPORT void terminate_with(std::string_view msg) noexcept; 14 | 15 | SNITCH_EXPORT extern function_ref assertion_failed_handler; 16 | 17 | [[noreturn]] SNITCH_EXPORT void assertion_failed(std::string_view msg); 18 | } // namespace snitch 19 | 20 | #endif 21 | -------------------------------------------------------------------------------- /include/snitch/snitch_file.hpp: -------------------------------------------------------------------------------- 1 | #ifndef SNITCH_FILE_HPP 2 | #define SNITCH_FILE_HPP 3 | 4 | #include "snitch/snitch_config.hpp" 5 | 6 | #include 7 | 8 | namespace snitch::impl { 9 | // Maximum length of a file path. 10 | constexpr std::size_t max_path_length = SNITCH_MAX_PATH_LENGTH; 11 | 12 | class file_writer { 13 | void* file_handle = nullptr; 14 | 15 | public: 16 | SNITCH_EXPORT constexpr file_writer() noexcept = default; 17 | 18 | // Requires: permission to write to the given path, path length less than max_path_length 19 | SNITCH_EXPORT explicit file_writer(std::string_view path); 20 | 21 | file_writer(const file_writer&) = delete; 22 | file_writer& operator=(const file_writer&) = delete; 23 | 24 | SNITCH_EXPORT file_writer(file_writer&& other) noexcept; 25 | 26 | SNITCH_EXPORT file_writer& operator=(file_writer&& other) noexcept; 27 | 28 | SNITCH_EXPORT ~file_writer(); 29 | 30 | SNITCH_EXPORT void write(std::string_view message) noexcept; 31 | }; 32 | } // namespace snitch::impl 33 | 34 | #endif 35 | -------------------------------------------------------------------------------- /include/snitch/snitch_macros_check.hpp: -------------------------------------------------------------------------------- 1 | #ifndef SNITCH_MACROS_CHECK_HPP 2 | #define SNITCH_MACROS_CHECK_HPP 3 | 4 | #include "snitch/snitch_config.hpp" 5 | #include "snitch/snitch_expression.hpp" 6 | #include "snitch/snitch_macros_check_base.hpp" 7 | #include "snitch/snitch_macros_utility.hpp" 8 | #include "snitch/snitch_macros_warnings.hpp" 9 | #include "snitch/snitch_matcher.hpp" 10 | #include "snitch/snitch_registry.hpp" 11 | #include "snitch/snitch_test_data.hpp" 12 | 13 | #if SNITCH_ENABLE 14 | 15 | # define SNITCH_REQUIRE_IMPL(CHECK, EXPECTED, MAYBE_ABORT, ...) \ 16 | do { \ 17 | auto SNITCH_CURRENT_CHECK = SNITCH_NEW_CHECK; \ 18 | SNITCH_WARNING_PUSH \ 19 | SNITCH_WARNING_DISABLE_PARENTHESES \ 20 | SNITCH_WARNING_DISABLE_CONSTANT_COMPARISON \ 21 | if constexpr (SNITCH_IS_DECOMPOSABLE(__VA_ARGS__)) { \ 22 | SNITCH_EXPR(CHECK, EXPECTED, __VA_ARGS__); \ 23 | SNITCH_REPORT_EXPRESSION(MAYBE_ABORT); \ 24 | } else { \ 25 | const auto SNITCH_CURRENT_EXPRESSION = snitch::impl::expression{ \ 26 | CHECK, #__VA_ARGS__, {}, static_cast(__VA_ARGS__) == EXPECTED}; \ 27 | SNITCH_REPORT_EXPRESSION(MAYBE_ABORT); \ 28 | } \ 29 | SNITCH_WARNING_POP \ 30 | } while (0) 31 | 32 | // clang-format off 33 | # define SNITCH_REQUIRE(...) SNITCH_REQUIRE_IMPL("REQUIRE", true, SNITCH_TESTING_ABORT, __VA_ARGS__) 34 | # define SNITCH_CHECK(...) SNITCH_REQUIRE_IMPL("CHECK", true, (void)0, __VA_ARGS__) 35 | # define SNITCH_REQUIRE_FALSE(...) SNITCH_REQUIRE_IMPL("REQUIRE_FALSE", false, SNITCH_TESTING_ABORT, __VA_ARGS__) 36 | # define SNITCH_CHECK_FALSE(...) SNITCH_REQUIRE_IMPL("CHECK_FALSE", false, (void)0, __VA_ARGS__) 37 | // clang-format on 38 | 39 | # define SNITCH_SUCCEED(MESSAGE) \ 40 | do { \ 41 | auto SNITCH_CURRENT_CHECK = SNITCH_NEW_CHECK; \ 42 | snitch::registry::report_assertion(true, (MESSAGE)); \ 43 | } while (0) 44 | 45 | # define SNITCH_FAIL(MESSAGE) \ 46 | do { \ 47 | auto SNITCH_CURRENT_CHECK = SNITCH_NEW_CHECK; \ 48 | snitch::registry::report_assertion(false, (MESSAGE)); \ 49 | SNITCH_TESTING_ABORT; \ 50 | } while (0) 51 | 52 | # define SNITCH_FAIL_CHECK(MESSAGE) \ 53 | do { \ 54 | auto SNITCH_CURRENT_CHECK = SNITCH_NEW_CHECK; \ 55 | snitch::registry::report_assertion(false, (MESSAGE)); \ 56 | } while (0) 57 | 58 | # define SNITCH_SKIP(MESSAGE) \ 59 | do { \ 60 | auto SNITCH_CURRENT_CHECK = SNITCH_NEW_CHECK; \ 61 | snitch::registry::report_skipped((MESSAGE)); \ 62 | SNITCH_TESTING_ABORT; \ 63 | } while (0) 64 | 65 | # define SNITCH_SKIP_CHECK(MESSAGE) \ 66 | do { \ 67 | auto SNITCH_CURRENT_CHECK = SNITCH_NEW_CHECK; \ 68 | snitch::registry::report_skipped((MESSAGE)); \ 69 | } while (0) 70 | 71 | # define SNITCH_REQUIRE_THAT_IMPL(CHECK, MAYBE_ABORT, EXPR, ...) \ 72 | do { \ 73 | auto SNITCH_CURRENT_CHECK = SNITCH_NEW_CHECK; \ 74 | const auto SNITCH_TEMP_RESULT = snitch::impl::match(EXPR, __VA_ARGS__); \ 75 | const auto SNITCH_CURRENT_EXPRESSION = snitch::impl::expression{ \ 76 | CHECK, #EXPR ", " #__VA_ARGS__, \ 77 | snitch::resize_or_truncate(SNITCH_TEMP_RESULT.second), \ 78 | SNITCH_TEMP_RESULT.first}; \ 79 | SNITCH_REPORT_EXPRESSION(MAYBE_ABORT); \ 80 | } while (0) 81 | 82 | // clang-format off 83 | # define SNITCH_REQUIRE_THAT(EXPR, ...) SNITCH_REQUIRE_THAT_IMPL("REQUIRE_THAT", SNITCH_TESTING_ABORT, EXPR, __VA_ARGS__) 84 | # define SNITCH_CHECK_THAT(EXPR, ...) SNITCH_REQUIRE_THAT_IMPL("CHECK_THAT", (void)0, EXPR, __VA_ARGS__) 85 | // clang-format on 86 | 87 | #else // SNITCH_ENABLE 88 | // clang-format off 89 | # define SNITCH_REQUIRE(...) SNITCH_DISCARD_ARGS(__VA_ARGS__) 90 | # define SNITCH_CHECK(...) SNITCH_DISCARD_ARGS(__VA_ARGS__) 91 | # define SNITCH_REQUIRE_FALSE(...) SNITCH_DISCARD_ARGS(__VA_ARGS__) 92 | # define SNITCH_CHECK_FALSE(...) SNITCH_DISCARD_ARGS(__VA_ARGS__) 93 | 94 | # define SNITCH_SUCCEED(MESSAGE) SNITCH_VOID_STATEMENT 95 | # define SNITCH_FAIL(MESSAGE) SNITCH_VOID_STATEMENT 96 | # define SNITCH_FAIL_CHECK(MESSAGE) SNITCH_VOID_STATEMENT 97 | # define SNITCH_SKIP(MESSAGE) SNITCH_VOID_STATEMENT 98 | # define SNITCH_SKIP_CHECK(MESSAGE) SNITCH_VOID_STATEMENT 99 | 100 | # define SNITCH_REQUIRE_THAT(EXPR, ...) SNITCH_DISCARD_ARGS(EXPR, __VA_ARGS__) 101 | # define SNITCH_CHECK_THAT(EXPR, ...) SNITCH_DISCARD_ARGS(EXPR, __VA_ARGS__) 102 | // clang-format on 103 | 104 | #endif // SNITCH_ENABLE 105 | 106 | // clang-format on 107 | #if SNITCH_WITH_SHORTHAND_MACROS 108 | # define SUCCEED(MESSAGE) SNITCH_SUCCEED(MESSAGE) 109 | # define FAIL(MESSAGE) SNITCH_FAIL(MESSAGE) 110 | # define FAIL_CHECK(MESSAGE) SNITCH_FAIL_CHECK(MESSAGE) 111 | # define SKIP(MESSAGE) SNITCH_SKIP(MESSAGE) 112 | # define SKIP_CHECK(MESSAGE) SNITCH_SKIP_CHECK(MESSAGE) 113 | 114 | # define REQUIRE(...) SNITCH_REQUIRE(__VA_ARGS__) 115 | # define CHECK(...) SNITCH_CHECK(__VA_ARGS__) 116 | # define REQUIRE_FALSE(...) SNITCH_REQUIRE_FALSE(__VA_ARGS__) 117 | # define CHECK_FALSE(...) SNITCH_CHECK_FALSE(__VA_ARGS__) 118 | # define REQUIRE_THAT(EXP, ...) SNITCH_REQUIRE_THAT(EXP, __VA_ARGS__) 119 | # define CHECK_THAT(EXP, ...) SNITCH_CHECK_THAT(EXP, __VA_ARGS__) 120 | #endif 121 | // clang-format on 122 | 123 | #endif 124 | -------------------------------------------------------------------------------- /include/snitch/snitch_macros_check_base.hpp: -------------------------------------------------------------------------------- 1 | #ifndef SNITCH_MACROS_CHECK_BASE_HPP 2 | #define SNITCH_MACROS_CHECK_BASE_HPP 3 | 4 | #include "snitch/snitch_config.hpp" 5 | #include "snitch/snitch_expression.hpp" 6 | #include "snitch/snitch_registry.hpp" 7 | 8 | #include 9 | 10 | #if SNITCH_WITH_EXCEPTIONS 11 | # define SNITCH_TESTING_ABORT \ 12 | throw snitch::impl::abort_exception {} 13 | #else 14 | # define SNITCH_TESTING_ABORT std::terminate() 15 | #endif 16 | 17 | #define SNITCH_NEW_CHECK \ 18 | snitch::impl::scoped_test_check { \ 19 | SNITCH_CURRENT_LOCATION \ 20 | } 21 | 22 | #define SNITCH_EXPR(TYPE, EXPECTED, ...) \ 23 | auto SNITCH_CURRENT_EXPRESSION = \ 24 | (snitch::impl::expression_extractor{TYPE, #__VA_ARGS__} <= __VA_ARGS__) \ 25 | .to_expression() 26 | 27 | #define SNITCH_IS_DECOMPOSABLE(...) \ 28 | snitch::impl::is_decomposable{ \ 29 | std::declval(), \ 30 | std::declval()} <= __VA_ARGS__) \ 31 | .to_expression())> 32 | 33 | #define SNITCH_REPORT_EXPRESSION(MAYBE_ABORT) \ 34 | snitch::registry::report_assertion( \ 35 | SNITCH_CURRENT_EXPRESSION.success, SNITCH_CURRENT_EXPRESSION); \ 36 | if (!SNITCH_CURRENT_EXPRESSION.success) { \ 37 | MAYBE_ABORT; \ 38 | } 39 | 40 | #endif 41 | -------------------------------------------------------------------------------- /include/snitch/snitch_macros_consteval.hpp: -------------------------------------------------------------------------------- 1 | #ifndef SNITCH_MACROS_CONSTEVAL_HPP 2 | #define SNITCH_MACROS_CONSTEVAL_HPP 3 | 4 | #include "snitch/snitch_config.hpp" 5 | #include "snitch/snitch_expression.hpp" 6 | #include "snitch/snitch_macros_check_base.hpp" 7 | #include "snitch/snitch_macros_utility.hpp" 8 | #include "snitch/snitch_macros_warnings.hpp" 9 | #include "snitch/snitch_matcher.hpp" 10 | #include "snitch/snitch_registry.hpp" 11 | #include "snitch/snitch_test_data.hpp" 12 | 13 | #if SNITCH_ENABLE 14 | 15 | # define SNITCH_CONSTEVAL_REQUIRE_IMPL(CHECK, EXPECTED, MAYBE_ABORT, ...) \ 16 | do { \ 17 | auto SNITCH_CURRENT_CHECK = SNITCH_NEW_CHECK; \ 18 | SNITCH_WARNING_PUSH \ 19 | SNITCH_WARNING_DISABLE_PARENTHESES \ 20 | SNITCH_WARNING_DISABLE_CONSTANT_COMPARISON \ 21 | if constexpr (SNITCH_IS_DECOMPOSABLE(__VA_ARGS__)) { \ 22 | constexpr SNITCH_EXPR(CHECK, EXPECTED, __VA_ARGS__); \ 23 | SNITCH_REPORT_EXPRESSION(MAYBE_ABORT); \ 24 | } else { \ 25 | constexpr auto SNITCH_CURRENT_EXPRESSION = snitch::impl::expression{ \ 26 | CHECK, #__VA_ARGS__, {}, static_cast(__VA_ARGS__) == EXPECTED}; \ 27 | SNITCH_REPORT_EXPRESSION(MAYBE_ABORT); \ 28 | } \ 29 | SNITCH_WARNING_POP \ 30 | } while (0) 31 | 32 | // clang-format off 33 | # define SNITCH_CONSTEVAL_REQUIRE(...) SNITCH_CONSTEVAL_REQUIRE_IMPL("CONSTEVAL_REQUIRE", true, SNITCH_TESTING_ABORT, __VA_ARGS__) 34 | # define SNITCH_CONSTEVAL_CHECK(...) SNITCH_CONSTEVAL_REQUIRE_IMPL("CONSTEVAL_CHECK", true, (void)0, __VA_ARGS__) 35 | # define SNITCH_CONSTEVAL_REQUIRE_FALSE(...) SNITCH_CONSTEVAL_REQUIRE_IMPL("CONSTEVAL_REQUIRE_FALSE", false, SNITCH_TESTING_ABORT, __VA_ARGS__) 36 | # define SNITCH_CONSTEVAL_CHECK_FALSE(...) SNITCH_CONSTEVAL_REQUIRE_IMPL("CONSTEVAL_CHECK_FALSE", false, (void)0, __VA_ARGS__) 37 | // clang-format on 38 | 39 | # define SNITCH_CONSTEVAL_REQUIRE_THAT_IMPL(CHECK, MAYBE_ABORT, EXPR, ...) \ 40 | do { \ 41 | auto SNITCH_CURRENT_CHECK = SNITCH_NEW_CHECK; \ 42 | constexpr auto SNITCH_TEMP_RESULT = snitch::impl::match(EXPR, __VA_ARGS__); \ 43 | constexpr auto SNITCH_CURRENT_EXPRESSION = snitch::impl::expression{ \ 44 | CHECK, #EXPR ", " #__VA_ARGS__, \ 45 | snitch::resize_or_truncate(SNITCH_TEMP_RESULT.second), \ 46 | SNITCH_TEMP_RESULT.first}; \ 47 | SNITCH_REPORT_EXPRESSION(MAYBE_ABORT); \ 48 | } while (0) 49 | 50 | // clang-format off 51 | # define SNITCH_CONSTEVAL_REQUIRE_THAT(EXPR, ...) SNITCH_CONSTEVAL_REQUIRE_THAT_IMPL("CONSTEVAL_REQUIRE_THAT", SNITCH_TESTING_ABORT, EXPR, __VA_ARGS__) 52 | # define SNITCH_CONSTEVAL_CHECK_THAT(EXPR, ...) SNITCH_CONSTEVAL_REQUIRE_THAT_IMPL("CONSTEVAL_CHECK_THAT", (void)0, EXPR, __VA_ARGS__) 53 | // clang-format on 54 | 55 | #else // SNITCH_ENABLE 56 | 57 | // clang-format off 58 | # define SNITCH_CONSTEVAL_REQUIRE(...) SNITCH_DISCARD_ARGS(__VA_ARGS__) 59 | # define SNITCH_CONSTEVAL_CHECK(...) SNITCH_DISCARD_ARGS(__VA_ARGS__) 60 | # define SNITCH_CONSTEVAL_REQUIRE_FALSE(...) SNITCH_DISCARD_ARGS(__VA_ARGS__) 61 | # define SNITCH_CONSTEVAL_CHECK_FALSE(...) SNITCH_DISCARD_ARGS(__VA_ARGS__) 62 | # define SNITCH_CONSTEVAL_REQUIRE_THAT(EXPR, ...) SNITCH_DISCARD_ARGS(EXPR, __VA_ARGS__) 63 | # define SNITCH_CONSTEVAL_CHECK_THAT(EXPR, ...) SNITCH_DISCARD_ARGS(EXPR, __VA_ARGS__) 64 | // clang-format on 65 | 66 | #endif // SNITCH_ENABLE 67 | 68 | // clang-format off 69 | #if SNITCH_WITH_SHORTHAND_MACROS 70 | # define CONSTEVAL_REQUIRE(...) SNITCH_CONSTEVAL_REQUIRE(__VA_ARGS__) 71 | # define CONSTEVAL_CHECK(...) SNITCH_CONSTEVAL_CHECK(__VA_ARGS__) 72 | # define CONSTEVAL_REQUIRE_FALSE(...) SNITCH_CONSTEVAL_REQUIRE_FALSE(__VA_ARGS__) 73 | # define CONSTEVAL_CHECK_FALSE(...) SNITCH_CONSTEVAL_CHECK_FALSE(__VA_ARGS__) 74 | # define CONSTEVAL_REQUIRE_THAT(EXP, ...) SNITCH_CONSTEVAL_REQUIRE_THAT(EXP, __VA_ARGS__) 75 | # define CONSTEVAL_CHECK_THAT(EXP, ...) SNITCH_CONSTEVAL_CHECK_THAT(EXP, __VA_ARGS__) 76 | #endif 77 | // clang-format on 78 | 79 | #endif 80 | -------------------------------------------------------------------------------- /include/snitch/snitch_macros_constexpr.hpp: -------------------------------------------------------------------------------- 1 | #ifndef SNITCH_MACROS_CONSTEXPR_HPP 2 | #define SNITCH_MACROS_CONSTEXPR_HPP 3 | 4 | #include "snitch/snitch_config.hpp" 5 | #include "snitch/snitch_expression.hpp" 6 | #include "snitch/snitch_macros_check_base.hpp" 7 | #include "snitch/snitch_macros_utility.hpp" 8 | #include "snitch/snitch_macros_warnings.hpp" 9 | #include "snitch/snitch_matcher.hpp" 10 | #include "snitch/snitch_registry.hpp" 11 | #include "snitch/snitch_test_data.hpp" 12 | 13 | #if SNITCH_ENABLE 14 | 15 | # define SNITCH_CONSTEXPR_REQUIRE_IMPL(CHECK, EXPECTED, MAYBE_ABORT, ...) \ 16 | do { \ 17 | auto SNITCH_CURRENT_CHECK = SNITCH_NEW_CHECK; \ 18 | SNITCH_WARNING_PUSH \ 19 | SNITCH_WARNING_DISABLE_PARENTHESES \ 20 | SNITCH_WARNING_DISABLE_CONSTANT_COMPARISON \ 21 | if constexpr (SNITCH_IS_DECOMPOSABLE(__VA_ARGS__)) { \ 22 | { \ 23 | constexpr SNITCH_EXPR(CHECK "[compile-time]", EXPECTED, __VA_ARGS__); \ 24 | SNITCH_REPORT_EXPRESSION(MAYBE_ABORT); \ 25 | } \ 26 | { \ 27 | SNITCH_EXPR(CHECK "[run-time]", EXPECTED, __VA_ARGS__); \ 28 | SNITCH_REPORT_EXPRESSION(MAYBE_ABORT); \ 29 | } \ 30 | } else { \ 31 | { \ 32 | constexpr auto SNITCH_CURRENT_EXPRESSION = snitch::impl::expression{ \ 33 | CHECK "[compile-time]", \ 34 | #__VA_ARGS__, \ 35 | {}, \ 36 | static_cast(__VA_ARGS__) == EXPECTED}; \ 37 | SNITCH_REPORT_EXPRESSION(MAYBE_ABORT); \ 38 | } \ 39 | { \ 40 | const auto SNITCH_CURRENT_EXPRESSION = snitch::impl::expression{ \ 41 | CHECK "[run-time]", \ 42 | #__VA_ARGS__, \ 43 | {}, \ 44 | static_cast(__VA_ARGS__) == EXPECTED}; \ 45 | SNITCH_REPORT_EXPRESSION(MAYBE_ABORT); \ 46 | } \ 47 | } \ 48 | SNITCH_WARNING_POP \ 49 | } while (0) 50 | 51 | // clang-format off 52 | # define SNITCH_CONSTEXPR_REQUIRE(...) SNITCH_CONSTEXPR_REQUIRE_IMPL("CONSTEXPR_REQUIRE", true, SNITCH_TESTING_ABORT, __VA_ARGS__) 53 | # define SNITCH_CONSTEXPR_CHECK(...) SNITCH_CONSTEXPR_REQUIRE_IMPL("CONSTEXPR_CHECK", true, (void)0, __VA_ARGS__) 54 | # define SNITCH_CONSTEXPR_REQUIRE_FALSE(...) SNITCH_CONSTEXPR_REQUIRE_IMPL("CONSTEXPR_REQUIRE_FALSE", false, SNITCH_TESTING_ABORT, __VA_ARGS__) 55 | # define SNITCH_CONSTEXPR_CHECK_FALSE(...) SNITCH_CONSTEXPR_REQUIRE_IMPL("CONSTEXPR_CHECK_FALSE", false, (void)0, __VA_ARGS__) 56 | // clang-format on 57 | 58 | # define SNITCH_CONSTEXPR_REQUIRE_THAT_IMPL(CHECK, MAYBE_ABORT, EXPR, ...) \ 59 | do { \ 60 | auto SNITCH_CURRENT_CHECK = SNITCH_NEW_CHECK; \ 61 | { \ 62 | constexpr auto SNITCH_TEMP_RESULT = snitch::impl::match(EXPR, __VA_ARGS__); \ 63 | constexpr auto SNITCH_CURRENT_EXPRESSION = snitch::impl::expression{ \ 64 | CHECK "[compile-time]", #EXPR ", " #__VA_ARGS__, \ 65 | snitch::resize_or_truncate( \ 66 | SNITCH_TEMP_RESULT.second), \ 67 | SNITCH_TEMP_RESULT.first}; \ 68 | SNITCH_REPORT_EXPRESSION(MAYBE_ABORT); \ 69 | } \ 70 | { \ 71 | const auto SNITCH_TEMP_RESULT = snitch::impl::match(EXPR, __VA_ARGS__); \ 72 | const auto SNITCH_CURRENT_EXPRESSION = snitch::impl::expression{ \ 73 | CHECK "[run-time]", #EXPR ", " #__VA_ARGS__, \ 74 | snitch::resize_or_truncate( \ 75 | SNITCH_TEMP_RESULT.second), \ 76 | SNITCH_TEMP_RESULT.first}; \ 77 | SNITCH_REPORT_EXPRESSION(MAYBE_ABORT); \ 78 | } \ 79 | } while (0) 80 | 81 | // clang-format off 82 | # define SNITCH_CONSTEXPR_REQUIRE_THAT(EXPR, ...) SNITCH_CONSTEXPR_REQUIRE_THAT_IMPL("CONSTEXPR_REQUIRE_THAT", SNITCH_TESTING_ABORT, EXPR, __VA_ARGS__) 83 | # define SNITCH_CONSTEXPR_CHECK_THAT(EXPR, ...) SNITCH_CONSTEXPR_REQUIRE_THAT_IMPL("CONSTEXPR_CHECK_THAT", (void)0, EXPR, __VA_ARGS__) 84 | // clang-format on 85 | 86 | #else // SNITCH_ENABLE 87 | 88 | // clang-format off 89 | # define SNITCH_CONSTEXPR_REQUIRE(...) SNITCH_DISCARD_ARGS(__VA_ARGS__) 90 | # define SNITCH_CONSTEXPR_CHECK(...) SNITCH_DISCARD_ARGS(__VA_ARGS__) 91 | # define SNITCH_CONSTEXPR_REQUIRE_FALSE(...) SNITCH_DISCARD_ARGS(__VA_ARGS__) 92 | # define SNITCH_CONSTEXPR_CHECK_FALSE(...) SNITCH_DISCARD_ARGS(__VA_ARGS__) 93 | # define SNITCH_CONSTEXPR_REQUIRE_THAT(EXPR, ...) SNITCH_DISCARD_ARGS(EXPR, __VA_ARGS__) 94 | # define SNITCH_CONSTEXPR_CHECK_THAT(EXPR, ...) SNITCH_DISCARD_ARGS(EXPR, __VA_ARGS__) 95 | // clang-format on 96 | #endif // SNITCH_ENABLE 97 | 98 | // clang-format off 99 | #if SNITCH_WITH_SHORTHAND_MACROS 100 | # define CONSTEXPR_REQUIRE(...) SNITCH_CONSTEXPR_REQUIRE(__VA_ARGS__) 101 | # define CONSTEXPR_CHECK(...) SNITCH_CONSTEXPR_CHECK(__VA_ARGS__) 102 | # define CONSTEXPR_REQUIRE_FALSE(...) SNITCH_CONSTEXPR_REQUIRE_FALSE(__VA_ARGS__) 103 | # define CONSTEXPR_CHECK_FALSE(...) SNITCH_CONSTEXPR_CHECK_FALSE(__VA_ARGS__) 104 | # define CONSTEXPR_REQUIRE_THAT(EXP, ...) SNITCH_CONSTEXPR_REQUIRE_THAT(EXP, __VA_ARGS__) 105 | # define CONSTEXPR_CHECK_THAT(EXP, ...) SNITCH_CONSTEXPR_CHECK_THAT(EXP, __VA_ARGS__) 106 | #endif 107 | // clang-format on 108 | 109 | #endif 110 | -------------------------------------------------------------------------------- /include/snitch/snitch_macros_misc.hpp: -------------------------------------------------------------------------------- 1 | #ifndef SNITCH_MACROS_MISC_HPP 2 | #define SNITCH_MACROS_MISC_HPP 3 | 4 | #include "snitch/snitch_capture.hpp" 5 | #include "snitch/snitch_config.hpp" 6 | #include "snitch/snitch_macros_utility.hpp" 7 | #include "snitch/snitch_section.hpp" 8 | #include "snitch/snitch_test_data.hpp" 9 | 10 | #if SNITCH_ENABLE 11 | # define SNITCH_SECTION(...) \ 12 | if (snitch::impl::section_entry_checker SNITCH_MACRO_CONCAT(section_id_, __COUNTER__){ \ 13 | {__VA_ARGS__}, SNITCH_CURRENT_LOCATION, snitch::impl::get_current_test()}) 14 | 15 | # define SNITCH_CAPTURE(...) \ 16 | auto SNITCH_MACRO_CONCAT(capture_id_, __COUNTER__) = snitch::impl::add_captures( \ 17 | snitch::impl::get_current_test(), #__VA_ARGS__, __VA_ARGS__) 18 | 19 | # define SNITCH_INFO(...) \ 20 | auto SNITCH_MACRO_CONCAT(capture_id_, __COUNTER__) = \ 21 | snitch::impl::add_info(snitch::impl::get_current_test(), __VA_ARGS__) 22 | #else // SNITCH_ENABLE 23 | // clang-format off 24 | # define SNITCH_SECTION(NAME, ...) if constexpr (false) 25 | # define SNITCH_CAPTURE(...) SNITCH_DISCARD_ARGS(__VA_ARGS__) 26 | # define SNITCH_INFO(...) SNITCH_DISCARD_ARGS(__VA_ARGS__) 27 | // clang-format on 28 | #endif // SNITCH_ENABLE 29 | 30 | // clang-format off 31 | #if SNITCH_WITH_SHORTHAND_MACROS 32 | # define SECTION(NAME, ...) SNITCH_SECTION(NAME, __VA_ARGS__) 33 | # define CAPTURE(...) SNITCH_CAPTURE(__VA_ARGS__) 34 | # define INFO(...) SNITCH_INFO(__VA_ARGS__) 35 | #endif 36 | // clang-format on 37 | 38 | #endif 39 | -------------------------------------------------------------------------------- /include/snitch/snitch_macros_reporter.hpp: -------------------------------------------------------------------------------- 1 | #ifndef SNITCH_MACROS_REPORTER_HPP 2 | #define SNITCH_MACROS_REPORTER_HPP 3 | 4 | #include "snitch/snitch_config.hpp" 5 | #include "snitch/snitch_macros_utility.hpp" 6 | #include "snitch/snitch_registry.hpp" 7 | 8 | #if SNITCH_ENABLE 9 | # define SNITCH_REGISTER_REPORTER_CALLBACKS(NAME, ...) \ 10 | static const std::string_view SNITCH_MACRO_CONCAT(reporter_id_, __COUNTER__) \ 11 | [[maybe_unused]] = snitch::tests.add_reporter(NAME, __VA_ARGS__) 12 | 13 | # define SNITCH_REGISTER_REPORTER(NAME, TYPE) \ 14 | static const std::string_view SNITCH_MACRO_CONCAT(reporter_id_, __COUNTER__) \ 15 | [[maybe_unused]] = snitch::tests.add_reporter(NAME) 16 | #else // SNITCH_ENABLE 17 | # define SNITCH_REGISTER_REPORTER_CALLBACKS(NAME, ...) /* nothing */ 18 | # define SNITCH_REGISTER_REPORTER(NAME, TYPE) static_assert(NAME) 19 | #endif // SNITCH_ENABLE 20 | 21 | // clang-format off 22 | #if SNITCH_WITH_SHORTHAND_MACROS 23 | # define REGISTER_REPORTER_CALLBACKS(...) SNITCH_REGISTER_REPORTER_CALLBACKS(__VA_ARGS__) 24 | # define REGISTER_REPORTER(...) SNITCH_REGISTER_REPORTER(__VA_ARGS__) 25 | #endif 26 | // clang-format on 27 | 28 | #endif 29 | -------------------------------------------------------------------------------- /include/snitch/snitch_macros_utility.hpp: -------------------------------------------------------------------------------- 1 | #ifndef SNITCH_MACROS_UTILITY_HPP 2 | #define SNITCH_MACROS_UTILITY_HPP 3 | 4 | #include "snitch/snitch_config.hpp" 5 | #include "snitch/snitch_macros_warnings.hpp" 6 | 7 | #define SNITCH_CONCAT_IMPL(x, y) x##y 8 | #define SNITCH_MACRO_CONCAT(x, y) SNITCH_CONCAT_IMPL(x, y) 9 | 10 | #define SNITCH_CURRENT_LOCATION \ 11 | snitch::source_location { \ 12 | std::string_view{__FILE__}, static_cast(__LINE__) \ 13 | } 14 | 15 | #define SNITCH_DISCARD_ARGS(...) \ 16 | do { \ 17 | SNITCH_WARNING_PUSH \ 18 | SNITCH_WARNING_DISABLE_PARENTHESES \ 19 | SNITCH_WARNING_DISABLE_CONSTANT_COMPARISON \ 20 | static_cast(sizeof(__VA_ARGS__, 0)); \ 21 | SNITCH_WARNING_POP \ 22 | } while (0) 23 | 24 | #define SNITCH_VOID_STATEMENT static_cast(0) 25 | 26 | #endif 27 | -------------------------------------------------------------------------------- /include/snitch/snitch_macros_warnings.hpp: -------------------------------------------------------------------------------- 1 | #ifndef SNITCH_MACROS_WARNINGS_HPP 2 | #define SNITCH_MACROS_WARNINGS_HPP 3 | 4 | #include "snitch/snitch_config.hpp" 5 | 6 | // clang-format off 7 | #if defined(__clang__) 8 | # define SNITCH_WARNING_PUSH _Pragma("clang diagnostic push") 9 | # define SNITCH_WARNING_POP _Pragma("clang diagnostic pop") 10 | # define SNITCH_WARNING_DISABLE_PARENTHESES _Pragma("clang diagnostic ignored \"-Wparentheses\"") 11 | # define SNITCH_WARNING_DISABLE_CONSTANT_COMPARISON 12 | #elif defined(__GNUC__) 13 | # define SNITCH_WARNING_PUSH _Pragma("GCC diagnostic push") 14 | # define SNITCH_WARNING_POP _Pragma("GCC diagnostic pop") 15 | # define SNITCH_WARNING_DISABLE_PARENTHESES _Pragma("GCC diagnostic ignored \"-Wparentheses\"") 16 | # define SNITCH_WARNING_DISABLE_CONSTANT_COMPARISON 17 | #elif defined(_MSC_VER) 18 | # define SNITCH_WARNING_PUSH _Pragma("warning(push)") 19 | # define SNITCH_WARNING_POP _Pragma("warning(pop)") 20 | # define SNITCH_WARNING_DISABLE_PARENTHESES 21 | # define SNITCH_WARNING_DISABLE_CONSTANT_COMPARISON _Pragma("warning(disable: 4127)") 22 | #else 23 | # define SNITCH_WARNING_PUSH 24 | # define SNITCH_WARNING_POP 25 | # define SNITCH_WARNING_DISABLE_PARENTHESES 26 | # define SNITCH_WARNING_DISABLE_CONSTANT_COMPARISON 27 | #endif 28 | // clang-format on 29 | 30 | #endif 31 | -------------------------------------------------------------------------------- /include/snitch/snitch_main.hpp: -------------------------------------------------------------------------------- 1 | #ifndef SNITCH_MAIN_HPP 2 | #define SNITCH_MAIN_HPP 3 | 4 | #include "snitch/snitch_config.hpp" 5 | 6 | namespace snitch { 7 | SNITCH_EXPORT int main(int argc, char* argv[]); 8 | } // namespace snitch 9 | 10 | #endif 11 | -------------------------------------------------------------------------------- /include/snitch/snitch_matcher.hpp: -------------------------------------------------------------------------------- 1 | #ifndef SNITCH_MATCHER_HPP 2 | #define SNITCH_MATCHER_HPP 3 | 4 | #include "snitch/snitch_append.hpp" 5 | #include "snitch/snitch_config.hpp" 6 | #include "snitch/snitch_error_handling.hpp" 7 | #include "snitch/snitch_string.hpp" 8 | #include "snitch/snitch_string_utility.hpp" 9 | 10 | #include 11 | 12 | namespace snitch::matchers { 13 | enum class match_status { failed, matched }; 14 | } 15 | 16 | namespace snitch { 17 | template 18 | concept matcher_for = requires(const T& m, const U& value) { 19 | { m.match(value) } -> convertible_to; 20 | { m.describe_match(value, matchers::match_status{}) } -> convertible_to; 21 | }; 22 | } // namespace snitch 23 | 24 | namespace snitch::impl { 25 | template 26 | concept exception_with_what = requires(const T& e) { 27 | { e.what() } -> convertible_to; 28 | }; 29 | 30 | template 31 | [[nodiscard]] constexpr auto match(T&& value, M&& matcher) noexcept { 32 | using result_type = decltype(matcher.describe_match(value, matchers::match_status::failed)); 33 | if (!matcher.match(value)) { 34 | return std::pair( 35 | false, matcher.describe_match(value, matchers::match_status::failed)); 36 | } else { 37 | return std::pair( 38 | true, matcher.describe_match(value, matchers::match_status::matched)); 39 | } 40 | } 41 | } // namespace snitch::impl 42 | 43 | namespace snitch::matchers { 44 | struct contains_substring { 45 | std::string_view substring_pattern; 46 | 47 | SNITCH_EXPORT explicit contains_substring(std::string_view pattern) noexcept; 48 | 49 | SNITCH_EXPORT bool match(std::string_view message) const noexcept; 50 | 51 | SNITCH_EXPORT small_string 52 | describe_match(std::string_view message, match_status status) const noexcept; 53 | }; 54 | 55 | template 56 | struct is_any_of { 57 | small_vector list; 58 | 59 | template 60 | explicit is_any_of(const Args&... args) noexcept : list({args...}) {} 61 | 62 | bool match(const T& value) const noexcept { 63 | for (const auto& v : list) { 64 | if (v == value) { 65 | return true; 66 | } 67 | } 68 | 69 | return false; 70 | } 71 | 72 | small_string 73 | describe_match(const T& value, match_status status) const noexcept { 74 | small_string description_buffer; 75 | append_or_truncate( 76 | description_buffer, "'", value, "' was ", 77 | (status == match_status::failed ? "not " : ""), "found in {"); 78 | 79 | bool first = true; 80 | for (const auto& v : list) { 81 | if (!first) { 82 | append_or_truncate(description_buffer, ", '", v, "'"); 83 | } else { 84 | append_or_truncate(description_buffer, "'", v, "'"); 85 | } 86 | first = false; 87 | } 88 | append_or_truncate(description_buffer, "}"); 89 | 90 | return description_buffer; 91 | } 92 | }; 93 | 94 | template 95 | is_any_of(T, Args...) -> is_any_of; 96 | 97 | struct with_what_contains : private contains_substring { 98 | SNITCH_EXPORT explicit with_what_contains(std::string_view pattern) noexcept; 99 | 100 | template 101 | bool match(const E& e) const noexcept { 102 | return contains_substring::match(e.what()); 103 | } 104 | 105 | template 106 | small_string 107 | describe_match(const E& e, match_status status) const noexcept { 108 | return contains_substring::describe_match(e.what(), status); 109 | } 110 | }; 111 | 112 | template M> 113 | bool operator==(const T& value, const M& m) noexcept { 114 | return m.match(value); 115 | } 116 | 117 | template M> 118 | bool operator==(const M& m, const T& value) noexcept { 119 | return m.match(value); 120 | } 121 | } // namespace snitch::matchers 122 | 123 | #endif 124 | -------------------------------------------------------------------------------- /include/snitch/snitch_reporter_catch2_xml.hpp: -------------------------------------------------------------------------------- 1 | #ifndef SNITCH_REPORTER_CATCH2_XML_HPP 2 | #define SNITCH_REPORTER_CATCH2_XML_HPP 3 | 4 | #include "snitch/snitch_config.hpp" 5 | 6 | #if SNITCH_WITH_CATCH2_XML_REPORTER || SNITCH_WITH_ALL_REPORTERS 7 | 8 | # include "snitch/snitch_test_data.hpp" 9 | 10 | # include 11 | # include 12 | 13 | namespace snitch::reporter::catch2_xml { 14 | struct reporter { 15 | std::size_t indent_level = 0; 16 | 17 | SNITCH_EXPORT explicit reporter(registry& r) noexcept; 18 | 19 | SNITCH_EXPORT bool configure(registry&, std::string_view, std::string_view) noexcept; 20 | 21 | SNITCH_EXPORT void report(const registry& r, const snitch::event::data& event) noexcept; 22 | }; 23 | } // namespace snitch::reporter::catch2_xml 24 | 25 | #endif 26 | #endif 27 | -------------------------------------------------------------------------------- /include/snitch/snitch_reporter_console.hpp: -------------------------------------------------------------------------------- 1 | #ifndef SNITCH_REPORTER_CONSOLE_HPP 2 | #define SNITCH_REPORTER_CONSOLE_HPP 3 | 4 | #include "snitch/snitch_config.hpp" 5 | #include "snitch/snitch_test_data.hpp" 6 | 7 | #include 8 | 9 | namespace snitch::reporter::console { 10 | struct reporter { 11 | std::size_t counter = 0; 12 | 13 | reporter() = default; 14 | 15 | SNITCH_EXPORT explicit reporter(registry& r) noexcept; 16 | 17 | SNITCH_EXPORT bool configure(registry&, std::string_view, std::string_view) noexcept; 18 | 19 | SNITCH_EXPORT void report(const registry& r, const snitch::event::data& event) noexcept; 20 | }; 21 | } // namespace snitch::reporter::console 22 | 23 | #endif 24 | -------------------------------------------------------------------------------- /include/snitch/snitch_reporter_teamcity.hpp: -------------------------------------------------------------------------------- 1 | #ifndef SNITCH_REPORTER_TEAMCITY_HPP 2 | #define SNITCH_REPORTER_TEAMCITY_HPP 3 | 4 | #include "snitch/snitch_config.hpp" 5 | 6 | #if SNITCH_WITH_TEAMCITY_REPORTER || SNITCH_WITH_ALL_REPORTERS 7 | 8 | # include "snitch/snitch_test_data.hpp" 9 | 10 | # include 11 | 12 | namespace snitch::reporter::teamcity { 13 | SNITCH_EXPORT void initialize(registry& r) noexcept; 14 | 15 | SNITCH_EXPORT void report(const registry& r, const snitch::event::data& event) noexcept; 16 | } // namespace snitch::reporter::teamcity 17 | 18 | #endif 19 | #endif 20 | -------------------------------------------------------------------------------- /include/snitch/snitch_section.hpp: -------------------------------------------------------------------------------- 1 | #ifndef SNITCH_SECTION_HPP 2 | #define SNITCH_SECTION_HPP 3 | 4 | #include "snitch/snitch_config.hpp" 5 | #include "snitch/snitch_test_data.hpp" 6 | 7 | namespace snitch::impl { 8 | struct section_entry_checker { 9 | section_id id = {}; 10 | source_location location = {}; 11 | test_state& state; 12 | bool entered = false; 13 | 14 | SNITCH_EXPORT ~section_entry_checker(); 15 | 16 | // Requires: number of sections < max_nested_sections. 17 | SNITCH_EXPORT explicit operator bool(); 18 | }; 19 | } // namespace snitch::impl 20 | 21 | #endif 22 | -------------------------------------------------------------------------------- /include/snitch/snitch_string.hpp: -------------------------------------------------------------------------------- 1 | #ifndef SNITCH_STRING_HPP 2 | #define SNITCH_STRING_HPP 3 | 4 | #include "snitch/snitch_config.hpp" 5 | #include "snitch/snitch_vector.hpp" 6 | 7 | #include 8 | 9 | namespace snitch { 10 | using small_string_span = small_vector_span; 11 | using small_string_view = small_vector_span; 12 | 13 | template 14 | class small_string { 15 | std::array data_buffer = {}; 16 | std::size_t data_size = 0u; 17 | 18 | public: 19 | constexpr small_string() noexcept = default; 20 | constexpr small_string(const small_string& other) noexcept = default; 21 | constexpr small_string(small_string&& other) noexcept = default; 22 | 23 | // Requires: str.size() <= MaxLength. 24 | constexpr small_string(std::string_view str) { 25 | resize(str.size()); 26 | for (std::size_t i = 0; i < str.size(); ++i) { 27 | data_buffer[i] = str[i]; 28 | } 29 | } 30 | 31 | constexpr small_string& operator=(const small_string& other) noexcept = default; 32 | constexpr small_string& operator=(small_string&& other) noexcept = default; 33 | 34 | constexpr std::string_view str() const noexcept { 35 | return std::string_view(data(), length()); 36 | } 37 | 38 | constexpr std::size_t capacity() const noexcept { 39 | return MaxLength; 40 | } 41 | constexpr std::size_t available() const noexcept { 42 | return MaxLength - data_size; 43 | } 44 | constexpr std::size_t size() const noexcept { 45 | return data_size; 46 | } 47 | constexpr std::size_t length() const noexcept { 48 | return data_size; 49 | } 50 | constexpr bool empty() const noexcept { 51 | return data_size == 0u; 52 | } 53 | constexpr void clear() noexcept { 54 | span().clear(); 55 | } 56 | 57 | // Requires: new_size <= capacity(). 58 | constexpr void resize(std::size_t length) { 59 | span().resize(length); 60 | } 61 | 62 | // Requires: size() + elem <= capacity(). 63 | constexpr void grow(std::size_t chars) { 64 | span().grow(chars); 65 | } 66 | 67 | // Requires: size() < capacity(). 68 | constexpr char& push_back(char t) { 69 | return span().push_back(t); 70 | } 71 | 72 | // Requires: !empty(). 73 | constexpr void pop_back() { 74 | return span().pop_back(); 75 | } 76 | 77 | // Requires: !empty(). 78 | constexpr char& back() { 79 | return span().back(); 80 | } 81 | 82 | // Requires: !empty(). 83 | constexpr const char& back() const { 84 | return span().back(); 85 | } 86 | 87 | constexpr char* data() noexcept { 88 | return data_buffer.data(); 89 | } 90 | constexpr const char* data() const noexcept { 91 | return data_buffer.data(); 92 | } 93 | constexpr char* begin() noexcept { 94 | return data(); 95 | } 96 | constexpr char* end() noexcept { 97 | return begin() + length(); 98 | } 99 | constexpr const char* begin() const noexcept { 100 | return data(); 101 | } 102 | constexpr const char* end() const noexcept { 103 | return begin() + length(); 104 | } 105 | constexpr const char* cbegin() const noexcept { 106 | return data(); 107 | } 108 | constexpr const char* cend() const noexcept { 109 | return begin() + length(); 110 | } 111 | 112 | constexpr small_string_span span() noexcept { 113 | return small_string_span(data_buffer.data(), MaxLength, &data_size); 114 | } 115 | 116 | constexpr small_string_view span() const noexcept { 117 | return small_string_view(data_buffer.data(), MaxLength, &data_size); 118 | } 119 | 120 | constexpr operator small_string_span() noexcept { 121 | return span(); 122 | } 123 | 124 | constexpr operator small_string_view() const noexcept { 125 | return span(); 126 | } 127 | 128 | constexpr operator std::string_view() const noexcept { 129 | return std::string_view(data(), length()); 130 | } 131 | 132 | // Requires: i < size(). 133 | constexpr char& operator[](std::size_t i) { 134 | return span()[i]; 135 | } 136 | 137 | // Requires: i < size(). 138 | constexpr char operator[](std::size_t i) const { 139 | return const_cast(this)->span()[i]; 140 | } 141 | }; 142 | } // namespace snitch 143 | 144 | #endif 145 | -------------------------------------------------------------------------------- /include/snitch/snitch_string_utility.hpp: -------------------------------------------------------------------------------- 1 | #ifndef SNITCH_STRING_UTILITY_HPP 2 | #define SNITCH_STRING_UTILITY_HPP 3 | 4 | #include "snitch/snitch_append.hpp" 5 | #include "snitch/snitch_config.hpp" 6 | #include "snitch/snitch_string.hpp" 7 | 8 | #include 9 | #include 10 | #include 11 | 12 | namespace snitch { 13 | constexpr void truncate_end(small_string_span ss) noexcept { 14 | std::size_t num_dots = 3; 15 | std::size_t final_length = ss.size() + num_dots; 16 | if (final_length > ss.capacity()) { 17 | final_length = ss.capacity(); 18 | } 19 | 20 | const std::size_t offset = final_length >= num_dots ? final_length - num_dots : 0; 21 | num_dots = final_length - offset; 22 | 23 | ss.resize(final_length); 24 | for (std::size_t i = 0; i < num_dots; ++i) { 25 | ss[offset + i] = '.'; 26 | } 27 | } 28 | 29 | template 30 | constexpr bool append_or_truncate(small_string_span ss, Args&&... args) noexcept { 31 | if (!append(ss, std::forward(args)...)) { 32 | truncate_end(ss); 33 | return false; 34 | } 35 | 36 | return true; 37 | } 38 | 39 | template 40 | constexpr small_string resize_or_truncate(const small_string& str) noexcept { 41 | if constexpr (N == M) { 42 | return str; 43 | } else if constexpr (N > M) { 44 | small_string out; 45 | append(out, std::string_view{str}); 46 | return out; 47 | } else { 48 | small_string out; 49 | append_or_truncate(out, std::string_view{str}); 50 | return out; 51 | } 52 | } 53 | 54 | template 55 | constexpr small_string resize_or_truncate(std::string_view str) noexcept { 56 | small_string out; 57 | append(out, str); 58 | return out; 59 | } 60 | 61 | SNITCH_EXPORT [[nodiscard]] bool replace_all( 62 | small_string_span string, std::string_view pattern, std::string_view replacement) noexcept; 63 | 64 | // Requires: replacement.length() > pattern.length() 65 | SNITCH_EXPORT [[nodiscard]] bool escape_all_or_truncate( 66 | small_string_span string, std::string_view pattern, std::string_view replacement); 67 | 68 | SNITCH_EXPORT [[nodiscard]] std::size_t 69 | find_first_not_escaped(std::string_view str, char c) noexcept; 70 | 71 | SNITCH_EXPORT [[nodiscard]] bool is_match(std::string_view string, std::string_view regex) noexcept; 72 | } // namespace snitch 73 | 74 | #endif 75 | -------------------------------------------------------------------------------- /include/snitch/snitch_time.hpp: -------------------------------------------------------------------------------- 1 | #ifndef SNITCH_TIME_HPP 2 | #define SNITCH_TIME_HPP 3 | 4 | #include "snitch/snitch_config.hpp" 5 | 6 | #if SNITCH_WITH_TIMINGS 7 | 8 | namespace snitch { 9 | using time_point_t = std::size_t; 10 | 11 | SNITCH_EXPORT time_point_t get_current_time() noexcept; 12 | 13 | SNITCH_EXPORT float get_duration_in_seconds(time_point_t start, time_point_t end) noexcept; 14 | } // namespace snitch 15 | 16 | #endif 17 | #endif 18 | -------------------------------------------------------------------------------- /include/snitch/snitch_type_id.hpp: -------------------------------------------------------------------------------- 1 | #ifndef SNITCH_TYPE_ID_HPP 2 | #define SNITCH_TYPE_ID_HPP 3 | 4 | #include "snitch/snitch_config.hpp" 5 | 6 | namespace snitch { 7 | using type_id_t = const void*; 8 | } 9 | 10 | namespace snitch::impl { 11 | template 12 | struct type_id { 13 | constexpr static char value = 0; 14 | }; 15 | } // namespace snitch::impl 16 | 17 | namespace snitch { 18 | template 19 | type_id_t type_id() noexcept { 20 | return &impl::type_id::value; 21 | } 22 | 23 | template<> 24 | constexpr type_id_t type_id() noexcept { 25 | return nullptr; 26 | } 27 | } // namespace snitch 28 | 29 | #endif 30 | -------------------------------------------------------------------------------- /include/snitch/snitch_type_name.hpp: -------------------------------------------------------------------------------- 1 | #ifndef SNITCH_TYPE_NAME_HPP 2 | #define SNITCH_TYPE_NAME_HPP 3 | 4 | #include "snitch/snitch_config.hpp" 5 | 6 | #include 7 | 8 | namespace snitch::impl { 9 | template 10 | constexpr std::string_view get_type_name() noexcept { 11 | #if defined(__clang__) 12 | constexpr auto prefix = std::string_view{"[T = "}; 13 | constexpr auto suffix = "]"; 14 | constexpr auto function = std::string_view{__PRETTY_FUNCTION__}; 15 | #elif defined(__GNUC__) 16 | constexpr auto prefix = std::string_view{"with T = "}; 17 | constexpr auto suffix = "; "; 18 | constexpr auto function = std::string_view{__PRETTY_FUNCTION__}; 19 | #elif defined(_MSC_VER) 20 | constexpr auto prefix = std::string_view{"get_type_name<"}; 21 | constexpr auto suffix = ">(void)"; 22 | constexpr auto function = std::string_view{__FUNCSIG__}; 23 | #else 24 | # error Unsupported compiler 25 | #endif 26 | 27 | const auto start = function.find(prefix) + prefix.size(); 28 | const auto end = function.find(suffix); 29 | const auto size = end - start; 30 | 31 | return function.substr(start, size); 32 | } 33 | } // namespace snitch::impl 34 | 35 | namespace snitch { 36 | template 37 | constexpr std::string_view type_name = impl::get_type_name(); 38 | } 39 | 40 | #endif 41 | -------------------------------------------------------------------------------- /make_snitch_all.py: -------------------------------------------------------------------------------- 1 | import sys 2 | import os 3 | import re 4 | 5 | if len(sys.argv) != 3: 6 | print('error: expected two command line arguments: ') 7 | exit() 8 | 9 | root_dir = sys.argv[1] 10 | binary_dir = sys.argv[2] 11 | 12 | output_dir = os.path.join(binary_dir, 'snitch') 13 | output_filename = 'snitch_all.hpp' 14 | 15 | # Make sure the output directory exists; should always be true since we read 16 | # 'snitch_config.hpp' from there, but in case this changes in the future: 17 | os.makedirs(output_dir, exist_ok=True) 18 | 19 | # Gather header inclusion structure. 20 | header_map = {} 21 | def add_headers(filename): 22 | headers = [] 23 | with open(filename) as header: 24 | for line in header.readlines(): 25 | include = re.search(r'#\s*include "(snitch/snitch_[^"]+hpp)"', line) 26 | if not include: 27 | continue 28 | 29 | include = include.group(1) 30 | if 'snitch_config.hpp' in include: 31 | include_dir = binary_dir 32 | else: 33 | include_dir = os.path.join(root_dir, 'include') 34 | 35 | child_header = os.path.join(include_dir, include) 36 | headers.append(child_header) 37 | 38 | if include not in header_map: 39 | header_map[child_header] = add_headers(child_header) 40 | 41 | return headers 42 | 43 | add_headers(os.path.join(root_dir, 'include', 'snitch', 'snitch.hpp')) 44 | 45 | # Add leaf headers that don't include any other. 46 | input_filenames = list(include for include, children in header_map.items() if len(children) == 0) 47 | 48 | # Add other headers iteratively. 49 | while len(input_filenames) < len(header_map): 50 | last_length = len(input_filenames) 51 | 52 | for include, children in header_map.items(): 53 | if not include in input_filenames and all(child in input_filenames for child in children): 54 | input_filenames.append(include) 55 | 56 | if len(input_filenames) == last_length: 57 | raise Exception('Circular include pattern detected') 58 | 59 | # Add implementation. 60 | main_source = os.path.join(root_dir, 'src', 'snitch.cpp') 61 | with open(main_source) as src: 62 | for line in src.readlines(): 63 | file = re.search(r'#\s*include "(snitch_[^"]+cpp)"', line) 64 | if file: 65 | input_filenames.append(os.path.join(root_dir, 'src', file.group(1))) 66 | 67 | with open(os.path.join(output_dir, output_filename), 'w') as output_file: 68 | # Add preamble 69 | output_file.write('#ifndef SNITCH_ALL_HPP\n') 70 | output_file.write('#define SNITCH_ALL_HPP\n') 71 | 72 | file_count = 0 73 | for input_filename in input_filenames: 74 | # Add guard for implementation files 75 | if '.cpp' in input_filename: 76 | output_file.write('#if defined(SNITCH_IMPLEMENTATION)\n') 77 | 78 | # Write whole file 79 | with open(input_filename, 'r') as input_file: 80 | for line in input_file.readlines(): 81 | # Remove includes to snitch/*.hpp; it's all one header now 82 | if re.match(r'#\s*include "snitch', line): 83 | continue 84 | output_file.write(line) 85 | 86 | # Close guard for implementation files 87 | if '.cpp' in input_filename: 88 | output_file.write('\n#endif\n') 89 | 90 | file_count += 1 91 | if file_count != len(input_filenames): 92 | output_file.write('\n') 93 | 94 | output_file.write('\n#endif\n') 95 | -------------------------------------------------------------------------------- /meson.build: -------------------------------------------------------------------------------- 1 | project('snitch', 'cpp', 2 | default_options: ['cpp_std=c++20', 'default_library=static'], 3 | version: '1.3.2' 4 | ) 5 | 6 | cpp_arguments = [] 7 | 8 | cxx = meson.get_compiler('cpp') 9 | compiler_id = cxx.get_id() 10 | host_system = host_machine.system() 11 | is_mingw = host_system == 'windows' and cxx.get_id() == 'gcc' 12 | 13 | if get_option('default_library') == 'shared' 14 | if compiler_id == 'msvc' or is_mingw 15 | # Nothing to do; default is already to hide symbols unless exported. 16 | elif compiler_id == 'gcc' or compiler_id == 'clang' 17 | # Set default visibility to "hidden" so only exported symbols are visible. 18 | cpp_arguments += cxx.get_supported_arguments([ 19 | '-fvisibility=hidden', 20 | '-fvisibility-inlines-hidden' 21 | ]) 22 | endif 23 | endif 24 | 25 | add_project_arguments(cpp_arguments, language : 'cpp') 26 | add_project_link_arguments(cpp_arguments, language : 'cpp') 27 | 28 | include_dirs = include_directories('.', 'include') 29 | 30 | headers = files('include/snitch/snitch.hpp', 31 | 'include/snitch/snitch_any.hpp', 32 | 'include/snitch/snitch_append.hpp', 33 | 'include/snitch/snitch_capture.hpp', 34 | 'include/snitch/snitch_cli.hpp', 35 | 'include/snitch/snitch_concepts.hpp', 36 | 'include/snitch/snitch_console.hpp', 37 | 'include/snitch/snitch_error_handling.hpp', 38 | 'include/snitch/snitch_expression.hpp', 39 | 'include/snitch/snitch_file.hpp', 40 | 'include/snitch/snitch_fixed_point.hpp', 41 | 'include/snitch/snitch_function.hpp', 42 | 'include/snitch/snitch_macros_check.hpp', 43 | 'include/snitch/snitch_macros_check_base.hpp', 44 | 'include/snitch/snitch_macros_consteval.hpp', 45 | 'include/snitch/snitch_macros_constexpr.hpp', 46 | 'include/snitch/snitch_macros_exceptions.hpp', 47 | 'include/snitch/snitch_macros_misc.hpp', 48 | 'include/snitch/snitch_macros_reporter.hpp', 49 | 'include/snitch/snitch_macros_test_case.hpp', 50 | 'include/snitch/snitch_macros_utility.hpp', 51 | 'include/snitch/snitch_macros_warnings.hpp', 52 | 'include/snitch/snitch_matcher.hpp', 53 | 'include/snitch/snitch_registry.hpp', 54 | 'include/snitch/snitch_reporter_catch2_xml.hpp', 55 | 'include/snitch/snitch_reporter_console.hpp', 56 | 'include/snitch/snitch_reporter_teamcity.hpp', 57 | 'include/snitch/snitch_section.hpp', 58 | 'include/snitch/snitch_string.hpp', 59 | 'include/snitch/snitch_string_utility.hpp', 60 | 'include/snitch/snitch_test_data.hpp', 61 | 'include/snitch/snitch_type_name.hpp', 62 | 'include/snitch/snitch_type_id.hpp', 63 | 'include/snitch/snitch_vector.hpp') 64 | 65 | sources = files('src/snitch_append.cpp', 66 | 'src/snitch_capture.cpp', 67 | 'src/snitch_cli.cpp', 68 | 'src/snitch_console.cpp', 69 | 'src/snitch_error_handling.cpp', 70 | 'src/snitch_file.cpp', 71 | 'src/snitch_main.cpp', 72 | 'src/snitch_matcher.cpp', 73 | 'src/snitch_registry.cpp', 74 | 'src/snitch_reporter_catch2_xml.cpp', 75 | 'src/snitch_reporter_console.cpp', 76 | 'src/snitch_reporter_teamcity.cpp', 77 | 'src/snitch_section.cpp', 78 | 'src/snitch_string_utility.cpp', 79 | 'src/snitch_test_data.cpp') 80 | 81 | if get_option('enable') 82 | if get_option('unity_build') 83 | main = files('src/snitch.cpp') 84 | else 85 | main = sources 86 | endif 87 | else 88 | main = files('src/snitch_main.cpp') 89 | endif 90 | 91 | make_snitch_all = files('make_snitch_all.py') 92 | 93 | subdir('snitch') 94 | 95 | install_headers(headers, subdir: 'snitch') 96 | 97 | if get_option('create_library') 98 | snitch = library('snitch', 99 | conf_file, main, headers, 100 | include_directories: include_dirs, 101 | install: true, 102 | ) 103 | 104 | snitch_dep = declare_dependency( 105 | link_with: snitch, 106 | include_directories: include_dirs 107 | ) 108 | 109 | import('pkgconfig').generate( 110 | snitch, 111 | filebase: 'snitch', 112 | description: 'Lightweight C++20 testing framework.', 113 | url: 'https://github.com/cschreib/snitch', 114 | ) 115 | else 116 | snitch_dep = declare_dependency(include_directories: include_dirs) 117 | endif 118 | 119 | if meson.version().version_compare('>=0.54.0') 120 | meson.override_dependency('snitch', snitch_dep) 121 | endif 122 | 123 | if get_option('enable') 124 | test('snitch_all_test', 125 | executable('snitch_all_test', 126 | [snitch_all, 'tests/testing.cpp'], 127 | cpp_args: ['-DSNITCH_TEST_WITH_SNITCH', '-DSNITCH_TEST_HEADER_ONLY'], 128 | dependencies: [snitch_dep] 129 | ) 130 | ) 131 | endif 132 | -------------------------------------------------------------------------------- /meson_options.txt: -------------------------------------------------------------------------------- 1 | # Maximum lengths. 2 | option('max_test_cases' , type: 'integer', value: 5000, description: 'Maximum number of test cases in a test application.') 3 | option('max_nested_sections' , type: 'integer', value: 8 , description: 'Maximum depth of nested sections in a test case.') 4 | option('max_expr_length' , type: 'integer', value: 1024, description: 'Maximum length of a printed expression when reporting failure.') 5 | option('max_message_length' , type: 'integer', value: 1024, description: 'Maximum length of error or status messages.') 6 | option('max_test_name_length' , type: 'integer', value: 1024, description: 'Maximum length of a test case name.') 7 | option('max_tag_length' , type: 'integer', value: 256 , description: 'Maximum length of a test tag.') 8 | option('max_captures' , type: 'integer', value: 8 , description: 'Maximum number of captured expressions in a test case.') 9 | option('max_capture_length' , type: 'integer', value: 256 , description: 'Maximum length of a captured expression.') 10 | option('max_unique_tags' , type: 'integer', value: 1024, description: 'Maximum number of unique tags in a test application.') 11 | option('max_command_line_args' , type: 'integer', value: 1024, description: 'Maximum number of command line arguments to a test application.') 12 | option('max_registered_reporters', type: 'integer', value: 8 , description: 'Maximum number of registered reporter that can be selected from the command line.') 13 | option('max_path_length' , type: 'integer', value: 1024, description: 'Maximum length of a file path when writing output to file.') 14 | option('max_reporter_size_bytes' , type: 'integer', value: 128, description: 'Maximum size (in bytes) of a reporter object.') 15 | 16 | # Feature toggles. 17 | option('enable' , type: 'boolean', value: true, description: 'Enable/disable snitch at build time.') 18 | option('define_main' , type: 'boolean', value: true, description: 'Define main() in snitch -- disable to provide your own main() function.') 19 | option('with_exceptions' , type: 'boolean', value: true, description: 'Use exceptions in snitch implementation -- will be forced OFF if exceptions are not available.') 20 | option('with_multithreading' , type: 'boolean', value: true, description: 'Make the testing framework thread-safe -- disable if multithreading is not needed.') 21 | option('with_timings' , type: 'boolean', value: true, description: 'Measure the time taken by each test case -- disable to speed up tests.') 22 | option('with_shorthand_macros' , type: 'boolean', value: true, description: 'Use short names for test macros -- disable if this causes conflicts.') 23 | option('constexpr_float_use_bitcast' , type: 'boolean', value: true, description: 'Use std::bit_cast if available to implement exact constexpr float-to-string conversion.') 24 | option('snitch_append_to_chars' , type: 'boolean', value: true, description: 'Use std::to_chars for string conversions -- disable for greater compatability with a slight performance cost.') 25 | option('default_with_color' , type: 'boolean', value: true, description: 'Enable terminal colors by default -- can also be controlled by command line interface.') 26 | option('decompose_successful_assertions', type: 'boolean', value: true, description: 'Enable expression decomposition even for successful assertions -- more expensive.') 27 | option('with_all_reporters' , type: 'boolean', value: true, description: 'Allow all built-in reporters to be selected from the command line -- disable for faster compilation.') 28 | option('with_teamcity_reporter' , type: 'boolean', value: true, description: 'Allow the TeamCity reporter to be selected from the command line -- enable if needed.') 29 | option('with_catch2_xml_reporter' , type: 'boolean', value: true, description: 'Allow the Catch2 XML reporter to be selected from the command line -- enable if needed.') 30 | 31 | # Building and packaging options; not part of the library API. 32 | option('create_header_only' , type: 'boolean', value: true, description: 'Create a single-header header-only version of snitch.') 33 | option('create_library' , type: 'boolean', value: true, description: 'Build a compiled library version of snitch.') 34 | option('unity_build' , type: 'boolean', value: true, description: 'Build sources as single file instead of separate files (faster full build).') 35 | -------------------------------------------------------------------------------- /snitch.sublime-project: -------------------------------------------------------------------------------- 1 | { 2 | "folders": 3 | [ 4 | { 5 | "path": ".", 6 | } 7 | ], 8 | "settings": 9 | { 10 | "cmake": 11 | { 12 | "build_folder": "$folder/build", 13 | }, 14 | "ClangFormat": 15 | { 16 | "format_on_save": true, 17 | }, 18 | "TestExplorer": 19 | { 20 | "data_location": ".sublime-tests", 21 | "frameworks": [ 22 | { 23 | "id": "runtime-tests", 24 | "type": "catch2", 25 | "executable_pattern": "build/tests/snitch_runtime_tests_self", 26 | "path_prefix_style": "basename" 27 | }, 28 | { 29 | "id": "runtime-tests-self", 30 | "type": "doctest-cpp", 31 | "executable_pattern": "build/tests/snitch_runtime_tests", 32 | "path_prefix_style": "basename" 33 | }, 34 | { 35 | "id": "approval-tests", 36 | "type": "doctest-cpp", 37 | "executable_pattern": "build/tests/snitch_approval_tests", 38 | "cwd": "tests/approval_tests", 39 | "path_prefix_style": "basename" 40 | }, 41 | ] 42 | }, 43 | }, 44 | "build_systems": 45 | [ 46 | { 47 | "file_regex": "(.+[^:]):(\\d+):(\\d+): (?:fatal )?((?:error|warning): .+)$", 48 | "name": "snitch (Linux)", 49 | "shell_cmd": "make -j12", 50 | "syntax": "Packages/CMakeBuilder/Syntax/Make.sublime-syntax", 51 | "variants": 52 | [ 53 | { 54 | "name": "clean", 55 | "shell_cmd": "make -j12 clean", 56 | }, 57 | { 58 | "name": "install", 59 | "shell_cmd": "make -j12 install", 60 | }, 61 | { 62 | "name": "install/local", 63 | "shell_cmd": "make -j12 install/local", 64 | }, 65 | { 66 | "name": "install/strip", 67 | "shell_cmd": "make -j12 install/strip", 68 | }, 69 | { 70 | "name": "list_install_components", 71 | "shell_cmd": "make -j12 list_install_components", 72 | }, 73 | { 74 | "name": "rebuild_cache", 75 | "shell_cmd": "make -j12 rebuild_cache", 76 | }, 77 | { 78 | "name": "test", 79 | "shell_cmd": "make -j12 test", 80 | }, 81 | { 82 | "name": "snitch_runtime_tests_run", 83 | "shell_cmd": "make -j12 snitch_runtime_tests_run", 84 | }, 85 | { 86 | "name": "snitch_runtime_tests_self_header_only_run", 87 | "shell_cmd": "make -j12 snitch_runtime_tests_self_header_only_run", 88 | }, 89 | { 90 | "name": "snitch_runtime_tests_self_run", 91 | "shell_cmd": "make -j12 snitch_runtime_tests_self_run", 92 | }, 93 | { 94 | "name": "doctest_with_main", 95 | "shell_cmd": "make -j12 doctest_with_main", 96 | }, 97 | { 98 | "name": "snitch", 99 | "shell_cmd": "make -j12 snitch", 100 | }, 101 | { 102 | "name": "snitch_runtime_tests", 103 | "shell_cmd": "make -j12 snitch_runtime_tests", 104 | }, 105 | { 106 | "name": "snitch_runtime_tests_self", 107 | "shell_cmd": "make -j12 snitch_runtime_tests_self", 108 | }, 109 | { 110 | "name": "snitch_runtime_tests_self_header_only", 111 | "shell_cmd": "make -j12 snitch_runtime_tests_self_header_only", 112 | }, 113 | { 114 | "name": "src/snitch.o", 115 | "shell_cmd": "make -j12 src/snitch.o", 116 | }, 117 | { 118 | "name": "src/snitch.i", 119 | "shell_cmd": "make -j12 src/snitch.i", 120 | }, 121 | { 122 | "name": "src/snitch.s", 123 | "shell_cmd": "make -j12 src/snitch.s", 124 | }, 125 | ], 126 | "working_dir": "$folder/build", 127 | } 128 | ], 129 | } 130 | -------------------------------------------------------------------------------- /snitch/meson.build: -------------------------------------------------------------------------------- 1 | version = meson.project_version() 2 | vers = version.split('.') 3 | 4 | git_command = run_command(find_program('git'), 'log', '-1', '--format=%h', check: false) 5 | if git_command.returncode() == 0 6 | git_hash = git_command.stdout().strip() 7 | full_version = version + '.' + git_hash 8 | else 9 | full_version = version 10 | endif 11 | 12 | is_shared = get_option('default_library') == 'shared' 13 | 14 | conf_data = configuration_data({ 15 | 'PROJECT_VERSION' : version, 16 | 'PROJECT_VERSION_MAJOR' : vers[0], 17 | 'PROJECT_VERSION_MINOR' : vers[1], 18 | 'PROJECT_VERSION_PATCH' : vers[2], 19 | 20 | 'SNITCH_FULL_VERSION' : full_version, 21 | 22 | 'SNITCH_MAX_TEST_CASES' : get_option('max_test_cases'), 23 | 'SNITCH_MAX_NESTED_SECTIONS' : get_option('max_nested_sections'), 24 | 'SNITCH_MAX_EXPR_LENGTH' : get_option('max_expr_length'), 25 | 'SNITCH_MAX_MESSAGE_LENGTH' : get_option('max_message_length'), 26 | 'SNITCH_MAX_TEST_NAME_LENGTH' : get_option('max_test_name_length'), 27 | 'SNITCH_MAX_TAG_LENGTH' : get_option('max_tag_length'), 28 | 'SNITCH_MAX_CAPTURES' : get_option('max_captures'), 29 | 'SNITCH_MAX_CAPTURE_LENGTH' : get_option('max_capture_length'), 30 | 'SNITCH_MAX_UNIQUE_TAGS' : get_option('max_unique_tags'), 31 | 'SNITCH_MAX_COMMAND_LINE_ARGS' : get_option('max_command_line_args'), 32 | 'SNITCH_MAX_REGISTERED_REPORTERS' : get_option('max_registered_reporters'), 33 | 'SNITCH_MAX_PATH_LENGTH' : get_option('max_path_length'), 34 | 'SNITCH_MAX_REPORTER_SIZE_BYTES' : get_option('max_reporter_size_bytes'), 35 | 36 | 'SNITCH_ENABLE' : get_option('enable').to_int(), 37 | 'SNITCH_DEFINE_MAIN' : get_option('define_main').to_int(), 38 | 'SNITCH_WITH_EXCEPTIONS' : get_option('with_exceptions').to_int(), 39 | 'SNITCH_WITH_MULTITHREADING' : get_option('with_multithreading').to_int(), 40 | 'SNITCH_WITH_TIMINGS' : get_option('with_timings').to_int(), 41 | 'SNITCH_WITH_SHORTHAND_MACROS' : get_option('with_shorthand_macros').to_int(), 42 | 'SNITCH_CONSTEXPR_FLOAT_USE_BITCAST' : get_option('constexpr_float_use_bitcast').to_int(), 43 | 'SNITCH_APPEND_TO_CHARS' : get_option('snitch_append_to_chars').to_int(), 44 | 'SNITCH_DEFAULT_WITH_COLOR' : get_option('default_with_color').to_int(), 45 | 'SNITCH_DECOMPOSE_SUCCESSFUL_ASSERTIONS' : get_option('decompose_successful_assertions').to_int(), 46 | 'SNITCH_WITH_ALL_REPORTERS' : get_option('with_all_reporters').to_int(), 47 | 'SNITCH_WITH_TEAMCITY_REPORTER' : get_option('with_teamcity_reporter').to_int(), 48 | 'SNITCH_WITH_CATCH2_XML_REPORTER' : get_option('with_catch2_xml_reporter').to_int(), 49 | 50 | 'SNITCH_SHARED_LIBRARY' : is_shared.to_int() 51 | }) 52 | 53 | conf_file = configure_file( 54 | input: meson.project_source_root() / 'include/snitch/snitch_config.hpp.config', 55 | output: 'snitch_config.hpp', 56 | format: 'cmake', 57 | configuration: conf_data, 58 | install_dir: 'include/snitch' 59 | ) 60 | 61 | snitch_all = custom_target( 62 | output: 'snitch_all.hpp', 63 | build_by_default: get_option('create_header_only'), 64 | depend_files: [conf_file, headers, main, sources], 65 | command: [find_program('python3'), make_snitch_all, 66 | meson.project_source_root(), meson.project_build_root()], 67 | depfile: 'snitch_all.d', 68 | install: true, 69 | install_dir: 'include/snitch' 70 | ) 71 | -------------------------------------------------------------------------------- /src/snitch.cpp: -------------------------------------------------------------------------------- 1 | #include "snitch_append.cpp" 2 | #include "snitch_capture.cpp" 3 | #include "snitch_cli.cpp" 4 | #include "snitch_console.cpp" 5 | #include "snitch_error_handling.cpp" 6 | #include "snitch_file.cpp" 7 | #include "snitch_main.cpp" 8 | #include "snitch_matcher.cpp" 9 | #include "snitch_registry.cpp" 10 | #include "snitch_reporter_catch2_xml.cpp" 11 | #include "snitch_reporter_console.cpp" 12 | #include "snitch_reporter_teamcity.cpp" 13 | #include "snitch_section.cpp" 14 | #include "snitch_string_utility.cpp" 15 | #include "snitch_test_data.cpp" 16 | #include "snitch_time.cpp" 17 | -------------------------------------------------------------------------------- /src/snitch_append.cpp: -------------------------------------------------------------------------------- 1 | #include "snitch/snitch_append.hpp" 2 | 3 | #include "snitch/snitch_concepts.hpp" 4 | #include "snitch/snitch_string.hpp" 5 | 6 | #include // for std::uintptr_t 7 | #include // for std::memmove 8 | #if SNITCH_APPEND_TO_CHARS 9 | # include // for std::to_chars 10 | # include // for std::errc 11 | #endif 12 | 13 | namespace snitch::impl { 14 | namespace { 15 | using snitch::small_string_span; 16 | using namespace std::literals; 17 | 18 | #if SNITCH_APPEND_TO_CHARS 19 | template 20 | bool append_to(small_string_span ss, T value) noexcept { 21 | constexpr auto fmt = std::chars_format::scientific; 22 | constexpr auto precision = same_as> ? 6 : 15; 23 | auto [end, err] = std::to_chars(ss.end(), ss.begin() + ss.capacity(), value, fmt, precision); 24 | if (err != std::errc{}) { 25 | // Not enough space, try into a temporary string that *should* be big enough, 26 | // and copy whatever we can. 32 characters is enough for all integers and floating 27 | // point values encoded on 64 bit or less. 28 | small_string<32> fallback; 29 | auto [end2, err2] = std::to_chars( 30 | fallback.end(), fallback.begin() + fallback.capacity(), value, fmt, precision); 31 | if (err2 != std::errc{}) { 32 | return false; 33 | } 34 | fallback.grow(end2 - fallback.begin()); 35 | return append(ss, fallback); 36 | } 37 | 38 | ss.grow(end - ss.end()); 39 | return true; 40 | } 41 | 42 | template 43 | bool append_to(small_string_span ss, T value) noexcept { 44 | auto [end, err] = std::to_chars(ss.end(), ss.begin() + ss.capacity(), value, Base); 45 | if (err != std::errc{}) { 46 | // Not enough space, try into a temporary string that *should* be big enough, 47 | // and copy whatever we can. 32 characters is enough for all integers and floating 48 | // point values encoded on 64 bit or less. 49 | small_string<32> fallback; 50 | auto [end2, err2] = 51 | std::to_chars(fallback.end(), fallback.begin() + fallback.capacity(), value, Base); 52 | if (err2 != std::errc{}) { 53 | return false; 54 | } 55 | fallback.grow(end2 - fallback.begin()); 56 | return append(ss, fallback); 57 | } 58 | ss.grow(end - ss.end()); 59 | return true; 60 | } 61 | #else 62 | template 63 | bool append_to(small_string_span ss, T value) noexcept { 64 | return append_constexpr(ss, value); 65 | } 66 | 67 | template 68 | bool append_to(small_string_span ss, T value) noexcept { 69 | return append_constexpr(ss, value); 70 | } 71 | #endif 72 | } // namespace 73 | 74 | bool append_fast(small_string_span ss, std::string_view str) noexcept { 75 | if (str.empty()) { 76 | return true; 77 | } 78 | 79 | const bool could_fit = str.size() <= ss.available(); 80 | const std::size_t copy_count = std::min(str.size(), ss.available()); 81 | 82 | const std::size_t offset = ss.size(); 83 | ss.grow(copy_count); 84 | std::memmove(ss.begin() + offset, str.data(), copy_count); 85 | 86 | return could_fit; 87 | } 88 | 89 | bool append_fast(small_string_span ss, const void* ptr) noexcept { 90 | if (ptr == nullptr) { 91 | return append(ss, nullptr); 92 | } 93 | 94 | if (!append_fast(ss, "0x"sv)) { 95 | return false; 96 | } 97 | 98 | const auto int_ptr = reinterpret_cast(ptr); 99 | 100 | // Pad with zeros. 101 | constexpr std::size_t max_digits = 2 * sizeof(void*); 102 | std::size_t padding = max_digits - num_digits<16>(int_ptr); 103 | while (padding > 0) { 104 | constexpr std::string_view zeroes = "0000000000000000"; 105 | const std::size_t batch = std::min(zeroes.size(), padding); 106 | if (!append_fast(ss, zeroes.substr(0, batch))) { 107 | return false; 108 | } 109 | 110 | padding -= batch; 111 | } 112 | return append_to<16>(ss, int_ptr); 113 | } 114 | 115 | bool append_fast(small_string_span ss, large_uint_t i) noexcept { 116 | return append_to(ss, i); 117 | } 118 | 119 | bool append_fast(small_string_span ss, large_int_t i) noexcept { 120 | return append_to(ss, i); 121 | } 122 | 123 | bool append_fast(small_string_span ss, float f) noexcept { 124 | return append_to(ss, f); 125 | } 126 | 127 | bool append_fast(small_string_span ss, double d) noexcept { 128 | return append_to(ss, d); 129 | } 130 | } // namespace snitch::impl 131 | -------------------------------------------------------------------------------- /src/snitch_capture.cpp: -------------------------------------------------------------------------------- 1 | #include "snitch/snitch_capture.hpp" 2 | 3 | #include "snitch/snitch_console.hpp" 4 | #include "snitch/snitch_registry.hpp" 5 | 6 | namespace snitch::impl { 7 | namespace { 8 | void trim(std::string_view& str, std::string_view patterns) noexcept { 9 | std::size_t start = str.find_first_not_of(patterns); 10 | if (start == str.npos) { 11 | return; 12 | } 13 | 14 | str.remove_prefix(start); 15 | 16 | std::size_t end = str.find_last_not_of(patterns); 17 | if (end != str.npos) { 18 | str.remove_suffix(str.size() - end - 1); 19 | } 20 | } 21 | } // namespace 22 | 23 | scoped_capture::~scoped_capture() { 24 | #if SNITCH_WITH_EXCEPTIONS 25 | if (std::uncaught_exceptions() > 0 && !state.held_info.has_value()) { 26 | // We are unwinding the stack because an exception has been thrown; 27 | // keep a copy of the full capture state since we will want to preserve the information 28 | // when reporting the exception. 29 | state.held_info = state.info; 30 | } 31 | #endif 32 | 33 | state.info.captures.resize(state.info.captures.size() - count); 34 | } 35 | 36 | std::string_view extract_next_name(std::string_view& names) noexcept { 37 | std::string_view result; 38 | 39 | auto pos = names.find_first_of(",()\"\"''"); 40 | 41 | bool in_string = false; 42 | bool in_char = false; 43 | int parens = 0; 44 | while (pos != names.npos) { 45 | switch (names[pos]) { 46 | case '"': 47 | if (!in_char) { 48 | in_string = !in_string; 49 | } 50 | break; 51 | case '\'': 52 | if (!in_string) { 53 | in_char = !in_char; 54 | } 55 | break; 56 | case '(': 57 | if (!in_string && !in_char) { 58 | ++parens; 59 | } 60 | break; 61 | case ')': 62 | if (!in_string && !in_char) { 63 | --parens; 64 | } 65 | break; 66 | case ',': 67 | if (!in_string && !in_char && parens == 0) { 68 | result = names.substr(0, pos); 69 | trim(result, " \t\n\r"); 70 | names.remove_prefix(pos + 1); 71 | return result; 72 | } 73 | break; 74 | } 75 | 76 | pos = names.find_first_of(",()\"\"''", pos + 1); 77 | } 78 | 79 | std::swap(result, names); 80 | trim(result, " \t\n\r"); 81 | return result; 82 | } 83 | 84 | small_string& add_capture(test_state& state) { 85 | if (state.info.captures.available() == 0) { 86 | state.reg.print( 87 | make_colored("error:", state.reg.with_color, color::fail), 88 | " max number of captures reached; " 89 | "please increase 'SNITCH_MAX_CAPTURES' (currently ", 90 | max_captures, ")\n."); 91 | assertion_failed("max number of captures reached"); 92 | } 93 | 94 | #if SNITCH_WITH_EXCEPTIONS 95 | if (std::uncaught_exceptions() == 0) { 96 | notify_exception_handled(); 97 | } 98 | #endif 99 | 100 | state.info.captures.grow(1); 101 | state.info.captures.back().clear(); 102 | return state.info.captures.back(); 103 | } 104 | } // namespace snitch::impl 105 | -------------------------------------------------------------------------------- /src/snitch_console.cpp: -------------------------------------------------------------------------------- 1 | #include "snitch/snitch_console.hpp" 2 | 3 | #include // for std::fwrite 4 | 5 | namespace snitch::impl { 6 | void stdout_print(std::string_view message) noexcept { 7 | std::fwrite(message.data(), sizeof(char), message.length(), stdout); 8 | } 9 | } // namespace snitch::impl 10 | -------------------------------------------------------------------------------- /src/snitch_error_handling.cpp: -------------------------------------------------------------------------------- 1 | #include "snitch/snitch_error_handling.hpp" 2 | 3 | #include "snitch/snitch_cli.hpp" 4 | 5 | #include // for std::terminate 6 | 7 | namespace snitch { 8 | [[noreturn]] void terminate_with(std::string_view msg) noexcept { 9 | cli::console_print("terminate called with message: "); 10 | cli::console_print(msg); 11 | cli::console_print("\n"); 12 | 13 | std::terminate(); 14 | } 15 | 16 | [[noreturn]] void assertion_failed(std::string_view msg) { 17 | assertion_failed_handler(msg); 18 | 19 | // The assertion handler should either spin, throw, or terminate, but never return. 20 | // We cannot enforce [[noreturn]] through the function_ref wrapper. So just in case 21 | // it accidentally returns, we terminate. 22 | std::terminate(); 23 | } 24 | 25 | function_ref assertion_failed_handler = &terminate_with; 26 | } // namespace snitch 27 | -------------------------------------------------------------------------------- /src/snitch_file.cpp: -------------------------------------------------------------------------------- 1 | #include "snitch/snitch_file.hpp" 2 | 3 | #include "snitch/snitch_append.hpp" 4 | #include "snitch/snitch_error_handling.hpp" 5 | 6 | #include // for std::fwrite 7 | 8 | namespace snitch::impl { 9 | file_writer::file_writer(std::string_view path) { 10 | // Unfortunately, fopen() needs a null-terminated string, so need a copy... 11 | small_string null_terminated_path; 12 | if (!append(null_terminated_path, path)) { 13 | assertion_failed("output file path is too long"); 14 | } 15 | 16 | #if defined(_MSC_VER) 17 | // MSVC thinks std::fopen is unsafe. 18 | std::FILE* tmp_handle = nullptr; 19 | fopen_s(&tmp_handle, null_terminated_path.data(), "w"); 20 | file_handle = tmp_handle; 21 | #else 22 | file_handle = std::fopen(null_terminated_path.data(), "w"); 23 | #endif 24 | 25 | if (file_handle == nullptr) { 26 | assertion_failed("output file could not be opened for writing"); 27 | } 28 | } 29 | 30 | file_writer::file_writer(file_writer&& other) noexcept { 31 | std::swap(file_handle, other.file_handle); 32 | } 33 | 34 | file_writer& file_writer::operator=(file_writer&& other) noexcept { 35 | std::swap(file_handle, other.file_handle); 36 | return *this; 37 | } 38 | 39 | file_writer::~file_writer() { 40 | if (file_handle == nullptr) { 41 | return; 42 | } 43 | 44 | std::fclose(static_cast(file_handle)); 45 | } 46 | 47 | void file_writer::write(std::string_view message) noexcept { 48 | if (file_handle == nullptr) { 49 | return; 50 | } 51 | 52 | std::fwrite( 53 | message.data(), sizeof(char), message.length(), static_cast(file_handle)); 54 | std::fflush(static_cast(file_handle)); 55 | } 56 | } // namespace snitch::impl 57 | -------------------------------------------------------------------------------- /src/snitch_main.cpp: -------------------------------------------------------------------------------- 1 | #include "snitch/snitch_main.hpp" 2 | 3 | #include "snitch/snitch_cli.hpp" 4 | #include "snitch/snitch_registry.hpp" 5 | 6 | namespace snitch { 7 | SNITCH_EXPORT int main(int argc, char* argv[]) { 8 | if constexpr (snitch::is_enabled) { 9 | std::optional args = snitch::cli::parse_arguments(argc, argv); 10 | if (!args) { 11 | return 1; 12 | } 13 | snitch::tests.configure(*args); 14 | return snitch::tests.run_tests(*args) ? 0 : 1; 15 | } else { 16 | return 0; 17 | } 18 | } 19 | } // namespace snitch 20 | 21 | #if SNITCH_DEFINE_MAIN 22 | SNITCH_EXPORT int main(int argc, char* argv[]) { 23 | return snitch::main(argc, argv); 24 | } 25 | #endif 26 | -------------------------------------------------------------------------------- /src/snitch_matcher.cpp: -------------------------------------------------------------------------------- 1 | #include "snitch/snitch_matcher.hpp" 2 | 3 | namespace snitch::matchers { 4 | contains_substring::contains_substring(std::string_view pattern) noexcept : 5 | substring_pattern(pattern) {} 6 | 7 | bool contains_substring::match(std::string_view message) const noexcept { 8 | return message.find(substring_pattern) != message.npos; 9 | } 10 | 11 | small_string 12 | contains_substring::describe_match(std::string_view message, match_status status) const noexcept { 13 | small_string description_buffer; 14 | append_or_truncate( 15 | description_buffer, (status == match_status::matched ? "found" : "could not find"), " '", 16 | substring_pattern, "' in '", message, "'"); 17 | return description_buffer; 18 | } 19 | 20 | with_what_contains::with_what_contains(std::string_view pattern) noexcept : 21 | contains_substring(pattern) {} 22 | } // namespace snitch::matchers 23 | -------------------------------------------------------------------------------- /src/snitch_reporter_teamcity.cpp: -------------------------------------------------------------------------------- 1 | #include "snitch/snitch_config.hpp" 2 | 3 | #if SNITCH_WITH_TEAMCITY_REPORTER || SNITCH_WITH_ALL_REPORTERS 4 | 5 | # include "snitch/snitch_macros_reporter.hpp" 6 | # include "snitch/snitch_registry.hpp" 7 | # include "snitch/snitch_reporter_teamcity.hpp" 8 | # include "snitch/snitch_string.hpp" 9 | # include "snitch/snitch_string_utility.hpp" 10 | # include "snitch/snitch_test_data.hpp" 11 | 12 | # include 13 | 14 | namespace snitch::reporter::teamcity { 15 | namespace { 16 | struct assertion { 17 | const snitch::assertion_location& location; 18 | const snitch::section_info& sections; 19 | const snitch::capture_info& captures; 20 | const snitch::assertion_data& data; 21 | }; 22 | 23 | struct key_value { 24 | std::string_view key; 25 | std::variant value; 26 | }; 27 | 28 | bool escape(small_string_span string) noexcept { 29 | return escape_all_or_truncate(string, "|", "||") && escape_all_or_truncate(string, "'", "|'") && 30 | escape_all_or_truncate(string, "\n", "|n") && 31 | escape_all_or_truncate(string, "\r", "|r") && 32 | escape_all_or_truncate(string, "[", "|[") && escape_all_or_truncate(string, "]", "|]"); 33 | } 34 | 35 | template 36 | std::string_view make_escaped(small_string_span buffer, const T& value) noexcept { 37 | buffer.clear(); 38 | append_or_truncate(buffer, value); 39 | escape(buffer); 40 | return std::string_view{buffer.data(), buffer.size()}; 41 | } 42 | 43 | void print_assertion(const registry& r, const assertion& msg) noexcept { 44 | small_string buffer; 45 | 46 | r.print("'", make_escaped(buffer, msg.location.file), ":", msg.location.line, "|n"); 47 | for (const auto& c : msg.captures) { 48 | r.print("with ", make_escaped(buffer, c), "|n"); 49 | } 50 | 51 | constexpr std::string_view indent = " "; 52 | 53 | std::visit( 54 | overload{ 55 | [&](std::string_view message) { r.print(indent, make_escaped(buffer, message), "'"); }, 56 | [&](const snitch::expression_info& exp) { 57 | r.print(indent, exp.type, "(", make_escaped(buffer, exp.expected), ")"); 58 | 59 | constexpr std::size_t long_line_threshold = 64; 60 | if (!exp.actual.empty()) { 61 | if (exp.expected.size() + exp.type.size() + 3 > long_line_threshold || 62 | exp.actual.size() + 5 > long_line_threshold) { 63 | r.print("|n", indent, "got: ", make_escaped(buffer, exp.actual), "'"); 64 | } else { 65 | r.print(", got: ", make_escaped(buffer, exp.actual), "'"); 66 | } 67 | } else { 68 | r.print("'"); 69 | } 70 | }}, 71 | msg.data); 72 | } 73 | 74 | void send_message( 75 | const registry& r, std::string_view message, std::initializer_list args) noexcept { 76 | constexpr std::string_view teamcity_header = "##teamCity["; 77 | constexpr std::string_view teamcity_footer = "]\n"; 78 | 79 | r.print(teamcity_header, message); 80 | for (const auto& arg : args) { 81 | r.print(" ", arg.key, "="); 82 | std::visit( 83 | snitch::overload{ 84 | [&](std::string_view msg) { r.print("'", msg, "'"); }, 85 | [&](const assertion& msg) { print_assertion(r, msg); }}, 86 | arg.value); 87 | } 88 | r.print(teamcity_footer); 89 | } 90 | 91 | small_string 92 | make_suite_name(std::string_view app, const filter_info& filters) noexcept { 93 | small_string name; 94 | append_or_truncate(name, app); 95 | for (const auto& filter : filters) { 96 | append_or_truncate(name, " \"", filter, "\""); 97 | } 98 | escape(name); 99 | return name; 100 | } 101 | 102 | small_string make_full_name(const test_id& id) noexcept { 103 | small_string name; 104 | snitch::impl::make_full_name(name, id); 105 | escape(name); 106 | return name; 107 | } 108 | 109 | constexpr std::size_t max_duration_length = 32; 110 | 111 | # if SNITCH_WITH_TIMINGS 112 | small_string make_duration(float duration) noexcept { 113 | small_string string; 114 | append_or_truncate(string, static_cast(duration * 1e6f)); 115 | return string; 116 | } 117 | # endif 118 | } // namespace 119 | 120 | void initialize(registry& r) noexcept { 121 | // TeamCity needs test_case_started and test_case_ended events, which are only printed on 122 | // verbosity 'high', so ensure the requested verbosity is at least as much. 123 | r.verbose = r.verbose < registry::verbosity::high ? registry::verbosity::high : r.verbose; 124 | } 125 | 126 | void report(const registry& r, const snitch::event::data& event) noexcept { 127 | std::visit( 128 | snitch::overload{ 129 | [&](const snitch::event::test_run_started& e) { 130 | send_message(r, "testSuiteStarted", {{"name", make_suite_name(e.name, e.filters)}}); 131 | }, 132 | [&](const snitch::event::test_run_ended& e) { 133 | send_message( 134 | r, "testSuiteFinished", {{"name", make_suite_name(e.name, e.filters)}}); 135 | }, 136 | [&](const snitch::event::test_case_started& e) { 137 | send_message(r, "testStarted", {{"name", make_full_name(e.id)}}); 138 | }, 139 | [&](const snitch::event::test_case_ended& e) { 140 | # if SNITCH_WITH_TIMINGS 141 | send_message( 142 | r, "testFinished", 143 | {{"name", make_full_name(e.id)}, {"duration", make_duration(e.duration)}}); 144 | # else 145 | send_message(r, "testFinished", {{"name", make_full_name(e.id)}}); 146 | # endif 147 | }, 148 | [&](const snitch::event::section_started& e) { 149 | send_message( 150 | r, "blockOpened", {{"name", e.id.name}, {"description", e.id.description}}); 151 | }, 152 | [&](const snitch::event::section_ended& e) { 153 | send_message(r, "blockClosed", {{"name", e.id.name}}); 154 | }, 155 | [&](const snitch::event::test_case_skipped& e) { 156 | send_message( 157 | r, "testIgnored", 158 | {{"name", make_full_name(e.id)}, 159 | {"message", assertion{e.location, e.sections, e.captures, e.message}}}); 160 | }, 161 | [&](const snitch::event::assertion_failed& e) { 162 | send_message( 163 | r, e.expected || e.allowed ? "testStdOut" : "testFailed", 164 | {{"name", make_full_name(e.id)}, 165 | {e.expected || e.allowed ? "out" : "message", 166 | assertion{e.location, e.sections, e.captures, e.data}}}); 167 | }, 168 | [&](const snitch::event::assertion_succeeded& e) { 169 | send_message( 170 | r, "testStdOut", 171 | {{"name", make_full_name(e.id)}, 172 | {"out", assertion{e.location, e.sections, e.captures, e.data}}}); 173 | }, 174 | [&](const snitch::event::list_test_run_started&) {}, 175 | [&](const snitch::event::list_test_run_ended&) {}, 176 | [&](const snitch::event::test_case_listed& e) { r.print(make_full_name(e.id), "\n"); }}, 177 | event); 178 | } 179 | } // namespace snitch::reporter::teamcity 180 | 181 | SNITCH_REGISTER_REPORTER_CALLBACKS( 182 | "teamcity", 183 | &snitch::reporter::teamcity::initialize, 184 | {}, 185 | &snitch::reporter::teamcity::report, 186 | {}); 187 | 188 | #endif 189 | -------------------------------------------------------------------------------- /src/snitch_section.cpp: -------------------------------------------------------------------------------- 1 | #include "snitch/snitch_section.hpp" 2 | 3 | #include "snitch/snitch_console.hpp" 4 | #include "snitch/snitch_registry.hpp" 5 | #include "snitch/snitch_test_data.hpp" 6 | #include "snitch/snitch_time.hpp" 7 | 8 | #if SNITCH_WITH_EXCEPTIONS 9 | # include 10 | #endif 11 | 12 | namespace snitch::impl { 13 | section_entry_checker::~section_entry_checker() { 14 | auto& sections = state.info.sections; 15 | 16 | if (entered) { 17 | #if SNITCH_WITH_EXCEPTIONS 18 | if (std::uncaught_exceptions() > 0 && !state.held_info.has_value()) { 19 | // We are unwinding the stack because an exception has been thrown; 20 | // keep a copy of the full section state since we will want to preserve the information 21 | // when reporting the exception. 22 | state.held_info = state.info; 23 | } 24 | #endif 25 | 26 | pop_location(state); 27 | 28 | bool last_entry = false; 29 | if (sections.depth == sections.levels.size()) { 30 | // We just entered this section, and there was no child section in it. 31 | // This is a leaf; flag that a leaf has been executed so that no other leaf 32 | // is executed in this run. 33 | // Note: don't pop this level from the section state yet, it may have siblings 34 | // that we don't know about yet. Popping will be done when we exit from the parent, 35 | // since then we will know if there is any sibling. 36 | sections.leaf_executed = true; 37 | last_entry = true; 38 | } else { 39 | // Check if there is any child section left to execute, at any depth below this one. 40 | bool no_child_section_left = true; 41 | for (std::size_t c = sections.depth; c < sections.levels.size(); ++c) { 42 | auto& child = sections.levels[c]; 43 | if (child.previous_section_id != child.max_section_id) { 44 | no_child_section_left = false; 45 | break; 46 | } 47 | } 48 | 49 | if (no_child_section_left) { 50 | // No more children, we can pop this level and never go back. 51 | sections.levels.pop_back(); 52 | last_entry = true; 53 | } 54 | } 55 | 56 | // Emit the section end event (only on last entry, and only if no exception in flight). 57 | #if SNITCH_WITH_EXCEPTIONS 58 | if (last_entry && std::uncaught_exceptions() == 0) 59 | #else 60 | if (last_entry) 61 | #endif 62 | { 63 | registry::report_section_ended(sections.current_section.back()); 64 | } 65 | 66 | sections.current_section.pop_back(); 67 | } 68 | 69 | --sections.depth; 70 | } 71 | 72 | section_entry_checker::operator bool() { 73 | #if SNITCH_WITH_EXCEPTIONS 74 | if (std::uncaught_exceptions() == 0) { 75 | notify_exception_handled(); 76 | } 77 | #endif 78 | 79 | auto& sections = state.info.sections; 80 | 81 | if (sections.depth >= sections.levels.size()) { 82 | if (sections.depth >= max_nested_sections) { 83 | using namespace snitch::impl; 84 | state.reg.print( 85 | make_colored("error:", state.reg.with_color, color::fail), 86 | " max number of nested sections reached; " 87 | "please increase 'SNITCH_MAX_NESTED_SECTIONS' (currently ", 88 | max_nested_sections, ")\n."); 89 | assertion_failed("max number of nested sections reached"); 90 | } 91 | 92 | sections.levels.push_back({}); 93 | } 94 | 95 | ++sections.depth; 96 | 97 | auto& level = sections.levels[sections.depth - 1]; 98 | 99 | ++level.current_section_id; 100 | if (level.current_section_id > level.max_section_id) { 101 | level.max_section_id = level.current_section_id; 102 | } 103 | 104 | if (sections.leaf_executed) { 105 | // We have already executed another leaf section; can't execute more 106 | // on this run, so don't bother going inside this one now. 107 | return false; 108 | } 109 | 110 | const bool previous_was_preceeding_sibling = 111 | level.current_section_id == level.previous_section_id + 1; 112 | const bool children_remaining_in_self = level.current_section_id == level.previous_section_id && 113 | sections.depth < sections.levels.size(); 114 | 115 | if (!previous_was_preceeding_sibling && !children_remaining_in_self) { 116 | // Skip this section if: 117 | // - The section entered in the previous run was not its immediate previous sibling, and 118 | // - This section was not already entered in the previous run with remaining children. 119 | return false; 120 | } 121 | 122 | // Entering this section. 123 | 124 | // Push new section on the stack. 125 | level.previous_section_id = level.current_section_id; 126 | sections.current_section.push_back( 127 | #if SNITCH_WITH_TIMINGS 128 | section{.id = id, .location = location, .start_time = get_current_time()} 129 | #else 130 | section{.id = id, .location = location} 131 | #endif 132 | ); 133 | 134 | push_location(state, {location.file, location.line, location_type::section_scope}); 135 | entered = true; 136 | 137 | // Emit the section start event (only on first entry). 138 | if (previous_was_preceeding_sibling) { 139 | registry::report_section_started(sections.current_section.back()); 140 | } 141 | 142 | return true; 143 | } 144 | } // namespace snitch::impl 145 | -------------------------------------------------------------------------------- /src/snitch_test_data.cpp: -------------------------------------------------------------------------------- 1 | #include "snitch/snitch_test_data.hpp" 2 | 3 | #include "snitch/snitch_registry.hpp" 4 | 5 | #if SNITCH_WITH_EXCEPTIONS 6 | # include 7 | #endif 8 | 9 | namespace snitch::impl { 10 | namespace { 11 | SNITCH_THREAD_LOCAL test_state* thread_current_test = nullptr; 12 | } 13 | 14 | test_state& get_current_test() noexcept { 15 | test_state* current = thread_current_test; 16 | if (current == nullptr) { 17 | terminate_with("no test case is currently running on this thread"); 18 | } 19 | 20 | return *current; 21 | } 22 | 23 | test_state* try_get_current_test() noexcept { 24 | return thread_current_test; 25 | } 26 | 27 | void set_current_test(test_state* current) noexcept { 28 | thread_current_test = current; 29 | } 30 | 31 | void push_location(test_state& test, const assertion_location& location) noexcept { 32 | test.info.locations.push_back(location); 33 | } 34 | 35 | void pop_location(test_state& test) noexcept { 36 | test.info.locations.pop_back(); 37 | } 38 | 39 | scoped_test_check::scoped_test_check(const source_location& location) noexcept : 40 | test(get_current_test()) { 41 | 42 | #if SNITCH_WITH_EXCEPTIONS 43 | if (std::uncaught_exceptions() == 0) { 44 | notify_exception_handled(); 45 | } 46 | #endif 47 | 48 | push_location(test, {location.file, location.line, location_type::in_check}); 49 | test.in_check = true; 50 | } 51 | 52 | scoped_test_check::~scoped_test_check() noexcept { 53 | test.in_check = false; 54 | 55 | #if SNITCH_WITH_EXCEPTIONS 56 | if (std::uncaught_exceptions() > 0 && !test.held_info.has_value()) { 57 | // We are unwinding the stack because an exception has been thrown; 58 | // keep a copy of the full location state since we will want to preserve the information 59 | // when reporting the exception. 60 | test.held_info = test.info; 61 | } 62 | #endif 63 | 64 | pop_location(test); 65 | } 66 | } // namespace snitch::impl 67 | 68 | namespace snitch { 69 | #if SNITCH_WITH_EXCEPTIONS 70 | void notify_exception_handled() noexcept { 71 | auto& state = impl::get_current_test(); 72 | if (!state.held_info.has_value()) { 73 | return; 74 | } 75 | 76 | // Close all sections that were left open by the exception. 77 | auto& current_held_section = state.held_info.value().sections.current_section; 78 | const auto& current_section = state.info.sections.current_section; 79 | while (current_held_section.size() > current_section.size()) { 80 | registry::report_section_ended(current_held_section.back()); 81 | current_held_section.pop_back(); 82 | } 83 | 84 | state.held_info.reset(); 85 | } 86 | #endif 87 | } // namespace snitch 88 | -------------------------------------------------------------------------------- /src/snitch_time.cpp: -------------------------------------------------------------------------------- 1 | #include "snitch/snitch_time.hpp" 2 | 3 | #if SNITCH_WITH_TIMINGS 4 | # include 5 | 6 | namespace snitch { 7 | namespace impl { 8 | using clock = std::chrono::steady_clock; 9 | using tick_resolution = std::chrono::nanoseconds; 10 | } // namespace impl 11 | 12 | time_point_t get_current_time() noexcept { 13 | static auto start_time = impl::clock::now(); 14 | return static_cast( 15 | std::chrono::duration_cast(impl::clock::now() - start_time).count()); 16 | } 17 | 18 | float get_duration_in_seconds(time_point_t start, time_point_t end) noexcept { 19 | return std::chrono::duration_cast>( 20 | impl::tick_resolution(end - start)) 21 | .count(); 22 | } 23 | } // namespace snitch 24 | #endif 25 | -------------------------------------------------------------------------------- /tests/CMakeLists.txt: -------------------------------------------------------------------------------- 1 | cmake_minimum_required(VERSION 3.20) 2 | 3 | function(add_platform_definitions TARGET) 4 | target_compile_features(${TARGET} INTERFACE cxx_std_20) 5 | if (CMAKE_SYSTEM_NAME MATCHES "Emscripten") 6 | target_compile_definitions(${TARGET} PRIVATE SNITCH_PLATFORM_WASM) 7 | target_compile_definitions(${TARGET} PRIVATE SNITCH_COMPILER_EMSCRIPTEN) 8 | elseif (APPLE) 9 | target_compile_definitions(${TARGET} PRIVATE SNITCH_PLATFORM_OSX) 10 | elseif (UNIX) 11 | target_compile_definitions(${TARGET} PRIVATE SNITCH_PLATFORM_LINUX) 12 | elseif (WIN32) 13 | target_compile_definitions(${TARGET} PRIVATE SNITCH_PLATFORM_WINDOWS) 14 | if (CMAKE_CXX_COMPILER_ID STREQUAL "MSVC") 15 | target_compile_definitions(${TARGET} PRIVATE SNITCH_COMPILER_MSVC) 16 | endif() 17 | endif() 18 | 19 | if (CMAKE_CXX_COMPILER_ID STREQUAL "GNU") 20 | target_compile_options(${TARGET} PRIVATE -Wall) 21 | target_compile_options(${TARGET} PRIVATE -Wextra) 22 | target_compile_options(${TARGET} PRIVATE -Werror) 23 | target_compile_options(${TARGET} PRIVATE -pedantic) 24 | elseif (CMAKE_CXX_COMPILER_ID STREQUAL "Clang" OR CMAKE_CXX_COMPILER_ID STREQUAL "AppleClang") 25 | target_compile_options(${TARGET} PRIVATE -Wall) 26 | target_compile_options(${TARGET} PRIVATE -Wextra) 27 | target_compile_options(${TARGET} PRIVATE -Werror) 28 | elseif(CMAKE_CXX_COMPILER_ID STREQUAL "MSVC") 29 | target_compile_options(${TARGET} PRIVATE /W4) 30 | target_compile_options(${TARGET} PRIVATE /WX) 31 | target_compile_options(${TARGET} PRIVATE /EHs) 32 | # Increase default stack size to match default for Linux 33 | target_link_options(${TARGET} PRIVATE /STACK:8388608) 34 | endif() 35 | 36 | if(CMAKE_SYSTEM_NAME MATCHES "Emscripten") 37 | # Increase default stack size to match default for Linux 38 | target_link_options(${TARGET} PRIVATE "SHELL:-s STACK_SIZE=8388608") 39 | endif() 40 | endfunction() 41 | 42 | set(TEST_UTILITY_FILES 43 | ${PROJECT_SOURCE_DIR}/tests/testing.cpp 44 | ${PROJECT_SOURCE_DIR}/tests/testing_event.cpp 45 | ${PROJECT_SOURCE_DIR}/tests/testing_reporters.cpp) 46 | 47 | set(RUNTIME_TEST_FILES 48 | ${TEST_UTILITY_FILES} 49 | ${PROJECT_SOURCE_DIR}/tests/runtime_tests/any.cpp 50 | ${PROJECT_SOURCE_DIR}/tests/runtime_tests/capture.cpp 51 | ${PROJECT_SOURCE_DIR}/tests/runtime_tests/check.cpp 52 | ${PROJECT_SOURCE_DIR}/tests/runtime_tests/cli.cpp 53 | ${PROJECT_SOURCE_DIR}/tests/runtime_tests/function_ref.cpp 54 | ${PROJECT_SOURCE_DIR}/tests/runtime_tests/macros.cpp 55 | ${PROJECT_SOURCE_DIR}/tests/runtime_tests/matchers.cpp 56 | ${PROJECT_SOURCE_DIR}/tests/runtime_tests/registry.cpp 57 | ${PROJECT_SOURCE_DIR}/tests/runtime_tests/regressions.cpp 58 | ${PROJECT_SOURCE_DIR}/tests/runtime_tests/section.cpp 59 | ${PROJECT_SOURCE_DIR}/tests/runtime_tests/skip.cpp 60 | ${PROJECT_SOURCE_DIR}/tests/runtime_tests/small_string.cpp 61 | ${PROJECT_SOURCE_DIR}/tests/runtime_tests/small_vector.cpp 62 | ${PROJECT_SOURCE_DIR}/tests/runtime_tests/string_utility.cpp 63 | ${PROJECT_SOURCE_DIR}/tests/runtime_tests/type_id.cpp 64 | ${PROJECT_SOURCE_DIR}/tests/runtime_tests/type_name.cpp) 65 | 66 | set(APPROVAL_TEST_FILES 67 | ${TEST_UTILITY_FILES} 68 | ${PROJECT_SOURCE_DIR}/tests/approval_tests/reporter_catch2_xml.cpp 69 | ${PROJECT_SOURCE_DIR}/tests/approval_tests/reporter_console.cpp 70 | ${PROJECT_SOURCE_DIR}/tests/approval_tests/reporter_teamcity.cpp) 71 | 72 | if (CMAKE_SYSTEM_NAME MATCHES "Emscripten") 73 | # For Emcripten, we need the working directory to be where the binaries are created, 74 | # because Emscripten will generate *.data files there that we need to load. 75 | # We don't have access to the filesystem otherwise. 76 | set(APPROVAL_TEST_WORKING_DIRECTORY ${CMAKE_CURRENT_BINARY_DIR}) 77 | set(RUNTIME_TEST_WORKING_DIRECTORY ${CMAKE_CURRENT_BINARY_DIR}) 78 | else() 79 | set(APPROVAL_TEST_WORKING_DIRECTORY ${CMAKE_CURRENT_SOURCE_DIR}/approval_tests) 80 | set(RUNTIME_TEST_WORKING_DIRECTORY ${CMAKE_CURRENT_SOURCE_DIR}/runtime_tests) 81 | endif() 82 | 83 | function(copy_shared_library TARGET) 84 | if (BUILD_SHARED_LIBS AND WIN32) 85 | add_custom_command(TARGET ${TARGET} POST_BUILD 86 | COMMAND ${CMAKE_COMMAND} -E copy_if_different $ $) 87 | endif() 88 | endfunction() 89 | 90 | if (SNITCH_USE_SYSTEM_DOCTEST) 91 | find_package(doctest REQUIRED) 92 | else() 93 | include(FetchContent) 94 | 95 | set(DOCTEST_NO_INSTALL ON) 96 | 97 | FetchContent_Declare(doctest 98 | GIT_REPOSITORY https://github.com/doctest/doctest.git 99 | GIT_TAG v2.4.9) 100 | FetchContent_MakeAvailable(doctest) 101 | endif() 102 | 103 | # Test snitch with doctest. 104 | add_executable(snitch_runtime_tests 105 | ${RUNTIME_TEST_FILES}) 106 | target_include_directories(snitch_runtime_tests PRIVATE 107 | ${PROJECT_SOURCE_DIR}/include 108 | ${PROJECT_BINARY_DIR} 109 | ${PROJECT_SOURCE_DIR}/tests) 110 | target_link_libraries(snitch_runtime_tests PRIVATE 111 | snitch-testlib 112 | doctest::doctest) 113 | add_platform_definitions(snitch_runtime_tests) 114 | target_compile_features(snitch_runtime_tests PUBLIC cxx_std_20) 115 | target_compile_definitions(snitch_runtime_tests PUBLIC 116 | SNITCH_WITH_SHORTHAND_MACROS=0) 117 | copy_shared_library(snitch_runtime_tests) 118 | 119 | add_custom_target(snitch_runtime_tests_run 120 | COMMAND snitch_runtime_tests 121 | WORKING_DIRECTORY ${RUNTIME_TEST_WORKING_DIRECTORY} 122 | SOURCES ${RUNTIME_TEST_FILES}) 123 | set_target_properties(snitch_runtime_tests_run PROPERTIES EXCLUDE_FROM_ALL True) 124 | 125 | # Test snitch with doctest (approval tests). 126 | add_executable(snitch_approval_tests 127 | ${APPROVAL_TEST_FILES}) 128 | target_include_directories(snitch_approval_tests PRIVATE 129 | ${PROJECT_SOURCE_DIR}/include 130 | ${PROJECT_BINARY_DIR} 131 | ${PROJECT_SOURCE_DIR}/tests) 132 | target_link_libraries(snitch_approval_tests PRIVATE 133 | snitch-testlib 134 | doctest::doctest) 135 | add_platform_definitions(snitch_approval_tests) 136 | target_compile_features(snitch_approval_tests PUBLIC cxx_std_20) 137 | target_compile_definitions(snitch_approval_tests PUBLIC 138 | SNITCH_WITH_SHORTHAND_MACROS=0) 139 | copy_shared_library(snitch_approval_tests) 140 | 141 | if (CMAKE_SYSTEM_NAME MATCHES "Emscripten") 142 | # Add approval test data to the preload-file list 143 | target_link_options(snitch_approval_tests PUBLIC 144 | "SHELL:--preload-file ${PROJECT_SOURCE_DIR}/tests/approval_tests/data@data") 145 | endif() 146 | 147 | add_custom_target(snitch_approval_tests_run 148 | COMMAND snitch_approval_tests 149 | WORKING_DIRECTORY ${APPROVAL_TEST_WORKING_DIRECTORY} 150 | SOURCES ${APPROVAL_TEST_FILES}) 151 | set_target_properties(snitch_approval_tests_run PROPERTIES EXCLUDE_FROM_ALL True) 152 | 153 | # Test snitch with itself. 154 | add_executable(snitch_runtime_tests_self 155 | ${RUNTIME_TEST_FILES}) 156 | target_include_directories(snitch_runtime_tests_self PRIVATE 157 | ${PROJECT_SOURCE_DIR}/include 158 | ${PROJECT_BINARY_DIR} 159 | ${PROJECT_SOURCE_DIR}/tests) 160 | target_link_libraries(snitch_runtime_tests_self PRIVATE snitch-testlib) 161 | add_platform_definitions(snitch_runtime_tests_self) 162 | target_compile_features(snitch_runtime_tests_self PUBLIC cxx_std_20) 163 | target_compile_definitions(snitch_runtime_tests_self PUBLIC 164 | SNITCH_TEST_WITH_SNITCH) 165 | copy_shared_library(snitch_runtime_tests_self) 166 | 167 | add_custom_target(snitch_runtime_tests_self_run 168 | COMMAND snitch_runtime_tests_self 169 | WORKING_DIRECTORY ${RUNTIME_TEST_WORKING_DIRECTORY} 170 | SOURCES ${RUNTIME_TEST_FILES}) 171 | set_target_properties(snitch_runtime_tests_self_run PROPERTIES EXCLUDE_FROM_ALL True) 172 | -------------------------------------------------------------------------------- /tests/approval_tests/data/actual/keepme: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/snitch-org/snitch/adbabb9a0c30652339b4a167774a7ec3f710c989/tests/approval_tests/data/actual/keepme -------------------------------------------------------------------------------- /tests/approval_tests/data/blanked/keepme: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/snitch-org/snitch/adbabb9a0c30652339b4a167774a7ec3f710c989/tests/approval_tests/data/blanked/keepme -------------------------------------------------------------------------------- /tests/approval_tests/data/expected/reporter_catch2_xml_allpass: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | 7 | 8 | 9 | 10 | 11 | 12 | 13 | 1 == 2 14 | 15 | 16 | 1 != 2 17 | 18 | 19 | 20 | 21 | 22 | 23 | 24 | 1 == 2 25 | 26 | 27 | 1 != 2 28 | 29 | 30 | 31 | 32 | 33 | 34 | 35 | 36 | 37 | 38 | 39 | 40 | 41 | 42 | 43 | 44 | 45 | 46 | 47 | 48 | 49 | 50 | 51 | 52 | 53 | 54 | 55 | 56 | 57 | 58 | 59 | 60 | 61 | 62 | 63 | 64 | aaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa... 65 | 66 | 67 | 68 | 69 | 70 | 71 | 72 | 73 | 74 | 75 | 76 | 77 | -------------------------------------------------------------------------------- /tests/approval_tests/data/expected/reporter_catch2_xml_notest: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | 7 | -------------------------------------------------------------------------------- /tests/approval_tests/data/expected/reporter_console_allfail: -------------------------------------------------------------------------------- 1 | starting test with snitch v* 2 | ========================================== 3 | failed: running test case "test fail" 4 | at *testing_reporters.cpp:* 5 | CHECK(1 == 2), got: 1 != 2 6 | failed: running test case "test shouldfail good fail" 7 | at *testing_reporters.cpp:* 8 | expected test to fail 9 | failed: running test case "test no tags fail" 10 | at *testing_reporters.cpp:* 11 | CHECK(1 == 2), got: 1 != 2 12 | failed: running test case "typed test no tags fail" 13 | at *testing_reporters.cpp:* 14 | for type int 15 | CHECK(1 == 2), got: 1 != 2 16 | failed: running test case "typed test no tags fail" 17 | at *testing_reporters.cpp:* 18 | for type float 19 | CHECK(1 == 2), got: 1 != 2 20 | failed: running test case "typed test with tags fail" 21 | at *testing_reporters.cpp:* 22 | for type int 23 | CHECK(1 == 2), got: 1 != 2 24 | failed: running test case "typed test with tags fail" 25 | at *testing_reporters.cpp:* 26 | for type float 27 | CHECK(1 == 2), got: 1 != 2 28 | failed: running test case "test fixture fail" 29 | at *testing_reporters.cpp:* 30 | CHECK(1 == 2), got: 1 != 2 31 | failed: running test case "test FAIL fail" 32 | at *testing_reporters.cpp:* 33 | something bad 34 | failed: running test case "test expression fail" 35 | at *testing_reporters.cpp:* 36 | CHECK(1 == 2), got: 1 != 2 37 | failed: running test case "test long expression fail" 38 | at *testing_reporters.cpp:* 39 | CHECK(some_very_long_name_that_forces_lines_to_wrap != some_very_long_name_that_forces_lines_to_wrap) 40 | got: 1 == 1 41 | failed: running test case "test too long expression fail" 42 | at *testing_reporters.cpp:* 43 | CHECK(super_long_string != super_long_string) 44 | failed: running test case "test too long message fail" 45 | at *testing_reporters.cpp:* 46 | aaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa... 47 | failed: running test case "test NOTHROW fail" 48 | at *testing_reporters.cpp:* 49 | expected throw_something(true) not to throw but it threw a std::exception; message: I threw 50 | failed: running test case "test THROW fail" 51 | at *testing_reporters.cpp:* 52 | std::runtime_error expected but no exception thrown 53 | failed: running test case "test THROW fail" 54 | at *testing_reporters.cpp:* 55 | std::system_error expected but other std::exception thrown; message: I threw 56 | failed: running test case "test THROW fail" 57 | at *testing_reporters.cpp:* 58 | could not match caught std::runtime_error with expected content: could not find 'I throws' in 'I threw' 59 | failed: running test case "test unexpected throw fail" 60 | somewhere inside test case at *testing_reporters.cpp:* 61 | unexpected std::exception caught; message: unexpected error 62 | failed: running test case "test unexpected throw in section fail" 63 | in section "section 1" 64 | in section "section 2" 65 | somewhere inside section at *testing_reporters.cpp:* 66 | unexpected std::exception caught; message: unexpected error 67 | failed: running test case "test unexpected throw in check fail" 68 | somewhere inside check at *testing_reporters.cpp:* 69 | unexpected std::exception caught; message: unexpected error 70 | failed: running test case "test unexpected throw in check & section fail" 71 | in section "section 1" 72 | somewhere inside check at *testing_reporters.cpp:* 73 | unexpected std::exception caught; message: unexpected error 74 | ========================================== 75 | error: all tests failed (19 out of 19 test cases, 35 assertions, * seconds) 76 | -------------------------------------------------------------------------------- /tests/approval_tests/data/expected/reporter_console_allpass: -------------------------------------------------------------------------------- 1 | starting test with snitch v* 2 | ========================================== 3 | allowed failure: running test case "test mayfail bad pass" 4 | at *testing_reporters.cpp:* 5 | CHECK(1 == 2), got: 1 != 2 6 | expected failure: running test case "test shouldfail bad pass" 7 | at *testing_reporters.cpp:* 8 | CHECK(1 == 2), got: 1 != 2 9 | failed: running test case "test too long message pass" 10 | at *testing_reporters.cpp:* 11 | aaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa... 12 | ========================================== 13 | error: some tests failed (1 out of 17 test cases, 26 assertions, * seconds) 14 | -------------------------------------------------------------------------------- /tests/approval_tests/data/expected/reporter_console_list_tests: -------------------------------------------------------------------------------- 1 | Matching test cases: 2 | test pass 3 | [tag2][tag1] 4 | test fail 5 | [tag2][tag1] 6 | test mayfail good pass 7 | [tag2][tag1][!mayfail] 8 | test mayfail bad pass 9 | [tag2][tag1][!mayfail] 10 | test shouldfail good fail 11 | [tag2][tag1][!shouldfail] 12 | test shouldfail bad pass 13 | [tag2][tag1][!shouldfail] 14 | test no tags pass 15 | test no tags fail 16 | typed test no tags pass 17 | typed test no tags pass 18 | typed test no tags fail 19 | typed test no tags fail 20 | typed test with tags pass 21 | [tag1] 22 | typed test with tags pass 23 | [tag1] 24 | typed test with tags fail 25 | [tag1] 26 | typed test with tags fail 27 | [tag1] 28 | test fixture pass 29 | [tag with space] 30 | test fixture fail 31 | [tag with space] 32 | test SUCCEED pass 33 | test FAIL fail 34 | test expression pass 35 | test expression fail 36 | test long expression pass 37 | test long expression fail 38 | test too long expression pass 39 | test too long expression fail 40 | test too long message pass 41 | test too long message fail 42 | test NOTHROW pass 43 | test NOTHROW fail 44 | test THROW pass 45 | test THROW fail 46 | test unexpected throw fail 47 | test unexpected throw in section fail 48 | test unexpected throw in check fail 49 | test unexpected throw in check & section fail 50 | test unexpected throw in check & section mayfail 51 | [!mayfail] 52 | test SKIP 53 | test INFO 54 | test multiple INFO 55 | test SECTION 56 | test SECTION mayfail 57 | [!mayfail] 58 | test multiple SECTION 59 | test SECTION & INFO 60 | test SECTION & CAPTURE 61 | test SKIP in SECTION 62 | 46 matching test cases 63 | -------------------------------------------------------------------------------- /tests/approval_tests/data/expected/reporter_console_notest: -------------------------------------------------------------------------------- 1 | starting test with snitch v* 2 | ========================================== 3 | ========================================== 4 | success: all tests passed (0 test cases, 0 assertions, * seconds) 5 | -------------------------------------------------------------------------------- /tests/approval_tests/data/expected/reporter_teamcity_allfail: -------------------------------------------------------------------------------- 1 | ##teamCity[testSuiteStarted name='test "* fail*"'] 2 | ##teamCity[testStarted name='test fail'] 3 | ##teamCity[testFailed name='test fail' message='*testing_reporters.cpp:*|n CHECK(1 == 2), got: 1 != 2'] 4 | ##teamCity[testFinished name='test fail' duration='*'] 5 | ##teamCity[testStarted name='test shouldfail good fail'] 6 | ##teamCity[testFailed name='test shouldfail good fail' message='*testing_reporters.cpp:*|n expected test to fail'] 7 | ##teamCity[testFinished name='test shouldfail good fail' duration='*'] 8 | ##teamCity[testStarted name='test no tags fail'] 9 | ##teamCity[testFailed name='test no tags fail' message='*testing_reporters.cpp:*|n CHECK(1 == 2), got: 1 != 2'] 10 | ##teamCity[testFinished name='test no tags fail' duration='*'] 11 | ##teamCity[testStarted name='typed test no tags fail '] 12 | ##teamCity[testFailed name='typed test no tags fail ' message='*testing_reporters.cpp:*|n CHECK(1 == 2), got: 1 != 2'] 13 | ##teamCity[testFinished name='typed test no tags fail ' duration='*'] 14 | ##teamCity[testStarted name='typed test no tags fail '] 15 | ##teamCity[testFailed name='typed test no tags fail ' message='*testing_reporters.cpp:*|n CHECK(1 == 2), got: 1 != 2'] 16 | ##teamCity[testFinished name='typed test no tags fail ' duration='*'] 17 | ##teamCity[testStarted name='typed test with tags fail '] 18 | ##teamCity[testFailed name='typed test with tags fail ' message='*testing_reporters.cpp:*|n CHECK(1 == 2), got: 1 != 2'] 19 | ##teamCity[testFinished name='typed test with tags fail ' duration='*'] 20 | ##teamCity[testStarted name='typed test with tags fail '] 21 | ##teamCity[testFailed name='typed test with tags fail ' message='*testing_reporters.cpp:*|n CHECK(1 == 2), got: 1 != 2'] 22 | ##teamCity[testFinished name='typed test with tags fail ' duration='*'] 23 | ##teamCity[testStarted name='test fixture fail'] 24 | ##teamCity[testFailed name='test fixture fail' message='*testing_reporters.cpp:*|n CHECK(1 == 2), got: 1 != 2'] 25 | ##teamCity[testFinished name='test fixture fail' duration='*'] 26 | ##teamCity[testStarted name='test FAIL fail'] 27 | ##teamCity[testFailed name='test FAIL fail' message='*testing_reporters.cpp:*|n something bad'] 28 | ##teamCity[testFinished name='test FAIL fail' duration='*'] 29 | ##teamCity[testStarted name='test expression fail'] 30 | ##teamCity[testFailed name='test expression fail' message='*testing_reporters.cpp:*|n CHECK(1 == 2), got: 1 != 2'] 31 | ##teamCity[testFinished name='test expression fail' duration='*'] 32 | ##teamCity[testStarted name='test long expression fail'] 33 | ##teamCity[testFailed name='test long expression fail' message='*testing_reporters.cpp:*|n CHECK(some_very_long_name_that_forces_lines_to_wrap != some_very_long_name_that_forces_lines_to_wrap)|n got: 1 == 1'] 34 | ##teamCity[testFinished name='test long expression fail' duration='*'] 35 | ##teamCity[testStarted name='test too long expression fail'] 36 | ##teamCity[testFailed name='test too long expression fail' message='*testing_reporters.cpp:*|n CHECK(super_long_string != super_long_string)'] 37 | ##teamCity[testFinished name='test too long expression fail' duration='*'] 38 | ##teamCity[testStarted name='test too long message fail'] 39 | ##teamCity[testFailed name='test too long message fail' message='*testing_reporters.cpp:*|n aaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa...'] 40 | ##teamCity[testFinished name='test too long message fail' duration='*'] 41 | ##teamCity[testStarted name='test NOTHROW fail'] 42 | ##teamCity[testFailed name='test NOTHROW fail' message='*testing_reporters.cpp:*|n expected throw_something(true) not to throw but it threw a std::exception; message: I threw'] 43 | ##teamCity[testFinished name='test NOTHROW fail' duration='*'] 44 | ##teamCity[testStarted name='test THROW fail'] 45 | ##teamCity[testFailed name='test THROW fail' message='*testing_reporters.cpp:*|n std::runtime_error expected but no exception thrown'] 46 | ##teamCity[testFailed name='test THROW fail' message='*testing_reporters.cpp:*|n std::system_error expected but other std::exception thrown; message: I threw'] 47 | ##teamCity[testFailed name='test THROW fail' message='*testing_reporters.cpp:*|n could not match caught std::runtime_error with expected content: could not find |'I throws|' in |'I threw|''] 48 | ##teamCity[testFinished name='test THROW fail' duration='*'] 49 | ##teamCity[testStarted name='test unexpected throw fail'] 50 | ##teamCity[testFailed name='test unexpected throw fail' message='*testing_reporters.cpp:*|n unexpected std::exception caught; message: unexpected error'] 51 | ##teamCity[testFinished name='test unexpected throw fail' duration='*'] 52 | ##teamCity[testStarted name='test unexpected throw in section fail'] 53 | ##teamCity[blockOpened name='section 1' description=''] 54 | ##teamCity[blockOpened name='section 2' description=''] 55 | ##teamCity[testFailed name='test unexpected throw in section fail' message='*testing_reporters.cpp:*|n unexpected std::exception caught; message: unexpected error'] 56 | ##teamCity[blockClosed name='section 2'] 57 | ##teamCity[blockClosed name='section 1'] 58 | ##teamCity[testFinished name='test unexpected throw in section fail' duration='*'] 59 | ##teamCity[testStarted name='test unexpected throw in check fail'] 60 | ##teamCity[testFailed name='test unexpected throw in check fail' message='*testing_reporters.cpp:*|n unexpected std::exception caught; message: unexpected error'] 61 | ##teamCity[testFinished name='test unexpected throw in check fail' duration='*'] 62 | ##teamCity[testStarted name='test unexpected throw in check & section fail'] 63 | ##teamCity[blockOpened name='section 1' description=''] 64 | ##teamCity[testFailed name='test unexpected throw in check & section fail' message='*testing_reporters.cpp:*|n unexpected std::exception caught; message: unexpected error'] 65 | ##teamCity[blockClosed name='section 1'] 66 | ##teamCity[testFinished name='test unexpected throw in check & section fail' duration='*'] 67 | ##teamCity[testSuiteFinished name='test "* fail*"'] 68 | -------------------------------------------------------------------------------- /tests/approval_tests/data/expected/reporter_teamcity_allpass: -------------------------------------------------------------------------------- 1 | ##teamCity[testSuiteStarted name='test "* pass*"'] 2 | ##teamCity[testStarted name='test pass'] 3 | ##teamCity[testFinished name='test pass' duration='*'] 4 | ##teamCity[testStarted name='test mayfail good pass'] 5 | ##teamCity[testFinished name='test mayfail good pass' duration='*'] 6 | ##teamCity[testStarted name='test mayfail bad pass'] 7 | ##teamCity[testStdOut name='test mayfail bad pass' out='*testing_reporters.cpp:*|n CHECK(1 == 2), got: 1 != 2'] 8 | ##teamCity[testFinished name='test mayfail bad pass' duration='*'] 9 | ##teamCity[testStarted name='test shouldfail bad pass'] 10 | ##teamCity[testStdOut name='test shouldfail bad pass' out='*testing_reporters.cpp:*|n CHECK(1 == 2), got: 1 != 2'] 11 | ##teamCity[testFinished name='test shouldfail bad pass' duration='*'] 12 | ##teamCity[testStarted name='test no tags pass'] 13 | ##teamCity[testFinished name='test no tags pass' duration='*'] 14 | ##teamCity[testStarted name='typed test no tags pass '] 15 | ##teamCity[testFinished name='typed test no tags pass ' duration='*'] 16 | ##teamCity[testStarted name='typed test no tags pass '] 17 | ##teamCity[testFinished name='typed test no tags pass ' duration='*'] 18 | ##teamCity[testStarted name='typed test with tags pass '] 19 | ##teamCity[testFinished name='typed test with tags pass ' duration='*'] 20 | ##teamCity[testStarted name='typed test with tags pass '] 21 | ##teamCity[testFinished name='typed test with tags pass ' duration='*'] 22 | ##teamCity[testStarted name='test fixture pass'] 23 | ##teamCity[testFinished name='test fixture pass' duration='*'] 24 | ##teamCity[testStarted name='test SUCCEED pass'] 25 | ##teamCity[testFinished name='test SUCCEED pass' duration='*'] 26 | ##teamCity[testStarted name='test expression pass'] 27 | ##teamCity[testFinished name='test expression pass' duration='*'] 28 | ##teamCity[testStarted name='test long expression pass'] 29 | ##teamCity[testFinished name='test long expression pass' duration='*'] 30 | ##teamCity[testStarted name='test too long expression pass'] 31 | ##teamCity[testFinished name='test too long expression pass' duration='*'] 32 | ##teamCity[testStarted name='test too long message pass'] 33 | ##teamCity[testFailed name='test too long message pass' message='*testing_reporters.cpp:*|n aaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa...'] 34 | ##teamCity[testFinished name='test too long message pass' duration='*'] 35 | ##teamCity[testStarted name='test NOTHROW pass'] 36 | ##teamCity[testFinished name='test NOTHROW pass' duration='*'] 37 | ##teamCity[testStarted name='test THROW pass'] 38 | ##teamCity[testFinished name='test THROW pass' duration='*'] 39 | ##teamCity[testSuiteFinished name='test "* pass*"'] 40 | -------------------------------------------------------------------------------- /tests/approval_tests/data/expected/reporter_teamcity_list_tests: -------------------------------------------------------------------------------- 1 | test pass 2 | test fail 3 | test mayfail good pass 4 | test mayfail bad pass 5 | test shouldfail good fail 6 | test shouldfail bad pass 7 | test no tags pass 8 | test no tags fail 9 | typed test no tags pass 10 | typed test no tags pass 11 | typed test no tags fail 12 | typed test no tags fail 13 | typed test with tags pass 14 | typed test with tags pass 15 | typed test with tags fail 16 | typed test with tags fail 17 | test fixture pass 18 | test fixture fail 19 | test SUCCEED pass 20 | test FAIL fail 21 | test expression pass 22 | test expression fail 23 | test long expression pass 24 | test long expression fail 25 | test too long expression pass 26 | test too long expression fail 27 | test too long message pass 28 | test too long message fail 29 | test NOTHROW pass 30 | test NOTHROW fail 31 | test THROW pass 32 | test THROW fail 33 | test unexpected throw fail 34 | test unexpected throw in section fail 35 | test unexpected throw in check fail 36 | test unexpected throw in check & section fail 37 | test unexpected throw in check & section mayfail 38 | test SKIP 39 | test INFO 40 | test multiple INFO 41 | test SECTION 42 | test SECTION mayfail 43 | test multiple SECTION 44 | test SECTION & INFO 45 | test SECTION & CAPTURE 46 | test SKIP in SECTION 47 | test escape |||'|n|r|[|] 48 | test escape very long 49 | -------------------------------------------------------------------------------- /tests/approval_tests/data/expected/reporter_teamcity_notest: -------------------------------------------------------------------------------- 1 | ##teamCity[testSuiteStarted name='test "bad_filter"'] 2 | ##teamCity[testSuiteFinished name='test "bad_filter"'] 3 | -------------------------------------------------------------------------------- /tests/approval_tests/reporter_catch2_xml.cpp: -------------------------------------------------------------------------------- 1 | #include "testing.hpp" 2 | #include "testing_assertions.hpp" 3 | #include "testing_event.hpp" 4 | #include "testing_reporters.hpp" 5 | 6 | #include 7 | 8 | #if SNITCH_WITH_CATCH2_XML_REPORTER || SNITCH_WITH_ALL_REPORTERS 9 | 10 | using namespace std::literals; 11 | using snitch::matchers::contains_substring; 12 | 13 | TEST_CASE("xml reporter", "[reporters]") { 14 | mock_framework framework; 15 | register_tests_for_reporters(framework.registry); 16 | framework.registry.add({"test escape <>&\"'"}, SNITCH_CURRENT_LOCATION, [] { 17 | SNITCH_FAIL("escape <>&\"' in messages"); 18 | }); 19 | framework.registry.add({"test escape very long"}, SNITCH_CURRENT_LOCATION, [] { 20 | SNITCH_FAIL(std::string(2 * snitch::max_message_length, '&')); 21 | }); 22 | 23 | std::optional reporter; 24 | auto init = [&](snitch::registry& r) { reporter.emplace(r); }; 25 | auto configure = [&](snitch::registry& r, std::string_view k, std::string_view v) noexcept { 26 | return reporter.value().configure(r, k, v); 27 | }; 28 | auto report = [&](const snitch::registry& r, const snitch::event::data& e) noexcept { 29 | return reporter.value().report(r, e); 30 | }; 31 | auto finish = [&](snitch::registry&) noexcept { reporter.reset(); }; 32 | 33 | framework.registry.add_reporter("xml", init, configure, report, finish); 34 | 35 | constexpr const char* reporter_name = "xml"; 36 | # define REPORTER_PREFIX "reporter_catch2_xml_" 37 | 38 | const std::vector ignores = { 39 | std::regex{R"|(durationInSeconds="([0-9.e+\-]{12})")|"}, 40 | std::regex{R"(catch2-version="([0-9]+\.[0-9]+\.[0-9]+\.[0-9a-z]+).snitch)"}, 41 | std::regex{R"(filename="(.+/snitch/tests/approval_tests/))"}, 42 | std::regex{R"(filename="(.+/snitch/tests/))"}, 43 | std::regex{R"((.+/snitch/tests/approval_tests/))"}, 44 | std::regex{R"((.+/snitch/tests/))"}, 45 | std::regex{R"(filename="(.+\\snitch\\tests\\approval_tests\\))"}, 46 | std::regex{R"(filename="(.+\\snitch\\tests\\))"}, 47 | std::regex{R"((.+\\snitch\\tests\\approval_tests\\))"}, 48 | std::regex{R"((.+\\snitch\\tests\\))"}, 49 | std::regex{R"|(line="([0-9]+)")|"}, 50 | std::regex{R"(([0-9]+))"}}; 51 | 52 | SECTION("default") { 53 | const arg_vector args{"test", "--reporter", reporter_name}; 54 | CHECK_FOR_DIFFERENCES(args, ignores, REPORTER_PREFIX "default"); 55 | } 56 | 57 | SECTION("no test") { 58 | const arg_vector args{"test", "--reporter", reporter_name, "bad_filter"}; 59 | CHECK_FOR_DIFFERENCES(args, ignores, REPORTER_PREFIX "notest"); 60 | } 61 | 62 | SECTION("all pass") { 63 | const arg_vector args{"test", "--reporter", reporter_name, "* pass*"}; 64 | CHECK_FOR_DIFFERENCES(args, ignores, REPORTER_PREFIX "allpass"); 65 | } 66 | 67 | SECTION("all fail") { 68 | const arg_vector args{"test", "--reporter", reporter_name, "* fail*"}; 69 | CHECK_FOR_DIFFERENCES(args, ignores, REPORTER_PREFIX "allfail"); 70 | } 71 | 72 | SECTION("full output") { 73 | const arg_vector args{"test", "--reporter", reporter_name, "--verbosity", "full"}; 74 | CHECK_FOR_DIFFERENCES(args, ignores, REPORTER_PREFIX "full"); 75 | } 76 | 77 | SECTION("list tests") { 78 | const arg_vector args{"test", "--reporter", reporter_name, "--list-tests"}; 79 | CHECK_FOR_DIFFERENCES(args, ignores, REPORTER_PREFIX "list_tests"); 80 | } 81 | } 82 | 83 | #endif 84 | -------------------------------------------------------------------------------- /tests/approval_tests/reporter_console.cpp: -------------------------------------------------------------------------------- 1 | #include "testing.hpp" 2 | #include "testing_assertions.hpp" 3 | #include "testing_event.hpp" 4 | #include "testing_reporters.hpp" 5 | 6 | #include 7 | #include 8 | 9 | using namespace std::literals; 10 | using snitch::matchers::contains_substring; 11 | 12 | TEST_CASE("console reporter", "[reporters]") { 13 | mock_framework framework; 14 | register_tests_for_reporters(framework.registry); 15 | 16 | framework.registry.add_reporter("console"); 17 | 18 | framework.registry.with_color = false; 19 | 20 | constexpr const char* reporter_name = "console"; 21 | #define REPORTER_PREFIX "reporter_console_" 22 | 23 | const std::vector ignores = { 24 | std::regex{R"(, ([0-9.e+\-]{12}) seconds)"}, 25 | std::regex{R"(snitch v([0-9]+\.[0-9]+\.[0-9]+\.[0-9a-z]+))"}, 26 | std::regex{R"(at (.+/snitch/tests/approval_tests/).+:([0-9]+))"}, 27 | std::regex{R"(at (.+/snitch/tests/).+:([0-9]+))"}, 28 | std::regex{R"(at (.+\\snitch\\tests\\approval_tests\\).+:([0-9]+))"}, 29 | std::regex{R"(at (.+\\snitch\\tests\\).+:([0-9]+))"}, 30 | std::regex{R"(^finished: .+\(([0-9.e+\-]{12}s)\))"}}; 31 | 32 | SECTION("default") { 33 | const arg_vector args{"test", "--reporter", reporter_name}; 34 | CHECK_FOR_DIFFERENCES(args, ignores, REPORTER_PREFIX "default"); 35 | } 36 | 37 | SECTION("no test") { 38 | const arg_vector args{"test", "--reporter", reporter_name, "bad_filter"}; 39 | CHECK_FOR_DIFFERENCES(args, ignores, REPORTER_PREFIX "notest"); 40 | } 41 | 42 | SECTION("all pass") { 43 | const arg_vector args{"test", "--reporter", reporter_name, "* pass*"}; 44 | CHECK_FOR_DIFFERENCES(args, ignores, REPORTER_PREFIX "allpass"); 45 | } 46 | 47 | SECTION("all fail") { 48 | const arg_vector args{"test", "--reporter", reporter_name, "* fail*"}; 49 | CHECK_FOR_DIFFERENCES(args, ignores, REPORTER_PREFIX "allfail"); 50 | } 51 | 52 | SECTION("with color") { 53 | const arg_vector args{"test", "--reporter", "console::color=always"}; 54 | CHECK_FOR_DIFFERENCES(args, ignores, REPORTER_PREFIX "withcolor"); 55 | } 56 | 57 | SECTION("full output") { 58 | const arg_vector args{"test", "--reporter", reporter_name, "--verbosity", "full"}; 59 | CHECK_FOR_DIFFERENCES(args, ignores, REPORTER_PREFIX "full"); 60 | } 61 | 62 | SECTION("list tests") { 63 | const arg_vector args{"test", "--reporter", reporter_name, "--list-tests"}; 64 | CHECK_FOR_DIFFERENCES(args, ignores, REPORTER_PREFIX "list_tests"); 65 | } 66 | } 67 | -------------------------------------------------------------------------------- /tests/approval_tests/reporter_teamcity.cpp: -------------------------------------------------------------------------------- 1 | #include "testing.hpp" 2 | #include "testing_assertions.hpp" 3 | #include "testing_event.hpp" 4 | #include "testing_reporters.hpp" 5 | 6 | #include 7 | 8 | #if SNITCH_WITH_TEAMCITY_REPORTER || SNITCH_WITH_ALL_REPORTERS 9 | 10 | using namespace std::literals; 11 | using snitch::matchers::contains_substring; 12 | 13 | TEST_CASE("teamcity reporter", "[reporters]") { 14 | mock_framework framework; 15 | register_tests_for_reporters(framework.registry); 16 | framework.registry.add({"test escape |'\n\r[]"}, SNITCH_CURRENT_LOCATION, [] { 17 | SNITCH_FAIL("escape | message || | '\n\r[]"); 18 | }); 19 | framework.registry.add({"test escape very long"}, SNITCH_CURRENT_LOCATION, [] { 20 | SNITCH_FAIL(std::string(2 * snitch::max_message_length, '|')); 21 | }); 22 | 23 | framework.registry.add_reporter( 24 | "teamcity", &snitch::reporter::teamcity::initialize, {}, 25 | &snitch::reporter::teamcity::report, {}); 26 | 27 | constexpr const char* reporter_name = "teamcity"; 28 | # define REPORTER_PREFIX "reporter_teamcity_" 29 | 30 | const std::vector ignores = { 31 | std::regex{R"( duration='([0-9]+)')"}, 32 | std::regex{R"( (?:message|out)='(.+/snitch/tests/approval_tests/).+:([0-9]+))"}, 33 | std::regex{R"( (?:message|out)='(.+/snitch/tests/).+:([0-9]+))"}, 34 | std::regex{R"( (?:message|out)='(.+\\snitch\\tests\\approval_tests\\).+:([0-9]+))"}, 35 | std::regex{R"( (?:message|out)='(.+\\snitch\\tests\\).+:([0-9]+))"}}; 36 | 37 | SECTION("default") { 38 | const arg_vector args{"test", "--reporter", reporter_name}; 39 | CHECK_FOR_DIFFERENCES(args, ignores, REPORTER_PREFIX "default"); 40 | } 41 | 42 | SECTION("no test") { 43 | const arg_vector args{"test", "--reporter", reporter_name, "bad_filter"}; 44 | CHECK_FOR_DIFFERENCES(args, ignores, REPORTER_PREFIX "notest"); 45 | } 46 | 47 | SECTION("all pass") { 48 | const arg_vector args{"test", "--reporter", reporter_name, "* pass*"}; 49 | CHECK_FOR_DIFFERENCES(args, ignores, REPORTER_PREFIX "allpass"); 50 | } 51 | 52 | SECTION("all fail") { 53 | const arg_vector args{"test", "--reporter", reporter_name, "* fail*"}; 54 | CHECK_FOR_DIFFERENCES(args, ignores, REPORTER_PREFIX "allfail"); 55 | } 56 | 57 | SECTION("full output") { 58 | const arg_vector args{"test", "--reporter", reporter_name, "--verbosity", "full"}; 59 | CHECK_FOR_DIFFERENCES(args, ignores, REPORTER_PREFIX "full"); 60 | } 61 | 62 | SECTION("list tests") { 63 | const arg_vector args{"test", "--reporter", reporter_name, "--list-tests"}; 64 | CHECK_FOR_DIFFERENCES(args, ignores, REPORTER_PREFIX "list_tests"); 65 | } 66 | } 67 | 68 | #endif 69 | -------------------------------------------------------------------------------- /tests/install_tests/CMakeLists.txt: -------------------------------------------------------------------------------- 1 | cmake_minimum_required(VERSION 3.8) 2 | project(test_package LANGUAGES CXX) 3 | 4 | find_package(snitch REQUIRED CONFIG) 5 | 6 | add_executable(standalone standalone.cpp) 7 | 8 | if (HEADER_ONLY) 9 | target_compile_definitions(standalone PRIVATE HEADER_ONLY) 10 | target_link_libraries(standalone PRIVATE snitch::snitch-header-only) 11 | else() 12 | target_link_libraries(standalone PRIVATE snitch::snitch) 13 | endif() 14 | 15 | target_compile_features(standalone PRIVATE cxx_std_20) 16 | -------------------------------------------------------------------------------- /tests/install_tests/standalone.cpp: -------------------------------------------------------------------------------- 1 | #if defined(HEADER_ONLY) 2 | # define SNITCH_IMPLEMENTATION 3 | # include 4 | #else 5 | # include 6 | #endif 7 | 8 | TEST_CASE("compiles and runs") { 9 | REQUIRE(true == !false); 10 | } 11 | -------------------------------------------------------------------------------- /tests/runtime_tests/any.cpp: -------------------------------------------------------------------------------- 1 | #include "testing.hpp" 2 | #include "testing_assertions.hpp" 3 | 4 | namespace { 5 | struct state_monitor { 6 | int* state = nullptr; 7 | 8 | state_monitor() = default; 9 | 10 | explicit state_monitor(int* s) : state(s) { 11 | *state += 1; 12 | } 13 | 14 | ~state_monitor() { 15 | *state -= 1; 16 | } 17 | }; 18 | } // namespace 19 | 20 | TEST_CASE("any", "[utility]") { 21 | constexpr std::size_t max_size = 16; 22 | 23 | int state1 = 0; 24 | int state2 = 0; 25 | 26 | SECTION("default construct") { 27 | snitch::inplace_any storage; 28 | } 29 | 30 | SECTION("construct in-place") { 31 | { 32 | snitch::inplace_any storage(std::in_place_type_t{}, &state1); 33 | CHECK(storage.has_value()); 34 | CHECK(storage.type() == snitch::type_id()); 35 | CHECK(state1 == 1); 36 | CHECK(storage.get().state == &state1); 37 | } 38 | CHECK(state1 == 0); 39 | } 40 | 41 | SECTION("move constructor") { 42 | { 43 | snitch::inplace_any storage1(std::in_place_type_t{}, &state1); 44 | snitch::inplace_any storage2(std::move(storage1)); 45 | CHECK(!storage1.has_value()); 46 | CHECK(storage2.has_value()); 47 | CHECK(state1 == 1); 48 | } 49 | CHECK(state1 == 0); 50 | } 51 | 52 | SECTION("move assignment on empty") { 53 | { 54 | snitch::inplace_any storage2; 55 | { 56 | snitch::inplace_any storage1( 57 | std::in_place_type_t{}, &state1); 58 | storage2 = std::move(storage1); 59 | CHECK(!storage1.has_value()); 60 | } 61 | 62 | CHECK(storage2.has_value()); 63 | CHECK(state1 == 1); 64 | } 65 | CHECK(state1 == 0); 66 | } 67 | 68 | SECTION("move assignment on full") { 69 | { 70 | snitch::inplace_any storage2(std::in_place_type_t{}, &state2); 71 | { 72 | snitch::inplace_any storage1( 73 | std::in_place_type_t{}, &state1); 74 | storage2 = std::move(storage1); 75 | CHECK(!storage1.has_value()); 76 | } 77 | 78 | CHECK(storage2.has_value()); 79 | CHECK(state1 == 1); 80 | CHECK(state2 == 0); 81 | } 82 | CHECK(state1 == 0); 83 | CHECK(state2 == 0); 84 | } 85 | 86 | SECTION("emplace and reset") { 87 | { 88 | snitch::inplace_any storage; 89 | storage.emplace(&state1); 90 | CHECK(storage.has_value()); 91 | CHECK(storage.type() == snitch::type_id()); 92 | CHECK(state1 == 1); 93 | CHECK(storage.get().state == &state1); 94 | 95 | storage.reset(); 96 | CHECK(!storage.has_value()); 97 | CHECK(state1 == 0); 98 | } 99 | CHECK(state1 == 0); 100 | CHECK(state2 == 0); 101 | } 102 | 103 | SECTION("emplace over existing") { 104 | { 105 | snitch::inplace_any storage; 106 | storage.emplace(&state1); 107 | storage.emplace(&state2); 108 | CHECK(storage.has_value()); 109 | CHECK(storage.type() == snitch::type_id()); 110 | CHECK(state1 == 0); 111 | CHECK(state2 == 1); 112 | CHECK(storage.get().state == &state2); 113 | } 114 | CHECK(state1 == 0); 115 | CHECK(state2 == 0); 116 | } 117 | 118 | SECTION("reset empty") { 119 | snitch::inplace_any storage; 120 | storage.reset(); 121 | CHECK(!storage.has_value()); 122 | } 123 | 124 | #if SNITCH_WITH_EXCEPTIONS 125 | SECTION("get empty") { 126 | assertion_exception_enabler enabler; 127 | snitch::inplace_any storage; 128 | 129 | CHECK_THROWS_WHAT( 130 | storage.get(), assertion_exception, "inplace_any is empty"); 131 | } 132 | 133 | SECTION("get wrong type") { 134 | assertion_exception_enabler enabler; 135 | snitch::inplace_any storage; 136 | storage.emplace(0); 137 | 138 | CHECK_THROWS_WHAT( 139 | storage.get(), assertion_exception, 140 | "inplace_any holds an object of a different type"); 141 | } 142 | #endif 143 | } 144 | -------------------------------------------------------------------------------- /tests/runtime_tests/function_ref.cpp: -------------------------------------------------------------------------------- 1 | #include "testing.hpp" 2 | 3 | #include 4 | 5 | namespace { 6 | std::size_t test_object_instances = 0u; 7 | bool function_called = false; 8 | int return_value = 0u; 9 | 10 | struct test_object { 11 | test_object() noexcept { 12 | ++test_object_instances; 13 | } 14 | test_object(const test_object&) noexcept { 15 | ++test_object_instances; 16 | } 17 | test_object(test_object&&) noexcept { 18 | ++test_object_instances; 19 | } 20 | }; 21 | 22 | using function_0_void = void() noexcept; 23 | using function_0_int = int() noexcept; 24 | using function_2_void = void(int, test_object) noexcept; 25 | using function_2_int = int(int, test_object) noexcept; 26 | 27 | template 28 | struct test_class; 29 | 30 | template 31 | struct test_class { 32 | R method(Args...) noexcept { 33 | function_called = true; 34 | if constexpr (!std::is_same_v) { 35 | return 42; 36 | } 37 | } 38 | 39 | R method_const(Args...) const noexcept { 40 | function_called = true; 41 | if constexpr (!std::is_same_v) { 42 | return 43; 43 | } 44 | } 45 | 46 | static R method_static(Args...) noexcept { 47 | function_called = true; 48 | if constexpr (!std::is_same_v) { 49 | return 44; 50 | } 51 | } 52 | }; 53 | 54 | // Needed for GCC. 55 | template struct test_class; 56 | template struct test_class; 57 | template struct test_class; 58 | template struct test_class; 59 | 60 | template 61 | struct type_holder {}; 62 | 63 | // MSVC has a bug that prevents us from writing the test nicely. Work around it. 64 | // https://developercommunity.visualstudio.com/t/Parameter-pack-argument-is-not-recognize/10191888 65 | template 66 | void call_function(snitch::function_ref& f) { 67 | if constexpr (std::is_same_v) { 68 | std::apply(f, std::tuple{}); 69 | } else { 70 | return_value = std::apply(f, std::tuple{}); 71 | } 72 | } 73 | } // namespace 74 | 75 | TEMPLATE_TEST_CASE( 76 | "function reference", 77 | "[utility]", 78 | function_0_void, 79 | function_0_int, 80 | function_2_void, 81 | function_2_int) { 82 | 83 | [&](type_holder) { 84 | test_object_instances = 0u; 85 | return_value = 0u; 86 | function_called = false; 87 | constexpr std::size_t expected_instances = sizeof...(Args) > 0 ? 3u : 0u; 88 | 89 | SECTION("from free function") { 90 | snitch::function_ref f = &test_class::method_static; 91 | 92 | call_function(f); 93 | 94 | CHECK(function_called); 95 | if (!std::is_same_v) { 96 | CHECK(return_value == 44); 97 | } 98 | CHECK(test_object_instances <= expected_instances); 99 | } 100 | 101 | SECTION("from non-const member function") { 102 | test_class obj; 103 | snitch::function_ref f = { 104 | obj, snitch::constant<&test_class::method>{}}; 105 | 106 | call_function(f); 107 | 108 | CHECK(function_called); 109 | if (!std::is_same_v) { 110 | CHECK(return_value == 42); 111 | } 112 | CHECK(test_object_instances <= expected_instances); 113 | } 114 | 115 | SECTION("from const member function") { 116 | const test_class obj; 117 | snitch::function_ref f = { 118 | obj, snitch::constant<&test_class::method_const>{}}; 119 | 120 | call_function(f); 121 | 122 | CHECK(function_called); 123 | if (!std::is_same_v) { 124 | CHECK(return_value == 43); 125 | } 126 | CHECK(test_object_instances <= expected_instances); 127 | } 128 | 129 | SECTION("from stateless lambda") { 130 | snitch::function_ref f = 131 | snitch::function_ref{[](Args...) noexcept -> R { 132 | function_called = true; 133 | if constexpr (!std::is_same_v) { 134 | return 45; 135 | } 136 | }}; 137 | 138 | call_function(f); 139 | 140 | CHECK(function_called); 141 | if (!std::is_same_v) { 142 | CHECK(return_value == 45); 143 | } 144 | CHECK(test_object_instances <= expected_instances); 145 | } 146 | 147 | SECTION("from stateful lambda") { 148 | int answer = 46; 149 | auto lambda = [&](Args...) noexcept -> R { 150 | function_called = true; 151 | if constexpr (!std::is_same_v) { 152 | return answer; 153 | } 154 | }; 155 | 156 | snitch::function_ref f = snitch::function_ref{lambda}; 157 | 158 | call_function(f); 159 | 160 | CHECK(function_called); 161 | if (!std::is_same_v) { 162 | CHECK(return_value == 46); 163 | } 164 | CHECK(test_object_instances <= expected_instances); 165 | } 166 | 167 | SECTION("from other function") { 168 | snitch::function_ref f1 = &test_class::method_static; 169 | snitch::function_ref f2(f1); 170 | 171 | call_function(f1); 172 | 173 | CHECK(function_called); 174 | if (!std::is_same_v) { 175 | CHECK(return_value == 44); 176 | } 177 | CHECK(test_object_instances <= expected_instances); 178 | 179 | test_object_instances = 0u; 180 | return_value = 0u; 181 | function_called = false; 182 | 183 | call_function(f2); 184 | 185 | CHECK(function_called); 186 | if (!std::is_same_v) { 187 | CHECK(return_value == 44); 188 | } 189 | CHECK(test_object_instances <= expected_instances); 190 | } 191 | }(type_holder{}); 192 | } 193 | -------------------------------------------------------------------------------- /tests/runtime_tests/macros.cpp: -------------------------------------------------------------------------------- 1 | #include "testing.hpp" 2 | 3 | using namespace std::literals; 4 | 5 | namespace { 6 | bool test_called = false; 7 | } // namespace 8 | 9 | TEST_CASE("test without tags") { 10 | CHECK(!test_called); 11 | test_called = true; 12 | } 13 | 14 | namespace { 15 | std::size_t test_fixture_instances = 0u; 16 | 17 | struct test_fixture { 18 | test_fixture() { 19 | ++test_fixture_instances; 20 | } 21 | 22 | ~test_fixture() { 23 | --test_fixture_instances; 24 | } 25 | 26 | bool used = false; 27 | }; 28 | } // namespace 29 | 30 | TEST_CASE_METHOD(test_fixture, "test with fixture 1") { 31 | CHECK(!used); 32 | CHECK(test_fixture_instances == 1u); 33 | used = true; 34 | } 35 | 36 | TEST_CASE_METHOD(test_fixture, "test with fixture 2") { 37 | CHECK(!used); 38 | CHECK(test_fixture_instances == 1u); 39 | used = true; 40 | } 41 | 42 | TEST_CASE_METHOD(test_fixture, "test with fixture and section") { 43 | SECTION("section 1") { 44 | CHECK(!used); 45 | CHECK(test_fixture_instances == 1u); 46 | } 47 | 48 | SECTION("section 2") { 49 | CHECK(!used); 50 | CHECK(test_fixture_instances == 1u); 51 | } 52 | 53 | used = true; 54 | } 55 | 56 | TEST_CASE("test after test with fixture") { 57 | CHECK(test_fixture_instances == 0u); 58 | } 59 | 60 | namespace { 61 | #if SNITCH_WITH_EXCEPTIONS 62 | // Dummy template parameters to allow commas when specifying the exception type 63 | template 64 | struct test_exception : std::exception { 65 | const char* what() const noexcept override { 66 | return "test exception"; 67 | } 68 | }; 69 | #endif 70 | 71 | template 72 | int foo() { 73 | if constexpr (i != j || i != k) { 74 | return 0; 75 | } else { 76 | #if SNITCH_WITH_EXCEPTIONS 77 | throw test_exception{}; 78 | #else 79 | return 1; 80 | #endif 81 | } 82 | } 83 | 84 | std::size_t matcher_created_count = 0u; 85 | } // namespace 86 | 87 | namespace snitch::matchers { 88 | struct match_anything { 89 | // Dummy variable to allow commas when constructing the matcher 90 | int i = 0; 91 | int j = 0; 92 | 93 | template 94 | bool match(T&&) const noexcept { 95 | return true; 96 | } 97 | 98 | template 99 | small_string describe_match(T&&, match_status) const noexcept { 100 | return "matched"sv; 101 | } 102 | }; 103 | 104 | struct tracked_matcher { 105 | tracked_matcher() { 106 | ++matcher_created_count; 107 | } 108 | tracked_matcher(const tracked_matcher&) { 109 | ++matcher_created_count; 110 | } 111 | tracked_matcher(tracked_matcher&&) { 112 | ++matcher_created_count; 113 | } 114 | 115 | template 116 | bool match(T&&) const noexcept { 117 | return true; 118 | } 119 | 120 | template 121 | small_string describe_match(T&&, match_status) const noexcept { 122 | return "matched"sv; 123 | } 124 | }; 125 | } // namespace snitch::matchers 126 | 127 | #if defined(SNITCH_TEST_WITH_SNITCH) 128 | TEST_CASE("check macros with commas") { 129 | REQUIRE(foo<1, 2, 3>() == 0); 130 | REQUIRE_FALSE(foo<1, 2, 3>() != 0); 131 | CHECK(foo<1, 2, 3>() == 0); 132 | CHECK_FALSE(foo<1, 2, 3>() != 0); 133 | 134 | // Unfortunately, macros cannot support the following without the extra parentheses 135 | // around the expression: 136 | 137 | REQUIRE_THAT((foo<1, 2, 3>()), snitch::matchers::match_anything{0, 0}); 138 | CHECK_THAT((foo<1, 2, 3>()), snitch::matchers::match_anything{0, 0}); 139 | 140 | # if SNITCH_WITH_EXCEPTIONS 141 | CHECK_THROWS_AS((foo<2, 2, 2>()), test_exception); 142 | REQUIRE_THROWS_AS((foo<2, 2, 2>()), test_exception); 143 | # endif 144 | 145 | // Even more unfortunately, macros cannot support 'test_exception' to be specified inline here, 146 | // it must be declared first with an alias without template parameters: 147 | 148 | # if SNITCH_WITH_EXCEPTIONS 149 | using expected_exception = test_exception; 150 | 151 | REQUIRE_THROWS_MATCHES( 152 | (foo<2, 2, 2>()), expected_exception, snitch::matchers::match_anything{0, 0}); 153 | 154 | CHECK_THROWS_MATCHES( 155 | (foo<2, 2, 2>()), expected_exception, snitch::matchers::match_anything{0, 0}); 156 | # endif 157 | } 158 | 159 | SNITCH_WARNING_PUSH 160 | SNITCH_WARNING_DISABLE_UNREACHABLE 161 | 162 | TEST_CASE("matcher is not copied") { 163 | matcher_created_count = 0u; 164 | REQUIRE_THAT(1, snitch::matchers::tracked_matcher{}); 165 | CHECK(matcher_created_count == 1u); 166 | 167 | matcher_created_count = 0u; 168 | CHECK_THAT(1, snitch::matchers::tracked_matcher{}); 169 | CHECK(matcher_created_count == 1u); 170 | 171 | # if SNITCH_WITH_EXCEPTIONS 172 | matcher_created_count = 0u; 173 | REQUIRE_THROWS_MATCHES(throw 1, int, snitch::matchers::tracked_matcher{}); 174 | CHECK(matcher_created_count == 1u); 175 | 176 | matcher_created_count = 0u; 177 | CHECK_THROWS_MATCHES(throw 1, int, snitch::matchers::tracked_matcher{}); 178 | CHECK(matcher_created_count == 1u); 179 | # endif 180 | } 181 | 182 | SNITCH_WARNING_POP 183 | #endif 184 | -------------------------------------------------------------------------------- /tests/runtime_tests/matchers.cpp: -------------------------------------------------------------------------------- 1 | #include "testing.hpp" 2 | 3 | #include 4 | 5 | using namespace std::literals; 6 | 7 | namespace snitch::matchers { 8 | struct has_prefix { 9 | std::string_view prefix; 10 | 11 | bool match(std::string_view s) const noexcept { 12 | return s.starts_with(prefix) && s.size() >= prefix.size() + 1 && s[prefix.size()] == ':'; 13 | } 14 | 15 | small_string 16 | describe_match(std::string_view s, match_status status) const noexcept { 17 | small_string message; 18 | append_or_truncate( 19 | message, status == match_status::matched ? "found" : "could not find", " prefix '", 20 | prefix, ":' in '", s, "'"); 21 | 22 | if (status == match_status::failed) { 23 | if (auto pos = s.find_first_of(':'); pos != s.npos) { 24 | append_or_truncate(message, "; found prefix '", s.substr(0, pos), ":'"); 25 | } else { 26 | append_or_truncate(message, "; no prefix found"); 27 | } 28 | } 29 | 30 | return message; 31 | } 32 | }; 33 | } // namespace snitch::matchers 34 | 35 | TEST_CASE("example matcher has_prefix", "[utility]") { 36 | CHECK("info: hello"sv == snitch::matchers::has_prefix{"info"}); 37 | CHECK("info: hello"sv != snitch::matchers::has_prefix{"warning"}); 38 | CHECK("hello"sv != snitch::matchers::has_prefix{"info"}); 39 | CHECK(snitch::matchers::has_prefix{"info"} == "info: hello"sv); 40 | CHECK(snitch::matchers::has_prefix{"warning"} != "info: hello"sv); 41 | CHECK(snitch::matchers::has_prefix{"info"} != "hello"sv); 42 | 43 | CHECK( 44 | snitch::matchers::has_prefix{"info"}.describe_match( 45 | "info: hello"sv, snitch::matchers::match_status::matched) == 46 | "found prefix 'info:' in 'info: hello'"sv); 47 | CHECK( 48 | snitch::matchers::has_prefix{"warning"}.describe_match( 49 | "info: hello"sv, snitch::matchers::match_status::failed) == 50 | "could not find prefix 'warning:' in 'info: hello'; found prefix 'info:'"sv); 51 | } 52 | 53 | TEST_CASE("matcher contains_substring", "[utility]") { 54 | CHECK("info: hello"sv == snitch::matchers::contains_substring{"hello"}); 55 | CHECK("info: hello"sv != snitch::matchers::contains_substring{"warning"}); 56 | CHECK(snitch::matchers::contains_substring{"hello"} == "info: hello"sv); 57 | CHECK(snitch::matchers::contains_substring{"warning"} != "info: hello"sv); 58 | 59 | CHECK( 60 | snitch::matchers::contains_substring{"hello"}.describe_match( 61 | "info: hello"sv, snitch::matchers::match_status::matched) == 62 | "found 'hello' in 'info: hello'"sv); 63 | CHECK( 64 | snitch::matchers::contains_substring{"warning"}.describe_match( 65 | "info: hello"sv, snitch::matchers::match_status::failed) == 66 | "could not find 'warning' in 'info: hello'"sv); 67 | } 68 | 69 | TEST_CASE("matcher with_what_contains", "[utility]") { 70 | CHECK(std::runtime_error{"not good"} == snitch::matchers::with_what_contains{"good"}); 71 | CHECK(std::runtime_error{"not good"} == snitch::matchers::with_what_contains{"not good"}); 72 | CHECK(std::runtime_error{"not good"} != snitch::matchers::with_what_contains{"bad"}); 73 | CHECK(std::runtime_error{"not good"} != snitch::matchers::with_what_contains{"is good"}); 74 | CHECK(snitch::matchers::with_what_contains{"good"} == std::runtime_error{"not good"}); 75 | CHECK(snitch::matchers::with_what_contains{"not good"} == std::runtime_error{"not good"}); 76 | CHECK(snitch::matchers::with_what_contains{"bad"} != std::runtime_error{"not good"}); 77 | CHECK(snitch::matchers::with_what_contains{"is good"} != std::runtime_error{"not good"}); 78 | 79 | CHECK( 80 | snitch::matchers::with_what_contains{"good"}.describe_match( 81 | std::runtime_error{"not good"}, snitch::matchers::match_status::matched) == 82 | "found 'good' in 'not good'"sv); 83 | CHECK( 84 | snitch::matchers::with_what_contains{"bad"}.describe_match( 85 | std::runtime_error{"not good"}, snitch::matchers::match_status::failed) == 86 | "could not find 'bad' in 'not good'"sv); 87 | } 88 | 89 | TEST_CASE("matcher is_any_of", "[utility]") { 90 | const auto m = snitch::matchers::is_any_of{1u, 2u, 3u}; 91 | 92 | CHECK(1u == m); 93 | CHECK(2u == m); 94 | CHECK(3u == m); 95 | CHECK(0u != m); 96 | CHECK(4u != m); 97 | CHECK(5u != m); 98 | CHECK(m == 1u); 99 | CHECK(m == 2u); 100 | CHECK(m == 3u); 101 | CHECK(m != 0u); 102 | CHECK(m != 4u); 103 | CHECK(m != 5u); 104 | 105 | CHECK( 106 | m.describe_match(2u, snitch::matchers::match_status::matched) == 107 | "'2' was found in {'1', '2', '3'}"sv); 108 | CHECK( 109 | m.describe_match(5u, snitch::matchers::match_status::failed) == 110 | "'5' was not found in {'1', '2', '3'}"sv); 111 | } 112 | -------------------------------------------------------------------------------- /tests/runtime_tests/regressions.cpp: -------------------------------------------------------------------------------- 1 | #include "testing.hpp" 2 | 3 | // Checks that we don't get parenthesis warnings from GCC when in template function. 4 | // https://github.com/catchorg/Catch2/issues/870 5 | // https://github.com/catchorg/Catch2/issues/565 6 | // The original issue was a GCC bug, which forced Catch2 to disable the parentheses warning 7 | // globally. This seems to have been fixed in GCC now. 8 | namespace { 9 | template 10 | void template_test_function() { 11 | T a = 1, b = 1; 12 | CHECK(a == b); 13 | } 14 | } // namespace 15 | 16 | TEST_CASE("Wparentheses", "[regressions]") { 17 | template_test_function(); 18 | } 19 | -------------------------------------------------------------------------------- /tests/runtime_tests/skip.cpp: -------------------------------------------------------------------------------- 1 | #include "testing.hpp" 2 | #include "testing_event.hpp" 3 | 4 | using namespace std::literals; 5 | 6 | SNITCH_WARNING_PUSH 7 | SNITCH_WARNING_DISABLE_UNREACHABLE 8 | 9 | TEST_CASE("skip", "[test macros]") { 10 | mock_framework framework; 11 | framework.setup_reporter(); 12 | 13 | SECTION("no skip") { 14 | framework.test_case.func = []() { SNITCH_FAIL_CHECK("trigger"); }; 15 | 16 | framework.run_test(); 17 | CHECK(framework.get_num_skips() == 0u); 18 | } 19 | 20 | #if SNITCH_WITH_EXCEPTIONS 21 | SECTION("only skip") { 22 | framework.test_case.func = []() { SNITCH_SKIP("hello"); }; 23 | 24 | framework.run_test(); 25 | CHECK(framework.get_num_skips() == 1u); 26 | } 27 | 28 | SECTION("skip failure") { 29 | framework.test_case.func = []() { 30 | SNITCH_SKIP("hello"); 31 | SNITCH_FAIL_CHECK("trigger"); 32 | }; 33 | 34 | framework.run_test(); 35 | CHECK(framework.get_num_skips() == 1u); 36 | CHECK(framework.get_num_failures() == 0u); 37 | } 38 | 39 | SECTION("skip section") { 40 | framework.test_case.func = []() { 41 | SNITCH_SECTION("section 1") { 42 | SNITCH_SKIP("hello"); 43 | } 44 | SNITCH_SECTION("section 2") { 45 | SNITCH_FAIL_CHECK("trigger"); 46 | } 47 | }; 48 | 49 | framework.run_test(); 50 | CHECK(framework.get_num_skips() == 1u); 51 | CHECK(framework.get_num_failures() == 0u); 52 | } 53 | #endif 54 | 55 | SECTION("only skip check") { 56 | framework.test_case.func = []() { SNITCH_SKIP_CHECK("hello"); }; 57 | 58 | framework.run_test(); 59 | CHECK(framework.get_num_skips() == 1u); 60 | } 61 | 62 | SECTION("skip check failure") { 63 | framework.test_case.func = []() { 64 | SNITCH_SKIP_CHECK("hello"); 65 | SNITCH_FAIL_CHECK("trigger"); 66 | SNITCH_CHECK(1 == 2); 67 | SNITCH_CHECK_THAT("hello"sv, snitch::matchers::contains_substring{"world"sv}); 68 | }; 69 | 70 | framework.run_test(); 71 | CHECK(framework.get_num_skips() == 1u); 72 | CHECK(framework.get_num_failures() == 0u); 73 | } 74 | 75 | SECTION("skip section") { 76 | framework.test_case.func = []() { 77 | SNITCH_SECTION("section 1") { 78 | SNITCH_SKIP_CHECK("hello"); 79 | } 80 | SNITCH_SECTION("section 2") { 81 | SNITCH_FAIL_CHECK("trigger"); 82 | } 83 | }; 84 | 85 | framework.run_test(); 86 | CHECK(framework.get_num_skips() == 1u); 87 | CHECK(framework.get_num_failures() == 0u); 88 | } 89 | } 90 | 91 | SNITCH_WARNING_POP 92 | -------------------------------------------------------------------------------- /tests/runtime_tests/type_id.cpp: -------------------------------------------------------------------------------- 1 | #include "testing.hpp" 2 | 3 | #include 4 | 5 | using namespace std::literals; 6 | 7 | TEST_CASE("type id", "[utility]") { 8 | SECTION("all unique") { 9 | std::array types = { 10 | snitch::type_id(), 11 | snitch::type_id(), 12 | snitch::type_id(), 13 | snitch::type_id(), 14 | snitch::type_id(), 15 | snitch::type_id>(), 16 | snitch::type_id>()}; 17 | 18 | CHECK(std::unique(types.begin(), types.end()) == types.end()); 19 | } 20 | 21 | SECTION("constant") { 22 | CHECK(snitch::type_id() == snitch::type_id()); 23 | CHECK(snitch::type_id() == snitch::type_id()); 24 | CHECK(snitch::type_id() == snitch::type_id()); 25 | } 26 | 27 | SECTION("void") { 28 | CHECK(snitch::type_id() == nullptr); 29 | } 30 | } 31 | -------------------------------------------------------------------------------- /tests/runtime_tests/type_name.cpp: -------------------------------------------------------------------------------- 1 | #include "testing.hpp" 2 | 3 | using namespace std::literals; 4 | 5 | struct global_test_struct { 6 | int i = 0; 7 | bool b = true; 8 | }; 9 | 10 | TEST_CASE("type name", "[utility]") { 11 | struct test_struct { 12 | int i = 0; 13 | bool b = true; 14 | }; 15 | 16 | CHECK(snitch::type_name == "int"); 17 | CHECK(snitch::type_name == "unsigned int"); 18 | CHECK(snitch::type_name == snitch::matchers::is_any_of("long int"sv, "long"sv)); 19 | CHECK(snitch::type_name == "float"); 20 | CHECK(snitch::type_name == "double"); 21 | CHECK(snitch::type_name == snitch::matchers::is_any_of("void*"sv, "void *"sv)); 22 | 23 | CHECK( 24 | snitch::type_name == 25 | snitch::matchers::is_any_of("const void*"sv, "const void *"sv)); 26 | 27 | CHECK( 28 | snitch::type_name == 29 | snitch::matchers::is_any_of( 30 | "std::basic_string_view"sv, "std::string_view"sv, 31 | "class std::basic_string_view >"sv, 32 | "std::__1::basic_string_view >"sv)); 33 | 34 | CHECK( 35 | snitch::type_name == 36 | snitch::matchers::is_any_of("global_test_struct"sv, "struct global_test_struct"sv)); 37 | 38 | CHECK(snitch::type_name.ends_with("test_struct")); 39 | } 40 | -------------------------------------------------------------------------------- /tests/testing.cpp: -------------------------------------------------------------------------------- 1 | #define SNITCH_IMPLEMENTATION 2 | 3 | #if defined(SNITCH_TEST_WITH_SNITCH) 4 | # undef SNITCH_DEFINE_MAIN 5 | # define SNITCH_DEFINE_MAIN 1 6 | #else 7 | # define DOCTEST_CONFIG_IMPLEMENT 8 | # define DOCTEST_CONFIG_IMPLEMENT_WITH_MAIN 9 | #endif 10 | 11 | #include "testing.hpp" 12 | 13 | #if defined(SNITCH_TEST_WITH_SNITCH) && !defined(SNITCH_TEST_HEADER_ONLY) 14 | int main(int argc, char* argv[]) { 15 | return snitch::main(argc, argv); 16 | } 17 | #endif 18 | 19 | bool contains_color_codes(std::string_view msg) noexcept { 20 | constexpr std::array codes = {snitch::impl::color::error, snitch::impl::color::warning, 21 | snitch::impl::color::status, snitch::impl::color::fail, 22 | snitch::impl::color::skipped, snitch::impl::color::pass, 23 | snitch::impl::color::highlight1, snitch::impl::color::highlight2, 24 | snitch::impl::color::reset}; 25 | 26 | for (const auto c : codes) { 27 | if (msg.find(c) != std::string_view::npos) { 28 | return true; 29 | } 30 | } 31 | 32 | return false; 33 | } 34 | -------------------------------------------------------------------------------- /tests/testing.hpp: -------------------------------------------------------------------------------- 1 | #if defined(SNITCH_TEST_WITH_SNITCH) 2 | // The library being tested is also the library used for testing... 3 | # if defined(SNITCH_TEST_HEADER_ONLY) 4 | # include "snitch/snitch_all.hpp" 5 | # else 6 | # include "snitch/snitch.hpp" 7 | # endif 8 | # define CHECK_THROWS_WHAT(EXPR, EXCEPT, MESSAGE) \ 9 | CHECK_THROWS_MATCHES(EXPR, EXCEPT, snitch::matchers::with_what_contains{(MESSAGE)}) 10 | #else 11 | 12 | // The library being tested. 13 | # if defined(SNITCH_TEST_HEADER_ONLY) 14 | # include "snitch/snitch_all.hpp" 15 | # else 16 | # include "snitch/snitch.hpp" 17 | # endif 18 | # if !SNITCH_WITH_EXCEPTIONS 19 | # define DOCTEST_CONFIG_NO_EXCEPTIONS_BUT_WITH_ALL_ASSERTS 20 | # endif 21 | // The library used for testing. 22 | # include "doctest/doctest.h" 23 | // Adjust doctest macros to match the snitch API 24 | # define SECTION(name) DOCTEST_SUBCASE(name) 25 | # undef TEST_CASE 26 | # define TEST_CASE(name, ...) DOCTEST_TEST_CASE(name) 27 | # define TEMPLATE_TEST_CASE(name, tags, ...) \ 28 | DOCTEST_TEST_CASE_TEMPLATE(tags " " name, TestType, __VA_ARGS__) 29 | # define SKIP(message) return 30 | # define TEST_CASE_METHOD(fixture, name, ...) DOCTEST_TEST_CASE_FIXTURE(fixture, name) 31 | # define CONSTEXPR_CHECK(...) \ 32 | if constexpr (__VA_ARGS__) { \ 33 | CHECK(__VA_ARGS__); \ 34 | } else { \ 35 | CHECK(__VA_ARGS__); \ 36 | } 37 | # define CONSTEXPR_REQUIRE(...) \ 38 | if constexpr (__VA_ARGS__) { \ 39 | REQUIRE(__VA_ARGS__); \ 40 | } else { \ 41 | REQUIRE(__VA_ARGS__); \ 42 | } 43 | # define CHECK_THROWS_WHAT(EXPR, EXCEPT, MESSAGE) CHECK_THROWS_WITH_AS(EXPR, MESSAGE, EXCEPT) 44 | 45 | # include 46 | 47 | namespace concepts { 48 | struct any_arg { 49 | template 50 | operator T() const noexcept; 51 | }; 52 | 53 | template 54 | concept matcher = requires(const T& m) { 55 | { m.match(any_arg{}) } -> snitch::convertible_to; 56 | { 57 | m.describe_match(any_arg{}, snitch::matchers::match_status{}) 58 | } -> snitch::convertible_to; 59 | }; 60 | 61 | template 62 | concept function = std::is_function_v; 63 | } // namespace concepts 64 | 65 | namespace snitch { 66 | template 67 | std::ostream& operator<<(std::ostream& str, const snitch::small_string& in) { 68 | return str << std::string_view{in}; 69 | } 70 | } // namespace snitch 71 | 72 | namespace doctest::detail { 73 | template 74 | struct filldata { 75 | static void fill(std::ostream* stream, T* in) { 76 | *stream << (in != nullptr ? "funcptr" : "nullptr"); 77 | } 78 | }; 79 | } // namespace doctest::detail 80 | #endif 81 | 82 | #if defined(__clang__) 83 | # define SNITCH_WARNING_DISABLE_UNREACHABLE 84 | # define SNITCH_WARNING_DISABLE_INT_BOOLEAN 85 | # define SNITCH_WARNING_DISABLE_PRECEDENCE 86 | # define SNITCH_WARNING_DISABLE_ASSIGNMENT 87 | #elif defined(__GNUC__) 88 | # define SNITCH_WARNING_DISABLE_UNREACHABLE 89 | # define SNITCH_WARNING_DISABLE_INT_BOOLEAN \ 90 | _Pragma("GCC diagnostic ignored \"-Wint-in-bool-context\"") 91 | # define SNITCH_WARNING_DISABLE_PRECEDENCE 92 | # define SNITCH_WARNING_DISABLE_ASSIGNMENT 93 | #elif defined(_MSC_VER) 94 | # define SNITCH_WARNING_DISABLE_UNREACHABLE _Pragma("warning(disable: 4702)") 95 | # define SNITCH_WARNING_DISABLE_INT_BOOLEAN 96 | # define SNITCH_WARNING_DISABLE_PRECEDENCE _Pragma("warning(disable: 4554)") 97 | # define SNITCH_WARNING_DISABLE_ASSIGNMENT _Pragma("warning(disable: 4706)") 98 | #else 99 | # define SNITCH_WARNING_DISABLE_UNREACHABLE 100 | # define SNITCH_WARNING_DISABLE_INT_BOOLEAN 101 | # define SNITCH_WARNING_DISABLE_PRECEDENCE 102 | # define SNITCH_WARNING_DISABLE_ASSIGNMENT 103 | #endif 104 | 105 | bool contains_color_codes(std::string_view msg) noexcept; 106 | -------------------------------------------------------------------------------- /tests/testing_assertions.hpp: -------------------------------------------------------------------------------- 1 | #if SNITCH_WITH_EXCEPTIONS 2 | struct assertion_exception : public std::exception { 3 | snitch::small_string message = {}; 4 | 5 | explicit assertion_exception(std::string_view msg) { 6 | snitch::append_or_truncate(message, msg); 7 | if (message.available() > 0) { 8 | message.push_back('\0'); 9 | } else { 10 | message.back() = '\0'; 11 | } 12 | } 13 | 14 | const char* what() const noexcept override { 15 | return message.data(); 16 | } 17 | }; 18 | 19 | struct assertion_exception_enabler { 20 | snitch::function_ref prev_handler; 21 | 22 | assertion_exception_enabler() : prev_handler(snitch::assertion_failed_handler) { 23 | snitch::assertion_failed_handler = [](std::string_view msg) { 24 | throw assertion_exception(msg); 25 | }; 26 | } 27 | 28 | ~assertion_exception_enabler() { 29 | snitch::assertion_failed_handler = prev_handler; 30 | } 31 | }; 32 | #endif 33 | -------------------------------------------------------------------------------- /tests/testing_reporters.hpp: -------------------------------------------------------------------------------- 1 | #include 2 | #include 3 | #include 4 | 5 | void register_tests_for_reporters(snitch::registry& r); 6 | 7 | const std::filesystem::path test_data_path = std::filesystem::path("data"); 8 | 9 | struct print_to_file { 10 | std::ofstream file; 11 | 12 | print_to_file(snitch::registry& r, std::string_view filename) : 13 | file(test_data_path / "actual" / filename) { 14 | r.print_callback = {*this, snitch::constant<&print_to_file::print>{}}; 15 | } 16 | 17 | void print(std::string_view msg) noexcept { 18 | file.write(msg.data(), msg.size()); 19 | } 20 | }; 21 | 22 | void regex_blank(std::string& line, const std::regex& ignores); 23 | void regex_blank(std::string& line, const std::vector& ignores); 24 | 25 | #define CHECK_FOR_DIFFERENCES(ARGS, IGNORES, FILENAME) \ 26 | do { \ 27 | { \ 28 | print_to_file file_override{framework.registry, FILENAME}; \ 29 | auto input = \ 30 | snitch::cli::parse_arguments(static_cast((ARGS).size()), (ARGS).data()); \ 31 | framework.registry.configure(input.value()); \ 32 | framework.registry.run_tests(input.value()); \ 33 | } \ 34 | { \ 35 | std::ifstream file_actual(test_data_path / "actual" / (FILENAME)); \ 36 | std::ofstream file_blanked(test_data_path / "blanked" / (FILENAME)); \ 37 | std::string line_actual; \ 38 | while (std::getline(file_actual, line_actual)) { \ 39 | regex_blank(line_actual, (IGNORES)); \ 40 | file_blanked.write(line_actual.data(), line_actual.size()); \ 41 | file_blanked.write("\n", 1); \ 42 | } \ 43 | } \ 44 | { \ 45 | INFO("checking ", FILENAME); \ 46 | std::ifstream file_expected(test_data_path / "expected" / (FILENAME)); \ 47 | std::ifstream file_actual(test_data_path / "blanked" / (FILENAME)); \ 48 | std::string line_expected; \ 49 | std::string line_actual; \ 50 | std::size_t line_number = 1; \ 51 | while ((std::getline(file_expected, line_expected), \ 52 | std::getline(file_actual, line_actual)) && \ 53 | file_expected && file_actual) { \ 54 | CAPTURE(line_number); \ 55 | CHECK(std::string_view{line_actual} == std::string_view{line_expected}); \ 56 | ++line_number; \ 57 | } \ 58 | if (file_expected.is_open() && !file_expected.eof()) { \ 59 | FAIL_CHECK("expected more output"); \ 60 | } else if (!file_actual.eof()) { \ 61 | FAIL_CHECK("unexpected extra output"); \ 62 | } \ 63 | } \ 64 | } while (0) 65 | --------------------------------------------------------------------------------