Merge remote-tracking branch 'origin/upstream'
diff --git a/.github/FUNDING.yml b/.github/FUNDING.yml
new file mode 100644
index 0000000..9989e17
--- /dev/null
+++ b/.github/FUNDING.yml
@@ -0,0 +1 @@
+github: charlesnicholson
diff --git a/.github/workflows/presubmit.yml b/.github/workflows/presubmit.yml
new file mode 100644
index 0000000..0ab7ef1
--- /dev/null
+++ b/.github/workflows/presubmit.yml
@@ -0,0 +1,191 @@
+name: Presubmit Checks
+
+on:
+  pull_request:
+    branches: [ main ]
+  schedule:
+    - cron: '0 2 * * 0'  # Weekly
+  workflow_dispatch:
+
+permissions:
+  packages: read
+
+jobs:
+  pylint:
+    runs-on: ubuntu-latest
+
+    container:
+      image: ghcr.io/charlesnicholson/docker-image:latest
+      credentials:
+        username: ${{ github.actor }}
+        password: ${{ secrets.GITHUB_TOKEN }}
+
+    steps:
+      - uses: actions/checkout@v3
+      - name: Pylint build.py
+        run: . /work/venv/bin/activate && python -m pylint build.py tests/size_report.py
+
+  download:
+    runs-on: ubuntu-latest
+
+    container:
+      image: ghcr.io/charlesnicholson/docker-image:latest
+      credentials:
+        username: ${{ github.actor }}
+        password: ${{ secrets.GITHUB_TOKEN }}
+
+    steps:
+      - uses: actions/checkout@v3
+        with:
+          submodules: recursive
+      - name: Build
+        run: ./b --download --paland -v
+
+  sanitizers:
+    runs-on: ubuntu-latest
+
+    container:
+      image: ghcr.io/charlesnicholson/docker-image:latest
+      credentials:
+        username: ${{ github.actor }}
+        password: ${{ secrets.GITHUB_TOKEN }}
+
+    strategy:
+      matrix:
+        sanitizer: [ubsan, asan]
+        architecture: [32, 64]
+
+    steps:
+      - uses: actions/checkout@v3
+        with:
+          submodules: recursive
+      - name: Build
+        env:
+          CC: /usr/bin/clang
+          CXX: /usr/bin/clang++
+        run: ./b --arch ${{ matrix.architecture }} --${{ matrix.sanitizer }} --paland -v
+
+  linux-gcc:
+    runs-on: ubuntu-latest
+
+    container:
+      image: ghcr.io/charlesnicholson/docker-image:latest
+      credentials:
+        username: ${{ github.actor }}
+        password: ${{ secrets.GITHUB_TOKEN }}
+
+    strategy:
+      matrix:
+        configuration: [Debug, Release]
+        architecture: [32, 64]
+
+    steps:
+      - uses: actions/checkout@v3
+        with:
+          submodules: recursive
+      - name: Build
+        env:
+          CC: /usr/bin/gcc
+          CXX: /usr/bin/g++
+        run: ./b --cfg ${{ matrix.configuration }} --arch ${{ matrix.architecture }} --paland -v
+
+  linux-clang:
+    runs-on: ubuntu-latest
+
+    container:
+      image: ghcr.io/charlesnicholson/docker-image:latest
+      credentials:
+        username: ${{ github.actor }}
+        password: ${{ secrets.GITHUB_TOKEN }}
+
+    strategy:
+      matrix:
+        configuration: [Debug, Release]
+        architecture: [32, 64]
+
+    steps:
+      - uses: actions/checkout@v3
+        with:
+          submodules: recursive
+      - name: Build
+        env:
+          CC: /usr/bin/clang
+          CXX: /usr/bin/clang++
+        run: ./b --cfg ${{ matrix.configuration }} --arch ${{ matrix.architecture }} --paland -v
+
+  macos:
+    runs-on: macos-latest
+
+    strategy:
+      matrix:
+        configuration: [Debug, Release]
+
+    steps:
+      - uses: actions/checkout@v3
+        with:
+          submodules: recursive
+      - name: Set up Python 3.x
+        uses: actions/setup-python@v4
+        with:
+          python-version: '3.x'
+      - name: Build
+        run: ./b --cfg ${{ matrix.configuration }} --paland -v
+
+  win:
+    runs-on: windows-latest
+
+    strategy:
+      matrix:
+        configuration: [Debug, Release]
+        architecture: [32, 64]
+
+    steps:
+      - uses: actions/checkout@v3
+        with:
+          submodules: recursive
+      - name: Set up Python 3.x
+        uses: actions/setup-python@v4
+        with:
+          python-version: '3.x'
+      - name: Build
+        shell: cmd
+        run: |
+          call "C:\Program Files\Microsoft Visual Studio\2022\Enterprise\VC\Auxiliary\Build\vcvars${{ matrix.architecture }}.bat"
+          python.exe build.py --cfg ${{ matrix.configuration }} --paland -v --arch ${{ matrix.architecture }}
+
+  size-reports:
+    runs-on: ubuntu-latest
+
+    container:
+      image: ghcr.io/charlesnicholson/docker-image:latest
+      credentials:
+        username: ${{ github.actor }}
+        password: ${{ secrets.GITHUB_TOKEN }}
+
+    steps:
+      - uses: actions/checkout@v3
+
+      - name: Cortex-M0
+        shell: bash
+        run: |
+          . /work/venv/bin/activate
+          python3 tests/size_report.py -p cm0
+
+      - name: Cortex-M4
+        shell: bash
+        run: |
+          . /work/venv/bin/activate
+          python3 tests/size_report.py -p cm4
+
+      - name: Linux x64
+        shell: bash
+        run: |
+          . /work/venv/bin/activate
+          python3 tests/size_report.py -p host
+
+  all-checks-pass:
+    needs: [pylint, download, sanitizers, linux-gcc, linux-clang, macos, win, size-reports]
+    runs-on: ubuntu-latest
+    steps:
+    - run: echo Done
+
diff --git a/.gitignore b/.gitignore
new file mode 100644
index 0000000..a489e4d
--- /dev/null
+++ b/.gitignore
@@ -0,0 +1,64 @@
+# CLion junk
+cmake-build*
+.idea
+
+# vim backup files
+*~
+
+# preprocessed / asm files
+*.i
+*.ii
+*.s
+
+# Object files
+*.o
+*.ko
+*.obj
+*.elf
+*.pyc
+
+# Precompiled Headers
+*.gch
+*.pch
+
+# Libraries
+*.lib
+*.a
+*.la
+*.lo
+
+# Shared objects (inc. Windows DLLs)
+*.dll
+*.so
+*.so.*
+*.dylib
+
+# Executables
+*.exe
+*.out
+*.app
+*.i*86
+*.x86_64
+*.hex
+
+# Debug files
+*.dSYM/
+
+# temp / external directories
+build
+external
+
+# latex intermediates / unused artifacts
+doc/*.aux
+doc/*.log
+doc/*.mp
+doc/*.out
+doc/*.1
+doc/*.mps
+doc/*.toc
+
+# hooray for osx
+.DS_Store
+
+# hooray for windows
+*.TMP
diff --git a/.gitmodules b/.gitmodules
new file mode 100644
index 0000000..5838a2b
--- /dev/null
+++ b/.gitmodules
@@ -0,0 +1,3 @@
+[submodule "tests/mpaland-conformance"]
+	path = tests/mpaland-conformance
+	url = [email protected]:charlesnicholson/nanoprintf-paland-conformance.git
diff --git a/CMakeLists.txt b/CMakeLists.txt
new file mode 100644
index 0000000..74fe7ea
--- /dev/null
+++ b/CMakeLists.txt
@@ -0,0 +1,231 @@
+cmake_minimum_required(VERSION 3.15)
+project(nanoprintf)
+
+option(NPF_32BIT "Compile nanoprintf tests in 32-bit mode")
+option(NPF_PALAND "Compile and run the mpaland printf test suite")
+option(NPF_CLANG_ASAN "Compile and run tests with address sanitizer")
+option(NPF_CLANG_UBSAN "Compile and run tests with undefined behavior sanitizer")
+
+if (NPF_32BIT AND CMAKE_HOST_APPLE)
+  message(FATAL_ERROR "Apple doesn't support 32-bit mode anymore.")
+endif()
+
+if ((NOT CMAKE_CXX_COMPILER_ID MATCHES "Clang") AND (NPF_CLANG_ASAN OR NPF_CLANG_UBSAN))
+  message(FATAL_ERROR "Can only use asan/ubsan on Clang configurations.")
+endif()
+
+if (NPF_CLANG_ASAN AND NPF_CLANG_UBSAN)
+  message(FATAL_ERROR "Can only use one of asan/ubsan per configuration.")
+endif()
+
+if (NPF_32BIT AND NOT MSVC)
+  set(NPF_32BIT_FLAG -m32)
+  set(CMAKE_C_FLAGS ${CMAKE_C_FLAGS} ${NPF_32BIT_FLAG})
+  set(CMAKE_CXX_FLAGS ${CMAKE_CXX_FLAGS} ${NPF_32BIT_FLAG})
+endif()
+
+################ Common compile flags
+
+set(CMAKE_C_STANDARD 99)
+set(CMAKE_CXX_STANDARD 17)
+
+if (MSVC)
+  set(nanoprintf_common_flags /Wall /WX)
+else()
+  set(CMAKE_C_FLAGS_DEBUG "-O0 -g3")
+  set(CMAKE_C_FLAGS_RELWITHDEBINFO "-Os -g3")
+  set(CMAKE_C_FLAGS_RELEASE "-Os")
+  set(CMAKE_CXX_FLAGS_DEBUG "-O0 -g3")
+  set(CMAKE_CXX_FLAGS_RELWITHDEBINFO "-Os -g3")
+  set(CMAKE_CXX_FLAGS_RELEASE "-Os")
+
+  set(nanoprintf_common_flags -pedantic -Wall -Wextra -Wundef -Werror)
+  set(nanoprintf_link_flags "")
+
+  if (CMAKE_CXX_COMPILER_ID MATCHES "Clang")
+    list(APPEND nanoprintf_common_flags -Weverything)
+    if (NPF_CLANG_ASAN)
+      list(APPEND nanoprintf_common_flags -fsanitize=address)
+      list(APPEND nanoprintf_link_flags -fsanitize=address)
+    elseif (NPF_CLANG_UBSAN)
+      list(APPEND nanoprintf_common_flags -fsanitize=undefined)
+      list(APPEND nanoprintf_link_flags -fsanitize=undefined)
+    endif()
+    if (CMAKE_HOST_APPLE)
+      list(APPEND nanoprintf_common_flags -Wno-poison-system-directories)
+    endif()
+  else()
+    list(APPEND nanoprintf_common_flags
+         -Wconversion
+         -Wshadow
+         -Wfloat-equal
+         -Wsign-conversion
+         -Wswitch-enum
+         -Wswitch-default)
+  endif()
+endif()
+
+################ Doctest
+
+add_library(libdoctest_main OBJECT tests/doctest_main.cc)
+target_compile_options(libdoctest_main PRIVATE ${nanoprintf_common_flags})
+
+function(npf_test name files)
+  add_executable(${name} ${files})
+    target_compile_definitions(${name} PRIVATE DOCTEST_CONFIG_SUPER_FAST_ASSERTS)
+    target_compile_options(${name} PRIVATE ${nanoprintf_common_flags})
+    target_link_options(${name} PRIVATE ${nanoprintf_link_flags})
+    target_link_libraries(${name} libdoctest_main) # Doctest is slow, only build once.
+
+  # Set up a target that automatically runs + timestamps successful tests.
+  set(timestamp "${CMAKE_CURRENT_BINARY_DIR}/${name}.timestamp")
+  add_custom_target(run_${name} ALL DEPENDS ${timestamp})
+  add_custom_command(OUTPUT ${timestamp}
+                     COMMAND ${name} -m && ${CMAKE_COMMAND} -E touch ${timestamp}
+                     DEPENDS ${name}
+                     COMMENT "Running ${name}")
+endfunction()
+
+################ Language compilation tests
+
+function(npf_compilation_c_test target)
+  add_library(${target} tests/compilation_c.c)
+  target_compile_options(${target} PRIVATE ${nanoprintf_common_flags})
+endfunction()
+
+# Test every combination of compatible flags.
+foreach(fw 0 1)
+  foreach(precision 0 1)
+    foreach(large 0 1)
+      foreach(float 0 1)
+        foreach(binary 0 1)
+          foreach(wb 0 1)
+            if ((precision EQUAL 0) AND (float EQUAL 1))
+              continue()
+            endif()
+
+            set(test_name "")
+            if (fw EQUAL 1)
+              string(APPEND test_name "_fieldwidth")
+            endif()
+            if (precision EQUAL 1)
+              string(APPEND test_name "_precision")
+            endif()
+            if (large EQUAL 1)
+              string(APPEND test_name "_large")
+            endif()
+            if (float EQUAL 1)
+              string(APPEND test_name "_float")
+            endif()
+            if (binary EQUAL 1)
+              string(APPEND test_name "_binary")
+            endif()
+            if (wb EQUAL 1)
+              string(APPEND test_name "_writeback")
+            endif()
+
+            # Run a simple compilation test
+            set(compilation_test_name "npf_compile${test_name}_c")
+            npf_compilation_c_test(${compilation_test_name})
+              target_compile_definitions(
+                ${compilation_test_name}
+                PRIVATE
+                NANOPRINTF_USE_FIELD_WIDTH_FORMAT_SPECIFIERS=${fw}
+                NANOPRINTF_USE_PRECISION_FORMAT_SPECIFIERS=${precision}
+                NANOPRINTF_USE_LARGE_FORMAT_SPECIFIERS=${large}
+                NANOPRINTF_USE_FLOAT_FORMAT_SPECIFIERS=${float}
+                NANOPRINTF_USE_BINARY_FORMAT_SPECIFIERS=${binary}
+                NANOPRINTF_USE_WRITEBACK_FORMAT_SPECIFIERS=${wb})
+
+            # Run conformance tests (c++)
+            set(conformance_test_name "npf_conform${test_name}")
+            npf_test(${conformance_test_name} tests/conformance.cc)
+              target_compile_definitions(
+                ${conformance_test_name}
+                PRIVATE
+                NANOPRINTF_USE_FIELD_WIDTH_FORMAT_SPECIFIERS=${fw}
+                NANOPRINTF_USE_PRECISION_FORMAT_SPECIFIERS=${precision}
+                NANOPRINTF_USE_LARGE_FORMAT_SPECIFIERS=${large}
+                NANOPRINTF_USE_FLOAT_FORMAT_SPECIFIERS=${float}
+                NANOPRINTF_USE_BINARY_FORMAT_SPECIFIERS=${binary}
+                NANOPRINTF_USE_WRITEBACK_FORMAT_SPECIFIERS=${wb})
+
+            if (NPF_PALAND)
+              set(paland_test_name "npf_paland${test_name}")
+              npf_test(${paland_test_name} tests/mpaland-conformance/paland.cc)
+                target_compile_definitions(
+                  ${paland_test_name}
+                  PRIVATE
+                  NANOPRINTF_USE_FIELD_WIDTH_FORMAT_SPECIFIERS=${fw}
+                  NANOPRINTF_USE_PRECISION_FORMAT_SPECIFIERS=${precision}
+                  NANOPRINTF_USE_LARGE_FORMAT_SPECIFIERS=${large}
+                  NANOPRINTF_USE_FLOAT_FORMAT_SPECIFIERS=${float}
+                  NANOPRINTF_USE_BINARY_FORMAT_SPECIFIERS=${binary}
+                  NANOPRINTF_USE_WRITEBACK_FORMAT_SPECIFIERS=${wb})
+            endif()
+          endforeach()
+        endforeach()
+      endforeach()
+    endforeach()
+  endforeach()
+endforeach()
+
+# Test that nanoprintf compiles when no flags are set.
+npf_compilation_c_test(npf_c_default_flags)
+
+################ Static compilation test
+
+add_executable(npf_static tests/static_nanoprintf.c tests/static_main.c)
+  target_link_options(npf_static PRIVATE ${nanoprintf_link_flags})
+
+################# Examples
+
+add_executable(use_npf_directly
+               examples/use_npf_directly/your_project_nanoprintf.cc
+               examples/use_npf_directly/main.cc)
+  target_link_options(use_npf_directly PRIVATE ${nanoprintf_link_flags})
+
+add_executable(wrap_npf
+               examples/wrap_npf/your_project_printf.h
+               examples/wrap_npf/your_project_printf.cc
+               examples/wrap_npf/main.cc)
+  target_link_options(wrap_npf PRIVATE ${nanoprintf_link_flags})
+
+add_executable(npf_include_multiple tests/include_multiple.c)
+  target_compile_options(npf_include_multiple PRIVATE ${nanoprintf_common_flags})
+  target_link_options(npf_include_multiple PRIVATE ${nanoprintf_link_flags})
+
+############### Unit tests
+
+set(unit_test_files
+    nanoprintf.h
+    tests/unit_parse_format_spec.cc
+    tests/unit_binary.cc
+    tests/unit_bufputc.cc
+    tests/unit_ftoa_rev.cc
+    tests/unit_itoa_rev.cc
+    tests/unit_utoa_rev.cc
+    tests/unit_fsplit_abs.cc
+    tests/unit_snprintf.cc
+    tests/unit_snprintf_safe_empty.cc
+    tests/unit_vpprintf.cc)
+
+npf_test(unit_tests_normal_sized_formatters "${unit_test_files}")
+  target_compile_definitions(unit_tests_normal_sized_formatters
+                             PRIVATE
+                             NANOPRINTF_USE_LARGE_FORMAT_SPECIFIERS=0)
+  if (NPF_32BIT)
+  target_compile_definitions(unit_tests_normal_sized_formatters
+                             PRIVATE
+                             NANOPRINTF_32_BIT_TESTS)
+  endif()
+
+npf_test(unit_tests_large_sized_formatters "${unit_test_files}")
+  target_compile_definitions(unit_tests_large_sized_formatters
+                             PRIVATE
+                             NANOPRINTF_USE_LARGE_FORMAT_SPECIFIERS=1)
+  if (NPF_32BIT)
+  target_compile_definitions(unit_tests_normal_sized_formatters
+                             PRIVATE
+                             NANOPRINTF_32_BIT_TESTS)
+  endif()
diff --git a/CODE_OF_CONDUCT.md b/CODE_OF_CONDUCT.md
new file mode 100644
index 0000000..b44cd96
--- /dev/null
+++ b/CODE_OF_CONDUCT.md
@@ -0,0 +1,76 @@
+# Contributor Covenant Code of Conduct
+
+## Our Pledge
+
+In the interest of fostering an open and welcoming environment, we as
+contributors and maintainers pledge to making participation in our project and
+our community a harassment-free experience for everyone, regardless of age, body
+size, disability, ethnicity, sex characteristics, gender identity and expression,
+level of experience, education, socio-economic status, nationality, personal
+appearance, race, religion, or sexual identity and orientation.
+
+## Our Standards
+
+Examples of behavior that contributes to creating a positive environment
+include:
+
+* Using welcoming and inclusive language
+* Being respectful of differing viewpoints and experiences
+* Gracefully accepting constructive criticism
+* Focusing on what is best for the community
+* Showing empathy towards other community members
+
+Examples of unacceptable behavior by participants include:
+
+* The use of sexualized language or imagery and unwelcome sexual attention or
+ advances
+* Trolling, insulting/derogatory comments, and personal or political attacks
+* Public or private harassment
+* Publishing others' private information, such as a physical or electronic
+ address, without explicit permission
+* Other conduct which could reasonably be considered inappropriate in a
+ professional setting
+
+## Our Responsibilities
+
+Project maintainers are responsible for clarifying the standards of acceptable
+behavior and are expected to take appropriate and fair corrective action in
+response to any instances of unacceptable behavior.
+
+Project maintainers have the right and responsibility to remove, edit, or
+reject comments, commits, code, wiki edits, issues, and other contributions
+that are not aligned to this Code of Conduct, or to ban temporarily or
+permanently any contributor for other behaviors that they deem inappropriate,
+threatening, offensive, or harmful.
+
+## Scope
+
+This Code of Conduct applies both within project spaces and in public spaces
+when an individual is representing the project or its community. Examples of
+representing a project or community include using an official project e-mail
+address, posting via an official social media account, or acting as an appointed
+representative at an online or offline event. Representation of a project may be
+further defined and clarified by project maintainers.
+
+## Enforcement
+
+Instances of abusive, harassing, or otherwise unacceptable behavior may be
+reported by contacting the project team at [email protected]. All
+complaints will be reviewed and investigated and will result in a response that
+is deemed necessary and appropriate to the circumstances. The project team is
+obligated to maintain confidentiality with regard to the reporter of an incident.
+Further details of specific enforcement policies may be posted separately.
+
+Project maintainers who do not follow or enforce the Code of Conduct in good
+faith may face temporary or permanent repercussions as determined by other
+members of the project's leadership.
+
+## Attribution
+
+This Code of Conduct is adapted from the [Contributor Covenant][homepage], version 1.4,
+available at https://www.contributor-covenant.org/version/1/4/code-of-conduct.html
+
+[homepage]: https://www.contributor-covenant.org
+
+For answers to common questions about this code of conduct, see
+https://www.contributor-covenant.org/faq
diff --git a/CONTRIBUTORS.md b/CONTRIBUTORS.md
new file mode 100644
index 0000000..bffbd43
--- /dev/null
+++ b/CONTRIBUTORS.md
@@ -0,0 +1,10 @@
+# Contributors
+
+## Special thanks for all the people who had helped this project so far:
+
+* [Dino Dai Zovi](https://github.com/ddz)
+* [Shang Yuanchun](https://github.com/ideal)
+* [Shreyas Balakrishna](https://github.com/shreyasbharath)
+* [Jim Keener](https://github.com/jimktrains)
+* [Dean T](https://github.com/deanoburrito)
+* [Oskars Rubenis](https://github.com/Okarss)
diff --git a/LICENSE b/LICENSE
new file mode 100644
index 0000000..039373b
--- /dev/null
+++ b/LICENSE
@@ -0,0 +1,53 @@
+Nanoprintf is dual-licensed under both the "Unlicense" and the
+"Zero-Clause BSD" (0BSD) licenses. The intent of this dual-licensing
+structure is to make nanoprintf as consumable as possible in as many
+environments / countries / companies as possible without encumbering
+users.
+
+This license applies to all of the nanoprintf source code, build code,
+and tests, with the explicit exception of doctest.h, which exists under
+its own license and is included in this repository.
+
+The text of the two licenses follows below:
+
+============================== UNLICENSE ==============================
+
+This is free and unencumbered software released into the public domain.
+
+Anyone is free to copy, modify, publish, use, compile, sell, or
+distribute this software, either in source code form or as a compiled
+binary, for any purpose, commercial or non-commercial, and by any
+means.
+
+In jurisdictions that recognize copyright laws, the author or authors
+of this software dedicate any and all copyright interest in the
+software to the public domain. We make this dedication for the benefit
+of the public at large and to the detriment of our heirs and
+successors. We intend this dedication to be an overt act of
+relinquishment in perpetuity of all present and future rights to this
+software under copyright law.
+
+THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND,
+EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF
+MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT.
+IN NO EVENT SHALL THE AUTHORS BE LIABLE FOR ANY CLAIM, DAMAGES OR
+OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE,
+ARISING FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR
+OTHER DEALINGS IN THE SOFTWARE.
+
+For more information, please refer to <http://unlicense.org>
+
+================================ 0BSD =================================
+
+Copyright (C) 2019 by Charles Nicholson <[email protected]>
+
+Permission to use, copy, modify, and/or distribute this software for
+any purpose with or without fee is hereby granted.
+
+THE SOFTWARE IS PROVIDED "AS IS" AND THE AUTHOR DISCLAIMS ALL WARRANTIES
+WITH REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF
+MERCHANTABILITY AND FITNESS. IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR
+ANY SPECIAL, DIRECT, INDIRECT, OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES
+WHATSOEVER RESULTING FROM LOSS OF USE, DATA OR PROFITS, WHETHER IN AN
+ACTION OF CONTRACT, NEGLIGENCE OR OTHER TORTIOUS ACTION, ARISING OUT OF
+OR IN CONNECTION WITH THE USE OR PERFORMANCE OF THIS SOFTWARE.
diff --git a/METADATA b/METADATA
new file mode 100644
index 0000000..9ddecf4
--- /dev/null
+++ b/METADATA
@@ -0,0 +1,17 @@
+name: "nanoprintf"
+description:
+    "nanoprintf is an unencumbered implementation of snprintf and vsnprintf for "
+    "embedded systems that, when fully enabled, aim for C11 standard "
+    "compliance."
+
+third_party {
+  identifier {
+    type: "Git"
+    value: "https://github.com/charlesnicholson/nanoprintf"
+    primary_source: true
+    version: "v0.5.1"
+  }
+  version: "v0.5.1"
+  last_upgrade_date { year: 2024 month: 7 day: 15 }
+  license_type: PERMISSIVE
+}
diff --git a/MODULE_LICENSE_0BSD b/MODULE_LICENSE_0BSD
new file mode 100644
index 0000000..e69de29
--- /dev/null
+++ b/MODULE_LICENSE_0BSD
diff --git a/MODULE_LICENSE_UNLICENSE b/MODULE_LICENSE_UNLICENSE
new file mode 100644
index 0000000..e69de29
--- /dev/null
+++ b/MODULE_LICENSE_UNLICENSE
diff --git a/OWNERS b/OWNERS
new file mode 100644
index 0000000..2e8f086
--- /dev/null
+++ b/OWNERS
@@ -0,0 +1 @@
+include platform/system/core:main:/janitors/OWNERS
diff --git a/README.md b/README.md
new file mode 100644
index 0000000..7c28968
--- /dev/null
+++ b/README.md
@@ -0,0 +1,272 @@
+# nanoprintf
+
+[![Presubmit Checks](https://github.com/charlesnicholson/nanoprintf/workflows/Presubmit%20Checks/badge.svg)](https://github.com/charlesnicholson/nanoprintf/tree/main/.github/workflows)
+[![](https://img.shields.io/badge/asan-clean-brightgreen)](https://en.wikipedia.org/wiki/AddressSanitizer)
+[![](https://img.shields.io/badge/ubsan-clean-brightgreen)](https://clang.llvm.org/docs/UndefinedBehaviorSanitizer.html)
+[![](https://img.shields.io/badge/pylint-10.0-brightgreen.svg)](https://www.pylint.org/)
+[![](https://img.shields.io/badge/license-public_domain-brightgreen.svg)](https://github.com/charlesnicholson/nanoprintf/blob/master/LICENSE)
+[![](https://img.shields.io/badge/license-0BSD-brightgreen)](https://github.com/charlesnicholson/nanoprintf/blob/master/LICENSE)
+
+nanoprintf is an unencumbered implementation of snprintf and vsnprintf for embedded systems that, when fully enabled, aim for C11 standard compliance. The primary exceptions are `double` (they get casted to `float`), scientific notation (`%e`, `%g`, `%a`), and the conversions that require `wcrtomb` to exist. C23 binary integer output is optionally supported as per [N2630](http://www.open-std.org/jtc1/sc22/wg14/www/docs/n2630.pdf). Safety extensions for snprintf and vsnprintf can be optionally configured to return trimmed or fully-empty strings on buffer overflow events.
+
+Additionally, nanoprintf can be used to parse printf-style format strings to extract the various parameters and conversion specifiers, without doing any actual text formatting.
+
+nanoprintf makes no memory allocations and uses less than 100 bytes of stack. It compiles to between *~760-2500 bytes of object code* on a Cortex-M0 architecture, depending on configuration.
+
+All code is written in a minimal dialect of C99 for maximal compiler compatibility, compiles cleanly at the highest warning levels on clang + gcc + msvc, raises no issues from UBsan or Asan, and is exhaustively tested on 32-bit and 64-bit architectures. nanoprintf does include C standard headers but only uses them for C99 types and argument lists; no calls are made into stdlib / libc, with the exception of any internal double-to-float conversion ABI calls your compiler might emit. As usual, some Windows-specific headers are required if you're compiling natively for msvc.
+
+nanoprintf is a [single header file](https://github.com/charlesnicholson/nanoprintf/blob/master/nanoprintf.h) in the style of the [stb libraries](https://github.com/nothings/stb). The rest of the repository is tests and scaffolding and not required for use.
+
+nanoprintf is statically configurable so users can find a balance between size, compiler requirements, and feature set. Floating point conversion, "large" length modifiers, and size write-back are all configurable and are only compiled if explicitly requested, see [Configuration](https://github.com/charlesnicholson/nanoprintf#configuration) for details.
+
+## Usage
+
+Add the following code to one of your source files to compile the nanoprintf implementation:
+```
+// define your nanoprintf configuration macros here (see "Configuration" below)
+#define NANOPRINTF_IMPLEMENTATION
+#include "path/to/nanoprintf.h"
+```
+
+Then, in any file where you want to use nanoprintf, simply include the header and call the npf_ functions:
+```
+#include "nanoprintf.h"
+
+void print_to_uart(void) {
+  npf_pprintf(&my_uart_putc, NULL, "Hello %s%c %d %u %f\n", "worl", 'd', 1, 2, 3.f);
+}
+
+void print_to_buf(void *buf, unsigned len) {
+  npf_snprintf(buf, len, "Hello %s", "world");
+}
+```
+
+See the "[Use nanoprintf directly](https://github.com/charlesnicholson/nanoprintf/blob/master/examples/use_npf_directly/main.cc)" and "[Wrap nanoprintf](https://github.com/charlesnicholson/nanoprintf/blob/master/examples/wrap_npf/main.cc)" examples for more details.
+
+
+## Motivation
+
+I wanted a single-file public-domain drop-in printf that came in at under 1KB in the minimal configuration (bootloaders etc), and under 3KB with the floating-point bells and whistles enabled.
+
+In firmware work, I generally want stdio's string formatting without the syscall or file descriptor layer requirements; they're almost never needed in tiny systems where you want to log into small buffers or emit directly to a bus. Also, many embedded stdio implementations are larger or slower than they need to be- this is important for bootloader work. If you don't need any of the syscalls or stdio bells + whistles, you can simply use nanoprintf and `nosys.specs` and slim down your build.
+
+## Philosophy
+
+This code is optimized for size, not readability or structure. Unfortunately modularity and "cleanliness" (whatever that means) adds overhead at this small scale, so most of the functionality and logic is pushed together into `npf_vpprintf`. This is not what normal embedded systems code should look like; it's `#ifdef` soup and hard to make sense of, and I apologize if you have to spelunk around in the implementation. Hopefully the various tests will serve as guide rails if you hack around in it.
+
+Alternately, perhaps you're a significantly better programmer than I! In that case, please help me make this code smaller and cleaner without making the footprint larger, or nudge me in the right direction. :)
+
+## API
+
+nanoprintf has 4 main functions:
+* `npf_snprintf`: Use like [snprintf](https://en.cppreference.com/w/c/io/fprintf).
+* `npf_vsnprintf`: Use like [vsnprintf](https://en.cppreference.com/w/c/io/vfprintf) (`va_list` support).
+* `npf_pprintf`: Use like [printf](https://en.cppreference.com/w/c/io/fprintf) with a per-character write callback (semihosting, UART, etc).
+* `npf_vpprintf`: Use like `npf_pprintf` but takes a `va_list`.
+
+The `pprintf` variations take a callback that receives the character to print and a user-provided context pointer.
+
+Pass `NULL` or `nullptr` to `npf_[v]snprintf` to write nothing, and only return the length of the formatted string.
+
+nanoprintf does *not* provide `printf` or `putchar` itself; those are seen as system-level services and nanoprintf is a utility library. nanoprintf is hopefully a good building block for rolling your own `printf`, though.
+
+### Return Values
+
+The nanoprintf functions all return the same value: the number of characters that were either sent to the callback (for npf_pprintf) or the number of characters that would have been written to the buffer provided sufficient space. The null-terminator 0 byte is not part of the count.
+
+The C Standard allows for the printf functions to return negative values in case string or character encodings can not be performed, or if the output stream encounters EOF. Since nanoprintf is oblivious to OS resources like files, and does not support the `l` length modifier for `wchar_t` support, any runtime errors are either internal bugs (please report!) or incorrect usage. Because of this, nanoprintf only returns non-negative values representing how many bytes the formatted string contains (again, minus the null-terminator byte).
+
+## Configuration
+
+### Features
+nanoprintf has the following static configuration flags.
+
+* `NANOPRINTF_USE_FIELD_WIDTH_FORMAT_SPECIFIERS`: Set to `0` or `1`. Enables field width specifiers.
+* `NANOPRINTF_USE_PRECISION_FORMAT_SPECIFIERS`: Set to `0` or `1`. Enables precision specifiers.
+* `NANOPRINTF_USE_FLOAT_FORMAT_SPECIFIERS`: Set to `0` or `1`. Enables floating-point specifiers.
+* `NANOPRINTF_USE_LARGE_FORMAT_SPECIFIERS`: Set to `0` or `1`. Enables oversized modifiers.
+* `NANOPRINTF_USE_BINARY_FORMAT_SPECIFIERS`: Set to `0` or `1`. Enables binary specifiers.
+* `NANOPRINTF_USE_WRITEBACK_FORMAT_SPECIFIERS`: Set to `0` or `1`. Enables `%n` for write-back.
+* `NANOPRINTF_VISIBILITY_STATIC`: Optional define. Marks prototypes as `static` to sandbox nanoprintf.
+
+If no configuration flags are specified, nanoprintf will default to "reasonable" embedded values in an attempt to be helpful: floats are enabled, but writeback, binary, and large formatters are disabled. If any configuration flags are explicitly specified, nanoprintf requires that all flags are explicitly specified.
+
+If a disabled format specifier feature is used, no conversion will occur and the format specifier string simply will be printed instead.
+
+### Sprintf Safety
+By default, npf_snprintf and npf_vsnprintf behave according to the C Standard: the provided buffer will be filled but not overrun. If the string would have overrun the buffer, a null-terminator byte will be written to the final byte of the buffer. If the buffer is `null` or zero-sized, no bytes will be written.
+
+If you define `NANOPRINTF_SNPRINTF_SAFE_EMPTY_STRING_ON_OVERFLOW` and your string is larger than your buffer, the _first_ byte of the buffer will be overwritten with a null-terminator byte. This is similar in spirit to [Microsoft's snprintf_s](https://docs.microsoft.com/en-us/cpp/c-runtime-library/reference/snprintf-s-snprintf-s-l-snwprintf-s-snwprintf-s-l).
+
+In all cases, nanoprintf will return the number of bytes that would have been written to the buffer, had there been enough room. This value does not account for the null-terminator byte, in accordance with the C Standard.
+
+### Thread Safety
+nanoprintf uses only stack memory and no concurrency primitives, so internally it is oblivious to its execution environment. This makes it safe to call from multiple execution contexts concurrently, or to interrupt a `npf_` call with another `npf_` call (say, an ISR or something). If you use `npf_pprintf` concurrently with the same `npf_putc` target, it's up to you to ensure correctness inside your callback. If you `npf_snprintf` from multiple threads to the same buffer, you will have an obvious data race.
+
+## Formatting
+
+Like `printf`, `nanoprintf` expects a conversion specification string of the following form:
+
+`[flags][field width][.precision][length modifier][conversion specifier]`
+
+* **Flags**
+
+	None or more of the following:
+	* `0`: Pad the field with leading zero characters.
+	* `-`: Left-justify the conversion result in the field.
+	* `+`: Signed conversions always begin with `+` or `-` characters.
+	* ` `: (space) A space character is inserted if the first converted character is not a sign.
+	* `#`: Writes extra characters (`0x` for hex, `.` for empty floats, '0' for empty octals, etc).
+* **Field width** (if enabled)
+
+	A number that specifies the total field width for the conversion, adds padding. If field width is `*`, the field width is read from the next vararg.
+* **Precision** (if enabled)
+
+	Prefixed with a `.`, a number that specifies the precision of the number or string. If precision is `*`, the precision is read from the next vararg.
+* **Length modifier**
+
+	None or more of the following:
+	* `h`: Use `short` for integral and write-back vararg width.
+	* `L`: Use `long double` for float vararg width (note: it will then be casted down to `float`)
+	* `l`: Use `long`, `double`, or wide vararg width.
+	* `hh`: Use `char` for integral and write-back vararg width.
+	* `ll`: (large specifier) Use `long long` for integral and write-back vararg width.
+	* `j`: (large specifier) Use the `[u]intmax_t` types for integral and write-back vararg width.
+	* `z`: (large specifier) Use the `size_t` types for integral and write-back vararg width.
+	* `t`: (large specifier) Use the `ptrdiff_t` types for integral and write-back vararg width.
+* **Conversion specifier**
+
+	Exactly one of the following:
+	* `%`: Percent-sign literal
+	* `c`: Character
+	* `s`: Null-terminated strings
+	* `i`/`d`: Signed integers
+	* `u`: Unsigned integers
+	* `o`: Unsigned octal integers
+	* `x` / `X`: Unsigned hexadecimal integers
+	* `p`: Pointers
+	* `n`: Write the number of bytes written to the pointer vararg
+	* `f`/`F`: Floating-point decimal
+	* `e`/`E`: Floating-point scientific (unimplemented, prints float decimal)
+	* `g`/`G`: Floating-point shortest (unimplemented, prints float decimal)
+	* `a`/`A`: Floating-point hex (unimplemented, prints float decimal)
+	* `b`/`B`: Binary integers
+
+## Floating Point
+
+Floating point conversion is performed by extracting the value into 64:64 fixed-point with an extra field that specifies the number of leading zero fractional digits before the first nonzero digit. This is done for simplicity, speed, and code footprint.
+
+Because the float -> fixed code operates on the raw float value bits, no floating point operations are performed. This allows nanoprintf to efficiently format floats on soft-float architectures like Cortex-M0, and to function identically with or without optimizations like "fast math". Despite `nano` in the name, there's no way to do away with double entirely, since the C language standard says that floats are promoted to double any time they're passed into variadic argument lists. nanoprintf casts all doubles back down to floats before doing any conversions. No other single- or double- precision operations are performed.
+
+The `%e`/`%E`, `%a`/`%A`, and `%g`/`%G` specifiers are parsed but not formatted. If used, the output will be identical to if `%f`/`%F` was used. Pull requests welcome! :)
+
+## Limitations
+
+No wide-character support exists: the `%lc` and `%ls` fields require that the arg be converted to a char array as if by a call to [wcrtomb](http://man7.org/linux/man-pages/man3/wcrtomb.3.html). When locale and character set conversions get involved, it's hard to keep the name "nano". Accordingly, `%lc` and `%ls` behave like `%c` and `%s`, respectively.
+
+Currently the only supported float conversions are the decimal forms: `%f` and `%F`. Pull requests welcome!
+
+## Measurement
+
+The CI build is set up to use gcc and nm to measure the compiled size of every pull request. See the [Presubmit Checks](https://github.com/charlesnicholson/nanoprintf/actions/workflows/presubmit.yml) "size reports" job output for recent runs.
+
+The following size measurements are taken against the Cortex-M0 build.
+
+```
+Configuration "Minimal":
+arm-none-eabi-gcc -c -x c -Os -I/__w/nanoprintf/nanoprintf -o npf.o -mcpu=cortex-m0 -DNANOPRINTF_IMPLEMENTATION -DNANOPRINTF_USE_FIELD_WIDTH_FORMAT_SPECIFIERS=0 -DNANOPRINTF_USE_PRECISION_FORMAT_SPECIFIERS=0 -DNANOPRINTF_USE_FLOAT_FORMAT_SPECIFIERS=0 -DNANOPRINTF_USE_LARGE_FORMAT_SPECIFIERS=0 -DNANOPRINTF_USE_BINARY_FORMAT_SPECIFIERS=0 -DNANOPRINTF_USE_WRITEBACK_FORMAT_SPECIFIERS=0 -
+arm-none-eabi-nm --print-size --size-sort npf.o
+00000014 00000002 t npf_bufputc_nop
+00000016 00000010 t npf_putc_cnt
+00000000 00000014 t npf_bufputc
+00000298 00000016 T npf_pprintf
+000002e0 00000016 T npf_snprintf
+000002ae 00000032 T npf_vsnprintf
+00000026 00000272 T npf_vpprintf
+Total size: 0x2f6 (758) bytes
+
+Configuration "Binary":
+arm-none-eabi-gcc -c -x c -Os -I/__w/nanoprintf/nanoprintf -o npf.o -mcpu=cortex-m0 -DNANOPRINTF_IMPLEMENTATION -DNANOPRINTF_USE_FIELD_WIDTH_FORMAT_SPECIFIERS=0 -DNANOPRINTF_USE_PRECISION_FORMAT_SPECIFIERS=0 -DNANOPRINTF_USE_FLOAT_FORMAT_SPECIFIERS=0 -DNANOPRINTF_USE_LARGE_FORMAT_SPECIFIERS=0 -DNANOPRINTF_USE_BINARY_FORMAT_SPECIFIERS=1 -DNANOPRINTF_USE_WRITEBACK_FORMAT_SPECIFIERS=0 -
+arm-none-eabi-nm --print-size --size-sort npf.o
+00000014 00000002 t npf_bufputc_nop
+00000016 00000010 t npf_putc_cnt
+00000000 00000014 t npf_bufputc
+000002de 00000016 T npf_pprintf
+00000328 00000016 T npf_snprintf
+000002f4 00000034 T npf_vsnprintf
+00000026 000002b8 T npf_vpprintf
+Total size: 0x33e (830) bytes
+
+Configuration "Field Width + Precision":
+arm-none-eabi-gcc -c -x c -Os -I/__w/nanoprintf/nanoprintf -o npf.o -mcpu=cortex-m0 -DNANOPRINTF_IMPLEMENTATION -DNANOPRINTF_USE_FIELD_WIDTH_FORMAT_SPECIFIERS=1 -DNANOPRINTF_USE_PRECISION_FORMAT_SPECIFIERS=1 -DNANOPRINTF_USE_FLOAT_FORMAT_SPECIFIERS=0 -DNANOPRINTF_USE_LARGE_FORMAT_SPECIFIERS=0 -DNANOPRINTF_USE_BINARY_FORMAT_SPECIFIERS=0 -DNANOPRINTF_USE_WRITEBACK_FORMAT_SPECIFIERS=0 -
+arm-none-eabi-nm --print-size --size-sort npf.o
+00000014 00000002 t npf_bufputc_nop
+00000016 00000010 t npf_putc_cnt
+00000000 00000014 t npf_bufputc
+00000546 00000016 T npf_pprintf
+00000590 00000016 T npf_snprintf
+0000055c 00000034 T npf_vsnprintf
+00000026 00000520 T npf_vpprintf
+Total size: 0x5a6 (1446) bytes
+
+Configuration "Field Width + Precision + Binary":
+arm-none-eabi-gcc -c -x c -Os -I/__w/nanoprintf/nanoprintf -o npf.o -mcpu=cortex-m0 -DNANOPRINTF_IMPLEMENTATION -DNANOPRINTF_USE_FIELD_WIDTH_FORMAT_SPECIFIERS=1 -DNANOPRINTF_USE_PRECISION_FORMAT_SPECIFIERS=1 -DNANOPRINTF_USE_FLOAT_FORMAT_SPECIFIERS=0 -DNANOPRINTF_USE_LARGE_FORMAT_SPECIFIERS=0 -DNANOPRINTF_USE_BINARY_FORMAT_SPECIFIERS=1 -DNANOPRINTF_USE_WRITEBACK_FORMAT_SPECIFIERS=0 -
+arm-none-eabi-nm --print-size --size-sort npf.o
+00000014 00000002 t npf_bufputc_nop
+00000016 00000010 t npf_putc_cnt
+00000000 00000014 t npf_bufputc
+00000590 00000016 T npf_pprintf
+000005d8 00000016 T npf_snprintf
+000005a6 00000032 T npf_vsnprintf
+00000026 0000056a T npf_vpprintf
+Total size: 0x5ee (1518) bytes
+
+Configuration "Float":
+arm-none-eabi-gcc -c -x c -Os -I/__w/nanoprintf/nanoprintf -o npf.o -mcpu=cortex-m0 -DNANOPRINTF_IMPLEMENTATION -DNANOPRINTF_USE_FIELD_WIDTH_FORMAT_SPECIFIERS=0 -DNANOPRINTF_USE_PRECISION_FORMAT_SPECIFIERS=1 -DNANOPRINTF_USE_FLOAT_FORMAT_SPECIFIERS=1 -DNANOPRINTF_USE_LARGE_FORMAT_SPECIFIERS=0 -DNANOPRINTF_USE_BINARY_FORMAT_SPECIFIERS=0 -DNANOPRINTF_USE_WRITEBACK_FORMAT_SPECIFIERS=0 -
+arm-none-eabi-nm --print-size --size-sort npf.o
+00000014 00000002 t npf_bufputc_nop
+00000016 00000010 t npf_putc_cnt
+00000000 00000014 t npf_bufputc
+0000059c 00000016 T npf_pprintf
+000005e4 00000016 T npf_snprintf
+000005b2 00000032 T npf_vsnprintf
+00000026 00000576 T npf_vpprintf
+Total size: 0x5fa (1530) bytes
+
+Configuration "Everything":
+arm-none-eabi-gcc -c -x c -Os -I/__w/nanoprintf/nanoprintf -o npf.o -mcpu=cortex-m0 -DNANOPRINTF_IMPLEMENTATION -DNANOPRINTF_USE_FIELD_WIDTH_FORMAT_SPECIFIERS=1 -DNANOPRINTF_USE_PRECISION_FORMAT_SPECIFIERS=1 -DNANOPRINTF_USE_FLOAT_FORMAT_SPECIFIERS=1 -DNANOPRINTF_USE_LARGE_FORMAT_SPECIFIERS=1 -DNANOPRINTF_USE_BINARY_FORMAT_SPECIFIERS=1 -DNANOPRINTF_USE_WRITEBACK_FORMAT_SPECIFIERS=1 -
+arm-none-eabi-nm --print-size --size-sort npf.o
+00000014 00000002 t npf_bufputc_nop
+00000016 00000010 t npf_putc_cnt
+00000000 00000014 t npf_bufputc
+00000934 00000016 T npf_pprintf
+0000097c 00000016 T npf_snprintf
+0000094a 00000032 T npf_vsnprintf
+00000026 0000090e T npf_vpprintf
+Total size: 0x992 (2450) bytes
+```
+
+## Development
+
+To get the environment and run tests:
+
+1. Clone or fork this repository.
+1. Run `./b` from the root (or `py -3 build.py` from the root, for Windows users)
+
+This will build all of the unit, conformance, and compilation tests for your host environment. Any test failures will return a non-zero exit code.
+
+The nanoprintf development environment uses [cmake](https://cmake.org/) and [ninja](https://ninja-build.org/). If you have these in your path, `./b` will use them. If not, `./b` will download and deploy them into `path/to/your/nanoprintf/external`.
+
+nanoprintf uses GitHub Actions for all continuous integration builds. The GitHub Linux builds use [this](https://github.com/charlesnicholson/docker-images/packages/751874) Docker image from [my Docker repository](https://github.com/charlesnicholson/docker-images).
+
+The matrix builds [Debug, Release] x [32-bit, 64-bit] x [Mac, Windows, Linux] x [gcc, clang, msvc], minus the 32-bit clang Mac configurations.
+
+One test suite is a fork from the [printf test suite](), which is MIT licensed. It exists as a submodule for licensing purposes- nanoprintf is public domain, so this particular test suite is optional and excluded by default. To build it, retrieve it by updating submodules and add the `--paland` flag to your `./b` invocation. It is not required to use nanoprintf at all.
+
+## Acknowledgments
+
+I implemented Float-to-int conversion using the ideas from [Wojciech MuÅ‚a](mailto:zdjÄ™[email protected])'s [float -> 64:64 fixed algorithm](http://0x80.pl/notesen/2015-12-29-float-to-string.html).
+
+I ported the [printf test suite](https://github.com/eyalroz/printf/blob/master/test/test_suite.cpp) to nanoprintf. It was originally from the [mpaland printf project](https://github.com/mpaland/printf) codebase but adopted and improved by [Eyal Rozenberg](https://github.com/eyalroz) and others. (Nanoprintf has many of its own tests, but these are also very thorough and very good!)
+
+The binary implementation is based on the requirements specified by [Jörg Wunsch](https://github.com/dl8dtl)'s [N2630 proposal](http://www.open-std.org/jtc1/sc22/wg14/www/docs/n2630.pdf), hopefully to be accepted into C23!
diff --git a/b b/b
new file mode 100755
index 0000000..b0e6ba4
--- /dev/null
+++ b/b
@@ -0,0 +1,10 @@
+#!/usr/bin/env bash
+set -euo pipefail
+
+PYTHON3=$(command -v python3 || true)
+if [[ -x "$PYTHON3" ]]; then
+    "$PYTHON3" build.py "$@"
+else
+    echo Python3 is required to build nanoprintf tests.
+fi
+
diff --git a/build.py b/build.py
new file mode 100644
index 0000000..2cce703
--- /dev/null
+++ b/build.py
@@ -0,0 +1,185 @@
+"""Build script for nanoprintf. Configures and runs CMake to build tests."""
+
+import argparse
+import os
+import pathlib
+import shutil
+import subprocess
+import stat
+import sys
+import tarfile
+import urllib.request
+import zipfile
+
+SCRIPT_PATH = pathlib.Path(__file__).resolve().parent
+
+NINJA_URL = 'https://github.com/ninja-build/ninja/releases/download/v1.10.2/{}'
+CMAKE_URL = 'https://cmake.org/files/v3.22/{}'
+
+
+def parse_args():
+    """Parse command-line arguments."""
+    parser = argparse.ArgumentParser()
+    parser.add_argument(
+        '--cfg',
+        choices=[
+            'Debug',
+            'RelWithDebInfo',
+            'Release'],
+        default='Release',
+        const='Release',
+        nargs='?',
+        help='CMake configuration')
+    parser.add_argument(
+        '--arch',
+        type=int,
+        choices=(32, 64),
+        default=64,
+        const=64,
+        nargs='?',
+        help='Target architecture')
+    parser.add_argument(
+        '--paland',
+        help='Compile with Paland\'s printf conformance suite',
+        action='store_true')
+    parser.add_argument(
+        '--download',
+        help='Download CMake and Ninja, don\'t use local copies',
+        action='store_true')
+    parser.add_argument('--ubsan', action='store_true', help='Clang UB sanitizer')
+    parser.add_argument('--asan', action='store_true', help='Clang addr sanitizer')
+    parser.add_argument('-v', '--verbose', action='store_true', help='verbose')
+    return parser.parse_args()
+
+
+def download_file(url, local_path, verbose):
+    """Download a file from url to local_path."""
+    if verbose:
+        print(f'Downloading:\n  Remote: {url}\n  Local: {local_path}')
+    with urllib.request.urlopen(url) as rsp, open(local_path, 'wb') as file:
+        shutil.copyfileobj(rsp, file)
+
+
+def get_cmake(download, verbose):
+    """Return the path to system CMake, or download and unpack a local copy."""
+    if not download:
+        cmake = shutil.which('cmake')
+        if cmake:
+            return cmake
+
+    plat = {
+        'darwin': 'macos-universal',
+        'linux': 'linux-x86_64',
+        'win32': 'win64-x86_64'}[
+        sys.platform]
+    cmake_prefix = f'cmake-3.22.2-{plat}'
+    cmake_local_dir = SCRIPT_PATH / 'external' / 'cmake'
+    cmake_file = f'{cmake_prefix}.tar.gz'
+    cmake_local_tgz = cmake_local_dir / cmake_file
+    cmake_local_exe = cmake_local_dir / cmake_prefix / \
+        ('CMake.app/Contents' if sys.platform == 'darwin' else '') / 'bin' / 'cmake'
+
+    if not cmake_local_exe.exists():
+        if not cmake_local_tgz.exists():
+            cmake_local_dir.mkdir(parents=True, exist_ok=True)
+            download_file(
+                CMAKE_URL.format(cmake_file),
+                cmake_local_tgz,
+                verbose)
+        with tarfile.open(cmake_local_tgz, 'r') as tar:
+            for member in tar.getmembers():
+                member_path = pathlib.Path(cmake_local_dir / member.name).resolve()
+                if not cmake_local_dir in member_path.parents:
+                    raise ValueError('Tar file contents move upwards past sandbox root')
+            tar.extractall(path=cmake_local_dir)
+
+    return cmake_local_exe
+
+
+def get_ninja(download, verbose):
+    """Return the path to system Ninja, or download and unpack a local copy."""
+    if not download:
+        ninja = shutil.which('ninja')
+        if ninja:
+            return ninja
+
+    ninja_local_dir = SCRIPT_PATH / 'external' / 'ninja'
+    plat = {'darwin': 'mac', 'linux': 'linux', 'win32': 'win'}[sys.platform]
+    ninja_file = f'ninja-{plat}.zip'
+    ninja_local_zip = ninja_local_dir / ninja_file
+    ninja_local_exe = ninja_local_dir / f'ninja{".exe" if plat == "win" else ""}'
+
+    if not ninja_local_exe.exists():
+        if not ninja_local_zip.exists():
+            ninja_local_dir.mkdir(parents=True, exist_ok=True)
+            download_file(
+                NINJA_URL.format(ninja_file),
+                ninja_local_zip,
+                verbose)
+        with zipfile.ZipFile(ninja_local_zip, 'r') as zip_file:
+            zip_file.extractall(ninja_local_dir)
+        os.chmod(ninja_local_exe, os.stat(
+            ninja_local_exe).st_mode | stat.S_IEXEC)
+
+    return ninja_local_exe
+
+
+def configure_cmake(cmake_exe, ninja, args):
+    """Prepare CMake for building nanoprintf tests under 'build/ninja/<cfg>'."""
+    build_path = SCRIPT_PATH / 'build' / 'ninja' / args.cfg
+    if (build_path / 'CMakeFiles').exists():
+        return True
+
+    sys.stdout.flush()
+    build_path.mkdir(parents=True)
+
+    cmake_args = [cmake_exe,
+                  SCRIPT_PATH,
+                  '-G',
+                  'Ninja',
+                  '-DCMAKE_EXPORT_COMPILE_COMMANDS=ON',
+                  f'-DCMAKE_MAKE_PROGRAM={ninja}',
+                  f'-DCMAKE_BUILD_TYPE={args.cfg}',
+                  f'-DNPF_PALAND={"ON" if args.paland else "OFF"}',
+                  f'-DNPF_32BIT={"ON" if args.arch == 32 else "OFF"}',
+                  f'-DNPF_CLANG_ASAN={"ON" if args.asan else "OFF"}',
+                  f'-DNPF_CLANG_UBSAN={"ON" if args.ubsan else "OFF"}']
+    try:
+        return subprocess.run(
+            cmake_args,
+            cwd=build_path,
+            check=True).returncode == 0
+    except subprocess.CalledProcessError as cpe:
+        return cpe.returncode == 0
+
+
+def build_cmake(cmake_exe, args):
+    """Run CMake in build mode to compile and run the nanoprintf test suite."""
+    sys.stdout.flush()
+    build_path = SCRIPT_PATH / 'build' / 'ninja' / args.cfg
+    cmake_args = [cmake_exe, '--build', build_path] + \
+        (['--', '-v'] if args.verbose else [])
+    try:
+        return subprocess.run(cmake_args, check=True).returncode == 0
+    except subprocess.CalledProcessError as cpe:
+        return cpe.returncode == 0
+
+
+def main():
+    """Parse args, find or get tools, configure CMake, build and run tests."""
+    args = parse_args()
+
+    cmake = get_cmake(args.download, args.verbose)
+    if args.verbose:
+        print(f'Found CMake at {cmake}')
+
+    ninja = get_ninja(args.download, args.verbose)
+    if args.verbose:
+        print(f'Found ninja at {ninja}')
+
+    built_ok = configure_cmake(cmake, ninja, args) and build_cmake(cmake, args)
+    return int(not built_ok)  # 0 is success
+
+
+if __name__ == '__main__':
+    sys.exit(main())
diff --git a/examples/use_npf_directly/main.cc b/examples/use_npf_directly/main.cc
new file mode 100644
index 0000000..ff58704
--- /dev/null
+++ b/examples/use_npf_directly/main.cc
@@ -0,0 +1,8 @@
+#include "../../nanoprintf.h"
+#include <cstdio>
+
+int main() {
+    char buf[32];
+    npf_snprintf(buf, sizeof(buf), "%s %s", "hello", "nanoprintf");
+    printf("%s\n", buf);
+}
diff --git a/examples/use_npf_directly/your_project_nanoprintf.cc b/examples/use_npf_directly/your_project_nanoprintf.cc
new file mode 100644
index 0000000..259b42e
--- /dev/null
+++ b/examples/use_npf_directly/your_project_nanoprintf.cc
@@ -0,0 +1,10 @@
+#define NANOPRINTF_USE_FIELD_WIDTH_FORMAT_SPECIFIERS 1
+#define NANOPRINTF_USE_PRECISION_FORMAT_SPECIFIERS 1
+#define NANOPRINTF_USE_LARGE_FORMAT_SPECIFIERS 1
+#define NANOPRINTF_USE_FLOAT_FORMAT_SPECIFIERS 1
+#define NANOPRINTF_USE_BINARY_FORMAT_SPECIFIERS 1
+#define NANOPRINTF_USE_WRITEBACK_FORMAT_SPECIFIERS 1
+
+// Compile nanoprintf in this translation unit.
+#define NANOPRINTF_IMPLEMENTATION
+#include "../../nanoprintf.h"
diff --git a/examples/wrap_npf/main.cc b/examples/wrap_npf/main.cc
new file mode 100644
index 0000000..7782a1f
--- /dev/null
+++ b/examples/wrap_npf/main.cc
@@ -0,0 +1,9 @@
+#include "your_project_printf.h"
+#include <cstdio>
+
+int main() {
+    char buf[32];
+    your_project_snprintf(buf, sizeof(buf), "%s %s", "hello", "nanoprintf");
+    printf("%s\n", buf);
+}
+
diff --git a/examples/wrap_npf/your_project_printf.cc b/examples/wrap_npf/your_project_printf.cc
new file mode 100644
index 0000000..2fbcfa3
--- /dev/null
+++ b/examples/wrap_npf/your_project_printf.cc
@@ -0,0 +1,20 @@
+#include "your_project_printf.h"
+
+#define NANOPRINTF_USE_FIELD_WIDTH_FORMAT_SPECIFIERS 1
+#define NANOPRINTF_USE_PRECISION_FORMAT_SPECIFIERS 1
+#define NANOPRINTF_USE_LARGE_FORMAT_SPECIFIERS 1
+#define NANOPRINTF_USE_FLOAT_FORMAT_SPECIFIERS 1
+#define NANOPRINTF_USE_BINARY_FORMAT_SPECIFIERS 1
+#define NANOPRINTF_USE_WRITEBACK_FORMAT_SPECIFIERS 0
+
+// Compile nanoprintf in this translation unit.
+#define NANOPRINTF_IMPLEMENTATION
+#include "../../nanoprintf.h"
+
+int your_project_snprintf(char *buffer, size_t bufsz, char const *fmt, ...) {
+    va_list val;
+    va_start(val, fmt);
+    int const rv = npf_vsnprintf(buffer, bufsz, fmt, val);
+    va_end(val);
+    return rv;
+}
diff --git a/examples/wrap_npf/your_project_printf.h b/examples/wrap_npf/your_project_printf.h
new file mode 100644
index 0000000..a6cbffc
--- /dev/null
+++ b/examples/wrap_npf/your_project_printf.h
@@ -0,0 +1,5 @@
+#pragma once
+
+#include <stddef.h>
+
+int your_project_snprintf(char *buffer, size_t bufsz, const char *fmt, ...);
diff --git a/nanoprintf.h b/nanoprintf.h
new file mode 100644
index 0000000..23e4f05
--- /dev/null
+++ b/nanoprintf.h
@@ -0,0 +1,1116 @@
+/* nanoprintf: a tiny embeddable printf replacement written in C.
+   https://github.com/charlesnicholson/nanoprintf
+   [email protected]
+   dual-licensed under 0bsd and unlicense, take your pick. see eof for details. */
+
+#ifndef NANOPRINTF_H_INCLUDED
+#define NANOPRINTF_H_INCLUDED
+
+#include <stdarg.h>
+#include <stddef.h>
+
+// Define this to fully sandbox nanoprintf inside of a translation unit.
+#ifdef NANOPRINTF_VISIBILITY_STATIC
+  #define NPF_VISIBILITY static
+#else
+  #define NPF_VISIBILITY extern
+#endif
+
+#if defined(__clang__) || defined(__GNUC__) || defined(__GNUG__)
+  #define NPF_PRINTF_ATTR(FORMAT_INDEX, VARGS_INDEX) \
+    __attribute__((format(printf, FORMAT_INDEX, VARGS_INDEX)))
+#else
+  #define NPF_PRINTF_ATTR(FORMAT_INDEX, VARGS_INDEX)
+#endif
+
+// Public API
+
+#ifdef __cplusplus
+extern "C" {
+#endif
+
+// The npf_ functions all return the number of bytes required to express the
+// fully-formatted string, not including the null terminator character.
+// The npf_ functions do not return negative values, since the lack of 'l' length
+// modifier support makes encoding errors impossible.
+
+NPF_VISIBILITY int npf_snprintf(
+  char *buffer, size_t bufsz, const char *format, ...) NPF_PRINTF_ATTR(3, 4);
+
+NPF_VISIBILITY int npf_vsnprintf(
+  char *buffer, size_t bufsz, char const *format, va_list vlist) NPF_PRINTF_ATTR(3, 0);
+
+typedef void (*npf_putc)(int c, void *ctx);
+NPF_VISIBILITY int npf_pprintf(
+  npf_putc pc, void *pc_ctx, char const *format, ...) NPF_PRINTF_ATTR(3, 4);
+
+NPF_VISIBILITY int npf_vpprintf(
+  npf_putc pc, void *pc_ctx, char const *format, va_list vlist) NPF_PRINTF_ATTR(3, 0);
+
+#ifdef __cplusplus
+}
+#endif
+
+#endif // NANOPRINTF_H_INCLUDED
+
+/* The implementation of nanoprintf begins here, to be compiled only if
+   NANOPRINTF_IMPLEMENTATION is defined. In a multi-file library what follows would
+   be nanoprintf.c. */
+
+#ifdef NANOPRINTF_IMPLEMENTATION
+
+#ifndef NANOPRINTF_IMPLEMENTATION_INCLUDED
+#define NANOPRINTF_IMPLEMENTATION_INCLUDED
+
+#include <inttypes.h>
+#include <stdint.h>
+
+// Pick reasonable defaults if nothing's been configured.
+#if !defined(NANOPRINTF_USE_FIELD_WIDTH_FORMAT_SPECIFIERS) && \
+    !defined(NANOPRINTF_USE_PRECISION_FORMAT_SPECIFIERS) && \
+    !defined(NANOPRINTF_USE_FLOAT_FORMAT_SPECIFIERS) && \
+    !defined(NANOPRINTF_USE_LARGE_FORMAT_SPECIFIERS) && \
+    !defined(NANOPRINTF_USE_BINARY_FORMAT_SPECIFIERS) && \
+    !defined(NANOPRINTF_USE_WRITEBACK_FORMAT_SPECIFIERS)
+  #define NANOPRINTF_USE_FIELD_WIDTH_FORMAT_SPECIFIERS 1
+  #define NANOPRINTF_USE_PRECISION_FORMAT_SPECIFIERS 1
+  #define NANOPRINTF_USE_FLOAT_FORMAT_SPECIFIERS 1
+  #define NANOPRINTF_USE_LARGE_FORMAT_SPECIFIERS 0
+  #define NANOPRINTF_USE_BINARY_FORMAT_SPECIFIERS 0
+  #define NANOPRINTF_USE_WRITEBACK_FORMAT_SPECIFIERS 0
+#endif
+
+// If anything's been configured, everything must be configured.
+#ifndef NANOPRINTF_USE_FIELD_WIDTH_FORMAT_SPECIFIERS
+  #error NANOPRINTF_USE_FIELD_WIDTH_FORMAT_SPECIFIERS must be #defined to 0 or 1
+#endif
+#ifndef NANOPRINTF_USE_PRECISION_FORMAT_SPECIFIERS
+  #error NANOPRINTF_USE_PRECISION_FORMAT_SPECIFIERS must be #defined to 0 or 1
+#endif
+#ifndef NANOPRINTF_USE_FLOAT_FORMAT_SPECIFIERS
+  #error NANOPRINTF_USE_FLOAT_FORMAT_SPECIFIERS must be #defined to 0 or 1
+#endif
+#ifndef NANOPRINTF_USE_LARGE_FORMAT_SPECIFIERS
+  #error NANOPRINTF_USE_LARGE_FORMAT_SPECIFIERS must be #defined to 0 or 1
+#endif
+#ifndef NANOPRINTF_USE_BINARY_FORMAT_SPECIFIERS
+  #error NANOPRINTF_USE_BINARY_FORMAT_SPECIFIERS must be #defined to 0 or 1
+#endif
+#ifndef NANOPRINTF_USE_WRITEBACK_FORMAT_SPECIFIERS
+  #error NANOPRINTF_USE_WRITEBACK_FORMAT_SPECIFIERS must be #defined to 0 or 1
+#endif
+
+// Ensure flags are compatible.
+#if (NANOPRINTF_USE_FLOAT_FORMAT_SPECIFIERS == 1) && \
+    (NANOPRINTF_USE_PRECISION_FORMAT_SPECIFIERS == 0)
+  #error Precision format specifiers must be enabled if float support is enabled.
+#endif
+
+// intmax_t / uintmax_t require stdint from c99 / c++11
+#if NANOPRINTF_USE_LARGE_FORMAT_SPECIFIERS == 1
+  #ifndef _MSC_VER
+    #ifdef __cplusplus
+      #if __cplusplus < 201103L
+        #error large format specifier support requires C++11 or later.
+      #endif
+    #else
+      #if __STDC_VERSION__ < 199409L
+        #error nanoprintf requires C99 or later.
+      #endif
+    #endif
+  #endif
+#endif
+
+// Figure out if we can disable warnings with pragmas.
+#ifdef __clang__
+  #define NANOPRINTF_CLANG 1
+  #define NANOPRINTF_GCC_PAST_4_6 0
+#else
+  #define NANOPRINTF_CLANG 0
+  #if defined(__GNUC__) && ((__GNUC__ > 4) || ((__GNUC__ == 4) && (__GNUC_MINOR__ > 6)))
+    #define NANOPRINTF_GCC_PAST_4_6 1
+  #else
+    #define NANOPRINTF_GCC_PAST_4_6 0
+  #endif
+#endif
+
+#if NANOPRINTF_CLANG || NANOPRINTF_GCC_PAST_4_6
+  #define NANOPRINTF_HAVE_GCC_WARNING_PRAGMAS 1
+#else
+  #define NANOPRINTF_HAVE_GCC_WARNING_PRAGMAS 0
+#endif
+
+#if NANOPRINTF_HAVE_GCC_WARNING_PRAGMAS
+  #pragma GCC diagnostic push
+  #pragma GCC diagnostic ignored "-Wunused-function"
+  #pragma GCC diagnostic ignored "-Wimplicit-fallthrough"
+  #ifdef __cplusplus
+    #pragma GCC diagnostic ignored "-Wold-style-cast"
+  #endif
+  #pragma GCC diagnostic ignored "-Wpadded"
+  #pragma GCC diagnostic ignored "-Wfloat-equal"
+  #if NANOPRINTF_CLANG
+    #pragma GCC diagnostic ignored "-Wc++98-compat-pedantic"
+    #pragma GCC diagnostic ignored "-Wcovered-switch-default"
+    #pragma GCC diagnostic ignored "-Wdeclaration-after-statement"
+    #ifndef __APPLE__
+      #pragma GCC diagnostic ignored "-Wunsafe-buffer-usage"
+    #endif
+  #elif NANOPRINTF_GCC_PAST_4_6
+    #pragma GCC diagnostic ignored "-Wmaybe-uninitialized"
+  #endif
+#endif
+
+#ifdef _MSC_VER
+  #pragma warning(push)
+  #pragma warning(disable:4514) // unreferenced inline function removed
+  #pragma warning(disable:4505) // unreferenced function removed
+  #pragma warning(disable:4701) // possibly uninitialized
+  #pragma warning(disable:4706) // assignment in conditional
+  #pragma warning(disable:4710) // not inlined
+  #pragma warning(disable:4711) // selected for inline
+  #pragma warning(disable:4820) // padding after data member
+  #pragma warning(disable:5039) // extern "C" throw
+  #pragma warning(disable:5045) // spectre mitigation
+  #pragma warning(disable:5262) // implicit switch fall-through
+#endif
+
+#if (NANOPRINTF_USE_FIELD_WIDTH_FORMAT_SPECIFIERS == 1) || \
+    (NANOPRINTF_USE_PRECISION_FORMAT_SPECIFIERS == 1)
+typedef enum {
+  NPF_FMT_SPEC_OPT_NONE,
+  NPF_FMT_SPEC_OPT_LITERAL,
+  NPF_FMT_SPEC_OPT_STAR,
+} npf_fmt_spec_opt_t;
+#endif
+
+typedef enum {
+  NPF_FMT_SPEC_LEN_MOD_NONE,
+  NPF_FMT_SPEC_LEN_MOD_SHORT,       // 'h'
+  NPF_FMT_SPEC_LEN_MOD_LONG_DOUBLE, // 'L'
+  NPF_FMT_SPEC_LEN_MOD_CHAR,        // 'hh'
+  NPF_FMT_SPEC_LEN_MOD_LONG,        // 'l'
+#if NANOPRINTF_USE_LARGE_FORMAT_SPECIFIERS == 1
+  NPF_FMT_SPEC_LEN_MOD_LARGE_LONG_LONG, // 'll'
+  NPF_FMT_SPEC_LEN_MOD_LARGE_INTMAX,    // 'j'
+  NPF_FMT_SPEC_LEN_MOD_LARGE_SIZET,     // 'z'
+  NPF_FMT_SPEC_LEN_MOD_LARGE_PTRDIFFT,  // 't'
+#endif
+} npf_format_spec_length_modifier_t;
+
+typedef enum {
+  NPF_FMT_SPEC_CONV_PERCENT,      // '%'
+  NPF_FMT_SPEC_CONV_CHAR,         // 'c'
+  NPF_FMT_SPEC_CONV_STRING,       // 's'
+  NPF_FMT_SPEC_CONV_SIGNED_INT,   // 'i', 'd'
+#if NANOPRINTF_USE_BINARY_FORMAT_SPECIFIERS == 1
+  NPF_FMT_SPEC_CONV_BINARY,       // 'b'
+#endif
+  NPF_FMT_SPEC_CONV_OCTAL,        // 'o'
+  NPF_FMT_SPEC_CONV_HEX_INT,      // 'x', 'X'
+  NPF_FMT_SPEC_CONV_UNSIGNED_INT, // 'u'
+  NPF_FMT_SPEC_CONV_POINTER,      // 'p'
+#if NANOPRINTF_USE_WRITEBACK_FORMAT_SPECIFIERS == 1
+  NPF_FMT_SPEC_CONV_WRITEBACK,    // 'n'
+#endif
+#if NANOPRINTF_USE_FLOAT_FORMAT_SPECIFIERS == 1
+  NPF_FMT_SPEC_CONV_FLOAT_DEC,      // 'f', 'F'
+  NPF_FMT_SPEC_CONV_FLOAT_SCI,      // 'e', 'E'
+  NPF_FMT_SPEC_CONV_FLOAT_SHORTEST, // 'g', 'G'
+  NPF_FMT_SPEC_CONV_FLOAT_HEX,      // 'a', 'A'
+#endif
+} npf_format_spec_conversion_t;
+
+typedef struct npf_format_spec {
+  char prepend;          // ' ' or '+'
+  char alt_form;         // '#'
+
+#if NANOPRINTF_USE_FIELD_WIDTH_FORMAT_SPECIFIERS == 1
+  npf_fmt_spec_opt_t field_width_opt;
+  int field_width;
+  char left_justified;   // '-'
+  char leading_zero_pad; // '0'
+#endif
+
+#if NANOPRINTF_USE_PRECISION_FORMAT_SPECIFIERS == 1
+  npf_fmt_spec_opt_t prec_opt;
+  int prec;
+#endif
+
+  npf_format_spec_length_modifier_t length_modifier;
+  npf_format_spec_conversion_t conv_spec;
+  char case_adjust;
+} npf_format_spec_t;
+
+#if NANOPRINTF_USE_LARGE_FORMAT_SPECIFIERS == 0
+  typedef long npf_int_t;
+  typedef unsigned long npf_uint_t;
+#else
+  typedef intmax_t npf_int_t;
+  typedef uintmax_t npf_uint_t;
+#endif
+
+typedef struct npf_bufputc_ctx {
+  char *dst;
+  size_t len;
+  size_t cur;
+} npf_bufputc_ctx_t;
+
+static int npf_parse_format_spec(char const *format, npf_format_spec_t *out_spec);
+static void npf_bufputc(int c, void *ctx);
+static void npf_bufputc_nop(int c, void *ctx);
+static int npf_itoa_rev(char *buf, npf_int_t i);
+static int npf_utoa_rev(char *buf, npf_uint_t i, unsigned base, unsigned case_adjust);
+
+#if NANOPRINTF_USE_FLOAT_FORMAT_SPECIFIERS == 1
+static int npf_fsplit_abs(float f,
+                          uint64_t *out_int_part,
+                          uint64_t *out_frac_part,
+                          int *out_frac_base10_neg_e);
+static int npf_ftoa_rev(char *buf, float f, npf_format_spec_t const *spec, int *out_frac_chars);
+#endif
+
+#if NANOPRINTF_USE_BINARY_FORMAT_SPECIFIERS == 1
+static int npf_bin_len(npf_uint_t i);
+#endif
+
+#if NANOPRINTF_USE_LARGE_FORMAT_SPECIFIERS == 1
+  #ifdef _MSC_VER
+    #include <BaseTsd.h>
+    typedef SSIZE_T ssize_t;
+  #else
+    #include <sys/types.h>
+  #endif
+#endif
+
+#ifdef _MSC_VER
+  #include <intrin.h>
+#endif
+
+static int npf_max(int x, int y) { return (x > y) ? x : y; }
+
+int npf_parse_format_spec(char const *format, npf_format_spec_t *out_spec) {
+  char const *cur = format;
+
+#if NANOPRINTF_USE_FIELD_WIDTH_FORMAT_SPECIFIERS == 1
+  out_spec->left_justified = 0;
+  out_spec->leading_zero_pad = 0;
+#endif
+  out_spec->case_adjust = 'a'-'A'; // lowercase
+  out_spec->prepend = 0;
+  out_spec->alt_form = 0;
+
+  while (*++cur) { // cur points at the leading '%' character
+    switch (*cur) { // Optional flags
+#if NANOPRINTF_USE_FIELD_WIDTH_FORMAT_SPECIFIERS == 1
+      case '-': out_spec->left_justified = '-'; out_spec->leading_zero_pad = 0; continue;
+      case '0': out_spec->leading_zero_pad = !out_spec->left_justified; continue;
+#endif
+      case '+': out_spec->prepend = '+'; continue;
+      case ' ': if (out_spec->prepend == 0) { out_spec->prepend = ' '; } continue;
+      case '#': out_spec->alt_form = '#'; continue;
+      default: break;
+    }
+    break;
+  }
+
+#if NANOPRINTF_USE_FIELD_WIDTH_FORMAT_SPECIFIERS == 1
+  out_spec->field_width_opt = NPF_FMT_SPEC_OPT_NONE;
+  if (*cur == '*') {
+    out_spec->field_width_opt = NPF_FMT_SPEC_OPT_STAR;
+    ++cur;
+  } else {
+    out_spec->field_width = 0;
+    while ((*cur >= '0') && (*cur <= '9')) {
+      out_spec->field_width_opt = NPF_FMT_SPEC_OPT_LITERAL;
+      out_spec->field_width = (out_spec->field_width * 10) + (*cur++ - '0');
+    }
+  }
+#endif
+
+#if NANOPRINTF_USE_PRECISION_FORMAT_SPECIFIERS == 1
+  out_spec->prec = 0;
+  out_spec->prec_opt = NPF_FMT_SPEC_OPT_NONE;
+  if (*cur == '.') {
+    ++cur;
+    if (*cur == '*') {
+      out_spec->prec_opt = NPF_FMT_SPEC_OPT_STAR;
+      ++cur;
+    } else {
+      if (*cur == '-') {
+        ++cur;
+        out_spec->prec_opt = NPF_FMT_SPEC_OPT_NONE;
+      } else {
+        out_spec->prec_opt = NPF_FMT_SPEC_OPT_LITERAL;
+      }
+      while ((*cur >= '0') && (*cur <= '9')) {
+        out_spec->prec = (out_spec->prec * 10) + (*cur++ - '0');
+      }
+    }
+  }
+#endif
+
+  int tmp_conv = -1;
+  out_spec->length_modifier = NPF_FMT_SPEC_LEN_MOD_NONE;
+  switch (*cur++) { // Length modifier
+    case 'h':
+      out_spec->length_modifier = NPF_FMT_SPEC_LEN_MOD_SHORT;
+      if (*cur == 'h') {
+        out_spec->length_modifier = NPF_FMT_SPEC_LEN_MOD_CHAR;
+        ++cur;
+      }
+      break;
+    case 'l':
+      out_spec->length_modifier = NPF_FMT_SPEC_LEN_MOD_LONG;
+#if NANOPRINTF_USE_LARGE_FORMAT_SPECIFIERS == 1
+      if (*cur == 'l') {
+        out_spec->length_modifier = NPF_FMT_SPEC_LEN_MOD_LARGE_LONG_LONG;
+        ++cur;
+      }
+#endif
+      break;
+#if NANOPRINTF_USE_FLOAT_FORMAT_SPECIFIERS == 1
+    case 'L': out_spec->length_modifier = NPF_FMT_SPEC_LEN_MOD_LONG_DOUBLE; break;
+#endif
+#if NANOPRINTF_USE_LARGE_FORMAT_SPECIFIERS == 1
+    case 'j': out_spec->length_modifier = NPF_FMT_SPEC_LEN_MOD_LARGE_INTMAX; break;
+    case 'z': out_spec->length_modifier = NPF_FMT_SPEC_LEN_MOD_LARGE_SIZET; break;
+    case 't': out_spec->length_modifier = NPF_FMT_SPEC_LEN_MOD_LARGE_PTRDIFFT; break;
+#endif
+    default: --cur; break;
+  }
+
+  switch (*cur++) { // Conversion specifier
+    case '%': out_spec->conv_spec = NPF_FMT_SPEC_CONV_PERCENT;
+#if NANOPRINTF_USE_PRECISION_FORMAT_SPECIFIERS == 1
+      out_spec->prec_opt = NPF_FMT_SPEC_OPT_NONE;
+#endif
+      break;
+
+    case 'c': out_spec->conv_spec = NPF_FMT_SPEC_CONV_CHAR;
+#if NANOPRINTF_USE_PRECISION_FORMAT_SPECIFIERS == 1
+      out_spec->prec_opt = NPF_FMT_SPEC_OPT_NONE;
+#endif
+      break;
+
+    case 's': out_spec->conv_spec = NPF_FMT_SPEC_CONV_STRING;
+#if NANOPRINTF_USE_FIELD_WIDTH_FORMAT_SPECIFIERS == 1
+      out_spec->leading_zero_pad = 0;
+#endif
+      break;
+
+    case 'i':
+    case 'd': tmp_conv = NPF_FMT_SPEC_CONV_SIGNED_INT;
+    case 'o': if (tmp_conv == -1) { tmp_conv = NPF_FMT_SPEC_CONV_OCTAL; }
+    case 'u': if (tmp_conv == -1) { tmp_conv = NPF_FMT_SPEC_CONV_UNSIGNED_INT; }
+    case 'X': if (tmp_conv == -1) { out_spec->case_adjust = 0; }
+    case 'x': if (tmp_conv == -1) { tmp_conv = NPF_FMT_SPEC_CONV_HEX_INT; }
+      out_spec->conv_spec = (npf_format_spec_conversion_t)tmp_conv;
+#if (NANOPRINTF_USE_FIELD_WIDTH_FORMAT_SPECIFIERS == 1) && \
+    (NANOPRINTF_USE_PRECISION_FORMAT_SPECIFIERS == 1)
+      if (out_spec->prec_opt != NPF_FMT_SPEC_OPT_NONE) { out_spec->leading_zero_pad = 0; }
+#endif
+      break;
+
+#if NANOPRINTF_USE_FLOAT_FORMAT_SPECIFIERS == 1
+    case 'F': out_spec->case_adjust = 0;
+    case 'f':
+      out_spec->conv_spec = NPF_FMT_SPEC_CONV_FLOAT_DEC;
+      if (out_spec->prec_opt == NPF_FMT_SPEC_OPT_NONE) { out_spec->prec = 6; }
+      break;
+
+    case 'E': out_spec->case_adjust = 0;
+    case 'e':
+      out_spec->conv_spec = NPF_FMT_SPEC_CONV_FLOAT_SCI;
+      if (out_spec->prec_opt == NPF_FMT_SPEC_OPT_NONE) { out_spec->prec = 6; }
+      break;
+
+    case 'G': out_spec->case_adjust = 0;
+    case 'g':
+      out_spec->conv_spec = NPF_FMT_SPEC_CONV_FLOAT_SHORTEST;
+      if (out_spec->prec_opt == NPF_FMT_SPEC_OPT_NONE) { out_spec->prec = 6; }
+      break;
+
+    case 'A': out_spec->case_adjust = 0;
+    case 'a':
+      out_spec->conv_spec = NPF_FMT_SPEC_CONV_FLOAT_HEX;
+      if (out_spec->prec_opt == NPF_FMT_SPEC_OPT_NONE) { out_spec->prec = 6; }
+      break;
+#endif
+
+#if NANOPRINTF_USE_WRITEBACK_FORMAT_SPECIFIERS == 1
+    case 'n':
+      // todo: reject string if flags or width or precision exist
+      out_spec->conv_spec = NPF_FMT_SPEC_CONV_WRITEBACK;
+#if NANOPRINTF_USE_PRECISION_FORMAT_SPECIFIERS == 1
+      out_spec->prec_opt = NPF_FMT_SPEC_OPT_NONE;
+#endif
+      break;
+#endif
+
+    case 'p':
+      out_spec->conv_spec = NPF_FMT_SPEC_CONV_POINTER;
+#if NANOPRINTF_USE_PRECISION_FORMAT_SPECIFIERS == 1
+      out_spec->prec_opt = NPF_FMT_SPEC_OPT_NONE;
+#endif
+      break;
+
+#if NANOPRINTF_USE_BINARY_FORMAT_SPECIFIERS == 1
+    case 'B':
+      out_spec->case_adjust = 0;
+    case 'b':
+      out_spec->conv_spec = NPF_FMT_SPEC_CONV_BINARY;
+      break;
+#endif
+
+    default: return 0;
+  }
+
+  return (int)(cur - format);
+}
+
+int npf_itoa_rev(char *buf, npf_int_t i) {
+  int n = 0;
+  int const sign = (i >= 0) ? 1 : -1;
+  do { *buf++ = (char)('0' + (sign * (i % 10))); i /= 10; ++n; } while (i);
+  return n;
+}
+
+int npf_utoa_rev(char *buf, npf_uint_t i, unsigned base, unsigned case_adj) {
+  int n = 0;
+  do {
+    unsigned const d = (unsigned)(i % base);
+    *buf++ = (char)((d < 10) ? ('0' + d) : ('A' + case_adj + (d - 10)));
+    i /= base;
+    ++n;
+  } while (i);
+  return n;
+}
+
+#if NANOPRINTF_USE_FLOAT_FORMAT_SPECIFIERS == 1
+enum {
+  NPF_MANTISSA_BITS = 23,
+  NPF_EXPONENT_BITS = 8,
+  NPF_EXPONENT_BIAS = 127,
+  NPF_FRACTION_BIN_DIGITS = 64,
+  NPF_MAX_FRACTION_DEC_DIGITS = 9
+};
+
+int npf_fsplit_abs(float f, uint64_t *out_int_part, uint64_t *out_frac_part,
+                   int *out_frac_base10_neg_exp) {
+  /* conversion algorithm by Wojciech MuÅ‚a (zdjÄ™[email protected])
+     http://0x80.pl/notesen/2015-12-29-float-to-string.html
+     grisu2 (https://bit.ly/2JgMggX) and ryu (https://bit.ly/2RLXSg0)
+     are fast + precise + round, but require large lookup tables. */
+
+  uint32_t f_bits; { // union-cast is UB, let compiler optimize byte-copy loop.
+    char const *src = (char const *)&f;
+    char *dst = (char *)&f_bits;
+    for (unsigned i = 0; i < sizeof(f_bits); ++i) { dst[i] = src[i]; }
+  }
+
+  int const exponent =
+    ((int)((f_bits >> NPF_MANTISSA_BITS) & ((1u << NPF_EXPONENT_BITS) - 1u)) -
+      NPF_EXPONENT_BIAS) - NPF_MANTISSA_BITS;
+
+  if (exponent >= (64 - NPF_MANTISSA_BITS)) { return 0; } // value is out of range
+
+  uint32_t const implicit_one = ((uint32_t)1) << NPF_MANTISSA_BITS;
+  uint32_t const mantissa = f_bits & (implicit_one - 1);
+  uint32_t const mantissa_norm = mantissa | implicit_one;
+
+  if (exponent > 0) {
+    *out_int_part = (uint64_t)mantissa_norm << exponent;
+  } else if (exponent < 0) {
+    if (-exponent > NPF_MANTISSA_BITS) {
+      *out_int_part = 0;
+    } else {
+      *out_int_part = mantissa_norm >> -exponent;
+    }
+  } else {
+    *out_int_part = mantissa_norm;
+  }
+
+  uint64_t frac; {
+    int const shift = NPF_FRACTION_BIN_DIGITS + exponent - 4;
+    if ((shift >= (NPF_FRACTION_BIN_DIGITS - 4)) || (shift < 0)) {
+      frac = 0;
+    } else {
+      frac = ((uint64_t)mantissa_norm) << shift;
+    }
+    // multiply off the leading one's digit
+    frac &= 0x0fffffffffffffffllu;
+    frac *= 10;
+  }
+
+  { // Count the number of 0s at the beginning of the fractional part.
+    int frac_base10_neg_exp = 0;
+    while (frac && ((frac >> (NPF_FRACTION_BIN_DIGITS - 4))) == 0) {
+      ++frac_base10_neg_exp;
+      frac &= 0x0fffffffffffffffllu;
+      frac *= 10;
+    }
+    *out_frac_base10_neg_exp = frac_base10_neg_exp;
+  }
+
+  { // Convert the fractional part to base 10.
+    uint64_t frac_part = 0;
+    for (int i = 0; frac && (i < NPF_MAX_FRACTION_DEC_DIGITS); ++i) {
+      frac_part *= 10;
+      frac_part += (uint64_t)(frac >> (NPF_FRACTION_BIN_DIGITS - 4));
+      frac &= 0x0fffffffffffffffllu;
+      frac *= 10;
+    }
+    *out_frac_part = frac_part;
+  }
+  return 1;
+}
+
+int npf_ftoa_rev(char *buf, float f, npf_format_spec_t const *spec, int *out_frac_chars) {
+  uint32_t f_bits; { // union-cast is UB, let compiler optimize byte-copy loop.
+    char const *src = (char const *)&f;
+    char *dst = (char *)&f_bits;
+    for (unsigned i = 0; i < sizeof(f_bits); ++i) { dst[i] = src[i]; }
+  }
+
+  if ((uint8_t)(f_bits >> 23) == 0xFF) {
+    if (f_bits & 0x7fffff) {
+      for (int i = 0; i < 3; ++i) { *buf++ = (char)("NAN"[i] + spec->case_adjust); }
+    } else {
+      for (int i = 0; i < 3; ++i) { *buf++ = (char)("FNI"[i] + spec->case_adjust); }
+    }
+    return -3;
+  }
+
+  uint64_t int_part, frac_part;
+  int frac_base10_neg_exp;
+  if (npf_fsplit_abs(f, &int_part, &frac_part, &frac_base10_neg_exp) == 0) {
+    for (int i = 0; i < 3; ++i) { *buf++ = (char)("ROO"[i] + spec->case_adjust); }
+    return -3;
+  }
+
+  char *dst = buf;
+
+  while (frac_part) { // write the fractional digits
+    *dst++ = (char)('0' + (frac_part % 10));
+    frac_part /= 10;
+  }
+
+  // write the 0 digits between the . and the first fractional digit
+  while (frac_base10_neg_exp-- > 0) { *dst++ = '0'; }
+  *out_frac_chars = (int)(dst - buf);
+
+  // round the value to the specified precision
+  if (spec->prec < *out_frac_chars) {
+    char *digit = dst - spec->prec - 1;
+    unsigned carry = (*digit >= '5');
+    while (carry && (++digit < dst)) {
+      carry = (*digit == '9');
+      *digit = carry ? '0' : (*digit + 1);
+    }
+    int_part += carry; // overflow is not possible
+  }
+
+  *dst++ = '.';
+
+  // write the integer digits
+  do { *dst++ = (char)('0' + (int_part % 10)); int_part /= 10; } while (int_part);
+  return (int)(dst - buf);
+}
+
+#endif // NANOPRINTF_USE_FLOAT_FORMAT_SPECIFIERS
+
+#if NANOPRINTF_USE_BINARY_FORMAT_SPECIFIERS == 1
+int npf_bin_len(npf_uint_t u) {
+  // Return the length of the binary string format of 'u', preferring intrinsics.
+  if (!u) { return 1; }
+
+#ifdef _MSC_VER // Win64, use _BSR64 for everything. If x86, use _BSR when non-large.
+  #ifdef _M_X64
+    #define NPF_HAVE_BUILTIN_CLZ
+    #define NPF_CLZ _BitScanReverse64
+  #elif NANOPRINTF_USE_LARGE_FORMAT_SPECIFIERS == 0
+    #define NPF_HAVE_BUILTIN_CLZ
+    #define NPF_CLZ _BitScanReverse
+  #endif
+  #ifdef NPF_HAVE_BUILTIN_CLZ
+    unsigned long idx;
+    NPF_CLZ(&idx, u);
+    return (int)(idx + 1);
+  #endif
+#elif defined(NANOPRINTF_CLANG) || defined(NANOPRINTF_GCC_PAST_4_6)
+  #define NPF_HAVE_BUILTIN_CLZ
+  #if NANOPRINTF_USE_LARGE_FORMAT_SPECIFIERS == 1
+    #define NPF_CLZ(X) ((sizeof(long long) * 8) - (size_t)__builtin_clzll(X))
+  #else
+    #define NPF_CLZ(X) ((sizeof(long) * 8) - (size_t)__builtin_clzl(X))
+  #endif
+  return (int)NPF_CLZ(u);
+#endif
+
+#ifndef NPF_HAVE_BUILTIN_CLZ
+  int n;
+  for (n = 0; u; ++n, u >>= 1); // slow but small software fallback
+  return n;
+#else
+  #undef NPF_HAVE_BUILTIN_CLZ
+  #undef NPF_CLZ
+#endif
+}
+#endif
+
+void npf_bufputc(int c, void *ctx) {
+  npf_bufputc_ctx_t *bpc = (npf_bufputc_ctx_t *)ctx;
+  if (bpc->cur < bpc->len) { bpc->dst[bpc->cur++] = (char)c; }
+}
+
+void npf_bufputc_nop(int c, void *ctx) { (void)c; (void)ctx; }
+
+typedef struct npf_cnt_putc_ctx {
+  npf_putc pc;
+  void *ctx;
+  int n;
+} npf_cnt_putc_ctx_t;
+
+static void npf_putc_cnt(int c, void *ctx) {
+  npf_cnt_putc_ctx_t *pc_cnt = (npf_cnt_putc_ctx_t *)ctx;
+  ++pc_cnt->n;
+  pc_cnt->pc(c, pc_cnt->ctx); // sibling-call optimization
+}
+
+#define NPF_PUTC(VAL) do { npf_putc_cnt((int)(VAL), &pc_cnt); } while (0)
+
+#define NPF_EXTRACT(MOD, CAST_TO, EXTRACT_AS) \
+  case NPF_FMT_SPEC_LEN_MOD_##MOD: val = (CAST_TO)va_arg(args, EXTRACT_AS); break
+
+#define NPF_WRITEBACK(MOD, TYPE) \
+  case NPF_FMT_SPEC_LEN_MOD_##MOD: *(va_arg(args, TYPE *)) = (TYPE)pc_cnt.n; break
+
+int npf_vpprintf(npf_putc pc, void *pc_ctx, char const *format, va_list args) {
+  npf_format_spec_t fs;
+  char const *cur = format;
+  npf_cnt_putc_ctx_t pc_cnt;
+  pc_cnt.pc = pc;
+  pc_cnt.ctx = pc_ctx;
+  pc_cnt.n = 0;
+
+  while (*cur) {
+    int const fs_len = (*cur != '%') ? 0 : npf_parse_format_spec(cur, &fs);
+    if (!fs_len) { NPF_PUTC(*cur++); continue; }
+    cur += fs_len;
+
+    // Extract star-args immediately
+#if NANOPRINTF_USE_FIELD_WIDTH_FORMAT_SPECIFIERS == 1
+    if (fs.field_width_opt == NPF_FMT_SPEC_OPT_STAR) {
+      fs.field_width_opt = NPF_FMT_SPEC_OPT_LITERAL;
+      fs.field_width = va_arg(args, int);
+      if (fs.field_width < 0) {
+        fs.field_width = -fs.field_width;
+        fs.left_justified = 1;
+      }
+    }
+#endif
+#if NANOPRINTF_USE_PRECISION_FORMAT_SPECIFIERS == 1
+    if (fs.prec_opt == NPF_FMT_SPEC_OPT_STAR) {
+      fs.prec_opt = NPF_FMT_SPEC_OPT_NONE;
+      fs.prec = va_arg(args, int);
+      if (fs.prec >= 0) { fs.prec_opt = NPF_FMT_SPEC_OPT_LITERAL; }
+    }
+#endif
+
+    union { char cbuf_mem[32]; npf_uint_t binval; } u;
+    char *cbuf = u.cbuf_mem, sign_c = 0;
+    int cbuf_len = 0, need_0x = 0;
+#if NANOPRINTF_USE_FIELD_WIDTH_FORMAT_SPECIFIERS == 1
+    int field_pad = 0;
+    char pad_c = 0;
+#endif
+#if NANOPRINTF_USE_PRECISION_FORMAT_SPECIFIERS == 1
+    int prec_pad = 0;
+#if NANOPRINTF_USE_FIELD_WIDTH_FORMAT_SPECIFIERS == 1
+    int zero = 0;
+#endif
+#endif
+#if NANOPRINTF_USE_FLOAT_FORMAT_SPECIFIERS == 1
+    int frac_chars = 0, inf_or_nan = 0;
+#endif
+
+    // Extract and convert the argument to string, point cbuf at the text.
+    switch (fs.conv_spec) {
+      case NPF_FMT_SPEC_CONV_PERCENT:
+        *cbuf = '%';
+        cbuf_len = 1;
+        break;
+
+      case NPF_FMT_SPEC_CONV_CHAR:
+        *cbuf = (char)va_arg(args, int);
+        cbuf_len = 1;
+        break;
+
+      case NPF_FMT_SPEC_CONV_STRING: {
+        cbuf = va_arg(args, char *);
+#if NANOPRINTF_USE_PRECISION_FORMAT_SPECIFIERS == 1
+        for (char const *s = cbuf;
+             ((fs.prec_opt == NPF_FMT_SPEC_OPT_NONE) || (cbuf_len < fs.prec)) && *s;
+             ++s, ++cbuf_len);
+#else
+        for (char const *s = cbuf; *s; ++s, ++cbuf_len); // strlen
+#endif
+      } break;
+
+      case NPF_FMT_SPEC_CONV_SIGNED_INT: {
+        npf_int_t val = 0;
+        switch (fs.length_modifier) {
+          NPF_EXTRACT(NONE, int, int);
+          NPF_EXTRACT(SHORT, short, int);
+          NPF_EXTRACT(LONG_DOUBLE, int, int);
+          NPF_EXTRACT(CHAR, char, int);
+          NPF_EXTRACT(LONG, long, long);
+#if NANOPRINTF_USE_LARGE_FORMAT_SPECIFIERS == 1
+          NPF_EXTRACT(LARGE_LONG_LONG, long long, long long);
+          NPF_EXTRACT(LARGE_INTMAX, intmax_t, intmax_t);
+          NPF_EXTRACT(LARGE_SIZET, ssize_t, ssize_t);
+          NPF_EXTRACT(LARGE_PTRDIFFT, ptrdiff_t, ptrdiff_t);
+#endif
+          default: break;
+        }
+
+        sign_c = (val < 0) ? '-' : fs.prepend;
+
+#if NANOPRINTF_USE_PRECISION_FORMAT_SPECIFIERS == 1
+#if NANOPRINTF_USE_FIELD_WIDTH_FORMAT_SPECIFIERS == 1
+        zero = !val;
+#endif
+        // special case, if prec and value are 0, skip
+        if (!val && (fs.prec_opt == NPF_FMT_SPEC_OPT_LITERAL) && !fs.prec) {
+          cbuf_len = 0;
+        } else
+#endif
+        { cbuf_len = npf_itoa_rev(cbuf, val); }
+      } break;
+
+#if NANOPRINTF_USE_BINARY_FORMAT_SPECIFIERS == 1
+      case NPF_FMT_SPEC_CONV_BINARY:
+#endif
+      case NPF_FMT_SPEC_CONV_OCTAL:
+      case NPF_FMT_SPEC_CONV_HEX_INT:
+      case NPF_FMT_SPEC_CONV_UNSIGNED_INT: {
+        npf_uint_t val = 0;
+
+        switch (fs.length_modifier) {
+          NPF_EXTRACT(NONE, unsigned, unsigned);
+          NPF_EXTRACT(SHORT, unsigned short, unsigned);
+          NPF_EXTRACT(LONG_DOUBLE, unsigned, unsigned);
+          NPF_EXTRACT(CHAR, unsigned char, unsigned);
+          NPF_EXTRACT(LONG, unsigned long, unsigned long);
+#if NANOPRINTF_USE_LARGE_FORMAT_SPECIFIERS == 1
+          NPF_EXTRACT(LARGE_LONG_LONG, unsigned long long, unsigned long long);
+          NPF_EXTRACT(LARGE_INTMAX, uintmax_t, uintmax_t);
+          NPF_EXTRACT(LARGE_SIZET, size_t, size_t);
+          NPF_EXTRACT(LARGE_PTRDIFFT, size_t, size_t);
+#endif
+          default: break;
+        }
+
+#if NANOPRINTF_USE_PRECISION_FORMAT_SPECIFIERS == 1
+#if NANOPRINTF_USE_FIELD_WIDTH_FORMAT_SPECIFIERS == 1
+        zero = !val;
+#endif
+        if (!val && (fs.prec_opt == NPF_FMT_SPEC_OPT_LITERAL) && !fs.prec) {
+          // Zero value and explicitly-requested zero precision means "print nothing".
+          if ((fs.conv_spec == NPF_FMT_SPEC_CONV_OCTAL) && fs.alt_form) {
+            fs.prec = 1; // octal special case, print a single '0'
+          }
+        } else
+#endif
+#if NANOPRINTF_USE_BINARY_FORMAT_SPECIFIERS == 1
+        if (fs.conv_spec == NPF_FMT_SPEC_CONV_BINARY) {
+          cbuf_len = npf_bin_len(val); u.binval = val;
+        } else
+#endif
+        {
+          unsigned const base = (fs.conv_spec == NPF_FMT_SPEC_CONV_OCTAL) ?
+            8u : ((fs.conv_spec == NPF_FMT_SPEC_CONV_HEX_INT) ? 16u : 10u);
+          cbuf_len = npf_utoa_rev(cbuf, val, base, (unsigned)fs.case_adjust);
+        }
+
+        if (val && fs.alt_form && (fs.conv_spec == NPF_FMT_SPEC_CONV_OCTAL)) {
+          cbuf[cbuf_len++] = '0'; // OK to add leading octal '0' immediately.
+        }
+
+        if (val && fs.alt_form) { // 0x or 0b but can't write it yet.
+          if (fs.conv_spec == NPF_FMT_SPEC_CONV_HEX_INT) { need_0x = 'X'; }
+#if NANOPRINTF_USE_BINARY_FORMAT_SPECIFIERS == 1
+          else if (fs.conv_spec == NPF_FMT_SPEC_CONV_BINARY) { need_0x = 'B'; }
+#endif
+          if (need_0x) { need_0x += fs.case_adjust; }
+        }
+      } break;
+
+      case NPF_FMT_SPEC_CONV_POINTER: {
+        cbuf_len =
+          npf_utoa_rev(cbuf, (npf_uint_t)(uintptr_t)va_arg(args, void *), 16, 'a'-'A');
+        need_0x = 'x';
+      } break;
+
+#if NANOPRINTF_USE_WRITEBACK_FORMAT_SPECIFIERS == 1
+      case NPF_FMT_SPEC_CONV_WRITEBACK:
+        switch (fs.length_modifier) {
+          NPF_WRITEBACK(NONE, int);
+          NPF_WRITEBACK(SHORT, short);
+          NPF_WRITEBACK(LONG, long);
+          NPF_WRITEBACK(LONG_DOUBLE, double);
+          NPF_WRITEBACK(CHAR, signed char);
+#if NANOPRINTF_USE_LARGE_FORMAT_SPECIFIERS == 1
+          NPF_WRITEBACK(LARGE_LONG_LONG, long long);
+          NPF_WRITEBACK(LARGE_INTMAX, intmax_t);
+          NPF_WRITEBACK(LARGE_SIZET, size_t);
+          NPF_WRITEBACK(LARGE_PTRDIFFT, ptrdiff_t);
+#endif
+          default: break;
+        } break;
+#endif
+
+#if NANOPRINTF_USE_FLOAT_FORMAT_SPECIFIERS == 1
+      case NPF_FMT_SPEC_CONV_FLOAT_DEC:
+      case NPF_FMT_SPEC_CONV_FLOAT_SCI:
+      case NPF_FMT_SPEC_CONV_FLOAT_SHORTEST:
+      case NPF_FMT_SPEC_CONV_FLOAT_HEX: {
+        float val;
+        if (fs.length_modifier == NPF_FMT_SPEC_LEN_MOD_LONG_DOUBLE) {
+          val = (float)va_arg(args, long double);
+        } else {
+          val = (float)va_arg(args, double);
+        }
+
+        sign_c = (val < 0.f) ? '-' : fs.prepend;
+#if NANOPRINTF_USE_FIELD_WIDTH_FORMAT_SPECIFIERS == 1
+        zero = (val == 0.f);
+#endif
+        cbuf_len = npf_ftoa_rev(cbuf, val, &fs, &frac_chars);
+
+        if (cbuf_len < 0) {
+          cbuf_len = -cbuf_len;
+          inf_or_nan = 1;
+        } else {
+          int const prec_adj = npf_max(0, frac_chars - fs.prec);
+          cbuf += prec_adj;
+          cbuf_len -= prec_adj;
+        }
+      } break;
+#endif
+      default: break;
+    }
+
+#if NANOPRINTF_USE_FIELD_WIDTH_FORMAT_SPECIFIERS == 1
+    // Compute the field width pad character
+    if (fs.field_width_opt == NPF_FMT_SPEC_OPT_LITERAL) {
+      if (fs.leading_zero_pad) { // '0' flag is only legal with numeric types
+        if ((fs.conv_spec != NPF_FMT_SPEC_CONV_STRING) &&
+            (fs.conv_spec != NPF_FMT_SPEC_CONV_CHAR) &&
+            (fs.conv_spec != NPF_FMT_SPEC_CONV_PERCENT)) {
+#if NANOPRINTF_USE_PRECISION_FORMAT_SPECIFIERS == 1
+          if ((fs.prec_opt == NPF_FMT_SPEC_OPT_LITERAL) && !fs.prec && zero) {
+            pad_c = ' ';
+          } else
+#endif
+          { pad_c = '0'; }
+        }
+      } else { pad_c = ' '; }
+    }
+#endif
+
+    // Compute the number of bytes to truncate or '0'-pad.
+    if (fs.conv_spec != NPF_FMT_SPEC_CONV_STRING) {
+#if NANOPRINTF_USE_FLOAT_FORMAT_SPECIFIERS == 1
+      if (!inf_or_nan) { // float precision is after the decimal point
+        int const prec_start =
+          (fs.conv_spec == NPF_FMT_SPEC_CONV_FLOAT_DEC) ? frac_chars : cbuf_len;
+        prec_pad = npf_max(0, fs.prec - prec_start);
+      }
+#elif NANOPRINTF_USE_PRECISION_FORMAT_SPECIFIERS == 1
+      prec_pad = npf_max(0, fs.prec - cbuf_len);
+#endif
+    }
+
+#if NANOPRINTF_USE_FIELD_WIDTH_FORMAT_SPECIFIERS == 1
+    // Given the full converted length, how many pad bytes?
+    field_pad = fs.field_width - cbuf_len - !!sign_c;
+    if (need_0x) { field_pad -= 2; }
+
+#if NANOPRINTF_USE_FLOAT_FORMAT_SPECIFIERS == 1
+    if ((fs.conv_spec == NPF_FMT_SPEC_CONV_FLOAT_DEC) && !fs.prec && !fs.alt_form) {
+      ++field_pad; // 0-pad, no decimal point.
+    }
+#endif
+#if NANOPRINTF_USE_PRECISION_FORMAT_SPECIFIERS == 1
+    field_pad -= prec_pad;
+#endif
+    field_pad = npf_max(0, field_pad);
+#endif // NANOPRINTF_USE_FIELD_WIDTH_FORMAT_SPECIFIERS
+
+#if NANOPRINTF_USE_FIELD_WIDTH_FORMAT_SPECIFIERS == 1
+    // Apply right-justified field width if requested
+    if (!fs.left_justified && pad_c) { // If leading zeros pad, sign goes first.
+      if (pad_c == '0') {
+        if (sign_c) { NPF_PUTC(sign_c); sign_c = 0; }
+        // Pad byte is '0', write '0x' before '0' pad chars.
+        if (need_0x) { NPF_PUTC('0'); NPF_PUTC(need_0x); }
+      }
+      while (field_pad-- > 0) { NPF_PUTC(pad_c); }
+      // Pad byte is ' ', write '0x' after ' ' pad chars but before number.
+      if ((pad_c != '0') && need_0x) { NPF_PUTC('0'); NPF_PUTC(need_0x); }
+    } else
+#endif
+    { if (need_0x) { NPF_PUTC('0'); NPF_PUTC(need_0x); } } // no pad, '0x' requested.
+
+    // Write the converted payload
+    if (fs.conv_spec == NPF_FMT_SPEC_CONV_STRING) {
+      for (int i = 0; i < cbuf_len; ++i) { NPF_PUTC(cbuf[i]); }
+    } else {
+      if (sign_c) { NPF_PUTC(sign_c); }
+#if NANOPRINTF_USE_FLOAT_FORMAT_SPECIFIERS == 1
+      if (fs.conv_spec != NPF_FMT_SPEC_CONV_FLOAT_DEC) {
+#endif
+
+#if NANOPRINTF_USE_PRECISION_FORMAT_SPECIFIERS == 1
+        while (prec_pad-- > 0) { NPF_PUTC('0'); } // int precision leads.
+#endif
+
+#if NANOPRINTF_USE_FLOAT_FORMAT_SPECIFIERS == 1
+      } else {
+        // if 0 precision, skip the fractional part and '.'
+        // if 0 prec + alternative form, keep the '.'
+        if (!fs.prec && !fs.alt_form) { ++cbuf; --cbuf_len; }
+      }
+#endif
+
+#if NANOPRINTF_USE_BINARY_FORMAT_SPECIFIERS == 1
+      if (fs.conv_spec == NPF_FMT_SPEC_CONV_BINARY) {
+        while (cbuf_len) { NPF_PUTC('0' + ((u.binval >> --cbuf_len) & 1)); }
+      } else
+#endif
+      { while (cbuf_len-- > 0) { NPF_PUTC(cbuf[cbuf_len]); } } // payload is reversed
+
+#if NANOPRINTF_USE_FLOAT_FORMAT_SPECIFIERS == 1
+      // real precision comes after the number.
+      if ((fs.conv_spec == NPF_FMT_SPEC_CONV_FLOAT_DEC) && !inf_or_nan) {
+        while (prec_pad-- > 0) { NPF_PUTC('0'); }
+      }
+#endif
+    }
+
+#if NANOPRINTF_USE_FIELD_WIDTH_FORMAT_SPECIFIERS == 1
+    if (fs.left_justified && pad_c) { // Apply left-justified field width
+      while (field_pad-- > 0) { NPF_PUTC(pad_c); }
+    }
+#endif
+  }
+
+  return pc_cnt.n;
+}
+
+#undef NPF_PUTC
+#undef NPF_EXTRACT
+#undef NPF_WRITEBACK
+
+int npf_pprintf(npf_putc pc, void *pc_ctx, char const *format, ...) {
+  va_list val;
+  va_start(val, format);
+  int const rv = npf_vpprintf(pc, pc_ctx, format, val);
+  va_end(val);
+  return rv;
+}
+
+int npf_snprintf(char *buffer, size_t bufsz, const char *format, ...) {
+  va_list val;
+  va_start(val, format);
+  int const rv = npf_vsnprintf(buffer, bufsz, format, val);
+  va_end(val);
+  return rv;
+}
+
+int npf_vsnprintf(char *buffer, size_t bufsz, char const *format, va_list vlist) {
+  npf_bufputc_ctx_t bufputc_ctx;
+  bufputc_ctx.dst = buffer;
+  bufputc_ctx.len = bufsz;
+  bufputc_ctx.cur = 0;
+
+  npf_putc const pc = buffer ? npf_bufputc : npf_bufputc_nop;
+  int const n = npf_vpprintf(pc, &bufputc_ctx, format, vlist);
+  pc('\0', &bufputc_ctx);
+
+  if (buffer && bufsz) {
+#ifdef NANOPRINTF_SNPRINTF_SAFE_EMPTY_STRING_ON_OVERFLOW
+    if (n >= (int)bufsz) { buffer[0] = '\0'; }
+#else
+    buffer[bufsz - 1] = '\0';
+#endif
+  }
+
+  return n;
+}
+
+#if NANOPRINTF_HAVE_GCC_WARNING_PRAGMAS
+  #pragma GCC diagnostic pop
+#endif
+
+#ifdef _MSC_VER
+  #pragma warning(pop)
+#endif
+
+#endif // NANOPRINTF_IMPLEMENTATION_INCLUDED
+#endif // NANOPRINTF_IMPLEMENTATION
+
+/*
+  nanoprintf is dual-licensed under both the "Unlicense" and the
+  "Zero-Clause BSD" (0BSD) licenses. The intent of this dual-licensing
+  structure is to make nanoprintf as consumable as possible in as many
+  environments / countries / companies as possible without any
+  encumberances.
+
+  The text of the two licenses follows below:
+
+  ============================== UNLICENSE ==============================
+
+  This is free and unencumbered software released into the public domain.
+
+  Anyone is free to copy, modify, publish, use, compile, sell, or
+  distribute this software, either in source code form or as a compiled
+  binary, for any purpose, commercial or non-commercial, and by any
+  means.
+
+  In jurisdictions that recognize copyright laws, the author or authors
+  of this software dedicate any and all copyright interest in the
+  software to the public domain. We make this dedication for the benefit
+  of the public at large and to the detriment of our heirs and
+  successors. We intend this dedication to be an overt act of
+  relinquishment in perpetuity of all present and future rights to this
+  software under copyright law.
+
+  THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND,
+  EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF
+  MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT.
+  IN NO EVENT SHALL THE AUTHORS BE LIABLE FOR ANY CLAIM, DAMAGES OR
+  OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE,
+  ARISING FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR
+  OTHER DEALINGS IN THE SOFTWARE.
+
+  For more information, please refer to <http://unlicense.org>
+
+  ================================ 0BSD =================================
+
+  Copyright (C) 2019- by Charles Nicholson <[email protected]>
+
+  Permission to use, copy, modify, and/or distribute this software for
+  any purpose with or without fee is hereby granted.
+
+  THE SOFTWARE IS PROVIDED "AS IS" AND THE AUTHOR DISCLAIMS ALL WARRANTIES
+  WITH REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF
+  MERCHANTABILITY AND FITNESS. IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR
+  ANY SPECIAL, DIRECT, INDIRECT, OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES
+  WHATSOEVER RESULTING FROM LOSS OF USE, DATA OR PROFITS, WHETHER IN AN
+  ACTION OF CONTRACT, NEGLIGENCE OR OTHER TORTIOUS ACTION, ARISING OUT OF
+  OR IN CONNECTION WITH THE USE OR PERFORMANCE OF THIS SOFTWARE.
+*/
+
diff --git a/tests/compilation_c.c b/tests/compilation_c.c
new file mode 100644
index 0000000..1b3427b
--- /dev/null
+++ b/tests/compilation_c.c
@@ -0,0 +1,6 @@
+#ifdef _MSC_VER
+  #pragma warning(disable:4464) // relative include uses ..
+#endif
+
+#define NANOPRINTF_IMPLEMENTATION
+#include "../nanoprintf.h"
diff --git a/tests/conformance.cc b/tests/conformance.cc
new file mode 100644
index 0000000..35e9188
--- /dev/null
+++ b/tests/conformance.cc
@@ -0,0 +1,501 @@
+// CMake drives the conformance test with a large flag matrix.
+// All of the nanoprintf configuration preprocessor symbols are injected.
+
+#ifdef _MSC_VER
+  #pragma warning(disable:4464) // relative include uses ..
+  #pragma warning(disable:4514) // unreferenced inline function removed
+  #pragma warning(disable:5039) // extern "c" throw
+  #pragma warning(disable:4710) // function not inlined
+  #pragma warning(disable:4711) // selected for inline
+  #pragma warning(disable:5264) // const variable not used (shut up doctest)
+#endif
+
+#define NANOPRINTF_IMPLEMENTATION
+#include "../nanoprintf.h"
+
+#include <string>
+#include <limits.h>
+#include <cmath>
+
+#if NANOPRINTF_HAVE_GCC_WARNING_PRAGMAS
+  #pragma GCC diagnostic push
+  #if NANOPRINTF_CLANG
+    #pragma GCC diagnostic ignored "-Wc++98-compat-pedantic"
+    #pragma GCC diagnostic ignored "-Wformat-pedantic"
+    #pragma GCC diagnostic ignored "-Wformat-nonliteral"
+    #pragma GCC diagnostic ignored "-Wold-style-cast"
+    #ifndef __APPLE__
+      #pragma GCC diagnostic ignored "-Wreserved-identifier"
+      #pragma GCC diagnostic ignored "-Wunsafe-buffer-usage"
+    #endif
+  #endif
+  #pragma GCC diagnostic ignored "-Wformat"
+#endif
+
+#include "doctest.h"
+
+namespace {
+void require_conform(const std::string& expected, char const *fmt, ...) {
+  char buf[256];
+
+  std::string sys_printf_result; {
+    va_list args;
+    va_start(args, fmt);
+    vsnprintf(buf, sizeof(buf), fmt, args);
+    va_end(args);
+    buf[sizeof(buf)-1] = '\0';
+    sys_printf_result = buf;
+  }
+
+  std::string npf_result; {
+    va_list args;
+    va_start(args, fmt);
+    npf_vsnprintf(buf, sizeof(buf), fmt, args);
+    va_end(args);
+    buf[sizeof(buf)-1] = '\0';
+    npf_result = buf;
+  }
+
+  REQUIRE(sys_printf_result == expected);
+  REQUIRE(npf_result == expected);
+}
+}
+
+TEST_CASE("conformance to system printf") {
+  SUBCASE("percent") {
+    require_conform("%", "%%");
+#if NANOPRINTF_USE_FIELD_WIDTH_FORMAT_SPECIFIERS == 1
+    require_conform("%", "%-%");
+#endif // NANOPRINTF_USE_FIELD_WIDTH_FORMAT_SPECIFIERS
+    require_conform("%", "% %");
+    require_conform("%", "%+%");
+    require_conform("%", "%#%");
+    // require_conform("         %", "%10%"); clang adds width, gcc doesn't
+    // require_conform("%         ", "%-10%"); clang adds -width, gcc doesn't
+    // require_conform("         %", "%10.10%"); clang adds width + precision.
+    // require_conform("%012%"); Undefined
+  }
+
+  SUBCASE("char") {
+    // every char
+    for (int i = CHAR_MIN; i < CHAR_MAX; ++i) {
+        char output[2] = {(char)i, 0};
+        require_conform(output, "%c", i);
+    }
+
+    require_conform("A", "%+c", 'A');
+    require_conform("", "%+c", 0);
+
+#if NANOPRINTF_USE_FIELD_WIDTH_FORMAT_SPECIFIERS == 1
+    // right justify field width
+    require_conform("A", "%1c", 'A');
+    require_conform(" A", "%2c", 'A');
+    require_conform("  A", "%3c", 'A');
+
+    // left justify field width
+    require_conform("A", "%-1c", 'A');
+    require_conform("A ", "%-2c", 'A');
+    require_conform("A  ", "%-3c", 'A');
+
+    require_conform("     A", "% 6c", 'A');
+    require_conform("   A", "%+4c", 'A');
+#endif // NANOPRINTF_USE_FIELD_WIDTH_FORMAT_SPECIFIERS
+  }
+
+  SUBCASE("string") {
+    require_conform("one", "%s", "one");
+    require_conform("onetwothree", "%s%s%s", "one", "two", "three");
+
+#if NANOPRINTF_USE_FIELD_WIDTH_FORMAT_SPECIFIERS == 1
+    require_conform("       two", "%10s", "two");
+    require_conform("B---       E", "B%-10sE", "---");
+    require_conform("B       ---E", "B%10sE", "---");
+#endif // NANOPRINTF_USE_FIELD_WIDTH_FORMAT_SPECIFIERS
+
+#if NANOPRINTF_USE_PRECISION_FORMAT_SPECIFIERS == 1
+    require_conform("thr", "%.3s", "three");
+    require_conform("four", "%.100s", "four");
+    // require_conform("abc", "%010s", "abc");  // undefined
+    require_conform("", "%.0s", "five");
+#endif // NANOPRINTF_USE_PRECISION_FORMAT_SPECIFIERS
+
+#if (NANOPRINTF_USE_FIELD_WIDTH_FORMAT_SPECIFIERS == 1) && \
+    (NANOPRINTF_USE_PRECISION_FORMAT_SPECIFIERS == 1)
+    require_conform("       six", "%10.3s", "sixAAAAAAAA");
+#endif
+  }
+
+  SUBCASE("unsigned int") {
+    require_conform("0", "%u", 0);
+    require_conform("4294967295", "%u", UINT_MAX);
+    require_conform("0", "%+u", 0);
+    require_conform("1", "%+u", 1);
+    require_conform("13", "%hu", (1 << 21u) + 13u);  // "short" mod clips
+#if ULONG_MAX > UINT_MAX
+    require_conform("4294967296", "%lu", (unsigned long)UINT_MAX + 1u);
+#endif
+    require_conform("0", "%hhu", 256u);
+
+#if NANOPRINTF_USE_FIELD_WIDTH_FORMAT_SPECIFIERS == 1
+    require_conform("   1", "%+4u", 1);  // undefined but usually skips +
+    require_conform("     0", "% 6u", 0);
+#endif // NANOPRINTF_USE_FIELD_WIDTH_FORMAT_SPECIFIERS
+
+#if NANOPRINTF_USE_PRECISION_FORMAT_SPECIFIERS == 1
+    require_conform("", "%.0u", 0);
+    require_conform("01", "%.2u", 1);
+#endif // NANOPRINTF_USE_PRECISION_FORMAT_SPECIFIERS
+
+#if (NANOPRINTF_USE_FIELD_WIDTH_FORMAT_SPECIFIERS == 1) && \
+    (NANOPRINTF_USE_PRECISION_FORMAT_SPECIFIERS == 1)
+    require_conform("    0123", "%8.4u", 123);
+#endif
+
+#if (NANOPRINTF_USE_LARGE_FORMAT_SPECIFIERS == 1)
+#if ULLONG_MAX == 18446744073709551615llu
+    require_conform("18446744073709551615", "%llu", ULLONG_MAX);
+#else
+    require_conform("4294967295", "%llu", ULLONG_MAX);
+#endif
+#if UINTMAX_MAX == 18446744073709551615llu
+    require_conform("18446744073709551615", "%ju", UINTMAX_MAX);
+#else
+    require_conform("4294967295", "%ju", UINTMAX_MAX);
+#endif
+#if SIZE_MAX == 18446744073709551615llu
+    require_conform("18446744073709551615", "%zu", SIZE_MAX);
+    require_conform("18446744073709551615", "%tu", SIZE_MAX);
+#else
+    require_conform("4294967295", "%zu", SIZE_MAX);
+    require_conform("4294967295", "%tu", SIZE_MAX);
+#endif
+#endif
+  }
+
+  SUBCASE("signed int") {
+    require_conform("-2147483648", "%i", INT_MIN);
+    require_conform("0", "%i", 0);
+    require_conform("2147483647", "%i", INT_MAX);
+    require_conform("-1", "%+i", -1);
+    require_conform("+0", "%+i", 0);
+    require_conform("+1", "%+i", 1);
+    // require_conform("%.-123i", 400); xcode libc doesn't ignore negative
+    require_conform("-128", "%hhi", 128);
+
+#if NANOPRINTF_USE_FIELD_WIDTH_FORMAT_SPECIFIERS == 1
+    require_conform("  -1", "% 4i", -1);
+    require_conform("   0", "% 4i", 0);
+    require_conform("   1", "% 4i", 1);
+    require_conform("  +1", "%+4i", 1);
+    require_conform("  +0", "%+4i", 0);
+    require_conform("  -1", "%+4i", -1);
+    require_conform("0001", "%04i", 1);
+    require_conform("0000", "%04i", 0);
+    require_conform("-001", "%04i", -1);
+    require_conform("+001", "%+04i", 1);
+#endif // NANOPRINTF_USE_FIELD_WIDTH_FORMAT_SPECIFIERS
+
+#if NANOPRINTF_USE_PRECISION_FORMAT_SPECIFIERS == 1
+    require_conform("", "%.0i", 0);
+    require_conform("+", "%+.0i", 0);
+    require_conform("+01", "%+.2i", 1);
+#endif // NANOPRINTF_USE_PRECISION_FORMAT_SPECIFIERS
+
+#if (NANOPRINTF_USE_FIELD_WIDTH_FORMAT_SPECIFIERS == 1) && \
+    (NANOPRINTF_USE_PRECISION_FORMAT_SPECIFIERS == 1)
+    require_conform(" +01", "%+4.2i", 1);
+    require_conform(" 0", "%02.1d", 0);
+#endif
+
+#if (NANOPRINTF_USE_LARGE_FORMAT_SPECIFIERS == 1)
+#if LLONG_MAX == 9223372036854775807ll
+    require_conform("9223372036854775807", "%lli", LLONG_MAX);
+#else
+    require_conform("2147483647", "%lli", LLONG_MAX);
+#endif
+
+#if INTMAX_MAX == 9223372036854775807ll
+    require_conform("9223372036854775807", "%ji", INTMAX_MAX);
+#else
+    require_conform("2147483647", "%ji", INTMAX_MAX);
+#endif
+
+#ifdef _MSC_VER
+#define SSIZE_MAX LONG_MAX
+#endif // _MSC_VER
+
+#if SSIZE_MAX == 2147483647
+    require_conform("2147483647", "%zi", SSIZE_MAX);
+#else
+    require_conform("9223372036854775807", "%zi", SSIZE_MAX);
+#endif
+
+#if PTRDIFF_MAX == 9223372036854775807ll
+    require_conform("9223372036854775807", "%ti", PTRDIFF_MAX);
+#else
+    require_conform("2147483647", "%ti", PTRDIFF_MAX);
+#endif
+#endif // NANOPRINTF_USE_LARGE_FORMAT_SPECIFIERS
+  }
+
+  SUBCASE("octal") {
+    require_conform("0", "%o", 0);
+    require_conform("0", "%#o", 0);
+    require_conform("37777777777", "%o", UINT_MAX);
+    require_conform("17", "%ho", (1 << 29u) + 15u);
+#if ULONG_MAX > UINT_MAX
+    require_conform("40000000000", "%lo", (unsigned long)UINT_MAX + 1u);
+#endif
+    require_conform("2", "%hho", 258u);
+
+#if NANOPRINTF_USE_FIELD_WIDTH_FORMAT_SPECIFIERS == 1
+    require_conform("      2322", "%10o", 1234);
+    require_conform("     02322", "%#10o", 1234);
+    require_conform("0001", "%04o", 1);
+    require_conform("0000", "%04o", 0);
+    require_conform("0", "%+o", 0);
+    require_conform("1", "%+o", 1);
+    require_conform("   1", "%+4o", 1);
+    require_conform("     1", "% 6o", 1);
+#endif // NANOPRINTF_USE_FIELD_WIDTH_FORMAT_SPECIFIERS
+
+#if NANOPRINTF_USE_PRECISION_FORMAT_SPECIFIERS == 1
+    require_conform("", "%.0o", 0);
+    require_conform("0", "%#.0o", 0);
+#endif // NANOPRINTF_USE_PRECISION_FORMAT_SPECIFIERS
+
+#if (NANOPRINTF_USE_LARGE_FORMAT_SPECIFIERS == 1)
+#if ULLONG_MAX == 01777777777777777777777llu
+    require_conform("1777777777777777777777", "%llo", ULLONG_MAX);
+#else
+    require_conform("37777777777", "%llo", ULLONG_MAX);
+#endif
+
+#if UINTMAX_MAX == 01777777777777777777777llu
+    require_conform("1777777777777777777777", "%jo", UINTMAX_MAX);
+#else
+    require_conform("37777777777", "%jo", UINTMAX_MAX);
+#endif
+
+#if SIZE_MAX == 01777777777777777777777llu
+    require_conform("1777777777777777777777", "%zo", SIZE_MAX);
+    require_conform("1777777777777777777777", "%to", SIZE_MAX);
+#else
+    require_conform("37777777777", "%zo", SIZE_MAX);
+    require_conform("37777777777", "%to", SIZE_MAX);
+#endif
+#endif // NANOPRINTF_USE_LARGE_FORMAT_SPECIFIERS
+  }
+
+  SUBCASE("hex") {
+    require_conform("0", "%x", 0);
+    require_conform("12345678", "%x", 0x12345678);
+    require_conform("ffffffff", "%x", UINT_MAX);
+    require_conform("0", "%X", 0);
+    require_conform("90ABCDEF", "%X", 0x90ABCDEF);
+    require_conform("FFFFFFFF", "%X", UINT_MAX);
+    require_conform("0", "%#x", 0);
+    require_conform("0", "%+x", 0);
+    require_conform("1", "%+x", 1);
+    require_conform("7b", "%hx", (1 << 26u) + 123u);
+#if ULONG_MAX > UINT_MAX
+    require_conform("100000000", "%lx", (unsigned long)UINT_MAX + 1u);
+#endif
+    require_conform("b", "%hhx", 256u + 0xb);
+
+#if NANOPRINTF_USE_FIELD_WIDTH_FORMAT_SPECIFIERS == 1
+    require_conform("      1234", "%10x", 0x1234);
+    require_conform("    0x1234", "%#10x", 0x1234);
+    require_conform("0001", "%04u", 1);
+    require_conform("0000", "%04u", 0);
+    require_conform("     0", "% 6x", 0);
+    require_conform("     1", "% 6x", 1);
+#endif // NANOPRINTF_USE_FIELD_WIDTH_FORMAT_SPECIFIERS
+
+#if NANOPRINTF_USE_PRECISION_FORMAT_SPECIFIERS == 1
+    require_conform("", "%.0x", 0);
+    require_conform("", "%.0X", 0);
+    require_conform("", "%#.0X", 0);
+#endif // NANOPRINTF_USE_PRECISION_FORMAT_SPECIFIERS
+
+#if (NANOPRINTF_USE_LARGE_FORMAT_SPECIFIERS == 1)
+#if ULLONG_MAX == 0xffffffffffffffffllu
+    require_conform("ffffffffffffffff", "%llx", ULLONG_MAX);
+#else
+    require_conform("ffffffff", "%llx", ULLONG_MAX);
+#endif
+
+#if UINTMAX_MAX == 0xffffffffffffffffllu
+    require_conform("ffffffffffffffff", "%jx", UINTMAX_MAX);
+#else
+    require_conform("ffffffff", "%jx", UINTMAX_MAX);
+#endif
+
+#if SIZE_MAX == 0xffffffffffffffffllu
+    require_conform("ffffffffffffffff", "%zx", SIZE_MAX);
+    require_conform("ffffffffffffffff", "%tx", SIZE_MAX);
+#else
+    require_conform("ffffffff", "%zx", SIZE_MAX);
+    require_conform("ffffffff", "%tx", SIZE_MAX);
+#endif
+#endif // NANOPRINTF_USE_LARGE_FORMAT_SPECIFIERS
+  }
+
+#if !defined(_MSC_VER)  // Visual Studio prints "00000ABCDEF" (upper, no 0x)
+  SUBCASE("pointer") {
+    // require_conform("%p", nullptr); implementation defined
+    int x, *p = &x;
+    char buf[32];
+    snprintf(buf, sizeof(buf), "%p", (void *)p);
+    require_conform(buf, "%p", p);
+    // require_conform("%030p", p); 0 flag + 'p' is undefined
+    // require_conform("%.30p", p); precision + 'p' is undefined
+
+#if NANOPRINTF_USE_FIELD_WIDTH_FORMAT_SPECIFIERS == 1
+    snprintf(buf, sizeof(buf), "%30p", (void *)p);
+    require_conform(buf, "%30p", p);
+#endif // NANOPRINTF_USE_FIELD_WIDTH_FORMAT_SPECIFIERS
+  }
+#endif // _MSC_VER
+
+#if NANOPRINTF_USE_WRITEBACK_FORMAT_SPECIFIERS == 1
+  SUBCASE("writeback int") {
+    int writeback = -1;
+    npf_pprintf(+[](int, void*) {}, nullptr, "%n", &writeback);
+    REQUIRE(writeback == 0);
+    npf_pprintf(+[](int, void*) {}, nullptr, " %n", &writeback);
+    REQUIRE(writeback == 1);
+    npf_pprintf(+[](int, void*) {}, nullptr, "  %n", &writeback);
+    REQUIRE(writeback == 2);
+    npf_pprintf(+[](int, void*) {}, nullptr, "%s%n", "abcd", &writeback);
+    REQUIRE(writeback == 4);
+    npf_pprintf(+[](int, void*) {}, nullptr, "%u%s%n", 0, "abcd", &writeback);
+    REQUIRE(writeback == 5);
+  }
+
+  SUBCASE("writeback short") {
+    short writeback = -1;
+    npf_pprintf(+[](int, void*) {}, nullptr, "1234%hn", &writeback);
+    REQUIRE(writeback == 4);
+  }
+
+  SUBCASE("writeback long") {
+    long writeback = -1;
+    npf_pprintf(+[](int, void*) {}, nullptr, "1234567%ln", &writeback);
+    REQUIRE(writeback == 7);
+  }
+
+  SUBCASE("writeback char") {
+    signed char writeback = -1;
+    npf_pprintf(+[](int, void*) {}, nullptr, "1234567%hhn", &writeback);
+    REQUIRE(writeback == 7);
+  }
+
+#if NANOPRINTF_USE_LARGE_FORMAT_SPECIFIERS == 1
+  SUBCASE("writeback long long") {
+    long long writeback = -1;
+    npf_pprintf(+[](int, void*) {}, nullptr, "12345678%lln", &writeback);
+    REQUIRE(writeback == 8);
+  }
+
+  SUBCASE("writeback intmax_t") {
+    intmax_t writeback = -1;
+    npf_pprintf(+[](int, void*) {}, nullptr, "12345678%jn", &writeback);
+    REQUIRE(writeback == 8);
+  }
+
+  SUBCASE("writeback size_t") {
+    intmax_t writeback = 100000;
+    npf_pprintf(+[](int, void*) {}, nullptr, "12345678%zn", &writeback);
+    REQUIRE(writeback == 8);
+  }
+
+  SUBCASE("writeback ptrdiff_t") {
+    ptrdiff_t writeback = -1;
+    npf_pprintf(+[](int, void*) {}, nullptr, "12345678%tn", &writeback);
+    REQUIRE(writeback == 8);
+  }
+#endif // NANOPRINTF_USE_LARGE_FORMAT_SPECIFIERS
+#endif // NANOPRINTF_USE_WRITEBACK_FORMAT_SPECIFIERS
+
+  SUBCASE("star args") {
+#if NANOPRINTF_USE_FIELD_WIDTH_FORMAT_SPECIFIERS == 1
+    require_conform("         Z", "%*c", 10, 'Z');
+    require_conform("5     ", "%*u", -6, 5);  // * fw < 0 => '-' and abs(fw)
+#endif // NANOPRINTF_USE_FIELD_WIDTH_FORMAT_SPECIFIERS
+
+#if NANOPRINTF_USE_PRECISION_FORMAT_SPECIFIERS == 1
+    require_conform("01", "%.*i", 2, 1);
+    require_conform("h", "%.*s", 1, "hello world");
+    require_conform("1", "%.*u", -123, 1);  // ignore negative * precision
+#endif // NANOPRINTF_USE_PRECISION_FORMAT_SPECIFIERS
+
+#if (NANOPRINTF_USE_FIELD_WIDTH_FORMAT_SPECIFIERS == 1) && \
+    (NANOPRINTF_USE_PRECISION_FORMAT_SPECIFIERS == 1)
+    require_conform("        07", "%*.*i", 10, 2, 7);
+#endif
+  }
+
+#if NANOPRINTF_USE_FLOAT_FORMAT_SPECIFIERS == 1
+  SUBCASE("float NaN") {
+    std::string const lowercase_nan = []{
+      char buf[32];
+      REQUIRE(npf_snprintf(buf, sizeof(buf), "%f", (double)NAN) == 3);
+      return std::string{buf};
+    }();
+
+    // doctest can't do || inside REQUIRE
+    if (lowercase_nan != "nan") {
+      REQUIRE(lowercase_nan == "-nan");
+    } else {
+      REQUIRE(lowercase_nan == "nan");
+    }
+
+    std::string const uppercase_nan = []{
+      char buf[32];
+      REQUIRE(npf_snprintf(buf, sizeof(buf), "%F", (double)NAN) == 3);
+      return std::string{buf};
+    }();
+
+    // doctest can't do || inside REQUIRE
+    if (uppercase_nan != "NAN") {
+      REQUIRE(uppercase_nan == "-NAN");
+    } else {
+      REQUIRE(uppercase_nan == "NAN");
+    }
+  }
+
+  SUBCASE("float") {
+    require_conform("inf", "%f", (double)INFINITY);
+#if NANOPRINTF_USE_FIELD_WIDTH_FORMAT_SPECIFIERS == 1
+    require_conform(" inf", "%4f", (double)INFINITY);
+#endif // NANOPRINTF_USE_FIELD_WIDTH_FORMAT_SPECIFIERS
+    require_conform("inf", "%.100f", (double)INFINITY);
+    require_conform("INF", "%F", (double)INFINITY);
+    require_conform("0.000000", "%f", 0.0);
+    require_conform("0.00", "%.2f", 0.0);
+    require_conform("1.0", "%.1f", 1.0);
+    require_conform("1", "%.0f", 1.0);
+    require_conform("1.", "%#.0f", 1.0);
+    require_conform("1.00000000000", "%.11f", 1.0);
+    require_conform("1.5", "%.1f", 1.5);
+    require_conform("+1.5", "%+.1f", 1.5);
+    require_conform("-1.5", "%.1f", -1.5);
+    require_conform(" 1.5", "% .1f", 1.5);
+#if NANOPRINTF_USE_FIELD_WIDTH_FORMAT_SPECIFIERS == 1
+    require_conform(" 1.0", "%4.1f", 1.0);
+    require_conform(" 1.500", "%6.3f", 1.5);
+    require_conform("0001.500", "%08.3f", 1.5);
+    require_conform("+001.500", "%+08.3f", 1.5);
+    require_conform("-001.500", "%+08.3f", -1.5);
+#endif // NANOPRINTF_USE_FIELD_WIDTH_FORMAT_SPECIFIERS
+    require_conform("1.50000000000000000", "%.17f", 1.5);
+    require_conform("0.003906", "%f", 0.00390625);
+    require_conform("0.0039", "%.4f", 0.00390625);
+    require_conform("0.00390625", "%.8f", 0.00390625);
+    require_conform("0.00390625", "%.8Lf", (long double)0.00390625);
+    require_conform("-0.00390625", "%.8f", -0.00390625);
+    require_conform("-0.00390625", "%.8Lf", (long double)-0.00390625);
+  }
+#endif // NANOPRINTF_USE_FLOAT_FORMAT_SPECIFIERS
+}
diff --git a/tests/doctest.h b/tests/doctest.h
new file mode 100644
index 0000000..8f44284
--- /dev/null
+++ b/tests/doctest.h
@@ -0,0 +1,7019 @@
+// ====================================================================== lgtm [cpp/missing-header-guard]

+// == DO NOT MODIFY THIS FILE BY HAND - IT IS AUTO GENERATED BY CMAKE! ==

+// ======================================================================

+//

+// doctest.h - the lightest feature-rich C++ single-header testing framework for unit tests and TDD

+//

+// Copyright (c) 2016-2021 Viktor Kirilov

+//

+// Distributed under the MIT Software License

+// See accompanying file LICENSE.txt or copy at

+// https://opensource.org/licenses/MIT

+//

+// The documentation can be found at the library's page:

+// https://github.com/doctest/doctest/blob/master/doc/markdown/readme.md

+//

+// =================================================================================================

+// =================================================================================================

+// =================================================================================================

+//

+// The library is heavily influenced by Catch - https://github.com/catchorg/Catch2

+// which uses the Boost Software License - Version 1.0

+// see here - https://github.com/catchorg/Catch2/blob/master/LICENSE.txt

+//

+// The concept of subcases (sections in Catch) and expression decomposition are from there.

+// Some parts of the code are taken directly:

+// - stringification - the detection of "ostream& operator<<(ostream&, const T&)" and StringMaker<>

+// - the Approx() helper class for floating point comparison

+// - colors in the console

+// - breaking into a debugger

+// - signal / SEH handling

+// - timer

+// - XmlWriter class - thanks to Phil Nash for allowing the direct reuse (AKA copy/paste)

+//

+// The expression decomposing templates are taken from lest - https://github.com/martinmoene/lest

+// which uses the Boost Software License - Version 1.0

+// see here - https://github.com/martinmoene/lest/blob/master/LICENSE.txt

+//

+// =================================================================================================

+// =================================================================================================

+// =================================================================================================

+

+#ifndef DOCTEST_LIBRARY_INCLUDED

+#define DOCTEST_LIBRARY_INCLUDED

+

+// =================================================================================================

+// == VERSION ======================================================================================

+// =================================================================================================

+

+#define DOCTEST_VERSION_MAJOR 2

+#define DOCTEST_VERSION_MINOR 4

+#define DOCTEST_VERSION_PATCH 9

+

+// util we need here

+#define DOCTEST_TOSTR_IMPL(x) #x

+#define DOCTEST_TOSTR(x) DOCTEST_TOSTR_IMPL(x)

+

+#define DOCTEST_VERSION_STR                                                                        \

+    DOCTEST_TOSTR(DOCTEST_VERSION_MAJOR) "."                                                       \

+    DOCTEST_TOSTR(DOCTEST_VERSION_MINOR) "."                                                       \

+    DOCTEST_TOSTR(DOCTEST_VERSION_PATCH)

+

+#define DOCTEST_VERSION                                                                            \

+    (DOCTEST_VERSION_MAJOR * 10000 + DOCTEST_VERSION_MINOR * 100 + DOCTEST_VERSION_PATCH)

+

+// =================================================================================================

+// == COMPILER VERSION =============================================================================

+// =================================================================================================

+

+// ideas for the version stuff are taken from here: https://github.com/cxxstuff/cxx_detect

+

+#ifdef _MSC_VER

+#define DOCTEST_CPLUSPLUS _MSVC_LANG

+#else

+#define DOCTEST_CPLUSPLUS __cplusplus

+#endif

+

+#define DOCTEST_COMPILER(MAJOR, MINOR, PATCH) ((MAJOR)*10000000 + (MINOR)*100000 + (PATCH))

+

+// GCC/Clang and GCC/MSVC are mutually exclusive, but Clang/MSVC are not because of clang-cl...

+#if defined(_MSC_VER) && defined(_MSC_FULL_VER)

+#if _MSC_VER == _MSC_FULL_VER / 10000

+#define DOCTEST_MSVC DOCTEST_COMPILER(_MSC_VER / 100, _MSC_VER % 100, _MSC_FULL_VER % 10000)

+#else // MSVC

+#define DOCTEST_MSVC                                                                               \

+    DOCTEST_COMPILER(_MSC_VER / 100, (_MSC_FULL_VER / 100000) % 100, _MSC_FULL_VER % 100000)

+#endif // MSVC

+#endif // MSVC

+#if defined(__clang__) && defined(__clang_minor__)

+#define DOCTEST_CLANG DOCTEST_COMPILER(__clang_major__, __clang_minor__, __clang_patchlevel__)

+#elif defined(__GNUC__) && defined(__GNUC_MINOR__) && defined(__GNUC_PATCHLEVEL__) &&              \

+        !defined(__INTEL_COMPILER)

+#define DOCTEST_GCC DOCTEST_COMPILER(__GNUC__, __GNUC_MINOR__, __GNUC_PATCHLEVEL__)

+#endif // GCC

+

+#ifndef DOCTEST_MSVC

+#define DOCTEST_MSVC 0

+#endif // DOCTEST_MSVC

+#ifndef DOCTEST_CLANG

+#define DOCTEST_CLANG 0

+#endif // DOCTEST_CLANG

+#ifndef DOCTEST_GCC

+#define DOCTEST_GCC 0

+#endif // DOCTEST_GCC

+

+// =================================================================================================

+// == COMPILER WARNINGS HELPERS ====================================================================

+// =================================================================================================

+

+#if DOCTEST_CLANG

+#define DOCTEST_PRAGMA_TO_STR(x) _Pragma(#x)

+#define DOCTEST_CLANG_SUPPRESS_WARNING_PUSH _Pragma("clang diagnostic push")

+#define DOCTEST_CLANG_SUPPRESS_WARNING(w) DOCTEST_PRAGMA_TO_STR(clang diagnostic ignored w)

+#define DOCTEST_CLANG_SUPPRESS_WARNING_POP _Pragma("clang diagnostic pop")

+#define DOCTEST_CLANG_SUPPRESS_WARNING_WITH_PUSH(w)                                                \

+    DOCTEST_CLANG_SUPPRESS_WARNING_PUSH DOCTEST_CLANG_SUPPRESS_WARNING(w)

+#else // DOCTEST_CLANG

+#define DOCTEST_CLANG_SUPPRESS_WARNING_PUSH

+#define DOCTEST_CLANG_SUPPRESS_WARNING(w)

+#define DOCTEST_CLANG_SUPPRESS_WARNING_POP

+#define DOCTEST_CLANG_SUPPRESS_WARNING_WITH_PUSH(w)

+#endif // DOCTEST_CLANG

+

+#if DOCTEST_GCC

+#define DOCTEST_PRAGMA_TO_STR(x) _Pragma(#x)

+#define DOCTEST_GCC_SUPPRESS_WARNING_PUSH _Pragma("GCC diagnostic push")

+#define DOCTEST_GCC_SUPPRESS_WARNING(w) DOCTEST_PRAGMA_TO_STR(GCC diagnostic ignored w)

+#define DOCTEST_GCC_SUPPRESS_WARNING_POP _Pragma("GCC diagnostic pop")

+#define DOCTEST_GCC_SUPPRESS_WARNING_WITH_PUSH(w)                                                  \

+    DOCTEST_GCC_SUPPRESS_WARNING_PUSH DOCTEST_GCC_SUPPRESS_WARNING(w)

+#else // DOCTEST_GCC

+#define DOCTEST_GCC_SUPPRESS_WARNING_PUSH

+#define DOCTEST_GCC_SUPPRESS_WARNING(w)

+#define DOCTEST_GCC_SUPPRESS_WARNING_POP

+#define DOCTEST_GCC_SUPPRESS_WARNING_WITH_PUSH(w)

+#endif // DOCTEST_GCC

+

+#if DOCTEST_MSVC

+#define DOCTEST_MSVC_SUPPRESS_WARNING_PUSH __pragma(warning(push))

+#define DOCTEST_MSVC_SUPPRESS_WARNING(w) __pragma(warning(disable : w))

+#define DOCTEST_MSVC_SUPPRESS_WARNING_POP __pragma(warning(pop))

+#define DOCTEST_MSVC_SUPPRESS_WARNING_WITH_PUSH(w)                                                 \

+    DOCTEST_MSVC_SUPPRESS_WARNING_PUSH DOCTEST_MSVC_SUPPRESS_WARNING(w)

+#else // DOCTEST_MSVC

+#define DOCTEST_MSVC_SUPPRESS_WARNING_PUSH

+#define DOCTEST_MSVC_SUPPRESS_WARNING(w)

+#define DOCTEST_MSVC_SUPPRESS_WARNING_POP

+#define DOCTEST_MSVC_SUPPRESS_WARNING_WITH_PUSH(w)

+#endif // DOCTEST_MSVC

+

+// =================================================================================================

+// == COMPILER WARNINGS ============================================================================

+// =================================================================================================

+

+// both the header and the implementation suppress all of these,

+// so it only makes sense to aggregrate them like so

+#define DOCTEST_SUPPRESS_COMMON_WARNINGS_PUSH                                                      \

+    DOCTEST_CLANG_SUPPRESS_WARNING_PUSH                                                            \

+    DOCTEST_CLANG_SUPPRESS_WARNING("-Wunknown-pragmas")                                            \

+    DOCTEST_CLANG_SUPPRESS_WARNING("-Wweak-vtables")                                               \

+    DOCTEST_CLANG_SUPPRESS_WARNING("-Wpadded")                                                     \

+    DOCTEST_CLANG_SUPPRESS_WARNING("-Wmissing-prototypes")                                         \

+    DOCTEST_CLANG_SUPPRESS_WARNING("-Wc++98-compat")                                               \

+    DOCTEST_CLANG_SUPPRESS_WARNING("-Wc++98-compat-pedantic")                                      \

+                                                                                                   \

+    DOCTEST_GCC_SUPPRESS_WARNING_PUSH                                                              \

+    DOCTEST_GCC_SUPPRESS_WARNING("-Wunknown-pragmas")                                              \

+    DOCTEST_GCC_SUPPRESS_WARNING("-Wpragmas")                                                      \

+    DOCTEST_GCC_SUPPRESS_WARNING("-Weffc++")                                                       \

+    DOCTEST_GCC_SUPPRESS_WARNING("-Wstrict-overflow")                                              \

+    DOCTEST_GCC_SUPPRESS_WARNING("-Wstrict-aliasing")                                              \

+    DOCTEST_GCC_SUPPRESS_WARNING("-Wmissing-declarations")                                         \

+    DOCTEST_GCC_SUPPRESS_WARNING("-Wuseless-cast")                                                 \

+    DOCTEST_GCC_SUPPRESS_WARNING("-Wnoexcept")                                                     \

+                                                                                                   \

+    DOCTEST_MSVC_SUPPRESS_WARNING_PUSH                                                             \

+    /* these 4 also disabled globally via cmake: */                                                \

+    DOCTEST_MSVC_SUPPRESS_WARNING(4514) /* unreferenced inline function has been removed */        \

+    DOCTEST_MSVC_SUPPRESS_WARNING(4571) /* SEH related */                                          \

+    DOCTEST_MSVC_SUPPRESS_WARNING(4710) /* function not inlined */                                 \

+    DOCTEST_MSVC_SUPPRESS_WARNING(4711) /* function selected for inline expansion*/                \

+    /* */                                                                                          \

+    DOCTEST_MSVC_SUPPRESS_WARNING(4616) /* invalid compiler warning */                             \

+    DOCTEST_MSVC_SUPPRESS_WARNING(4619) /* invalid compiler warning */                             \

+    DOCTEST_MSVC_SUPPRESS_WARNING(4996) /* The compiler encountered a deprecated declaration */    \

+    DOCTEST_MSVC_SUPPRESS_WARNING(4706) /* assignment within conditional expression */             \

+    DOCTEST_MSVC_SUPPRESS_WARNING(4512) /* 'class' : assignment operator could not be generated */ \

+    DOCTEST_MSVC_SUPPRESS_WARNING(4127) /* conditional expression is constant */                   \

+    DOCTEST_MSVC_SUPPRESS_WARNING(4820) /* padding */                                              \

+    DOCTEST_MSVC_SUPPRESS_WARNING(4625) /* copy constructor was implicitly deleted */              \

+    DOCTEST_MSVC_SUPPRESS_WARNING(4626) /* assignment operator was implicitly deleted */           \

+    DOCTEST_MSVC_SUPPRESS_WARNING(5027) /* move assignment operator implicitly deleted */          \

+    DOCTEST_MSVC_SUPPRESS_WARNING(5026) /* move constructor was implicitly deleted */              \

+    DOCTEST_MSVC_SUPPRESS_WARNING(4640) /* construction of local static object not thread-safe */  \

+    DOCTEST_MSVC_SUPPRESS_WARNING(5045) /* Spectre mitigation for memory load */                   \

+    /* static analysis */                                                                          \

+    DOCTEST_MSVC_SUPPRESS_WARNING(26439) /* Function may not throw. Declare it 'noexcept' */       \

+    DOCTEST_MSVC_SUPPRESS_WARNING(26495) /* Always initialize a member variable */                 \

+    DOCTEST_MSVC_SUPPRESS_WARNING(26451) /* Arithmetic overflow ... */                             \

+    DOCTEST_MSVC_SUPPRESS_WARNING(26444) /* Avoid unnamed objects with custom ctor and dtor... */  \

+    DOCTEST_MSVC_SUPPRESS_WARNING(26812) /* Prefer 'enum class' over 'enum' */

+

+#define DOCTEST_SUPPRESS_COMMON_WARNINGS_POP                                                       \

+    DOCTEST_CLANG_SUPPRESS_WARNING_POP                                                             \

+    DOCTEST_GCC_SUPPRESS_WARNING_POP                                                               \

+    DOCTEST_MSVC_SUPPRESS_WARNING_POP

+

+DOCTEST_SUPPRESS_COMMON_WARNINGS_PUSH

+

+DOCTEST_CLANG_SUPPRESS_WARNING_PUSH

+DOCTEST_CLANG_SUPPRESS_WARNING("-Wnon-virtual-dtor")

+DOCTEST_CLANG_SUPPRESS_WARNING("-Wdeprecated")

+

+DOCTEST_GCC_SUPPRESS_WARNING_PUSH

+DOCTEST_GCC_SUPPRESS_WARNING("-Wctor-dtor-privacy")

+DOCTEST_GCC_SUPPRESS_WARNING("-Wnon-virtual-dtor")

+DOCTEST_GCC_SUPPRESS_WARNING("-Wsign-promo")

+

+DOCTEST_MSVC_SUPPRESS_WARNING_PUSH

+DOCTEST_MSVC_SUPPRESS_WARNING(4623) // default constructor was implicitly defined as deleted

+

+#define DOCTEST_MAKE_STD_HEADERS_CLEAN_FROM_WARNINGS_ON_WALL_BEGIN                                 \

+    DOCTEST_MSVC_SUPPRESS_WARNING_PUSH                                                             \

+    DOCTEST_MSVC_SUPPRESS_WARNING(4548) /* before comma no effect; expected side - effect */       \

+    DOCTEST_MSVC_SUPPRESS_WARNING(4265) /* virtual functions, but destructor is not virtual */     \

+    DOCTEST_MSVC_SUPPRESS_WARNING(4986) /* exception specification does not match previous */      \

+    DOCTEST_MSVC_SUPPRESS_WARNING(4350) /* 'member1' called instead of 'member2' */                \

+    DOCTEST_MSVC_SUPPRESS_WARNING(4668) /* not defined as a preprocessor macro */                  \

+    DOCTEST_MSVC_SUPPRESS_WARNING(4365) /* signed/unsigned mismatch */                             \

+    DOCTEST_MSVC_SUPPRESS_WARNING(4774) /* format string not a string literal */                   \

+    DOCTEST_MSVC_SUPPRESS_WARNING(4820) /* padding */                                              \

+    DOCTEST_MSVC_SUPPRESS_WARNING(4625) /* copy constructor was implicitly deleted */              \

+    DOCTEST_MSVC_SUPPRESS_WARNING(4626) /* assignment operator was implicitly deleted */           \

+    DOCTEST_MSVC_SUPPRESS_WARNING(5027) /* move assignment operator implicitly deleted */          \

+    DOCTEST_MSVC_SUPPRESS_WARNING(5026) /* move constructor was implicitly deleted */              \

+    DOCTEST_MSVC_SUPPRESS_WARNING(4623) /* default constructor was implicitly deleted */           \

+    DOCTEST_MSVC_SUPPRESS_WARNING(5039) /* pointer to pot. throwing function passed to extern C */ \

+    DOCTEST_MSVC_SUPPRESS_WARNING(5045) /* Spectre mitigation for memory load */                   \

+    DOCTEST_MSVC_SUPPRESS_WARNING(5105) /* macro producing 'defined' has undefined behavior */     \

+    DOCTEST_MSVC_SUPPRESS_WARNING(4738) /* storing float result in memory, loss of performance */

+

+#define DOCTEST_MAKE_STD_HEADERS_CLEAN_FROM_WARNINGS_ON_WALL_END DOCTEST_MSVC_SUPPRESS_WARNING_POP

+

+// =================================================================================================

+// == FEATURE DETECTION ============================================================================

+// =================================================================================================

+

+// general compiler feature support table: https://en.cppreference.com/w/cpp/compiler_support

+// MSVC C++11 feature support table: https://msdn.microsoft.com/en-us/library/hh567368.aspx

+// GCC C++11 feature support table: https://gcc.gnu.org/projects/cxx-status.html

+// MSVC version table:

+// https://en.wikipedia.org/wiki/Microsoft_Visual_C%2B%2B#Internal_version_numbering

+// MSVC++ 14.3 (17) _MSC_VER == 1930 (Visual Studio 2022)

+// MSVC++ 14.2 (16) _MSC_VER == 1920 (Visual Studio 2019)

+// MSVC++ 14.1 (15) _MSC_VER == 1910 (Visual Studio 2017)

+// MSVC++ 14.0      _MSC_VER == 1900 (Visual Studio 2015)

+// MSVC++ 12.0      _MSC_VER == 1800 (Visual Studio 2013)

+// MSVC++ 11.0      _MSC_VER == 1700 (Visual Studio 2012)

+// MSVC++ 10.0      _MSC_VER == 1600 (Visual Studio 2010)

+// MSVC++ 9.0       _MSC_VER == 1500 (Visual Studio 2008)

+// MSVC++ 8.0       _MSC_VER == 1400 (Visual Studio 2005)

+

+// Universal Windows Platform support

+#if defined(WINAPI_FAMILY) && (WINAPI_FAMILY == WINAPI_FAMILY_APP)

+#define DOCTEST_CONFIG_NO_WINDOWS_SEH

+#endif // WINAPI_FAMILY

+#if DOCTEST_MSVC && !defined(DOCTEST_CONFIG_WINDOWS_SEH)

+#define DOCTEST_CONFIG_WINDOWS_SEH

+#endif // MSVC

+#if defined(DOCTEST_CONFIG_NO_WINDOWS_SEH) && defined(DOCTEST_CONFIG_WINDOWS_SEH)

+#undef DOCTEST_CONFIG_WINDOWS_SEH

+#endif // DOCTEST_CONFIG_NO_WINDOWS_SEH

+

+#if !defined(_WIN32) && !defined(__QNX__) && !defined(DOCTEST_CONFIG_POSIX_SIGNALS) &&             \

+        !defined(__EMSCRIPTEN__) && !defined(__wasi__)

+#define DOCTEST_CONFIG_POSIX_SIGNALS

+#endif // _WIN32

+#if defined(DOCTEST_CONFIG_NO_POSIX_SIGNALS) && defined(DOCTEST_CONFIG_POSIX_SIGNALS)

+#undef DOCTEST_CONFIG_POSIX_SIGNALS

+#endif // DOCTEST_CONFIG_NO_POSIX_SIGNALS

+

+#ifndef DOCTEST_CONFIG_NO_EXCEPTIONS

+#if !defined(__cpp_exceptions) && !defined(__EXCEPTIONS) && !defined(_CPPUNWIND)                   \

+        || defined(__wasi__)

+#define DOCTEST_CONFIG_NO_EXCEPTIONS

+#endif // no exceptions

+#endif // DOCTEST_CONFIG_NO_EXCEPTIONS

+

+#ifdef DOCTEST_CONFIG_NO_EXCEPTIONS_BUT_WITH_ALL_ASSERTS

+#ifndef DOCTEST_CONFIG_NO_EXCEPTIONS

+#define DOCTEST_CONFIG_NO_EXCEPTIONS

+#endif // DOCTEST_CONFIG_NO_EXCEPTIONS

+#endif // DOCTEST_CONFIG_NO_EXCEPTIONS_BUT_WITH_ALL_ASSERTS

+

+#if defined(DOCTEST_CONFIG_NO_EXCEPTIONS) && !defined(DOCTEST_CONFIG_NO_TRY_CATCH_IN_ASSERTS)

+#define DOCTEST_CONFIG_NO_TRY_CATCH_IN_ASSERTS

+#endif // DOCTEST_CONFIG_NO_EXCEPTIONS && !DOCTEST_CONFIG_NO_TRY_CATCH_IN_ASSERTS

+

+#ifdef __wasi__

+#define DOCTEST_CONFIG_NO_MULTITHREADING

+#endif

+

+#if defined(DOCTEST_CONFIG_IMPLEMENT_WITH_MAIN) && !defined(DOCTEST_CONFIG_IMPLEMENT)

+#define DOCTEST_CONFIG_IMPLEMENT

+#endif // DOCTEST_CONFIG_IMPLEMENT_WITH_MAIN

+

+#if defined(_WIN32) || defined(__CYGWIN__)

+#if DOCTEST_MSVC

+#define DOCTEST_SYMBOL_EXPORT __declspec(dllexport)

+#define DOCTEST_SYMBOL_IMPORT __declspec(dllimport)

+#else // MSVC

+#define DOCTEST_SYMBOL_EXPORT __attribute__((dllexport))

+#define DOCTEST_SYMBOL_IMPORT __attribute__((dllimport))

+#endif // MSVC

+#else  // _WIN32

+#define DOCTEST_SYMBOL_EXPORT __attribute__((visibility("default")))

+#define DOCTEST_SYMBOL_IMPORT

+#endif // _WIN32

+

+#ifdef DOCTEST_CONFIG_IMPLEMENTATION_IN_DLL

+#ifdef DOCTEST_CONFIG_IMPLEMENT

+#define DOCTEST_INTERFACE DOCTEST_SYMBOL_EXPORT

+#else // DOCTEST_CONFIG_IMPLEMENT

+#define DOCTEST_INTERFACE DOCTEST_SYMBOL_IMPORT

+#endif // DOCTEST_CONFIG_IMPLEMENT

+#else  // DOCTEST_CONFIG_IMPLEMENTATION_IN_DLL

+#define DOCTEST_INTERFACE

+#endif // DOCTEST_CONFIG_IMPLEMENTATION_IN_DLL

+

+// needed for extern template instantiations

+// see https://github.com/fmtlib/fmt/issues/2228

+#if DOCTEST_MSVC

+#define DOCTEST_INTERFACE_DECL

+#define DOCTEST_INTERFACE_DEF DOCTEST_INTERFACE

+#else // DOCTEST_MSVC

+#define DOCTEST_INTERFACE_DECL DOCTEST_INTERFACE

+#define DOCTEST_INTERFACE_DEF

+#endif // DOCTEST_MSVC

+

+#define DOCTEST_EMPTY

+

+#if DOCTEST_MSVC

+#define DOCTEST_NOINLINE __declspec(noinline)

+#define DOCTEST_UNUSED

+#define DOCTEST_ALIGNMENT(x)

+#elif DOCTEST_CLANG && DOCTEST_CLANG < DOCTEST_COMPILER(3, 5, 0)

+#define DOCTEST_NOINLINE

+#define DOCTEST_UNUSED

+#define DOCTEST_ALIGNMENT(x)

+#else

+#define DOCTEST_NOINLINE __attribute__((noinline))

+#define DOCTEST_UNUSED __attribute__((unused))

+#define DOCTEST_ALIGNMENT(x) __attribute__((aligned(x)))

+#endif

+

+#ifndef DOCTEST_NORETURN

+#if DOCTEST_MSVC && (DOCTEST_MSVC < DOCTEST_COMPILER(19, 0, 0))

+#define DOCTEST_NORETURN

+#else // DOCTEST_MSVC

+#define DOCTEST_NORETURN [[noreturn]]

+#endif // DOCTEST_MSVC

+#endif // DOCTEST_NORETURN

+

+#ifndef DOCTEST_NOEXCEPT

+#if DOCTEST_MSVC && (DOCTEST_MSVC < DOCTEST_COMPILER(19, 0, 0))

+#define DOCTEST_NOEXCEPT

+#else // DOCTEST_MSVC

+#define DOCTEST_NOEXCEPT noexcept

+#endif // DOCTEST_MSVC

+#endif // DOCTEST_NOEXCEPT

+

+#ifndef DOCTEST_CONSTEXPR

+#if DOCTEST_MSVC && (DOCTEST_MSVC < DOCTEST_COMPILER(19, 0, 0))

+#define DOCTEST_CONSTEXPR const

+#define DOCTEST_CONSTEXPR_FUNC inline

+#else // DOCTEST_MSVC

+#define DOCTEST_CONSTEXPR constexpr

+#define DOCTEST_CONSTEXPR_FUNC constexpr

+#endif // DOCTEST_MSVC

+#endif // DOCTEST_CONSTEXPR

+

+// =================================================================================================

+// == FEATURE DETECTION END ========================================================================

+// =================================================================================================

+

+#define DOCTEST_DECLARE_INTERFACE(name)                                                            \

+    virtual ~name();                                                                               \

+    name() = default;                                                                              \

+    name(const name&) = delete;                                                                    \

+    name(name&&) = delete;                                                                         \

+    name& operator=(const name&) = delete;                                                         \

+    name& operator=(name&&) = delete;

+

+#define DOCTEST_DEFINE_INTERFACE(name)                                                             \

+    name::~name() = default;

+

+// internal macros for string concatenation and anonymous variable name generation

+#define DOCTEST_CAT_IMPL(s1, s2) s1##s2

+#define DOCTEST_CAT(s1, s2) DOCTEST_CAT_IMPL(s1, s2)

+#ifdef __COUNTER__ // not standard and may be missing for some compilers

+#define DOCTEST_ANONYMOUS(x) DOCTEST_CAT(x, __COUNTER__)

+#else // __COUNTER__

+#define DOCTEST_ANONYMOUS(x) DOCTEST_CAT(x, __LINE__)

+#endif // __COUNTER__

+

+#ifndef DOCTEST_CONFIG_ASSERTION_PARAMETERS_BY_VALUE

+#define DOCTEST_REF_WRAP(x) x&

+#else // DOCTEST_CONFIG_ASSERTION_PARAMETERS_BY_VALUE

+#define DOCTEST_REF_WRAP(x) x

+#endif // DOCTEST_CONFIG_ASSERTION_PARAMETERS_BY_VALUE

+

+// not using __APPLE__ because... this is how Catch does it

+#ifdef __MAC_OS_X_VERSION_MIN_REQUIRED

+#define DOCTEST_PLATFORM_MAC

+#elif defined(__IPHONE_OS_VERSION_MIN_REQUIRED)

+#define DOCTEST_PLATFORM_IPHONE

+#elif defined(_WIN32)

+#define DOCTEST_PLATFORM_WINDOWS

+#elif defined(__wasi__)

+#define DOCTEST_PLATFORM_WASI

+#else // DOCTEST_PLATFORM

+#define DOCTEST_PLATFORM_LINUX

+#endif // DOCTEST_PLATFORM

+

+namespace doctest { namespace detail {

+    static DOCTEST_CONSTEXPR int consume(const int*, int) noexcept { return 0; }

+}}

+

+#define DOCTEST_GLOBAL_NO_WARNINGS(var, ...)                                                         \

+    DOCTEST_CLANG_SUPPRESS_WARNING_WITH_PUSH("-Wglobal-constructors")                                \

+    static const int var = doctest::detail::consume(&var, __VA_ARGS__);                              \

+    DOCTEST_CLANG_SUPPRESS_WARNING_POP

+

+#ifndef DOCTEST_BREAK_INTO_DEBUGGER

+// should probably take a look at https://github.com/scottt/debugbreak

+#ifdef DOCTEST_PLATFORM_LINUX

+#if defined(__GNUC__) && (defined(__i386) || defined(__x86_64))

+// Break at the location of the failing check if possible

+#define DOCTEST_BREAK_INTO_DEBUGGER() __asm__("int $3\n" : :) // NOLINT(hicpp-no-assembler)

+#else

+#include <signal.h>

+#define DOCTEST_BREAK_INTO_DEBUGGER() raise(SIGTRAP)

+#endif

+#elif defined(DOCTEST_PLATFORM_MAC)

+#if defined(__x86_64) || defined(__x86_64__) || defined(__amd64__) || defined(__i386)

+#define DOCTEST_BREAK_INTO_DEBUGGER() __asm__("int $3\n" : :) // NOLINT(hicpp-no-assembler)

+#elif defined(__ppc__) || defined(__ppc64__)

+// https://www.cocoawithlove.com/2008/03/break-into-debugger.html

+#define DOCTEST_BREAK_INTO_DEBUGGER() __asm__("li r0, 20\nsc\nnop\nli r0, 37\nli r4, 2\nsc\nnop\n": : : "memory","r0","r3","r4") // NOLINT(hicpp-no-assembler)

+#else

+#define DOCTEST_BREAK_INTO_DEBUGGER() __asm__("brk #0"); // NOLINT(hicpp-no-assembler)

+#endif

+#elif DOCTEST_MSVC

+#define DOCTEST_BREAK_INTO_DEBUGGER() __debugbreak()

+#elif defined(__MINGW32__)

+DOCTEST_GCC_SUPPRESS_WARNING_WITH_PUSH("-Wredundant-decls")

+extern "C" __declspec(dllimport) void __stdcall DebugBreak();

+DOCTEST_GCC_SUPPRESS_WARNING_POP

+#define DOCTEST_BREAK_INTO_DEBUGGER() ::DebugBreak()

+#else // linux

+#define DOCTEST_BREAK_INTO_DEBUGGER() (static_cast<void>(0))

+#endif // linux

+#endif // DOCTEST_BREAK_INTO_DEBUGGER

+

+// this is kept here for backwards compatibility since the config option was changed

+#ifdef DOCTEST_CONFIG_USE_IOSFWD

+#ifndef DOCTEST_CONFIG_USE_STD_HEADERS

+#define DOCTEST_CONFIG_USE_STD_HEADERS

+#endif

+#endif // DOCTEST_CONFIG_USE_IOSFWD

+

+// for clang - always include ciso646 (which drags some std stuff) because

+// we want to check if we are using libc++ with the _LIBCPP_VERSION macro in

+// which case we don't want to forward declare stuff from std - for reference:

+// https://github.com/doctest/doctest/issues/126

+// https://github.com/doctest/doctest/issues/356

+#if DOCTEST_CLANG

+#include <ciso646>

+#ifdef _LIBCPP_VERSION

+#ifndef DOCTEST_CONFIG_USE_STD_HEADERS

+#define DOCTEST_CONFIG_USE_STD_HEADERS

+#endif

+#endif // _LIBCPP_VERSION

+#endif // clang

+

+#ifdef DOCTEST_CONFIG_USE_STD_HEADERS

+#ifndef DOCTEST_CONFIG_INCLUDE_TYPE_TRAITS

+#define DOCTEST_CONFIG_INCLUDE_TYPE_TRAITS

+#endif // DOCTEST_CONFIG_INCLUDE_TYPE_TRAITS

+DOCTEST_MAKE_STD_HEADERS_CLEAN_FROM_WARNINGS_ON_WALL_BEGIN

+#include <cstddef>

+#include <ostream>

+#include <istream>

+DOCTEST_MAKE_STD_HEADERS_CLEAN_FROM_WARNINGS_ON_WALL_END

+#else // DOCTEST_CONFIG_USE_STD_HEADERS

+

+// Forward declaring 'X' in namespace std is not permitted by the C++ Standard.

+DOCTEST_MSVC_SUPPRESS_WARNING_WITH_PUSH(4643)

+

+namespace std { // NOLINT(cert-dcl58-cpp)

+typedef decltype(nullptr) nullptr_t; // NOLINT(modernize-use-using)

+typedef decltype(sizeof(void*)) size_t; // NOLINT(modernize-use-using)

+template <class charT>

+struct char_traits;

+template <>

+struct char_traits<char>;

+template <class charT, class traits>

+class basic_ostream; // NOLINT(fuchsia-virtual-inheritance)

+typedef basic_ostream<char, char_traits<char>> ostream; // NOLINT(modernize-use-using)

+template<class traits>

+// NOLINTNEXTLINE

+basic_ostream<char, traits>& operator<<(basic_ostream<char, traits>&, const char*);

+template <class charT, class traits>

+class basic_istream;

+typedef basic_istream<char, char_traits<char>> istream; // NOLINT(modernize-use-using)

+template <class... Types>

+class tuple;

+#if DOCTEST_MSVC >= DOCTEST_COMPILER(19, 20, 0)

+// see this issue on why this is needed: https://github.com/doctest/doctest/issues/183

+template <class Ty>

+class allocator;

+template <class Elem, class Traits, class Alloc>

+class basic_string;

+using string = basic_string<char, char_traits<char>, allocator<char>>;

+#endif // VS 2019

+} // namespace std

+

+DOCTEST_MSVC_SUPPRESS_WARNING_POP

+

+#endif // DOCTEST_CONFIG_USE_STD_HEADERS

+

+#ifdef DOCTEST_CONFIG_INCLUDE_TYPE_TRAITS

+#include <type_traits>

+#endif // DOCTEST_CONFIG_INCLUDE_TYPE_TRAITS

+

+namespace doctest {

+

+using std::size_t;

+

+DOCTEST_INTERFACE extern bool is_running_in_test;

+

+#ifndef DOCTEST_CONFIG_STRING_SIZE_TYPE

+#define DOCTEST_CONFIG_STRING_SIZE_TYPE unsigned

+#endif

+

+// A 24 byte string class (can be as small as 17 for x64 and 13 for x86) that can hold strings with length

+// of up to 23 chars on the stack before going on the heap - the last byte of the buffer is used for:

+// - "is small" bit - the highest bit - if "0" then it is small - otherwise its "1" (128)

+// - if small - capacity left before going on the heap - using the lowest 5 bits

+// - if small - 2 bits are left unused - the second and third highest ones

+// - if small - acts as a null terminator if strlen() is 23 (24 including the null terminator)

+//              and the "is small" bit remains "0" ("as well as the capacity left") so its OK

+// Idea taken from this lecture about the string implementation of facebook/folly - fbstring

+// https://www.youtube.com/watch?v=kPR8h4-qZdk

+// TODO:

+// - optimizations - like not deleting memory unnecessarily in operator= and etc.

+// - resize/reserve/clear

+// - replace

+// - back/front

+// - iterator stuff

+// - find & friends

+// - push_back/pop_back

+// - assign/insert/erase

+// - relational operators as free functions - taking const char* as one of the params

+class DOCTEST_INTERFACE String

+{

+public:

+    using size_type = DOCTEST_CONFIG_STRING_SIZE_TYPE;

+

+private:

+    static DOCTEST_CONSTEXPR size_type len  = 24;      //!OCLINT avoid private static members

+    static DOCTEST_CONSTEXPR size_type last = len - 1; //!OCLINT avoid private static members

+

+    struct view // len should be more than sizeof(view) - because of the final byte for flags

+    {

+        char*    ptr;

+        size_type size;

+        size_type capacity;

+    };

+

+    union

+    {

+        char buf[len]; // NOLINT(*-avoid-c-arrays)

+        view data;

+    };

+

+    char* allocate(size_type sz);

+

+    bool isOnStack() const noexcept { return (buf[last] & 128) == 0; }

+    void setOnHeap() noexcept;

+    void setLast(size_type in = last) noexcept;

+    void setSize(size_type sz) noexcept;

+

+    void copy(const String& other);

+

+public:

+    static DOCTEST_CONSTEXPR size_type npos = static_cast<size_type>(-1);

+

+    String() noexcept;

+    ~String();

+

+    // cppcheck-suppress noExplicitConstructor

+    String(const char* in);

+    String(const char* in, size_type in_size);

+

+    String(std::istream& in, size_type in_size);

+

+    String(const String& other);

+    String& operator=(const String& other);

+

+    String& operator+=(const String& other);

+

+    String(String&& other) noexcept;

+    String& operator=(String&& other) noexcept;

+

+    char  operator[](size_type i) const;

+    char& operator[](size_type i);

+

+    // the only functions I'm willing to leave in the interface - available for inlining

+    const char* c_str() const { return const_cast<String*>(this)->c_str(); } // NOLINT

+    char*       c_str() {

+        if (isOnStack()) {

+            return reinterpret_cast<char*>(buf);

+        }

+        return data.ptr;

+    }

+

+    size_type size() const;

+    size_type capacity() const;

+

+    String substr(size_type pos, size_type cnt = npos) &&;

+    String substr(size_type pos, size_type cnt = npos) const &;

+

+    size_type find(char ch, size_type pos = 0) const;

+    size_type rfind(char ch, size_type pos = npos) const;

+

+    int compare(const char* other, bool no_case = false) const;

+    int compare(const String& other, bool no_case = false) const;

+

+friend DOCTEST_INTERFACE std::ostream& operator<<(std::ostream& s, const String& in);

+};

+

+DOCTEST_INTERFACE String operator+(const String& lhs, const String& rhs);

+

+DOCTEST_INTERFACE bool operator==(const String& lhs, const String& rhs);

+DOCTEST_INTERFACE bool operator!=(const String& lhs, const String& rhs);

+DOCTEST_INTERFACE bool operator<(const String& lhs, const String& rhs);

+DOCTEST_INTERFACE bool operator>(const String& lhs, const String& rhs);

+DOCTEST_INTERFACE bool operator<=(const String& lhs, const String& rhs);

+DOCTEST_INTERFACE bool operator>=(const String& lhs, const String& rhs);

+

+class DOCTEST_INTERFACE Contains {

+public:

+    explicit Contains(const String& string);

+

+    bool checkWith(const String& other) const;

+

+    String string;

+};

+

+DOCTEST_INTERFACE String toString(const Contains& in);

+

+DOCTEST_INTERFACE bool operator==(const String& lhs, const Contains& rhs);

+DOCTEST_INTERFACE bool operator==(const Contains& lhs, const String& rhs);

+DOCTEST_INTERFACE bool operator!=(const String& lhs, const Contains& rhs);

+DOCTEST_INTERFACE bool operator!=(const Contains& lhs, const String& rhs);

+

+namespace Color {

+    enum Enum

+    {

+        None = 0,

+        White,

+        Red,

+        Green,

+        Blue,

+        Cyan,

+        Yellow,

+        Grey,

+

+        Bright = 0x10,

+

+        BrightRed   = Bright | Red,

+        BrightGreen = Bright | Green,

+        LightGrey   = Bright | Grey,

+        BrightWhite = Bright | White

+    };

+

+    DOCTEST_INTERFACE std::ostream& operator<<(std::ostream& s, Color::Enum code);

+} // namespace Color

+

+namespace assertType {

+    enum Enum

+    {

+        // macro traits

+

+        is_warn    = 1,

+        is_check   = 2 * is_warn,

+        is_require = 2 * is_check,

+

+        is_normal      = 2 * is_require,

+        is_throws      = 2 * is_normal,

+        is_throws_as   = 2 * is_throws,

+        is_throws_with = 2 * is_throws_as,

+        is_nothrow     = 2 * is_throws_with,

+

+        is_false = 2 * is_nothrow,

+        is_unary = 2 * is_false, // not checked anywhere - used just to distinguish the types

+

+        is_eq = 2 * is_unary,

+        is_ne = 2 * is_eq,

+

+        is_lt = 2 * is_ne,

+        is_gt = 2 * is_lt,

+

+        is_ge = 2 * is_gt,

+        is_le = 2 * is_ge,

+

+        // macro types

+

+        DT_WARN    = is_normal | is_warn,

+        DT_CHECK   = is_normal | is_check,

+        DT_REQUIRE = is_normal | is_require,

+

+        DT_WARN_FALSE    = is_normal | is_false | is_warn,

+        DT_CHECK_FALSE   = is_normal | is_false | is_check,

+        DT_REQUIRE_FALSE = is_normal | is_false | is_require,

+

+        DT_WARN_THROWS    = is_throws | is_warn,

+        DT_CHECK_THROWS   = is_throws | is_check,

+        DT_REQUIRE_THROWS = is_throws | is_require,

+

+        DT_WARN_THROWS_AS    = is_throws_as | is_warn,

+        DT_CHECK_THROWS_AS   = is_throws_as | is_check,

+        DT_REQUIRE_THROWS_AS = is_throws_as | is_require,

+

+        DT_WARN_THROWS_WITH    = is_throws_with | is_warn,

+        DT_CHECK_THROWS_WITH   = is_throws_with | is_check,

+        DT_REQUIRE_THROWS_WITH = is_throws_with | is_require,

+

+        DT_WARN_THROWS_WITH_AS    = is_throws_with | is_throws_as | is_warn,

+        DT_CHECK_THROWS_WITH_AS   = is_throws_with | is_throws_as | is_check,

+        DT_REQUIRE_THROWS_WITH_AS = is_throws_with | is_throws_as | is_require,

+

+        DT_WARN_NOTHROW    = is_nothrow | is_warn,

+        DT_CHECK_NOTHROW   = is_nothrow | is_check,

+        DT_REQUIRE_NOTHROW = is_nothrow | is_require,

+

+        DT_WARN_EQ    = is_normal | is_eq | is_warn,

+        DT_CHECK_EQ   = is_normal | is_eq | is_check,

+        DT_REQUIRE_EQ = is_normal | is_eq | is_require,

+

+        DT_WARN_NE    = is_normal | is_ne | is_warn,

+        DT_CHECK_NE   = is_normal | is_ne | is_check,

+        DT_REQUIRE_NE = is_normal | is_ne | is_require,

+

+        DT_WARN_GT    = is_normal | is_gt | is_warn,

+        DT_CHECK_GT   = is_normal | is_gt | is_check,

+        DT_REQUIRE_GT = is_normal | is_gt | is_require,

+

+        DT_WARN_LT    = is_normal | is_lt | is_warn,

+        DT_CHECK_LT   = is_normal | is_lt | is_check,

+        DT_REQUIRE_LT = is_normal | is_lt | is_require,

+

+        DT_WARN_GE    = is_normal | is_ge | is_warn,

+        DT_CHECK_GE   = is_normal | is_ge | is_check,

+        DT_REQUIRE_GE = is_normal | is_ge | is_require,

+

+        DT_WARN_LE    = is_normal | is_le | is_warn,

+        DT_CHECK_LE   = is_normal | is_le | is_check,

+        DT_REQUIRE_LE = is_normal | is_le | is_require,

+

+        DT_WARN_UNARY    = is_normal | is_unary | is_warn,

+        DT_CHECK_UNARY   = is_normal | is_unary | is_check,

+        DT_REQUIRE_UNARY = is_normal | is_unary | is_require,

+

+        DT_WARN_UNARY_FALSE    = is_normal | is_false | is_unary | is_warn,

+        DT_CHECK_UNARY_FALSE   = is_normal | is_false | is_unary | is_check,

+        DT_REQUIRE_UNARY_FALSE = is_normal | is_false | is_unary | is_require,

+    };

+} // namespace assertType

+

+DOCTEST_INTERFACE const char* assertString(assertType::Enum at);

+DOCTEST_INTERFACE const char* failureString(assertType::Enum at);

+DOCTEST_INTERFACE const char* skipPathFromFilename(const char* file);

+

+struct DOCTEST_INTERFACE TestCaseData

+{

+    String      m_file;       // the file in which the test was registered (using String - see #350)

+    unsigned    m_line;       // the line where the test was registered

+    const char* m_name;       // name of the test case

+    const char* m_test_suite; // the test suite in which the test was added

+    const char* m_description;

+    bool        m_skip;

+    bool        m_no_breaks;

+    bool        m_no_output;

+    bool        m_may_fail;

+    bool        m_should_fail;

+    int         m_expected_failures;

+    double      m_timeout;

+};

+

+struct DOCTEST_INTERFACE AssertData

+{

+    // common - for all asserts

+    const TestCaseData* m_test_case;

+    assertType::Enum    m_at;

+    const char*         m_file;

+    int                 m_line;

+    const char*         m_expr;

+    bool                m_failed;

+

+    // exception-related - for all asserts

+    bool   m_threw;

+    String m_exception;

+

+    // for normal asserts

+    String m_decomp;

+

+    // for specific exception-related asserts

+    bool           m_threw_as;

+    const char*    m_exception_type;

+

+    class DOCTEST_INTERFACE StringContains {

+        private:

+            Contains content;

+            bool isContains;

+

+        public:

+            StringContains(const String& str) : content(str), isContains(false) { }

+            StringContains(Contains cntn) : content(static_cast<Contains&&>(cntn)), isContains(true) { }

+

+            bool check(const String& str) { return isContains ? (content == str) : (content.string == str); }

+

+            operator const String&() const { return content.string; }

+

+            const char* c_str() const { return content.string.c_str(); }

+    } m_exception_string;

+

+    AssertData(assertType::Enum at, const char* file, int line, const char* expr,

+        const char* exception_type, const StringContains& exception_string);

+};

+

+struct DOCTEST_INTERFACE MessageData

+{

+    String           m_string;

+    const char*      m_file;

+    int              m_line;

+    assertType::Enum m_severity;

+};

+

+struct DOCTEST_INTERFACE SubcaseSignature

+{

+    String      m_name;

+    const char* m_file;

+    int         m_line;

+

+    bool operator==(const SubcaseSignature& other) const;

+    bool operator<(const SubcaseSignature& other) const;

+};

+

+struct DOCTEST_INTERFACE IContextScope

+{

+    DOCTEST_DECLARE_INTERFACE(IContextScope)

+    virtual void stringify(std::ostream*) const = 0;

+};

+

+namespace detail {

+    struct DOCTEST_INTERFACE TestCase;

+} // namespace detail

+

+struct ContextOptions //!OCLINT too many fields

+{

+    std::ostream* cout = nullptr; // stdout stream

+    String        binary_name;    // the test binary name

+

+    const detail::TestCase* currentTest = nullptr;

+

+    // == parameters from the command line

+    String   out;       // output filename

+    String   order_by;  // how tests should be ordered

+    unsigned rand_seed; // the seed for rand ordering

+

+    unsigned first; // the first (matching) test to be executed

+    unsigned last;  // the last (matching) test to be executed

+

+    int abort_after;           // stop tests after this many failed assertions

+    int subcase_filter_levels; // apply the subcase filters for the first N levels

+

+    bool success;              // include successful assertions in output

+    bool case_sensitive;       // if filtering should be case sensitive

+    bool exit;                 // if the program should be exited after the tests are ran/whatever

+    bool duration;             // print the time duration of each test case

+    bool minimal;              // minimal console output (only test failures)

+    bool quiet;                // no console output

+    bool no_throw;             // to skip exceptions-related assertion macros

+    bool no_exitcode;          // if the framework should return 0 as the exitcode

+    bool no_run;               // to not run the tests at all (can be done with an "*" exclude)

+    bool no_intro;             // to not print the intro of the framework

+    bool no_version;           // to not print the version of the framework

+    bool no_colors;            // if output to the console should be colorized

+    bool force_colors;         // forces the use of colors even when a tty cannot be detected

+    bool no_breaks;            // to not break into the debugger

+    bool no_skip;              // don't skip test cases which are marked to be skipped

+    bool gnu_file_line;        // if line numbers should be surrounded with :x: and not (x):

+    bool no_path_in_filenames; // if the path to files should be removed from the output

+    bool no_line_numbers;      // if source code line numbers should be omitted from the output

+    bool no_debug_output;      // no output in the debug console when a debugger is attached

+    bool no_skipped_summary;   // don't print "skipped" in the summary !!! UNDOCUMENTED !!!

+    bool no_time_in_output;    // omit any time/timestamps from output !!! UNDOCUMENTED !!!

+

+    bool help;             // to print the help

+    bool version;          // to print the version

+    bool count;            // if only the count of matching tests is to be retrieved

+    bool list_test_cases;  // to list all tests matching the filters

+    bool list_test_suites; // to list all suites matching the filters

+    bool list_reporters;   // lists all registered reporters

+};

+

+namespace detail {

+    namespace types {

+#ifdef DOCTEST_CONFIG_INCLUDE_TYPE_TRAITS

+        using namespace std;

+#else

+        template <bool COND, typename T = void>

+        struct enable_if { };

+

+        template <typename T>

+        struct enable_if<true, T> { using type = T; };

+

+        struct true_type { static DOCTEST_CONSTEXPR bool value = true; };

+        struct false_type { static DOCTEST_CONSTEXPR bool value = false; };

+

+        template <typename T> struct remove_reference { using type = T; };

+        template <typename T> struct remove_reference<T&> { using type = T; };

+        template <typename T> struct remove_reference<T&&> { using type = T; };

+

+        template <typename T> struct is_rvalue_reference : false_type { };

+        template <typename T> struct is_rvalue_reference<T&&> : true_type { };

+

+        template<typename T> struct remove_const { using type = T; };

+        template <typename T> struct remove_const<const T> { using type = T; };

+

+        // Compiler intrinsics

+        template <typename T> struct is_enum { static DOCTEST_CONSTEXPR bool value = __is_enum(T); };

+        template <typename T> struct underlying_type { using type = __underlying_type(T); };

+

+        template <typename T> struct is_pointer : false_type { };

+        template <typename T> struct is_pointer<T*> : true_type { };

+

+        template <typename T> struct is_array : false_type { };

+        // NOLINTNEXTLINE(*-avoid-c-arrays)

+        template <typename T, size_t SIZE> struct is_array<T[SIZE]> : true_type { };

+#endif

+    }

+

+    // <utility>

+    template <typename T>

+    T&& declval();

+

+    template <class T>

+    DOCTEST_CONSTEXPR_FUNC T&& forward(typename types::remove_reference<T>::type& t) DOCTEST_NOEXCEPT {

+        return static_cast<T&&>(t);

+    }

+

+    template <class T>

+    DOCTEST_CONSTEXPR_FUNC T&& forward(typename types::remove_reference<T>::type&& t) DOCTEST_NOEXCEPT {

+        return static_cast<T&&>(t);

+    }

+

+    template <typename T>

+    struct deferred_false : types::false_type { };

+

+// MSVS 2015 :(

+#if defined(_MSC_VER) && _MSC_VER <= 1900

+    template <typename T, typename = void>

+    struct has_global_insertion_operator : types::false_type { };

+

+    template <typename T>

+    struct has_global_insertion_operator<T, decltype(::operator<<(declval<std::ostream&>(), declval<const T&>()), void())> : types::true_type { };

+

+    template <typename T, typename = void>

+    struct has_insertion_operator { static DOCTEST_CONSTEXPR bool value = has_global_insertion_operator<T>::value; };

+

+    template <typename T, bool global>

+    struct insert_hack;

+

+    template <typename T>

+    struct insert_hack<T, true> {

+        static void insert(std::ostream& os, const T& t) { ::operator<<(os, t); }

+    };

+

+    template <typename T>

+    struct insert_hack<T, false> {

+        static void insert(std::ostream& os, const T& t) { operator<<(os, t); }

+    };

+

+    template <typename T>

+    using insert_hack_t = insert_hack<T, has_global_insertion_operator<T>::value>;

+#else

+    template <typename T, typename = void>

+    struct has_insertion_operator : types::false_type { };

+#endif

+

+template <typename T>

+struct has_insertion_operator<T, decltype(operator<<(declval<std::ostream&>(), declval<const T&>()), void())> : types::true_type { };

+

+    DOCTEST_INTERFACE std::ostream* tlssPush();

+    DOCTEST_INTERFACE String tlssPop();

+

+    template <bool C>

+    struct StringMakerBase {

+        template <typename T>

+        static String convert(const DOCTEST_REF_WRAP(T)) {

+#ifdef DOCTEST_CONFIG_REQUIRE_STRINGIFICATION_FOR_ALL_USED_TYPES

+            static_assert(deferred_false<T>::value, "No stringification detected for type T. See string conversion manual");

+#endif

+            return "{?}";

+        }

+    };

+

+    template <typename T>

+    struct filldata;

+

+    template <typename T>

+    void filloss(std::ostream* stream, const T& in) {

+        filldata<T>::fill(stream, in);

+    }

+

+    template <typename T, size_t N>

+    void filloss(std::ostream* stream, const T (&in)[N]) { // NOLINT(*-avoid-c-arrays)

+        // T[N], T(&)[N], T(&&)[N] have same behaviour.

+        // Hence remove reference.

+        filloss<typename types::remove_reference<decltype(in)>::type>(stream, in);

+    }

+

+    template <typename T>

+    String toStream(const T& in) {

+        std::ostream* stream = tlssPush();

+        filloss(stream, in);

+        return tlssPop();

+    }

+

+    template <>

+    struct StringMakerBase<true> {

+        template <typename T>

+        static String convert(const DOCTEST_REF_WRAP(T) in) {

+            return toStream(in);

+        }

+    };

+} // namespace detail

+

+template <typename T>

+struct StringMaker : public detail::StringMakerBase<

+    detail::has_insertion_operator<T>::value || detail::types::is_pointer<T>::value || detail::types::is_array<T>::value>

+{};

+

+#ifndef DOCTEST_STRINGIFY

+#ifdef DOCTEST_CONFIG_DOUBLE_STRINGIFY

+#define DOCTEST_STRINGIFY(...) toString(toString(__VA_ARGS__))

+#else

+#define DOCTEST_STRINGIFY(...) toString(__VA_ARGS__)

+#endif

+#endif

+

+template <typename T>

+String toString() {

+#if DOCTEST_MSVC >= 0 && DOCTEST_CLANG == 0 && DOCTEST_GCC == 0

+    String ret = __FUNCSIG__; // class doctest::String __cdecl doctest::toString<TYPE>(void)

+    String::size_type beginPos = ret.find('<');

+    return ret.substr(beginPos + 1, ret.size() - beginPos - static_cast<String::size_type>(sizeof(">(void)")));

+#else

+    String ret = __PRETTY_FUNCTION__; // doctest::String toString() [with T = TYPE]

+    String::size_type begin = ret.find('=') + 2;

+    return ret.substr(begin, ret.size() - begin - 1);

+#endif

+}

+

+template <typename T, typename detail::types::enable_if<!detail::types::is_enum<T>::value, bool>::type = true>

+String toString(const DOCTEST_REF_WRAP(T) value) {

+    return StringMaker<T>::convert(value);

+}

+

+#ifdef DOCTEST_CONFIG_TREAT_CHAR_STAR_AS_STRING

+DOCTEST_INTERFACE String toString(const char* in);

+#endif // DOCTEST_CONFIG_TREAT_CHAR_STAR_AS_STRING

+

+#if DOCTEST_MSVC >= DOCTEST_COMPILER(19, 20, 0)

+// see this issue on why this is needed: https://github.com/doctest/doctest/issues/183

+DOCTEST_INTERFACE String toString(const std::string& in);

+#endif // VS 2019

+

+DOCTEST_INTERFACE String toString(String in);

+

+DOCTEST_INTERFACE String toString(std::nullptr_t);

+

+DOCTEST_INTERFACE String toString(bool in);

+

+DOCTEST_INTERFACE String toString(float in);

+DOCTEST_INTERFACE String toString(double in);

+DOCTEST_INTERFACE String toString(double long in);

+

+DOCTEST_INTERFACE String toString(char in);

+DOCTEST_INTERFACE String toString(char signed in);

+DOCTEST_INTERFACE String toString(char unsigned in);

+DOCTEST_INTERFACE String toString(short in);

+DOCTEST_INTERFACE String toString(short unsigned in);

+DOCTEST_INTERFACE String toString(signed in);

+DOCTEST_INTERFACE String toString(unsigned in);

+DOCTEST_INTERFACE String toString(long in);

+DOCTEST_INTERFACE String toString(long unsigned in);

+DOCTEST_INTERFACE String toString(long long in);

+DOCTEST_INTERFACE String toString(long long unsigned in);

+

+template <typename T, typename detail::types::enable_if<detail::types::is_enum<T>::value, bool>::type = true>

+String toString(const DOCTEST_REF_WRAP(T) value) {

+    using UT = typename detail::types::underlying_type<T>::type;

+    return (DOCTEST_STRINGIFY(static_cast<UT>(value)));

+}

+

+namespace detail {

+    template <typename T>

+    struct filldata

+    {

+        static void fill(std::ostream* stream, const T& in) {

+#if defined(_MSC_VER) && _MSC_VER <= 1900

+        insert_hack_t<T>::insert(*stream, in);

+#else

+        operator<<(*stream, in);

+#endif

+        }

+    };

+

+DOCTEST_MSVC_SUPPRESS_WARNING_WITH_PUSH(4866)

+// NOLINTBEGIN(*-avoid-c-arrays)

+    template <typename T, size_t N>

+    struct filldata<T[N]> {

+        static void fill(std::ostream* stream, const T(&in)[N]) {

+            *stream << "[";

+            for (size_t i = 0; i < N; i++) {

+                if (i != 0) { *stream << ", "; }

+                *stream << (DOCTEST_STRINGIFY(in[i]));

+            }

+            *stream << "]";

+        }

+    };

+// NOLINTEND(*-avoid-c-arrays)

+DOCTEST_MSVC_SUPPRESS_WARNING_POP

+

+    // Specialized since we don't want the terminating null byte!

+// NOLINTBEGIN(*-avoid-c-arrays)

+    template <size_t N>

+    struct filldata<const char[N]> {

+        static void fill(std::ostream* stream, const char (&in)[N]) {

+            *stream << String(in, in[N - 1] ? N : N - 1);

+        } // NOLINT(clang-analyzer-cplusplus.NewDeleteLeaks)

+    };

+// NOLINTEND(*-avoid-c-arrays)

+

+    template <>

+    struct filldata<const void*> {

+        static void fill(std::ostream* stream, const void* in);

+    };

+

+    template <typename T>

+    struct filldata<T*> {

+        static void fill(std::ostream* stream, const T* in) {

+            filldata<const void*>::fill(stream, in);

+        }

+    };

+}

+

+struct DOCTEST_INTERFACE Approx

+{

+    Approx(double value);

+

+    Approx operator()(double value) const;

+

+#ifdef DOCTEST_CONFIG_INCLUDE_TYPE_TRAITS

+    template <typename T>

+    explicit Approx(const T& value,

+                    typename detail::types::enable_if<std::is_constructible<double, T>::value>::type* =

+                            static_cast<T*>(nullptr)) {

+        *this = static_cast<double>(value);

+    }

+#endif // DOCTEST_CONFIG_INCLUDE_TYPE_TRAITS

+

+    Approx& epsilon(double newEpsilon);

+

+#ifdef DOCTEST_CONFIG_INCLUDE_TYPE_TRAITS

+    template <typename T>

+    typename std::enable_if<std::is_constructible<double, T>::value, Approx&>::type epsilon(

+            const T& newEpsilon) {

+        m_epsilon = static_cast<double>(newEpsilon);

+        return *this;

+    }

+#endif //  DOCTEST_CONFIG_INCLUDE_TYPE_TRAITS

+

+    Approx& scale(double newScale);

+

+#ifdef DOCTEST_CONFIG_INCLUDE_TYPE_TRAITS

+    template <typename T>

+    typename std::enable_if<std::is_constructible<double, T>::value, Approx&>::type scale(

+            const T& newScale) {

+        m_scale = static_cast<double>(newScale);

+        return *this;

+    }

+#endif // DOCTEST_CONFIG_INCLUDE_TYPE_TRAITS

+

+    // clang-format off

+    DOCTEST_INTERFACE friend bool operator==(double lhs, const Approx & rhs);

+    DOCTEST_INTERFACE friend bool operator==(const Approx & lhs, double rhs);

+    DOCTEST_INTERFACE friend bool operator!=(double lhs, const Approx & rhs);

+    DOCTEST_INTERFACE friend bool operator!=(const Approx & lhs, double rhs);

+    DOCTEST_INTERFACE friend bool operator<=(double lhs, const Approx & rhs);

+    DOCTEST_INTERFACE friend bool operator<=(const Approx & lhs, double rhs);

+    DOCTEST_INTERFACE friend bool operator>=(double lhs, const Approx & rhs);

+    DOCTEST_INTERFACE friend bool operator>=(const Approx & lhs, double rhs);

+    DOCTEST_INTERFACE friend bool operator< (double lhs, const Approx & rhs);

+    DOCTEST_INTERFACE friend bool operator< (const Approx & lhs, double rhs);

+    DOCTEST_INTERFACE friend bool operator> (double lhs, const Approx & rhs);

+    DOCTEST_INTERFACE friend bool operator> (const Approx & lhs, double rhs);

+

+#ifdef DOCTEST_CONFIG_INCLUDE_TYPE_TRAITS

+#define DOCTEST_APPROX_PREFIX \

+    template <typename T> friend typename std::enable_if<std::is_constructible<double, T>::value, bool>::type

+

+    DOCTEST_APPROX_PREFIX operator==(const T& lhs, const Approx& rhs) { return operator==(static_cast<double>(lhs), rhs); }

+    DOCTEST_APPROX_PREFIX operator==(const Approx& lhs, const T& rhs) { return operator==(rhs, lhs); }

+    DOCTEST_APPROX_PREFIX operator!=(const T& lhs, const Approx& rhs) { return !operator==(lhs, rhs); }

+    DOCTEST_APPROX_PREFIX operator!=(const Approx& lhs, const T& rhs) { return !operator==(rhs, lhs); }

+    DOCTEST_APPROX_PREFIX operator<=(const T& lhs, const Approx& rhs) { return static_cast<double>(lhs) < rhs.m_value || lhs == rhs; }

+    DOCTEST_APPROX_PREFIX operator<=(const Approx& lhs, const T& rhs) { return lhs.m_value < static_cast<double>(rhs) || lhs == rhs; }

+    DOCTEST_APPROX_PREFIX operator>=(const T& lhs, const Approx& rhs) { return static_cast<double>(lhs) > rhs.m_value || lhs == rhs; }

+    DOCTEST_APPROX_PREFIX operator>=(const Approx& lhs, const T& rhs) { return lhs.m_value > static_cast<double>(rhs) || lhs == rhs; }

+    DOCTEST_APPROX_PREFIX operator< (const T& lhs, const Approx& rhs) { return static_cast<double>(lhs) < rhs.m_value && lhs != rhs; }

+    DOCTEST_APPROX_PREFIX operator< (const Approx& lhs, const T& rhs) { return lhs.m_value < static_cast<double>(rhs) && lhs != rhs; }

+    DOCTEST_APPROX_PREFIX operator> (const T& lhs, const Approx& rhs) { return static_cast<double>(lhs) > rhs.m_value && lhs != rhs; }

+    DOCTEST_APPROX_PREFIX operator> (const Approx& lhs, const T& rhs) { return lhs.m_value > static_cast<double>(rhs) && lhs != rhs; }

+#undef DOCTEST_APPROX_PREFIX

+#endif // DOCTEST_CONFIG_INCLUDE_TYPE_TRAITS

+

+    // clang-format on

+

+    double m_epsilon;

+    double m_scale;

+    double m_value;

+};

+

+DOCTEST_INTERFACE String toString(const Approx& in);

+

+DOCTEST_INTERFACE const ContextOptions* getContextOptions();

+

+template <typename F>

+struct DOCTEST_INTERFACE_DECL IsNaN

+{

+    F value; bool flipped;

+    IsNaN(F f, bool flip = false) : value(f), flipped(flip) { }

+    IsNaN<F> operator!() const { return { value, !flipped }; }

+    operator bool() const;

+};

+#ifndef __MINGW32__

+extern template struct DOCTEST_INTERFACE_DECL IsNaN<float>;

+extern template struct DOCTEST_INTERFACE_DECL IsNaN<double>;

+extern template struct DOCTEST_INTERFACE_DECL IsNaN<long double>;

+#endif

+DOCTEST_INTERFACE String toString(IsNaN<float> in);

+DOCTEST_INTERFACE String toString(IsNaN<double> in);

+DOCTEST_INTERFACE String toString(IsNaN<double long> in);

+

+#ifndef DOCTEST_CONFIG_DISABLE

+

+namespace detail {

+    // clang-format off

+#ifdef DOCTEST_CONFIG_TREAT_CHAR_STAR_AS_STRING

+    template<class T>               struct decay_array       { using type = T; };

+    template<class T, unsigned N>   struct decay_array<T[N]> { using type = T*; };

+    template<class T>               struct decay_array<T[]>  { using type = T*; };

+

+    template<class T>   struct not_char_pointer              { static DOCTEST_CONSTEXPR value = 1; };

+    template<>          struct not_char_pointer<char*>       { static DOCTEST_CONSTEXPR value = 0; };

+    template<>          struct not_char_pointer<const char*> { static DOCTEST_CONSTEXPR value = 0; };

+

+    template<class T> struct can_use_op : public not_char_pointer<typename decay_array<T>::type> {};

+#endif // DOCTEST_CONFIG_TREAT_CHAR_STAR_AS_STRING

+    // clang-format on

+

+    struct DOCTEST_INTERFACE TestFailureException

+    {

+    };

+

+    DOCTEST_INTERFACE bool checkIfShouldThrow(assertType::Enum at);

+

+#ifndef DOCTEST_CONFIG_NO_EXCEPTIONS

+    DOCTEST_NORETURN

+#endif // DOCTEST_CONFIG_NO_EXCEPTIONS

+    DOCTEST_INTERFACE void throwException();

+

+    struct DOCTEST_INTERFACE Subcase

+    {

+        SubcaseSignature m_signature;

+        bool             m_entered = false;

+

+        Subcase(const String& name, const char* file, int line);

+        Subcase(const Subcase&) = delete;

+        Subcase(Subcase&&) = delete;

+        Subcase& operator=(const Subcase&) = delete;

+        Subcase& operator=(Subcase&&) = delete;

+        ~Subcase();

+

+        operator bool() const;

+

+        private:

+            bool checkFilters();

+    };

+

+    template <typename L, typename R>

+    String stringifyBinaryExpr(const DOCTEST_REF_WRAP(L) lhs, const char* op,

+                               const DOCTEST_REF_WRAP(R) rhs) {

+        return (DOCTEST_STRINGIFY(lhs)) + op + (DOCTEST_STRINGIFY(rhs));

+    }

+

+#if DOCTEST_CLANG && DOCTEST_CLANG < DOCTEST_COMPILER(3, 6, 0)

+DOCTEST_CLANG_SUPPRESS_WARNING_WITH_PUSH("-Wunused-comparison")

+#endif

+

+// This will check if there is any way it could find a operator like member or friend and uses it.

+// If not it doesn't find the operator or if the operator at global scope is defined after

+// this template, the template won't be instantiated due to SFINAE. Once the template is not

+// instantiated it can look for global operator using normal conversions.

+#define SFINAE_OP(ret,op) decltype((void)(doctest::detail::declval<L>() op doctest::detail::declval<R>()),ret{})

+

+#define DOCTEST_DO_BINARY_EXPRESSION_COMPARISON(op, op_str, op_macro)                              \

+    template <typename R>                                                                          \

+    DOCTEST_NOINLINE SFINAE_OP(Result,op) operator op(R&& rhs) {                                   \

+    bool res = op_macro(doctest::detail::forward<const L>(lhs), doctest::detail::forward<R>(rhs)); \

+        if(m_at & assertType::is_false)                                                            \

+            res = !res;                                                                            \

+        if(!res || doctest::getContextOptions()->success)                                          \

+            return Result(res, stringifyBinaryExpr(lhs, op_str, rhs));                             \

+        return Result(res);                                                                        \

+    }

+

+    // more checks could be added - like in Catch:

+    // https://github.com/catchorg/Catch2/pull/1480/files

+    // https://github.com/catchorg/Catch2/pull/1481/files

+#define DOCTEST_FORBIT_EXPRESSION(rt, op)                                                          \

+    template <typename R>                                                                          \

+    rt& operator op(const R&) {                                                                    \

+        static_assert(deferred_false<R>::value,                                                    \

+                      "Expression Too Complex Please Rewrite As Binary Comparison!");              \

+        return *this;                                                                              \

+    }

+

+    struct DOCTEST_INTERFACE Result // NOLINT(*-member-init)

+    {

+        bool   m_passed;

+        String m_decomp;

+

+        Result() = default; // TODO: Why do we need this? (To remove NOLINT)

+        Result(bool passed, const String& decomposition = String());

+

+        // forbidding some expressions based on this table: https://en.cppreference.com/w/cpp/language/operator_precedence

+        DOCTEST_FORBIT_EXPRESSION(Result, &)

+        DOCTEST_FORBIT_EXPRESSION(Result, ^)

+        DOCTEST_FORBIT_EXPRESSION(Result, |)

+        DOCTEST_FORBIT_EXPRESSION(Result, &&)

+        DOCTEST_FORBIT_EXPRESSION(Result, ||)

+        DOCTEST_FORBIT_EXPRESSION(Result, ==)

+        DOCTEST_FORBIT_EXPRESSION(Result, !=)

+        DOCTEST_FORBIT_EXPRESSION(Result, <)

+        DOCTEST_FORBIT_EXPRESSION(Result, >)

+        DOCTEST_FORBIT_EXPRESSION(Result, <=)

+        DOCTEST_FORBIT_EXPRESSION(Result, >=)

+        DOCTEST_FORBIT_EXPRESSION(Result, =)

+        DOCTEST_FORBIT_EXPRESSION(Result, +=)

+        DOCTEST_FORBIT_EXPRESSION(Result, -=)

+        DOCTEST_FORBIT_EXPRESSION(Result, *=)

+        DOCTEST_FORBIT_EXPRESSION(Result, /=)

+        DOCTEST_FORBIT_EXPRESSION(Result, %=)

+        DOCTEST_FORBIT_EXPRESSION(Result, <<=)

+        DOCTEST_FORBIT_EXPRESSION(Result, >>=)

+        DOCTEST_FORBIT_EXPRESSION(Result, &=)

+        DOCTEST_FORBIT_EXPRESSION(Result, ^=)

+        DOCTEST_FORBIT_EXPRESSION(Result, |=)

+    };

+

+#ifndef DOCTEST_CONFIG_NO_COMPARISON_WARNING_SUPPRESSION

+

+    DOCTEST_CLANG_SUPPRESS_WARNING_PUSH

+    DOCTEST_CLANG_SUPPRESS_WARNING("-Wsign-conversion")

+    DOCTEST_CLANG_SUPPRESS_WARNING("-Wsign-compare")

+    //DOCTEST_CLANG_SUPPRESS_WARNING("-Wdouble-promotion")

+    //DOCTEST_CLANG_SUPPRESS_WARNING("-Wconversion")

+    //DOCTEST_CLANG_SUPPRESS_WARNING("-Wfloat-equal")

+

+    DOCTEST_GCC_SUPPRESS_WARNING_PUSH

+    DOCTEST_GCC_SUPPRESS_WARNING("-Wsign-conversion")

+    DOCTEST_GCC_SUPPRESS_WARNING("-Wsign-compare")

+    //DOCTEST_GCC_SUPPRESS_WARNING("-Wdouble-promotion")

+    //DOCTEST_GCC_SUPPRESS_WARNING("-Wconversion")

+    //DOCTEST_GCC_SUPPRESS_WARNING("-Wfloat-equal")

+

+    DOCTEST_MSVC_SUPPRESS_WARNING_PUSH

+    // https://stackoverflow.com/questions/39479163 what's the difference between 4018 and 4389

+    DOCTEST_MSVC_SUPPRESS_WARNING(4388) // signed/unsigned mismatch

+    DOCTEST_MSVC_SUPPRESS_WARNING(4389) // 'operator' : signed/unsigned mismatch

+    DOCTEST_MSVC_SUPPRESS_WARNING(4018) // 'expression' : signed/unsigned mismatch

+    //DOCTEST_MSVC_SUPPRESS_WARNING(4805) // 'operation' : unsafe mix of type 'type' and type 'type' in operation

+

+#endif // DOCTEST_CONFIG_NO_COMPARISON_WARNING_SUPPRESSION

+

+    // clang-format off

+#ifndef DOCTEST_CONFIG_TREAT_CHAR_STAR_AS_STRING

+#define DOCTEST_COMPARISON_RETURN_TYPE bool

+#else // DOCTEST_CONFIG_TREAT_CHAR_STAR_AS_STRING

+#define DOCTEST_COMPARISON_RETURN_TYPE typename types::enable_if<can_use_op<L>::value || can_use_op<R>::value, bool>::type

+    inline bool eq(const char* lhs, const char* rhs) { return String(lhs) == String(rhs); }

+    inline bool ne(const char* lhs, const char* rhs) { return String(lhs) != String(rhs); }

+    inline bool lt(const char* lhs, const char* rhs) { return String(lhs) <  String(rhs); }

+    inline bool gt(const char* lhs, const char* rhs) { return String(lhs) >  String(rhs); }

+    inline bool le(const char* lhs, const char* rhs) { return String(lhs) <= String(rhs); }

+    inline bool ge(const char* lhs, const char* rhs) { return String(lhs) >= String(rhs); }

+#endif // DOCTEST_CONFIG_TREAT_CHAR_STAR_AS_STRING

+    // clang-format on

+

+#define DOCTEST_RELATIONAL_OP(name, op)                                                            \

+    template <typename L, typename R>                                                              \

+    DOCTEST_COMPARISON_RETURN_TYPE name(const DOCTEST_REF_WRAP(L) lhs,                             \

+                                        const DOCTEST_REF_WRAP(R) rhs) {                           \

+        return lhs op rhs;                                                                         \

+    }

+

+    DOCTEST_RELATIONAL_OP(eq, ==)

+    DOCTEST_RELATIONAL_OP(ne, !=)

+    DOCTEST_RELATIONAL_OP(lt, <)

+    DOCTEST_RELATIONAL_OP(gt, >)

+    DOCTEST_RELATIONAL_OP(le, <=)

+    DOCTEST_RELATIONAL_OP(ge, >=)

+

+#ifndef DOCTEST_CONFIG_TREAT_CHAR_STAR_AS_STRING

+#define DOCTEST_CMP_EQ(l, r) l == r

+#define DOCTEST_CMP_NE(l, r) l != r

+#define DOCTEST_CMP_GT(l, r) l > r

+#define DOCTEST_CMP_LT(l, r) l < r

+#define DOCTEST_CMP_GE(l, r) l >= r

+#define DOCTEST_CMP_LE(l, r) l <= r

+#else // DOCTEST_CONFIG_TREAT_CHAR_STAR_AS_STRING

+#define DOCTEST_CMP_EQ(l, r) eq(l, r)

+#define DOCTEST_CMP_NE(l, r) ne(l, r)

+#define DOCTEST_CMP_GT(l, r) gt(l, r)

+#define DOCTEST_CMP_LT(l, r) lt(l, r)

+#define DOCTEST_CMP_GE(l, r) ge(l, r)

+#define DOCTEST_CMP_LE(l, r) le(l, r)

+#endif // DOCTEST_CONFIG_TREAT_CHAR_STAR_AS_STRING

+

+    template <typename L>

+    // cppcheck-suppress copyCtorAndEqOperator

+    struct Expression_lhs

+    {

+        L                lhs;

+        assertType::Enum m_at;

+

+        explicit Expression_lhs(L&& in, assertType::Enum at)

+                : lhs(static_cast<L&&>(in))

+                , m_at(at) {}

+

+        DOCTEST_NOINLINE operator Result() {

+// this is needed only for MSVC 2015

+DOCTEST_MSVC_SUPPRESS_WARNING_WITH_PUSH(4800) // 'int': forcing value to bool

+            bool res = static_cast<bool>(lhs);

+DOCTEST_MSVC_SUPPRESS_WARNING_POP

+            if(m_at & assertType::is_false) { //!OCLINT bitwise operator in conditional

+                res = !res;

+            }

+

+            if(!res || getContextOptions()->success) {

+                return { res, (DOCTEST_STRINGIFY(lhs)) };

+            }

+            return { res };

+        }

+

+        /* This is required for user-defined conversions from Expression_lhs to L */

+        operator L() const { return lhs; }

+

+        // clang-format off

+        DOCTEST_DO_BINARY_EXPRESSION_COMPARISON(==, " == ", DOCTEST_CMP_EQ) //!OCLINT bitwise operator in conditional

+        DOCTEST_DO_BINARY_EXPRESSION_COMPARISON(!=, " != ", DOCTEST_CMP_NE) //!OCLINT bitwise operator in conditional

+        DOCTEST_DO_BINARY_EXPRESSION_COMPARISON(>,  " >  ", DOCTEST_CMP_GT) //!OCLINT bitwise operator in conditional

+        DOCTEST_DO_BINARY_EXPRESSION_COMPARISON(<,  " <  ", DOCTEST_CMP_LT) //!OCLINT bitwise operator in conditional

+        DOCTEST_DO_BINARY_EXPRESSION_COMPARISON(>=, " >= ", DOCTEST_CMP_GE) //!OCLINT bitwise operator in conditional

+        DOCTEST_DO_BINARY_EXPRESSION_COMPARISON(<=, " <= ", DOCTEST_CMP_LE) //!OCLINT bitwise operator in conditional

+        // clang-format on

+

+        // forbidding some expressions based on this table: https://en.cppreference.com/w/cpp/language/operator_precedence

+        DOCTEST_FORBIT_EXPRESSION(Expression_lhs, &)

+        DOCTEST_FORBIT_EXPRESSION(Expression_lhs, ^)

+        DOCTEST_FORBIT_EXPRESSION(Expression_lhs, |)

+        DOCTEST_FORBIT_EXPRESSION(Expression_lhs, &&)

+        DOCTEST_FORBIT_EXPRESSION(Expression_lhs, ||)

+        DOCTEST_FORBIT_EXPRESSION(Expression_lhs, =)

+        DOCTEST_FORBIT_EXPRESSION(Expression_lhs, +=)

+        DOCTEST_FORBIT_EXPRESSION(Expression_lhs, -=)

+        DOCTEST_FORBIT_EXPRESSION(Expression_lhs, *=)

+        DOCTEST_FORBIT_EXPRESSION(Expression_lhs, /=)

+        DOCTEST_FORBIT_EXPRESSION(Expression_lhs, %=)

+        DOCTEST_FORBIT_EXPRESSION(Expression_lhs, <<=)

+        DOCTEST_FORBIT_EXPRESSION(Expression_lhs, >>=)

+        DOCTEST_FORBIT_EXPRESSION(Expression_lhs, &=)

+        DOCTEST_FORBIT_EXPRESSION(Expression_lhs, ^=)

+        DOCTEST_FORBIT_EXPRESSION(Expression_lhs, |=)

+        // these 2 are unfortunate because they should be allowed - they have higher precedence over the comparisons, but the

+        // ExpressionDecomposer class uses the left shift operator to capture the left operand of the binary expression...

+        DOCTEST_FORBIT_EXPRESSION(Expression_lhs, <<)

+        DOCTEST_FORBIT_EXPRESSION(Expression_lhs, >>)

+    };

+

+#ifndef DOCTEST_CONFIG_NO_COMPARISON_WARNING_SUPPRESSION

+

+    DOCTEST_CLANG_SUPPRESS_WARNING_POP

+    DOCTEST_MSVC_SUPPRESS_WARNING_POP

+    DOCTEST_GCC_SUPPRESS_WARNING_POP

+

+#endif // DOCTEST_CONFIG_NO_COMPARISON_WARNING_SUPPRESSION

+

+#if DOCTEST_CLANG && DOCTEST_CLANG < DOCTEST_COMPILER(3, 6, 0)

+DOCTEST_CLANG_SUPPRESS_WARNING_POP

+#endif

+

+    struct DOCTEST_INTERFACE ExpressionDecomposer

+    {

+        assertType::Enum m_at;

+

+        ExpressionDecomposer(assertType::Enum at);

+

+        // The right operator for capturing expressions is "<=" instead of "<<" (based on the operator precedence table)

+        // but then there will be warnings from GCC about "-Wparentheses" and since "_Pragma()" is problematic this will stay for now...

+        // https://github.com/catchorg/Catch2/issues/870

+        // https://github.com/catchorg/Catch2/issues/565

+        template <typename L>

+        Expression_lhs<L> operator<<(L&& operand) {

+            return Expression_lhs<L>(static_cast<L&&>(operand), m_at);

+        }

+

+        template <typename L,typename types::enable_if<!doctest::detail::types::is_rvalue_reference<L>::value,void >::type* = nullptr>

+        Expression_lhs<const L&> operator<<(const L &operand) {

+            return Expression_lhs<const L&>(operand, m_at);

+        }

+    };

+

+    struct DOCTEST_INTERFACE TestSuite

+    {

+        const char* m_test_suite = nullptr;

+        const char* m_description = nullptr;

+        bool        m_skip = false;

+        bool        m_no_breaks = false;

+        bool        m_no_output = false;

+        bool        m_may_fail = false;

+        bool        m_should_fail = false;

+        int         m_expected_failures = 0;

+        double      m_timeout = 0;

+

+        TestSuite& operator*(const char* in);

+

+        template <typename T>

+        TestSuite& operator*(const T& in) {

+            in.fill(*this);

+            return *this;

+        }

+    };

+

+    using funcType = void (*)();

+

+    struct DOCTEST_INTERFACE TestCase : public TestCaseData

+    {

+        funcType m_test; // a function pointer to the test case

+

+        String m_type; // for templated test cases - gets appended to the real name

+        int m_template_id; // an ID used to distinguish between the different versions of a templated test case

+        String m_full_name; // contains the name (only for templated test cases!) + the template type

+

+        TestCase(funcType test, const char* file, unsigned line, const TestSuite& test_suite,

+                 const String& type = String(), int template_id = -1);

+

+        TestCase(const TestCase& other);

+        TestCase(TestCase&&) = delete;

+

+        DOCTEST_MSVC_SUPPRESS_WARNING_WITH_PUSH(26434) // hides a non-virtual function

+        TestCase& operator=(const TestCase& other);

+        DOCTEST_MSVC_SUPPRESS_WARNING_POP

+

+        TestCase& operator=(TestCase&&) = delete;

+

+        TestCase& operator*(const char* in);

+

+        template <typename T>

+        TestCase& operator*(const T& in) {

+            in.fill(*this);

+            return *this;

+        }

+

+        bool operator<(const TestCase& other) const;

+

+        ~TestCase() = default;

+    };

+

+    // forward declarations of functions used by the macros

+    DOCTEST_INTERFACE int  regTest(const TestCase& tc);

+    DOCTEST_INTERFACE int  setTestSuite(const TestSuite& ts);

+    DOCTEST_INTERFACE bool isDebuggerActive();

+

+    template<typename T>

+    int instantiationHelper(const T&) { return 0; }

+

+    namespace binaryAssertComparison {

+        enum Enum

+        {

+            eq = 0,

+            ne,

+            gt,

+            lt,

+            ge,

+            le

+        };

+    } // namespace binaryAssertComparison

+

+    // clang-format off

+    template <int, class L, class R> struct RelationalComparator     { bool operator()(const DOCTEST_REF_WRAP(L),     const DOCTEST_REF_WRAP(R)    ) const { return false;        } };

+

+#define DOCTEST_BINARY_RELATIONAL_OP(n, op) \

+    template <class L, class R> struct RelationalComparator<n, L, R> { bool operator()(const DOCTEST_REF_WRAP(L) lhs, const DOCTEST_REF_WRAP(R) rhs) const { return op(lhs, rhs); } };

+    // clang-format on

+

+    DOCTEST_BINARY_RELATIONAL_OP(0, doctest::detail::eq)

+    DOCTEST_BINARY_RELATIONAL_OP(1, doctest::detail::ne)

+    DOCTEST_BINARY_RELATIONAL_OP(2, doctest::detail::gt)

+    DOCTEST_BINARY_RELATIONAL_OP(3, doctest::detail::lt)

+    DOCTEST_BINARY_RELATIONAL_OP(4, doctest::detail::ge)

+    DOCTEST_BINARY_RELATIONAL_OP(5, doctest::detail::le)

+

+    struct DOCTEST_INTERFACE ResultBuilder : public AssertData

+    {

+        ResultBuilder(assertType::Enum at, const char* file, int line, const char* expr,

+                      const char* exception_type = "", const String& exception_string = "");

+

+        ResultBuilder(assertType::Enum at, const char* file, int line, const char* expr,

+                      const char* exception_type, const Contains& exception_string);

+

+        void setResult(const Result& res);

+

+        template <int comparison, typename L, typename R>

+        DOCTEST_NOINLINE bool binary_assert(const DOCTEST_REF_WRAP(L) lhs,

+                                            const DOCTEST_REF_WRAP(R) rhs) {

+            m_failed = !RelationalComparator<comparison, L, R>()(lhs, rhs);

+            if (m_failed || getContextOptions()->success) {

+                m_decomp = stringifyBinaryExpr(lhs, ", ", rhs);

+            }

+            return !m_failed;

+        }

+

+        template <typename L>

+        DOCTEST_NOINLINE bool unary_assert(const DOCTEST_REF_WRAP(L) val) {

+            m_failed = !val;

+

+            if (m_at & assertType::is_false) { //!OCLINT bitwise operator in conditional

+                m_failed = !m_failed;

+            }

+

+            if (m_failed || getContextOptions()->success) {

+                m_decomp = (DOCTEST_STRINGIFY(val));

+            }

+

+            return !m_failed;

+        }

+

+        void translateException();

+

+        bool log();

+        void react() const;

+    };

+

+    namespace assertAction {

+        enum Enum

+        {

+            nothing     = 0,

+            dbgbreak    = 1,

+            shouldthrow = 2

+        };

+    } // namespace assertAction

+

+    DOCTEST_INTERFACE void failed_out_of_a_testing_context(const AssertData& ad);

+

+    DOCTEST_INTERFACE bool decomp_assert(assertType::Enum at, const char* file, int line,

+                                         const char* expr, const Result& result);

+

+#define DOCTEST_ASSERT_OUT_OF_TESTS(decomp)                                                        \

+    do {                                                                                           \

+        if(!is_running_in_test) {                                                                  \

+            if(failed) {                                                                           \

+                ResultBuilder rb(at, file, line, expr);                                            \

+                rb.m_failed = failed;                                                              \

+                rb.m_decomp = decomp;                                                              \

+                failed_out_of_a_testing_context(rb);                                               \

+                if(isDebuggerActive() && !getContextOptions()->no_breaks)                          \

+                    DOCTEST_BREAK_INTO_DEBUGGER();                                                 \

+                if(checkIfShouldThrow(at))                                                         \

+                    throwException();                                                              \

+            }                                                                                      \

+            return !failed;                                                                        \

+        }                                                                                          \

+    } while(false)

+

+#define DOCTEST_ASSERT_IN_TESTS(decomp)                                                            \

+    ResultBuilder rb(at, file, line, expr);                                                        \

+    rb.m_failed = failed;                                                                          \

+    if(rb.m_failed || getContextOptions()->success)                                                \

+        rb.m_decomp = decomp;                                                                      \

+    if(rb.log())                                                                                   \

+        DOCTEST_BREAK_INTO_DEBUGGER();                                                             \

+    if(rb.m_failed && checkIfShouldThrow(at))                                                      \

+    throwException()

+

+    template <int comparison, typename L, typename R>

+    DOCTEST_NOINLINE bool binary_assert(assertType::Enum at, const char* file, int line,

+                                        const char* expr, const DOCTEST_REF_WRAP(L) lhs,

+                                        const DOCTEST_REF_WRAP(R) rhs) {

+        bool failed = !RelationalComparator<comparison, L, R>()(lhs, rhs);

+

+        // ###################################################################################

+        // IF THE DEBUGGER BREAKS HERE - GO 1 LEVEL UP IN THE CALLSTACK FOR THE FAILING ASSERT

+        // THIS IS THE EFFECT OF HAVING 'DOCTEST_CONFIG_SUPER_FAST_ASSERTS' DEFINED

+        // ###################################################################################

+        DOCTEST_ASSERT_OUT_OF_TESTS(stringifyBinaryExpr(lhs, ", ", rhs));

+        DOCTEST_ASSERT_IN_TESTS(stringifyBinaryExpr(lhs, ", ", rhs));

+        return !failed;

+    }

+

+    template <typename L>

+    DOCTEST_NOINLINE bool unary_assert(assertType::Enum at, const char* file, int line,

+                                       const char* expr, const DOCTEST_REF_WRAP(L) val) {

+        bool failed = !val;

+

+        if(at & assertType::is_false) //!OCLINT bitwise operator in conditional

+            failed = !failed;

+

+        // ###################################################################################

+        // IF THE DEBUGGER BREAKS HERE - GO 1 LEVEL UP IN THE CALLSTACK FOR THE FAILING ASSERT

+        // THIS IS THE EFFECT OF HAVING 'DOCTEST_CONFIG_SUPER_FAST_ASSERTS' DEFINED

+        // ###################################################################################

+        DOCTEST_ASSERT_OUT_OF_TESTS((DOCTEST_STRINGIFY(val)));

+        DOCTEST_ASSERT_IN_TESTS((DOCTEST_STRINGIFY(val)));

+        return !failed;

+    }

+

+    struct DOCTEST_INTERFACE IExceptionTranslator

+    {

+        DOCTEST_DECLARE_INTERFACE(IExceptionTranslator)

+        virtual bool translate(String&) const = 0;

+    };

+

+    template <typename T>

+    class ExceptionTranslator : public IExceptionTranslator //!OCLINT destructor of virtual class

+    {

+    public:

+        explicit ExceptionTranslator(String (*translateFunction)(T))

+                : m_translateFunction(translateFunction) {}

+

+        bool translate(String& res) const override {

+#ifndef DOCTEST_CONFIG_NO_EXCEPTIONS

+            try {

+                throw; // lgtm [cpp/rethrow-no-exception]

+                // cppcheck-suppress catchExceptionByValue

+            } catch(const T& ex) {

+                res = m_translateFunction(ex); //!OCLINT parameter reassignment

+                return true;

+            } catch(...) {}         //!OCLINT -  empty catch statement

+#endif                              // DOCTEST_CONFIG_NO_EXCEPTIONS

+            static_cast<void>(res); // to silence -Wunused-parameter

+            return false;

+        }

+

+    private:

+        String (*m_translateFunction)(T);

+    };

+

+    DOCTEST_INTERFACE void registerExceptionTranslatorImpl(const IExceptionTranslator* et);

+

+    // ContextScope base class used to allow implementing methods of ContextScope

+    // that don't depend on the template parameter in doctest.cpp.

+    struct DOCTEST_INTERFACE ContextScopeBase : public IContextScope {

+        ContextScopeBase(const ContextScopeBase&) = delete;

+

+        ContextScopeBase& operator=(const ContextScopeBase&) = delete;

+        ContextScopeBase& operator=(ContextScopeBase&&) = delete;

+

+        ~ContextScopeBase() override = default;

+

+    protected:

+        ContextScopeBase();

+        ContextScopeBase(ContextScopeBase&& other) noexcept;

+

+        void destroy();

+        bool need_to_destroy{true};

+    };

+

+    template <typename L> class ContextScope : public ContextScopeBase

+    {

+        L lambda_;

+

+    public:

+        explicit ContextScope(const L &lambda) : lambda_(lambda) {}

+        explicit ContextScope(L&& lambda) : lambda_(static_cast<L&&>(lambda)) { }

+

+        ContextScope(const ContextScope&) = delete;

+        ContextScope(ContextScope&&) noexcept = default;

+

+        ContextScope& operator=(const ContextScope&) = delete;

+        ContextScope& operator=(ContextScope&&) = delete;

+

+        void stringify(std::ostream* s) const override { lambda_(s); }

+

+        ~ContextScope() override {

+            if (need_to_destroy) {

+                destroy();

+            }

+        }

+    };

+

+    struct DOCTEST_INTERFACE MessageBuilder : public MessageData

+    {

+        std::ostream* m_stream;

+        bool          logged = false;

+

+        MessageBuilder(const char* file, int line, assertType::Enum severity);

+

+        MessageBuilder(const MessageBuilder&) = delete;

+        MessageBuilder(MessageBuilder&&) = delete;

+

+        MessageBuilder& operator=(const MessageBuilder&) = delete;

+        MessageBuilder& operator=(MessageBuilder&&) = delete;

+

+        ~MessageBuilder();

+

+        // the preferred way of chaining parameters for stringification

+DOCTEST_MSVC_SUPPRESS_WARNING_WITH_PUSH(4866)

+        template <typename T>

+        MessageBuilder& operator,(const T& in) {

+            *m_stream << (DOCTEST_STRINGIFY(in));

+            return *this;

+        }

+DOCTEST_MSVC_SUPPRESS_WARNING_POP

+

+        // kept here just for backwards-compatibility - the comma operator should be preferred now

+        template <typename T>

+        MessageBuilder& operator<<(const T& in) { return this->operator,(in); }

+

+        // the `,` operator has the lowest operator precedence - if `<<` is used by the user then

+        // the `,` operator will be called last which is not what we want and thus the `*` operator

+        // is used first (has higher operator precedence compared to `<<`) so that we guarantee that

+        // an operator of the MessageBuilder class is called first before the rest of the parameters

+        template <typename T>

+        MessageBuilder& operator*(const T& in) { return this->operator,(in); }

+

+        bool log();

+        void react();

+    };

+

+    template <typename L>

+    ContextScope<L> MakeContextScope(const L &lambda) {

+        return ContextScope<L>(lambda);

+    }

+} // namespace detail

+

+#define DOCTEST_DEFINE_DECORATOR(name, type, def)                                                  \

+    struct name                                                                                    \

+    {                                                                                              \

+        type data;                                                                                 \

+        name(type in = def)                                                                        \

+                : data(in) {}                                                                      \

+        void fill(detail::TestCase& state) const { state.DOCTEST_CAT(m_, name) = data; }           \

+        void fill(detail::TestSuite& state) const { state.DOCTEST_CAT(m_, name) = data; }          \

+    }

+

+DOCTEST_DEFINE_DECORATOR(test_suite, const char*, "");

+DOCTEST_DEFINE_DECORATOR(description, const char*, "");

+DOCTEST_DEFINE_DECORATOR(skip, bool, true);

+DOCTEST_DEFINE_DECORATOR(no_breaks, bool, true);

+DOCTEST_DEFINE_DECORATOR(no_output, bool, true);

+DOCTEST_DEFINE_DECORATOR(timeout, double, 0);

+DOCTEST_DEFINE_DECORATOR(may_fail, bool, true);

+DOCTEST_DEFINE_DECORATOR(should_fail, bool, true);

+DOCTEST_DEFINE_DECORATOR(expected_failures, int, 0);

+

+template <typename T>

+int registerExceptionTranslator(String (*translateFunction)(T)) {

+    DOCTEST_CLANG_SUPPRESS_WARNING_WITH_PUSH("-Wexit-time-destructors")

+    static detail::ExceptionTranslator<T> exceptionTranslator(translateFunction);

+    DOCTEST_CLANG_SUPPRESS_WARNING_POP

+    detail::registerExceptionTranslatorImpl(&exceptionTranslator);

+    return 0;

+}

+

+} // namespace doctest

+

+// in a separate namespace outside of doctest because the DOCTEST_TEST_SUITE macro

+// introduces an anonymous namespace in which getCurrentTestSuite gets overridden

+namespace doctest_detail_test_suite_ns {

+DOCTEST_INTERFACE doctest::detail::TestSuite& getCurrentTestSuite();

+} // namespace doctest_detail_test_suite_ns

+

+namespace doctest {

+#else  // DOCTEST_CONFIG_DISABLE

+template <typename T>

+int registerExceptionTranslator(String (*)(T)) {

+    return 0;

+}

+#endif // DOCTEST_CONFIG_DISABLE

+

+namespace detail {

+    using assert_handler = void (*)(const AssertData&);

+    struct ContextState;

+} // namespace detail

+

+class DOCTEST_INTERFACE Context

+{

+    detail::ContextState* p;

+

+    void parseArgs(int argc, const char* const* argv, bool withDefaults = false);

+

+public:

+    explicit Context(int argc = 0, const char* const* argv = nullptr);

+

+    Context(const Context&) = delete;

+    Context(Context&&) = delete;

+

+    Context& operator=(const Context&) = delete;

+    Context& operator=(Context&&) = delete;

+

+    ~Context(); // NOLINT(performance-trivially-destructible)

+

+    void applyCommandLine(int argc, const char* const* argv);

+

+    void addFilter(const char* filter, const char* value);

+    void clearFilters();

+    void setOption(const char* option, bool value);

+    void setOption(const char* option, int value);

+    void setOption(const char* option, const char* value);

+

+    bool shouldExit();

+

+    void setAsDefaultForAssertsOutOfTestCases();

+

+    void setAssertHandler(detail::assert_handler ah);

+

+    void setCout(std::ostream* out);

+

+    int run();

+};

+

+namespace TestCaseFailureReason {

+    enum Enum

+    {

+        None                     = 0,

+        AssertFailure            = 1,   // an assertion has failed in the test case

+        Exception                = 2,   // test case threw an exception

+        Crash                    = 4,   // a crash...

+        TooManyFailedAsserts     = 8,   // the abort-after option

+        Timeout                  = 16,  // see the timeout decorator

+        ShouldHaveFailedButDidnt = 32,  // see the should_fail decorator

+        ShouldHaveFailedAndDid   = 64,  // see the should_fail decorator

+        DidntFailExactlyNumTimes = 128, // see the expected_failures decorator

+        FailedExactlyNumTimes    = 256, // see the expected_failures decorator

+        CouldHaveFailedAndDid    = 512  // see the may_fail decorator

+    };

+} // namespace TestCaseFailureReason

+

+struct DOCTEST_INTERFACE CurrentTestCaseStats

+{

+    int    numAssertsCurrentTest;

+    int    numAssertsFailedCurrentTest;

+    double seconds;

+    int    failure_flags; // use TestCaseFailureReason::Enum

+    bool   testCaseSuccess;

+};

+

+struct DOCTEST_INTERFACE TestCaseException

+{

+    String error_string;

+    bool   is_crash;

+};

+

+struct DOCTEST_INTERFACE TestRunStats

+{

+    unsigned numTestCases;

+    unsigned numTestCasesPassingFilters;

+    unsigned numTestSuitesPassingFilters;

+    unsigned numTestCasesFailed;

+    int      numAsserts;

+    int      numAssertsFailed;

+};

+

+struct QueryData

+{

+    const TestRunStats*  run_stats = nullptr;

+    const TestCaseData** data      = nullptr;

+    unsigned             num_data  = 0;

+};

+

+struct DOCTEST_INTERFACE IReporter

+{

+    // The constructor has to accept "const ContextOptions&" as a single argument

+    // which has most of the options for the run + a pointer to the stdout stream

+    // Reporter(const ContextOptions& in)

+

+    // called when a query should be reported (listing test cases, printing the version, etc.)

+    virtual void report_query(const QueryData&) = 0;

+

+    // called when the whole test run starts

+    virtual void test_run_start() = 0;

+    // called when the whole test run ends (caching a pointer to the input doesn't make sense here)

+    virtual void test_run_end(const TestRunStats&) = 0;

+

+    // called when a test case is started (safe to cache a pointer to the input)

+    virtual void test_case_start(const TestCaseData&) = 0;

+    // called when a test case is reentered because of unfinished subcases (safe to cache a pointer to the input)

+    virtual void test_case_reenter(const TestCaseData&) = 0;

+    // called when a test case has ended

+    virtual void test_case_end(const CurrentTestCaseStats&) = 0;

+

+    // called when an exception is thrown from the test case (or it crashes)

+    virtual void test_case_exception(const TestCaseException&) = 0;

+

+    // called whenever a subcase is entered (don't cache pointers to the input)

+    virtual void subcase_start(const SubcaseSignature&) = 0;

+    // called whenever a subcase is exited (don't cache pointers to the input)

+    virtual void subcase_end() = 0;

+

+    // called for each assert (don't cache pointers to the input)

+    virtual void log_assert(const AssertData&) = 0;

+    // called for each message (don't cache pointers to the input)

+    virtual void log_message(const MessageData&) = 0;

+

+    // called when a test case is skipped either because it doesn't pass the filters, has a skip decorator

+    // or isn't in the execution range (between first and last) (safe to cache a pointer to the input)

+    virtual void test_case_skipped(const TestCaseData&) = 0;

+

+    DOCTEST_DECLARE_INTERFACE(IReporter)

+

+    // can obtain all currently active contexts and stringify them if one wishes to do so

+    static int                         get_num_active_contexts();

+    static const IContextScope* const* get_active_contexts();

+

+    // can iterate through contexts which have been stringified automatically in their destructors when an exception has been thrown

+    static int           get_num_stringified_contexts();

+    static const String* get_stringified_contexts();

+};

+

+namespace detail {

+    using reporterCreatorFunc =  IReporter* (*)(const ContextOptions&);

+

+    DOCTEST_INTERFACE void registerReporterImpl(const char* name, int prio, reporterCreatorFunc c, bool isReporter);

+

+    template <typename Reporter>

+    IReporter* reporterCreator(const ContextOptions& o) {

+        return new Reporter(o);

+    }

+} // namespace detail

+

+template <typename Reporter>

+int registerReporter(const char* name, int priority, bool isReporter) {

+    detail::registerReporterImpl(name, priority, detail::reporterCreator<Reporter>, isReporter);

+    return 0;

+}

+} // namespace doctest

+

+#ifdef DOCTEST_CONFIG_ASSERTS_RETURN_VALUES

+#define DOCTEST_FUNC_EMPTY [] { return false; }()

+#else

+#define DOCTEST_FUNC_EMPTY (void)0

+#endif

+

+// if registering is not disabled

+#ifndef DOCTEST_CONFIG_DISABLE

+

+#ifdef DOCTEST_CONFIG_ASSERTS_RETURN_VALUES

+#define DOCTEST_FUNC_SCOPE_BEGIN [&]

+#define DOCTEST_FUNC_SCOPE_END ()

+#define DOCTEST_FUNC_SCOPE_RET(v) return v

+#else

+#define DOCTEST_FUNC_SCOPE_BEGIN do

+#define DOCTEST_FUNC_SCOPE_END while(false)

+#define DOCTEST_FUNC_SCOPE_RET(v) (void)0

+#endif

+

+// common code in asserts - for convenience

+#define DOCTEST_ASSERT_LOG_REACT_RETURN(b)                                                         \

+    if(b.log()) DOCTEST_BREAK_INTO_DEBUGGER();                                                     \

+    b.react();                                                                                     \

+    DOCTEST_FUNC_SCOPE_RET(!b.m_failed)

+

+#ifdef DOCTEST_CONFIG_NO_TRY_CATCH_IN_ASSERTS

+#define DOCTEST_WRAP_IN_TRY(x) x;

+#else // DOCTEST_CONFIG_NO_TRY_CATCH_IN_ASSERTS

+#define DOCTEST_WRAP_IN_TRY(x)                                                                     \

+    try {                                                                                          \

+        x;                                                                                         \

+    } catch(...) { DOCTEST_RB.translateException(); }

+#endif // DOCTEST_CONFIG_NO_TRY_CATCH_IN_ASSERTS

+

+#ifdef DOCTEST_CONFIG_VOID_CAST_EXPRESSIONS

+#define DOCTEST_CAST_TO_VOID(...)                                                                  \

+    DOCTEST_GCC_SUPPRESS_WARNING_WITH_PUSH("-Wuseless-cast")                                       \

+    static_cast<void>(__VA_ARGS__);                                                                \

+    DOCTEST_GCC_SUPPRESS_WARNING_POP

+#else // DOCTEST_CONFIG_VOID_CAST_EXPRESSIONS

+#define DOCTEST_CAST_TO_VOID(...) __VA_ARGS__;

+#endif // DOCTEST_CONFIG_VOID_CAST_EXPRESSIONS

+

+// registers the test by initializing a dummy var with a function

+#define DOCTEST_REGISTER_FUNCTION(global_prefix, f, decorators)                                    \

+    global_prefix DOCTEST_GLOBAL_NO_WARNINGS(DOCTEST_ANONYMOUS(DOCTEST_ANON_VAR_), /* NOLINT */    \

+            doctest::detail::regTest(                                                              \

+                    doctest::detail::TestCase(                                                     \

+                            f, __FILE__, __LINE__,                                                 \

+                            doctest_detail_test_suite_ns::getCurrentTestSuite()) *                 \

+                    decorators))

+

+#define DOCTEST_IMPLEMENT_FIXTURE(der, base, func, decorators)                                     \

+    namespace { /* NOLINT */                                                                       \

+        struct der : public base                                                                   \

+        {                                                                                          \

+            void f();                                                                              \

+        };                                                                                         \

+        static inline DOCTEST_NOINLINE void func() {                                               \

+            der v;                                                                                 \

+            v.f();                                                                                 \

+        }                                                                                          \

+        DOCTEST_REGISTER_FUNCTION(DOCTEST_EMPTY, func, decorators)                                 \

+    }                                                                                              \

+    inline DOCTEST_NOINLINE void der::f() // NOLINT(misc-definitions-in-headers)

+

+#define DOCTEST_CREATE_AND_REGISTER_FUNCTION(f, decorators)                                        \

+    static void f();                                                                               \

+    DOCTEST_REGISTER_FUNCTION(DOCTEST_EMPTY, f, decorators)                                        \

+    static void f()

+

+#define DOCTEST_CREATE_AND_REGISTER_FUNCTION_IN_CLASS(f, proxy, decorators)                        \

+    static doctest::detail::funcType proxy() { return f; }                                         \

+    DOCTEST_REGISTER_FUNCTION(inline, proxy(), decorators)                                         \

+    static void f()

+

+// for registering tests

+#define DOCTEST_TEST_CASE(decorators)                                                              \

+    DOCTEST_CREATE_AND_REGISTER_FUNCTION(DOCTEST_ANONYMOUS(DOCTEST_ANON_FUNC_), decorators)

+

+// for registering tests in classes - requires C++17 for inline variables!

+#if DOCTEST_CPLUSPLUS >= 201703L

+#define DOCTEST_TEST_CASE_CLASS(decorators)                                                        \

+    DOCTEST_CREATE_AND_REGISTER_FUNCTION_IN_CLASS(DOCTEST_ANONYMOUS(DOCTEST_ANON_FUNC_),           \

+                                                  DOCTEST_ANONYMOUS(DOCTEST_ANON_PROXY_),          \

+                                                  decorators)

+#else // DOCTEST_TEST_CASE_CLASS

+#define DOCTEST_TEST_CASE_CLASS(...)                                                               \

+    TEST_CASES_CAN_BE_REGISTERED_IN_CLASSES_ONLY_IN_CPP17_MODE_OR_WITH_VS_2017_OR_NEWER

+#endif // DOCTEST_TEST_CASE_CLASS

+

+// for registering tests with a fixture

+#define DOCTEST_TEST_CASE_FIXTURE(c, decorators)                                                   \

+    DOCTEST_IMPLEMENT_FIXTURE(DOCTEST_ANONYMOUS(DOCTEST_ANON_CLASS_), c,                           \

+                              DOCTEST_ANONYMOUS(DOCTEST_ANON_FUNC_), decorators)

+

+// for converting types to strings without the <typeinfo> header and demangling

+#define DOCTEST_TYPE_TO_STRING_AS(str, ...)                                                        \

+    namespace doctest {                                                                            \

+        template <>                                                                                \

+        inline String toString<__VA_ARGS__>() {                                                    \

+            return str;                                                                            \

+        }                                                                                          \

+    }                                                                                              \

+    static_assert(true, "")

+

+#define DOCTEST_TYPE_TO_STRING(...) DOCTEST_TYPE_TO_STRING_AS(#__VA_ARGS__, __VA_ARGS__)

+

+#define DOCTEST_TEST_CASE_TEMPLATE_DEFINE_IMPL(dec, T, iter, func)                                 \

+    template <typename T>                                                                          \

+    static void func();                                                                            \

+    namespace { /* NOLINT */                                                                       \

+        template <typename Tuple>                                                                  \

+        struct iter;                                                                               \

+        template <typename Type, typename... Rest>                                                 \

+        struct iter<std::tuple<Type, Rest...>>                                                     \

+        {                                                                                          \

+            iter(const char* file, unsigned line, int index) {                                     \

+                doctest::detail::regTest(doctest::detail::TestCase(func<Type>, file, line,         \

+                                            doctest_detail_test_suite_ns::getCurrentTestSuite(),   \

+                                            doctest::toString<Type>(),                             \

+                                            int(line) * 1000 + index)                              \

+                                         * dec);                                                   \

+                iter<std::tuple<Rest...>>(file, line, index + 1);                                  \

+            }                                                                                      \

+        };                                                                                         \

+        template <>                                                                                \

+        struct iter<std::tuple<>>                                                                  \

+        {                                                                                          \

+            iter(const char*, unsigned, int) {}                                                    \

+        };                                                                                         \

+    }                                                                                              \

+    template <typename T>                                                                          \

+    static void func()

+

+#define DOCTEST_TEST_CASE_TEMPLATE_DEFINE(dec, T, id)                                              \

+    DOCTEST_TEST_CASE_TEMPLATE_DEFINE_IMPL(dec, T, DOCTEST_CAT(id, ITERATOR),                      \

+                                           DOCTEST_ANONYMOUS(DOCTEST_ANON_TMP_))

+

+#define DOCTEST_TEST_CASE_TEMPLATE_INSTANTIATE_IMPL(id, anon, ...)                                 \

+    DOCTEST_GLOBAL_NO_WARNINGS(DOCTEST_CAT(anon, DUMMY), /* NOLINT(cert-err58-cpp, fuchsia-statically-constructed-objects) */ \

+        doctest::detail::instantiationHelper(                                                      \

+            DOCTEST_CAT(id, ITERATOR)<__VA_ARGS__>(__FILE__, __LINE__, 0)))

+

+#define DOCTEST_TEST_CASE_TEMPLATE_INVOKE(id, ...)                                                 \

+    DOCTEST_TEST_CASE_TEMPLATE_INSTANTIATE_IMPL(id, DOCTEST_ANONYMOUS(DOCTEST_ANON_TMP_), std::tuple<__VA_ARGS__>) \

+    static_assert(true, "")

+

+#define DOCTEST_TEST_CASE_TEMPLATE_APPLY(id, ...)                                                  \

+    DOCTEST_TEST_CASE_TEMPLATE_INSTANTIATE_IMPL(id, DOCTEST_ANONYMOUS(DOCTEST_ANON_TMP_), __VA_ARGS__) \

+    static_assert(true, "")

+

+#define DOCTEST_TEST_CASE_TEMPLATE_IMPL(dec, T, anon, ...)                                         \

+    DOCTEST_TEST_CASE_TEMPLATE_DEFINE_IMPL(dec, T, DOCTEST_CAT(anon, ITERATOR), anon);             \

+    DOCTEST_TEST_CASE_TEMPLATE_INSTANTIATE_IMPL(anon, anon, std::tuple<__VA_ARGS__>)               \

+    template <typename T>                                                                          \

+    static void anon()

+

+#define DOCTEST_TEST_CASE_TEMPLATE(dec, T, ...)                                                    \

+    DOCTEST_TEST_CASE_TEMPLATE_IMPL(dec, T, DOCTEST_ANONYMOUS(DOCTEST_ANON_TMP_), __VA_ARGS__)

+

+// for subcases

+#define DOCTEST_SUBCASE(name)                                                                      \

+    if(const doctest::detail::Subcase & DOCTEST_ANONYMOUS(DOCTEST_ANON_SUBCASE_) DOCTEST_UNUSED =  \

+               doctest::detail::Subcase(name, __FILE__, __LINE__))

+

+// for grouping tests in test suites by using code blocks

+#define DOCTEST_TEST_SUITE_IMPL(decorators, ns_name)                                               \

+    namespace ns_name { namespace doctest_detail_test_suite_ns {                                   \

+            static DOCTEST_NOINLINE doctest::detail::TestSuite& getCurrentTestSuite() noexcept {   \

+                DOCTEST_MSVC_SUPPRESS_WARNING_WITH_PUSH(4640)                                      \

+                DOCTEST_CLANG_SUPPRESS_WARNING_WITH_PUSH("-Wexit-time-destructors")                \

+                DOCTEST_GCC_SUPPRESS_WARNING_WITH_PUSH("-Wmissing-field-initializers")             \

+                static doctest::detail::TestSuite data{};                                          \

+                static bool                       inited = false;                                  \

+                DOCTEST_MSVC_SUPPRESS_WARNING_POP                                                  \

+                DOCTEST_CLANG_SUPPRESS_WARNING_POP                                                 \

+                DOCTEST_GCC_SUPPRESS_WARNING_POP                                                   \

+                if(!inited) {                                                                      \

+                    data* decorators;                                                              \

+                    inited = true;                                                                 \

+                }                                                                                  \

+                return data;                                                                       \

+            }                                                                                      \

+        }                                                                                          \

+    }                                                                                              \

+    namespace ns_name

+

+#define DOCTEST_TEST_SUITE(decorators)                                                             \

+    DOCTEST_TEST_SUITE_IMPL(decorators, DOCTEST_ANONYMOUS(DOCTEST_ANON_SUITE_))

+

+// for starting a testsuite block

+#define DOCTEST_TEST_SUITE_BEGIN(decorators)                                                       \

+    DOCTEST_GLOBAL_NO_WARNINGS(DOCTEST_ANONYMOUS(DOCTEST_ANON_VAR_), /* NOLINT(cert-err58-cpp) */  \

+            doctest::detail::setTestSuite(doctest::detail::TestSuite() * decorators))              \

+    static_assert(true, "")

+

+// for ending a testsuite block

+#define DOCTEST_TEST_SUITE_END                                                                     \

+    DOCTEST_GLOBAL_NO_WARNINGS(DOCTEST_ANONYMOUS(DOCTEST_ANON_VAR_), /* NOLINT(cert-err58-cpp) */  \

+            doctest::detail::setTestSuite(doctest::detail::TestSuite() * ""))                      \

+    using DOCTEST_ANONYMOUS(DOCTEST_ANON_FOR_SEMICOLON_) = int

+

+// for registering exception translators

+#define DOCTEST_REGISTER_EXCEPTION_TRANSLATOR_IMPL(translatorName, signature)                      \

+    inline doctest::String translatorName(signature);                                              \

+    DOCTEST_GLOBAL_NO_WARNINGS(DOCTEST_ANONYMOUS(DOCTEST_ANON_TRANSLATOR_), /* NOLINT(cert-err58-cpp) */ \

+            doctest::registerExceptionTranslator(translatorName))                                  \

+    doctest::String translatorName(signature)

+

+#define DOCTEST_REGISTER_EXCEPTION_TRANSLATOR(signature)                                           \

+    DOCTEST_REGISTER_EXCEPTION_TRANSLATOR_IMPL(DOCTEST_ANONYMOUS(DOCTEST_ANON_TRANSLATOR_),        \

+                                               signature)

+

+// for registering reporters

+#define DOCTEST_REGISTER_REPORTER(name, priority, reporter)                                        \

+    DOCTEST_GLOBAL_NO_WARNINGS(DOCTEST_ANONYMOUS(DOCTEST_ANON_REPORTER_), /* NOLINT(cert-err58-cpp) */ \

+            doctest::registerReporter<reporter>(name, priority, true))                             \

+    static_assert(true, "")

+

+// for registering listeners

+#define DOCTEST_REGISTER_LISTENER(name, priority, reporter)                                        \

+    DOCTEST_GLOBAL_NO_WARNINGS(DOCTEST_ANONYMOUS(DOCTEST_ANON_REPORTER_), /* NOLINT(cert-err58-cpp) */ \

+            doctest::registerReporter<reporter>(name, priority, false))                            \

+    static_assert(true, "")

+

+// clang-format off

+// for logging - disabling formatting because it's important to have these on 2 separate lines - see PR #557

+#define DOCTEST_INFO(...)                                                                          \

+    DOCTEST_INFO_IMPL(DOCTEST_ANONYMOUS(DOCTEST_CAPTURE_),                                         \

+                      DOCTEST_ANONYMOUS(DOCTEST_CAPTURE_OTHER_),                                   \

+                      __VA_ARGS__)

+// clang-format on

+

+#define DOCTEST_INFO_IMPL(mb_name, s_name, ...)                                       \

+    auto DOCTEST_ANONYMOUS(DOCTEST_CAPTURE_) = doctest::detail::MakeContextScope(                  \

+        [&](std::ostream* s_name) {                                                                \

+        doctest::detail::MessageBuilder mb_name(__FILE__, __LINE__, doctest::assertType::is_warn); \

+        mb_name.m_stream = s_name;                                                                 \

+        mb_name * __VA_ARGS__;                                                                     \

+    })

+

+#define DOCTEST_CAPTURE(x) DOCTEST_INFO(#x " := ", x)

+

+#define DOCTEST_ADD_AT_IMPL(type, file, line, mb, ...)                                             \

+    DOCTEST_FUNC_SCOPE_BEGIN {                                                                     \

+        doctest::detail::MessageBuilder mb(file, line, doctest::assertType::type);                 \

+        mb * __VA_ARGS__;                                                                          \

+        if(mb.log())                                                                               \

+            DOCTEST_BREAK_INTO_DEBUGGER();                                                         \

+        mb.react();                                                                                \

+    } DOCTEST_FUNC_SCOPE_END

+

+// clang-format off

+#define DOCTEST_ADD_MESSAGE_AT(file, line, ...) DOCTEST_ADD_AT_IMPL(is_warn, file, line, DOCTEST_ANONYMOUS(DOCTEST_MESSAGE_), __VA_ARGS__)

+#define DOCTEST_ADD_FAIL_CHECK_AT(file, line, ...) DOCTEST_ADD_AT_IMPL(is_check, file, line, DOCTEST_ANONYMOUS(DOCTEST_MESSAGE_), __VA_ARGS__)

+#define DOCTEST_ADD_FAIL_AT(file, line, ...) DOCTEST_ADD_AT_IMPL(is_require, file, line, DOCTEST_ANONYMOUS(DOCTEST_MESSAGE_), __VA_ARGS__)

+// clang-format on

+

+#define DOCTEST_MESSAGE(...) DOCTEST_ADD_MESSAGE_AT(__FILE__, __LINE__, __VA_ARGS__)

+#define DOCTEST_FAIL_CHECK(...) DOCTEST_ADD_FAIL_CHECK_AT(__FILE__, __LINE__, __VA_ARGS__)

+#define DOCTEST_FAIL(...) DOCTEST_ADD_FAIL_AT(__FILE__, __LINE__, __VA_ARGS__)

+

+#define DOCTEST_TO_LVALUE(...) __VA_ARGS__ // Not removed to keep backwards compatibility.

+

+#ifndef DOCTEST_CONFIG_SUPER_FAST_ASSERTS

+

+#define DOCTEST_ASSERT_IMPLEMENT_2(assert_type, ...)                                               \

+    DOCTEST_CLANG_SUPPRESS_WARNING_WITH_PUSH("-Woverloaded-shift-op-parentheses")                  \

+    /* NOLINTNEXTLINE(clang-analyzer-cplusplus.NewDeleteLeaks) */                                  \

+    doctest::detail::ResultBuilder DOCTEST_RB(doctest::assertType::assert_type, __FILE__,          \

+                                               __LINE__, #__VA_ARGS__);                            \

+    DOCTEST_WRAP_IN_TRY(DOCTEST_RB.setResult(                                                      \

+            doctest::detail::ExpressionDecomposer(doctest::assertType::assert_type)                \

+            << __VA_ARGS__)) /* NOLINTNEXTLINE(clang-analyzer-cplusplus.NewDeleteLeaks) */         \

+    DOCTEST_ASSERT_LOG_REACT_RETURN(DOCTEST_RB)                                                    \

+    DOCTEST_CLANG_SUPPRESS_WARNING_POP

+

+#define DOCTEST_ASSERT_IMPLEMENT_1(assert_type, ...)                                               \

+    DOCTEST_FUNC_SCOPE_BEGIN {                                                                     \

+        DOCTEST_ASSERT_IMPLEMENT_2(assert_type, __VA_ARGS__);                                      \

+    } DOCTEST_FUNC_SCOPE_END // NOLINT(clang-analyzer-cplusplus.NewDeleteLeaks)

+

+#define DOCTEST_BINARY_ASSERT(assert_type, comp, ...)                                              \

+    DOCTEST_FUNC_SCOPE_BEGIN {                                                                     \

+        doctest::detail::ResultBuilder DOCTEST_RB(doctest::assertType::assert_type, __FILE__,      \

+                                                   __LINE__, #__VA_ARGS__);                        \

+        DOCTEST_WRAP_IN_TRY(                                                                       \

+                DOCTEST_RB.binary_assert<doctest::detail::binaryAssertComparison::comp>(           \

+                        __VA_ARGS__))                                                              \

+        DOCTEST_ASSERT_LOG_REACT_RETURN(DOCTEST_RB);                                               \

+    } DOCTEST_FUNC_SCOPE_END

+

+#define DOCTEST_UNARY_ASSERT(assert_type, ...)                                                     \

+    DOCTEST_FUNC_SCOPE_BEGIN {                                                                     \

+        doctest::detail::ResultBuilder DOCTEST_RB(doctest::assertType::assert_type, __FILE__,      \

+                                                   __LINE__, #__VA_ARGS__);                        \

+        DOCTEST_WRAP_IN_TRY(DOCTEST_RB.unary_assert(__VA_ARGS__))                                  \

+        DOCTEST_ASSERT_LOG_REACT_RETURN(DOCTEST_RB);                                               \

+    } DOCTEST_FUNC_SCOPE_END

+

+#else // DOCTEST_CONFIG_SUPER_FAST_ASSERTS

+

+// necessary for <ASSERT>_MESSAGE

+#define DOCTEST_ASSERT_IMPLEMENT_2 DOCTEST_ASSERT_IMPLEMENT_1

+

+#define DOCTEST_ASSERT_IMPLEMENT_1(assert_type, ...)                                               \

+    DOCTEST_CLANG_SUPPRESS_WARNING_WITH_PUSH("-Woverloaded-shift-op-parentheses")                  \

+    doctest::detail::decomp_assert(                                                                \

+            doctest::assertType::assert_type, __FILE__, __LINE__, #__VA_ARGS__,                    \

+            doctest::detail::ExpressionDecomposer(doctest::assertType::assert_type)                \

+                    << __VA_ARGS__) DOCTEST_CLANG_SUPPRESS_WARNING_POP

+

+#define DOCTEST_BINARY_ASSERT(assert_type, comparison, ...)                                        \

+    doctest::detail::binary_assert<doctest::detail::binaryAssertComparison::comparison>(           \

+            doctest::assertType::assert_type, __FILE__, __LINE__, #__VA_ARGS__, __VA_ARGS__)

+

+#define DOCTEST_UNARY_ASSERT(assert_type, ...)                                                     \

+    doctest::detail::unary_assert(doctest::assertType::assert_type, __FILE__, __LINE__,            \

+                                  #__VA_ARGS__, __VA_ARGS__)

+

+#endif // DOCTEST_CONFIG_SUPER_FAST_ASSERTS

+

+#define DOCTEST_WARN(...) DOCTEST_ASSERT_IMPLEMENT_1(DT_WARN, __VA_ARGS__)

+#define DOCTEST_CHECK(...) DOCTEST_ASSERT_IMPLEMENT_1(DT_CHECK, __VA_ARGS__)

+#define DOCTEST_REQUIRE(...) DOCTEST_ASSERT_IMPLEMENT_1(DT_REQUIRE, __VA_ARGS__)

+#define DOCTEST_WARN_FALSE(...) DOCTEST_ASSERT_IMPLEMENT_1(DT_WARN_FALSE, __VA_ARGS__)

+#define DOCTEST_CHECK_FALSE(...) DOCTEST_ASSERT_IMPLEMENT_1(DT_CHECK_FALSE, __VA_ARGS__)

+#define DOCTEST_REQUIRE_FALSE(...) DOCTEST_ASSERT_IMPLEMENT_1(DT_REQUIRE_FALSE, __VA_ARGS__)

+

+// clang-format off

+#define DOCTEST_WARN_MESSAGE(cond, ...) DOCTEST_FUNC_SCOPE_BEGIN { DOCTEST_INFO(__VA_ARGS__); DOCTEST_ASSERT_IMPLEMENT_2(DT_WARN, cond); } DOCTEST_FUNC_SCOPE_END

+#define DOCTEST_CHECK_MESSAGE(cond, ...) DOCTEST_FUNC_SCOPE_BEGIN { DOCTEST_INFO(__VA_ARGS__); DOCTEST_ASSERT_IMPLEMENT_2(DT_CHECK, cond); } DOCTEST_FUNC_SCOPE_END

+#define DOCTEST_REQUIRE_MESSAGE(cond, ...) DOCTEST_FUNC_SCOPE_BEGIN { DOCTEST_INFO(__VA_ARGS__); DOCTEST_ASSERT_IMPLEMENT_2(DT_REQUIRE, cond); } DOCTEST_FUNC_SCOPE_END

+#define DOCTEST_WARN_FALSE_MESSAGE(cond, ...) DOCTEST_FUNC_SCOPE_BEGIN { DOCTEST_INFO(__VA_ARGS__); DOCTEST_ASSERT_IMPLEMENT_2(DT_WARN_FALSE, cond); } DOCTEST_FUNC_SCOPE_END

+#define DOCTEST_CHECK_FALSE_MESSAGE(cond, ...) DOCTEST_FUNC_SCOPE_BEGIN { DOCTEST_INFO(__VA_ARGS__); DOCTEST_ASSERT_IMPLEMENT_2(DT_CHECK_FALSE, cond); } DOCTEST_FUNC_SCOPE_END

+#define DOCTEST_REQUIRE_FALSE_MESSAGE(cond, ...) DOCTEST_FUNC_SCOPE_BEGIN { DOCTEST_INFO(__VA_ARGS__); DOCTEST_ASSERT_IMPLEMENT_2(DT_REQUIRE_FALSE, cond); } DOCTEST_FUNC_SCOPE_END

+// clang-format on

+

+#define DOCTEST_WARN_EQ(...) DOCTEST_BINARY_ASSERT(DT_WARN_EQ, eq, __VA_ARGS__)

+#define DOCTEST_CHECK_EQ(...) DOCTEST_BINARY_ASSERT(DT_CHECK_EQ, eq, __VA_ARGS__)

+#define DOCTEST_REQUIRE_EQ(...) DOCTEST_BINARY_ASSERT(DT_REQUIRE_EQ, eq, __VA_ARGS__)

+#define DOCTEST_WARN_NE(...) DOCTEST_BINARY_ASSERT(DT_WARN_NE, ne, __VA_ARGS__)

+#define DOCTEST_CHECK_NE(...) DOCTEST_BINARY_ASSERT(DT_CHECK_NE, ne, __VA_ARGS__)

+#define DOCTEST_REQUIRE_NE(...) DOCTEST_BINARY_ASSERT(DT_REQUIRE_NE, ne, __VA_ARGS__)

+#define DOCTEST_WARN_GT(...) DOCTEST_BINARY_ASSERT(DT_WARN_GT, gt, __VA_ARGS__)

+#define DOCTEST_CHECK_GT(...) DOCTEST_BINARY_ASSERT(DT_CHECK_GT, gt, __VA_ARGS__)

+#define DOCTEST_REQUIRE_GT(...) DOCTEST_BINARY_ASSERT(DT_REQUIRE_GT, gt, __VA_ARGS__)

+#define DOCTEST_WARN_LT(...) DOCTEST_BINARY_ASSERT(DT_WARN_LT, lt, __VA_ARGS__)

+#define DOCTEST_CHECK_LT(...) DOCTEST_BINARY_ASSERT(DT_CHECK_LT, lt, __VA_ARGS__)

+#define DOCTEST_REQUIRE_LT(...) DOCTEST_BINARY_ASSERT(DT_REQUIRE_LT, lt, __VA_ARGS__)

+#define DOCTEST_WARN_GE(...) DOCTEST_BINARY_ASSERT(DT_WARN_GE, ge, __VA_ARGS__)

+#define DOCTEST_CHECK_GE(...) DOCTEST_BINARY_ASSERT(DT_CHECK_GE, ge, __VA_ARGS__)

+#define DOCTEST_REQUIRE_GE(...) DOCTEST_BINARY_ASSERT(DT_REQUIRE_GE, ge, __VA_ARGS__)

+#define DOCTEST_WARN_LE(...) DOCTEST_BINARY_ASSERT(DT_WARN_LE, le, __VA_ARGS__)

+#define DOCTEST_CHECK_LE(...) DOCTEST_BINARY_ASSERT(DT_CHECK_LE, le, __VA_ARGS__)

+#define DOCTEST_REQUIRE_LE(...) DOCTEST_BINARY_ASSERT(DT_REQUIRE_LE, le, __VA_ARGS__)

+

+#define DOCTEST_WARN_UNARY(...) DOCTEST_UNARY_ASSERT(DT_WARN_UNARY, __VA_ARGS__)

+#define DOCTEST_CHECK_UNARY(...) DOCTEST_UNARY_ASSERT(DT_CHECK_UNARY, __VA_ARGS__)

+#define DOCTEST_REQUIRE_UNARY(...) DOCTEST_UNARY_ASSERT(DT_REQUIRE_UNARY, __VA_ARGS__)

+#define DOCTEST_WARN_UNARY_FALSE(...) DOCTEST_UNARY_ASSERT(DT_WARN_UNARY_FALSE, __VA_ARGS__)

+#define DOCTEST_CHECK_UNARY_FALSE(...) DOCTEST_UNARY_ASSERT(DT_CHECK_UNARY_FALSE, __VA_ARGS__)

+#define DOCTEST_REQUIRE_UNARY_FALSE(...) DOCTEST_UNARY_ASSERT(DT_REQUIRE_UNARY_FALSE, __VA_ARGS__)

+

+#ifndef DOCTEST_CONFIG_NO_EXCEPTIONS

+

+#define DOCTEST_ASSERT_THROWS_AS(expr, assert_type, message, ...)                                  \

+    DOCTEST_FUNC_SCOPE_BEGIN {                                                                     \

+        if(!doctest::getContextOptions()->no_throw) {                                              \

+            doctest::detail::ResultBuilder DOCTEST_RB(doctest::assertType::assert_type, __FILE__,  \

+                                                       __LINE__, #expr, #__VA_ARGS__, message);    \

+            try {                                                                                  \

+                DOCTEST_CAST_TO_VOID(expr)                                                         \

+            } catch(const typename doctest::detail::types::remove_const<                           \

+                    typename doctest::detail::types::remove_reference<__VA_ARGS__>::type>::type&) {\

+                DOCTEST_RB.translateException();                                                   \

+                DOCTEST_RB.m_threw_as = true;                                                      \

+            } catch(...) { DOCTEST_RB.translateException(); }                                      \

+            DOCTEST_ASSERT_LOG_REACT_RETURN(DOCTEST_RB);                                           \

+        } else { /* NOLINT(*-else-after-return) */                                                 \

+            DOCTEST_FUNC_SCOPE_RET(false);                                                         \

+        }                                                                                          \

+    } DOCTEST_FUNC_SCOPE_END

+

+#define DOCTEST_ASSERT_THROWS_WITH(expr, expr_str, assert_type, ...)                               \

+    DOCTEST_FUNC_SCOPE_BEGIN {                                                                     \

+        if(!doctest::getContextOptions()->no_throw) {                                              \

+            doctest::detail::ResultBuilder DOCTEST_RB(doctest::assertType::assert_type, __FILE__,  \

+                                                       __LINE__, expr_str, "", __VA_ARGS__);       \

+            try {                                                                                  \

+                DOCTEST_CAST_TO_VOID(expr)                                                         \

+            } catch(...) { DOCTEST_RB.translateException(); }                                      \

+            DOCTEST_ASSERT_LOG_REACT_RETURN(DOCTEST_RB);                                           \

+        } else { /* NOLINT(*-else-after-return) */                                                 \

+           DOCTEST_FUNC_SCOPE_RET(false);                                                          \

+        }                                                                                          \

+    } DOCTEST_FUNC_SCOPE_END

+

+#define DOCTEST_ASSERT_NOTHROW(assert_type, ...)                                                   \

+    DOCTEST_FUNC_SCOPE_BEGIN {                                                                     \

+        doctest::detail::ResultBuilder DOCTEST_RB(doctest::assertType::assert_type, __FILE__,      \

+                                                   __LINE__, #__VA_ARGS__);                        \

+        try {                                                                                      \

+            DOCTEST_CAST_TO_VOID(__VA_ARGS__)                                                      \

+        } catch(...) { DOCTEST_RB.translateException(); }                                          \

+        DOCTEST_ASSERT_LOG_REACT_RETURN(DOCTEST_RB);                                               \

+    } DOCTEST_FUNC_SCOPE_END

+

+// clang-format off

+#define DOCTEST_WARN_THROWS(...) DOCTEST_ASSERT_THROWS_WITH((__VA_ARGS__), #__VA_ARGS__, DT_WARN_THROWS, "")

+#define DOCTEST_CHECK_THROWS(...) DOCTEST_ASSERT_THROWS_WITH((__VA_ARGS__), #__VA_ARGS__, DT_CHECK_THROWS, "")

+#define DOCTEST_REQUIRE_THROWS(...) DOCTEST_ASSERT_THROWS_WITH((__VA_ARGS__), #__VA_ARGS__, DT_REQUIRE_THROWS, "")

+

+#define DOCTEST_WARN_THROWS_AS(expr, ...) DOCTEST_ASSERT_THROWS_AS(expr, DT_WARN_THROWS_AS, "", __VA_ARGS__)

+#define DOCTEST_CHECK_THROWS_AS(expr, ...) DOCTEST_ASSERT_THROWS_AS(expr, DT_CHECK_THROWS_AS, "", __VA_ARGS__)

+#define DOCTEST_REQUIRE_THROWS_AS(expr, ...) DOCTEST_ASSERT_THROWS_AS(expr, DT_REQUIRE_THROWS_AS, "", __VA_ARGS__)

+

+#define DOCTEST_WARN_THROWS_WITH(expr, ...) DOCTEST_ASSERT_THROWS_WITH(expr, #expr, DT_WARN_THROWS_WITH, __VA_ARGS__)

+#define DOCTEST_CHECK_THROWS_WITH(expr, ...) DOCTEST_ASSERT_THROWS_WITH(expr, #expr, DT_CHECK_THROWS_WITH, __VA_ARGS__)

+#define DOCTEST_REQUIRE_THROWS_WITH(expr, ...) DOCTEST_ASSERT_THROWS_WITH(expr, #expr, DT_REQUIRE_THROWS_WITH, __VA_ARGS__)

+

+#define DOCTEST_WARN_THROWS_WITH_AS(expr, message, ...) DOCTEST_ASSERT_THROWS_AS(expr, DT_WARN_THROWS_WITH_AS, message, __VA_ARGS__)

+#define DOCTEST_CHECK_THROWS_WITH_AS(expr, message, ...) DOCTEST_ASSERT_THROWS_AS(expr, DT_CHECK_THROWS_WITH_AS, message, __VA_ARGS__)

+#define DOCTEST_REQUIRE_THROWS_WITH_AS(expr, message, ...) DOCTEST_ASSERT_THROWS_AS(expr, DT_REQUIRE_THROWS_WITH_AS, message, __VA_ARGS__)

+

+#define DOCTEST_WARN_NOTHROW(...) DOCTEST_ASSERT_NOTHROW(DT_WARN_NOTHROW, __VA_ARGS__)

+#define DOCTEST_CHECK_NOTHROW(...) DOCTEST_ASSERT_NOTHROW(DT_CHECK_NOTHROW, __VA_ARGS__)

+#define DOCTEST_REQUIRE_NOTHROW(...) DOCTEST_ASSERT_NOTHROW(DT_REQUIRE_NOTHROW, __VA_ARGS__)

+

+#define DOCTEST_WARN_THROWS_MESSAGE(expr, ...) DOCTEST_FUNC_SCOPE_BEGIN { DOCTEST_INFO(__VA_ARGS__); DOCTEST_WARN_THROWS(expr); } DOCTEST_FUNC_SCOPE_END

+#define DOCTEST_CHECK_THROWS_MESSAGE(expr, ...) DOCTEST_FUNC_SCOPE_BEGIN { DOCTEST_INFO(__VA_ARGS__); DOCTEST_CHECK_THROWS(expr); } DOCTEST_FUNC_SCOPE_END

+#define DOCTEST_REQUIRE_THROWS_MESSAGE(expr, ...) DOCTEST_FUNC_SCOPE_BEGIN { DOCTEST_INFO(__VA_ARGS__); DOCTEST_REQUIRE_THROWS(expr); } DOCTEST_FUNC_SCOPE_END

+#define DOCTEST_WARN_THROWS_AS_MESSAGE(expr, ex, ...) DOCTEST_FUNC_SCOPE_BEGIN { DOCTEST_INFO(__VA_ARGS__); DOCTEST_WARN_THROWS_AS(expr, ex); } DOCTEST_FUNC_SCOPE_END

+#define DOCTEST_CHECK_THROWS_AS_MESSAGE(expr, ex, ...) DOCTEST_FUNC_SCOPE_BEGIN { DOCTEST_INFO(__VA_ARGS__); DOCTEST_CHECK_THROWS_AS(expr, ex); } DOCTEST_FUNC_SCOPE_END

+#define DOCTEST_REQUIRE_THROWS_AS_MESSAGE(expr, ex, ...) DOCTEST_FUNC_SCOPE_BEGIN { DOCTEST_INFO(__VA_ARGS__); DOCTEST_REQUIRE_THROWS_AS(expr, ex); } DOCTEST_FUNC_SCOPE_END

+#define DOCTEST_WARN_THROWS_WITH_MESSAGE(expr, with, ...) DOCTEST_FUNC_SCOPE_BEGIN { DOCTEST_INFO(__VA_ARGS__); DOCTEST_WARN_THROWS_WITH(expr, with); } DOCTEST_FUNC_SCOPE_END

+#define DOCTEST_CHECK_THROWS_WITH_MESSAGE(expr, with, ...) DOCTEST_FUNC_SCOPE_BEGIN { DOCTEST_INFO(__VA_ARGS__); DOCTEST_CHECK_THROWS_WITH(expr, with); } DOCTEST_FUNC_SCOPE_END

+#define DOCTEST_REQUIRE_THROWS_WITH_MESSAGE(expr, with, ...) DOCTEST_FUNC_SCOPE_BEGIN { DOCTEST_INFO(__VA_ARGS__); DOCTEST_REQUIRE_THROWS_WITH(expr, with); } DOCTEST_FUNC_SCOPE_END

+#define DOCTEST_WARN_THROWS_WITH_AS_MESSAGE(expr, with, ex, ...) DOCTEST_FUNC_SCOPE_BEGIN { DOCTEST_INFO(__VA_ARGS__); DOCTEST_WARN_THROWS_WITH_AS(expr, with, ex); } DOCTEST_FUNC_SCOPE_END

+#define DOCTEST_CHECK_THROWS_WITH_AS_MESSAGE(expr, with, ex, ...) DOCTEST_FUNC_SCOPE_BEGIN { DOCTEST_INFO(__VA_ARGS__); DOCTEST_CHECK_THROWS_WITH_AS(expr, with, ex); } DOCTEST_FUNC_SCOPE_END

+#define DOCTEST_REQUIRE_THROWS_WITH_AS_MESSAGE(expr, with, ex, ...) DOCTEST_FUNC_SCOPE_BEGIN { DOCTEST_INFO(__VA_ARGS__); DOCTEST_REQUIRE_THROWS_WITH_AS(expr, with, ex); } DOCTEST_FUNC_SCOPE_END

+#define DOCTEST_WARN_NOTHROW_MESSAGE(expr, ...) DOCTEST_FUNC_SCOPE_BEGIN { DOCTEST_INFO(__VA_ARGS__); DOCTEST_WARN_NOTHROW(expr); } DOCTEST_FUNC_SCOPE_END

+#define DOCTEST_CHECK_NOTHROW_MESSAGE(expr, ...) DOCTEST_FUNC_SCOPE_BEGIN { DOCTEST_INFO(__VA_ARGS__); DOCTEST_CHECK_NOTHROW(expr); } DOCTEST_FUNC_SCOPE_END

+#define DOCTEST_REQUIRE_NOTHROW_MESSAGE(expr, ...) DOCTEST_FUNC_SCOPE_BEGIN { DOCTEST_INFO(__VA_ARGS__); DOCTEST_REQUIRE_NOTHROW(expr); } DOCTEST_FUNC_SCOPE_END

+// clang-format on

+

+#endif // DOCTEST_CONFIG_NO_EXCEPTIONS

+

+// =================================================================================================

+// == WHAT FOLLOWS IS VERSIONS OF THE MACROS THAT DO NOT DO ANY REGISTERING!                      ==

+// == THIS CAN BE ENABLED BY DEFINING DOCTEST_CONFIG_DISABLE GLOBALLY!                            ==

+// =================================================================================================

+#else // DOCTEST_CONFIG_DISABLE

+

+#define DOCTEST_IMPLEMENT_FIXTURE(der, base, func, name)                                           \

+    namespace /* NOLINT */ {                                                                       \

+        template <typename DOCTEST_UNUSED_TEMPLATE_TYPE>                                           \

+        struct der : public base                                                                   \

+        { void f(); };                                                                             \

+    }                                                                                              \

+    template <typename DOCTEST_UNUSED_TEMPLATE_TYPE>                                               \

+    inline void der<DOCTEST_UNUSED_TEMPLATE_TYPE>::f()

+

+#define DOCTEST_CREATE_AND_REGISTER_FUNCTION(f, name)                                              \

+    template <typename DOCTEST_UNUSED_TEMPLATE_TYPE>                                               \

+    static inline void f()

+

+// for registering tests

+#define DOCTEST_TEST_CASE(name)                                                                    \

+    DOCTEST_CREATE_AND_REGISTER_FUNCTION(DOCTEST_ANONYMOUS(DOCTEST_ANON_FUNC_), name)

+

+// for registering tests in classes

+#define DOCTEST_TEST_CASE_CLASS(name)                                                              \

+    DOCTEST_CREATE_AND_REGISTER_FUNCTION(DOCTEST_ANONYMOUS(DOCTEST_ANON_FUNC_), name)

+

+// for registering tests with a fixture

+#define DOCTEST_TEST_CASE_FIXTURE(x, name)                                                         \

+    DOCTEST_IMPLEMENT_FIXTURE(DOCTEST_ANONYMOUS(DOCTEST_ANON_CLASS_), x,                           \

+                              DOCTEST_ANONYMOUS(DOCTEST_ANON_FUNC_), name)

+

+// for converting types to strings without the <typeinfo> header and demangling

+#define DOCTEST_TYPE_TO_STRING_AS(str, ...) static_assert(true, "")

+#define DOCTEST_TYPE_TO_STRING(...) static_assert(true, "")

+

+// for typed tests

+#define DOCTEST_TEST_CASE_TEMPLATE(name, type, ...)                                                \

+    template <typename type>                                                                       \

+    inline void DOCTEST_ANONYMOUS(DOCTEST_ANON_TMP_)()

+

+#define DOCTEST_TEST_CASE_TEMPLATE_DEFINE(name, type, id)                                          \

+    template <typename type>                                                                       \

+    inline void DOCTEST_ANONYMOUS(DOCTEST_ANON_TMP_)()

+

+#define DOCTEST_TEST_CASE_TEMPLATE_INVOKE(id, ...) static_assert(true, "")

+#define DOCTEST_TEST_CASE_TEMPLATE_APPLY(id, ...) static_assert(true, "")

+

+// for subcases

+#define DOCTEST_SUBCASE(name)

+

+// for a testsuite block

+#define DOCTEST_TEST_SUITE(name) namespace // NOLINT

+

+// for starting a testsuite block

+#define DOCTEST_TEST_SUITE_BEGIN(name) static_assert(true, "")

+

+// for ending a testsuite block

+#define DOCTEST_TEST_SUITE_END using DOCTEST_ANONYMOUS(DOCTEST_ANON_FOR_SEMICOLON_) = int

+

+#define DOCTEST_REGISTER_EXCEPTION_TRANSLATOR(signature)                                           \

+    template <typename DOCTEST_UNUSED_TEMPLATE_TYPE>                                               \

+    static inline doctest::String DOCTEST_ANONYMOUS(DOCTEST_ANON_TRANSLATOR_)(signature)

+

+#define DOCTEST_REGISTER_REPORTER(name, priority, reporter)

+#define DOCTEST_REGISTER_LISTENER(name, priority, reporter)

+

+#define DOCTEST_INFO(...) (static_cast<void>(0))

+#define DOCTEST_CAPTURE(x) (static_cast<void>(0))

+#define DOCTEST_ADD_MESSAGE_AT(file, line, ...) (static_cast<void>(0))

+#define DOCTEST_ADD_FAIL_CHECK_AT(file, line, ...) (static_cast<void>(0))

+#define DOCTEST_ADD_FAIL_AT(file, line, ...) (static_cast<void>(0))

+#define DOCTEST_MESSAGE(...) (static_cast<void>(0))

+#define DOCTEST_FAIL_CHECK(...) (static_cast<void>(0))

+#define DOCTEST_FAIL(...) (static_cast<void>(0))

+

+#if defined(DOCTEST_CONFIG_EVALUATE_ASSERTS_EVEN_WHEN_DISABLED)                                    \

+ && defined(DOCTEST_CONFIG_ASSERTS_RETURN_VALUES)

+

+#define DOCTEST_WARN(...) [&] { return __VA_ARGS__; }()

+#define DOCTEST_CHECK(...) [&] { return __VA_ARGS__; }()

+#define DOCTEST_REQUIRE(...) [&] { return __VA_ARGS__; }()

+#define DOCTEST_WARN_FALSE(...) [&] { return !(__VA_ARGS__); }()

+#define DOCTEST_CHECK_FALSE(...) [&] { return !(__VA_ARGS__); }()

+#define DOCTEST_REQUIRE_FALSE(...) [&] { return !(__VA_ARGS__); }()

+

+#define DOCTEST_WARN_MESSAGE(cond, ...) [&] { return cond; }()

+#define DOCTEST_CHECK_MESSAGE(cond, ...) [&] { return cond; }()

+#define DOCTEST_REQUIRE_MESSAGE(cond, ...) [&] { return cond; }()

+#define DOCTEST_WARN_FALSE_MESSAGE(cond, ...) [&] { return !(cond); }()

+#define DOCTEST_CHECK_FALSE_MESSAGE(cond, ...) [&] { return !(cond); }()

+#define DOCTEST_REQUIRE_FALSE_MESSAGE(cond, ...) [&] { return !(cond); }()

+

+namespace doctest {

+namespace detail {

+#define DOCTEST_RELATIONAL_OP(name, op)                                                            \

+    template <typename L, typename R>                                                              \

+    bool name(const DOCTEST_REF_WRAP(L) lhs, const DOCTEST_REF_WRAP(R) rhs) { return lhs op rhs; }

+

+    DOCTEST_RELATIONAL_OP(eq, ==)

+    DOCTEST_RELATIONAL_OP(ne, !=)

+    DOCTEST_RELATIONAL_OP(lt, <)

+    DOCTEST_RELATIONAL_OP(gt, >)

+    DOCTEST_RELATIONAL_OP(le, <=)

+    DOCTEST_RELATIONAL_OP(ge, >=)

+} // namespace detail

+} // namespace doctest

+

+#define DOCTEST_WARN_EQ(...) [&] { return doctest::detail::eq(__VA_ARGS__); }()

+#define DOCTEST_CHECK_EQ(...) [&] { return doctest::detail::eq(__VA_ARGS__); }()

+#define DOCTEST_REQUIRE_EQ(...) [&] { return doctest::detail::eq(__VA_ARGS__); }()

+#define DOCTEST_WARN_NE(...) [&] { return doctest::detail::ne(__VA_ARGS__); }()

+#define DOCTEST_CHECK_NE(...) [&] { return doctest::detail::ne(__VA_ARGS__); }()

+#define DOCTEST_REQUIRE_NE(...) [&] { return doctest::detail::ne(__VA_ARGS__); }()

+#define DOCTEST_WARN_LT(...) [&] { return doctest::detail::lt(__VA_ARGS__); }()

+#define DOCTEST_CHECK_LT(...) [&] { return doctest::detail::lt(__VA_ARGS__); }()

+#define DOCTEST_REQUIRE_LT(...) [&] { return doctest::detail::lt(__VA_ARGS__); }()

+#define DOCTEST_WARN_GT(...) [&] { return doctest::detail::gt(__VA_ARGS__); }()

+#define DOCTEST_CHECK_GT(...) [&] { return doctest::detail::gt(__VA_ARGS__); }()

+#define DOCTEST_REQUIRE_GT(...) [&] { return doctest::detail::gt(__VA_ARGS__); }()

+#define DOCTEST_WARN_LE(...) [&] { return doctest::detail::le(__VA_ARGS__); }()

+#define DOCTEST_CHECK_LE(...) [&] { return doctest::detail::le(__VA_ARGS__); }()

+#define DOCTEST_REQUIRE_LE(...) [&] { return doctest::detail::le(__VA_ARGS__); }()

+#define DOCTEST_WARN_GE(...) [&] { return doctest::detail::ge(__VA_ARGS__); }()

+#define DOCTEST_CHECK_GE(...) [&] { return doctest::detail::ge(__VA_ARGS__); }()

+#define DOCTEST_REQUIRE_GE(...) [&] { return doctest::detail::ge(__VA_ARGS__); }()

+#define DOCTEST_WARN_UNARY(...) [&] { return __VA_ARGS__; }()

+#define DOCTEST_CHECK_UNARY(...) [&] { return __VA_ARGS__; }()

+#define DOCTEST_REQUIRE_UNARY(...) [&] { return __VA_ARGS__; }()

+#define DOCTEST_WARN_UNARY_FALSE(...) [&] { return !(__VA_ARGS__); }()

+#define DOCTEST_CHECK_UNARY_FALSE(...) [&] { return !(__VA_ARGS__); }()

+#define DOCTEST_REQUIRE_UNARY_FALSE(...) [&] { return !(__VA_ARGS__); }()

+

+#ifndef DOCTEST_CONFIG_NO_EXCEPTIONS

+

+#define DOCTEST_WARN_THROWS_WITH(expr, with, ...) [] { static_assert(false, "Exception translation is not available when doctest is disabled."); return false; }()

+#define DOCTEST_CHECK_THROWS_WITH(expr, with, ...) DOCTEST_WARN_THROWS_WITH(,,)

+#define DOCTEST_REQUIRE_THROWS_WITH(expr, with, ...) DOCTEST_WARN_THROWS_WITH(,,)

+#define DOCTEST_WARN_THROWS_WITH_AS(expr, with, ex, ...) DOCTEST_WARN_THROWS_WITH(,,)

+#define DOCTEST_CHECK_THROWS_WITH_AS(expr, with, ex, ...) DOCTEST_WARN_THROWS_WITH(,,)

+#define DOCTEST_REQUIRE_THROWS_WITH_AS(expr, with, ex, ...) DOCTEST_WARN_THROWS_WITH(,,)

+

+#define DOCTEST_WARN_THROWS_WITH_MESSAGE(expr, with, ...) DOCTEST_WARN_THROWS_WITH(,,)

+#define DOCTEST_CHECK_THROWS_WITH_MESSAGE(expr, with, ...) DOCTEST_WARN_THROWS_WITH(,,)

+#define DOCTEST_REQUIRE_THROWS_WITH_MESSAGE(expr, with, ...) DOCTEST_WARN_THROWS_WITH(,,)

+#define DOCTEST_WARN_THROWS_WITH_AS_MESSAGE(expr, with, ex, ...) DOCTEST_WARN_THROWS_WITH(,,)

+#define DOCTEST_CHECK_THROWS_WITH_AS_MESSAGE(expr, with, ex, ...) DOCTEST_WARN_THROWS_WITH(,,)

+#define DOCTEST_REQUIRE_THROWS_WITH_AS_MESSAGE(expr, with, ex, ...) DOCTEST_WARN_THROWS_WITH(,,)

+

+#define DOCTEST_WARN_THROWS(...) [&] { try { __VA_ARGS__; return false; } catch (...) { return true; } }()

+#define DOCTEST_CHECK_THROWS(...) [&] { try { __VA_ARGS__; return false; } catch (...) { return true; } }()

+#define DOCTEST_REQUIRE_THROWS(...) [&] { try { __VA_ARGS__; return false; } catch (...) { return true; } }()

+#define DOCTEST_WARN_THROWS_AS(expr, ...) [&] { try { expr; } catch (__VA_ARGS__) { return true; } catch (...) { } return false; }()

+#define DOCTEST_CHECK_THROWS_AS(expr, ...) [&] { try { expr; } catch (__VA_ARGS__) { return true; } catch (...) { } return false; }()

+#define DOCTEST_REQUIRE_THROWS_AS(expr, ...) [&] { try { expr; } catch (__VA_ARGS__) { return true; } catch (...) { } return false; }()

+#define DOCTEST_WARN_NOTHROW(...) [&] { try { __VA_ARGS__; return true; } catch (...) { return false; } }()

+#define DOCTEST_CHECK_NOTHROW(...) [&] { try { __VA_ARGS__; return true; } catch (...) { return false; } }()

+#define DOCTEST_REQUIRE_NOTHROW(...) [&] { try { __VA_ARGS__; return true; } catch (...) { return false; } }()

+

+#define DOCTEST_WARN_THROWS_MESSAGE(expr, ...) [&] { try { __VA_ARGS__; return false; } catch (...) { return true; } }()

+#define DOCTEST_CHECK_THROWS_MESSAGE(expr, ...) [&] { try { __VA_ARGS__; return false; } catch (...) { return true; } }()

+#define DOCTEST_REQUIRE_THROWS_MESSAGE(expr, ...) [&] { try { __VA_ARGS__; return false; } catch (...) { return true; } }()

+#define DOCTEST_WARN_THROWS_AS_MESSAGE(expr, ex, ...) [&] { try { expr; } catch (__VA_ARGS__) { return true; } catch (...) { } return false; }()

+#define DOCTEST_CHECK_THROWS_AS_MESSAGE(expr, ex, ...) [&] { try { expr; } catch (__VA_ARGS__) { return true; } catch (...) { } return false; }()

+#define DOCTEST_REQUIRE_THROWS_AS_MESSAGE(expr, ex, ...) [&] { try { expr; } catch (__VA_ARGS__) { return true; } catch (...) { } return false; }()

+#define DOCTEST_WARN_NOTHROW_MESSAGE(expr, ...) [&] { try { __VA_ARGS__; return true; } catch (...) { return false; } }()

+#define DOCTEST_CHECK_NOTHROW_MESSAGE(expr, ...) [&] { try { __VA_ARGS__; return true; } catch (...) { return false; } }()

+#define DOCTEST_REQUIRE_NOTHROW_MESSAGE(expr, ...) [&] { try { __VA_ARGS__; return true; } catch (...) { return false; } }()

+

+#endif // DOCTEST_CONFIG_NO_EXCEPTIONS

+

+#else // DOCTEST_CONFIG_EVALUATE_ASSERTS_EVEN_WHEN_DISABLED

+

+#define DOCTEST_WARN(...) DOCTEST_FUNC_EMPTY

+#define DOCTEST_CHECK(...) DOCTEST_FUNC_EMPTY

+#define DOCTEST_REQUIRE(...) DOCTEST_FUNC_EMPTY

+#define DOCTEST_WARN_FALSE(...) DOCTEST_FUNC_EMPTY

+#define DOCTEST_CHECK_FALSE(...) DOCTEST_FUNC_EMPTY

+#define DOCTEST_REQUIRE_FALSE(...) DOCTEST_FUNC_EMPTY

+

+#define DOCTEST_WARN_MESSAGE(cond, ...) DOCTEST_FUNC_EMPTY

+#define DOCTEST_CHECK_MESSAGE(cond, ...) DOCTEST_FUNC_EMPTY

+#define DOCTEST_REQUIRE_MESSAGE(cond, ...) DOCTEST_FUNC_EMPTY

+#define DOCTEST_WARN_FALSE_MESSAGE(cond, ...) DOCTEST_FUNC_EMPTY

+#define DOCTEST_CHECK_FALSE_MESSAGE(cond, ...) DOCTEST_FUNC_EMPTY

+#define DOCTEST_REQUIRE_FALSE_MESSAGE(cond, ...) DOCTEST_FUNC_EMPTY

+

+#define DOCTEST_WARN_EQ(...) DOCTEST_FUNC_EMPTY

+#define DOCTEST_CHECK_EQ(...) DOCTEST_FUNC_EMPTY

+#define DOCTEST_REQUIRE_EQ(...) DOCTEST_FUNC_EMPTY

+#define DOCTEST_WARN_NE(...) DOCTEST_FUNC_EMPTY

+#define DOCTEST_CHECK_NE(...) DOCTEST_FUNC_EMPTY

+#define DOCTEST_REQUIRE_NE(...) DOCTEST_FUNC_EMPTY

+#define DOCTEST_WARN_GT(...) DOCTEST_FUNC_EMPTY

+#define DOCTEST_CHECK_GT(...) DOCTEST_FUNC_EMPTY

+#define DOCTEST_REQUIRE_GT(...) DOCTEST_FUNC_EMPTY

+#define DOCTEST_WARN_LT(...) DOCTEST_FUNC_EMPTY

+#define DOCTEST_CHECK_LT(...) DOCTEST_FUNC_EMPTY

+#define DOCTEST_REQUIRE_LT(...) DOCTEST_FUNC_EMPTY

+#define DOCTEST_WARN_GE(...) DOCTEST_FUNC_EMPTY

+#define DOCTEST_CHECK_GE(...) DOCTEST_FUNC_EMPTY

+#define DOCTEST_REQUIRE_GE(...) DOCTEST_FUNC_EMPTY

+#define DOCTEST_WARN_LE(...) DOCTEST_FUNC_EMPTY

+#define DOCTEST_CHECK_LE(...) DOCTEST_FUNC_EMPTY

+#define DOCTEST_REQUIRE_LE(...) DOCTEST_FUNC_EMPTY

+

+#define DOCTEST_WARN_UNARY(...) DOCTEST_FUNC_EMPTY

+#define DOCTEST_CHECK_UNARY(...) DOCTEST_FUNC_EMPTY

+#define DOCTEST_REQUIRE_UNARY(...) DOCTEST_FUNC_EMPTY

+#define DOCTEST_WARN_UNARY_FALSE(...) DOCTEST_FUNC_EMPTY

+#define DOCTEST_CHECK_UNARY_FALSE(...) DOCTEST_FUNC_EMPTY

+#define DOCTEST_REQUIRE_UNARY_FALSE(...) DOCTEST_FUNC_EMPTY

+

+#ifndef DOCTEST_CONFIG_NO_EXCEPTIONS

+

+#define DOCTEST_WARN_THROWS(...) DOCTEST_FUNC_EMPTY

+#define DOCTEST_CHECK_THROWS(...) DOCTEST_FUNC_EMPTY

+#define DOCTEST_REQUIRE_THROWS(...) DOCTEST_FUNC_EMPTY

+#define DOCTEST_WARN_THROWS_AS(expr, ...) DOCTEST_FUNC_EMPTY

+#define DOCTEST_CHECK_THROWS_AS(expr, ...) DOCTEST_FUNC_EMPTY

+#define DOCTEST_REQUIRE_THROWS_AS(expr, ...) DOCTEST_FUNC_EMPTY

+#define DOCTEST_WARN_THROWS_WITH(expr, ...) DOCTEST_FUNC_EMPTY

+#define DOCTEST_CHECK_THROWS_WITH(expr, ...) DOCTEST_FUNC_EMPTY

+#define DOCTEST_REQUIRE_THROWS_WITH(expr, ...) DOCTEST_FUNC_EMPTY

+#define DOCTEST_WARN_THROWS_WITH_AS(expr, with, ...) DOCTEST_FUNC_EMPTY

+#define DOCTEST_CHECK_THROWS_WITH_AS(expr, with, ...) DOCTEST_FUNC_EMPTY

+#define DOCTEST_REQUIRE_THROWS_WITH_AS(expr, with, ...) DOCTEST_FUNC_EMPTY

+#define DOCTEST_WARN_NOTHROW(...) DOCTEST_FUNC_EMPTY

+#define DOCTEST_CHECK_NOTHROW(...) DOCTEST_FUNC_EMPTY

+#define DOCTEST_REQUIRE_NOTHROW(...) DOCTEST_FUNC_EMPTY

+

+#define DOCTEST_WARN_THROWS_MESSAGE(expr, ...) DOCTEST_FUNC_EMPTY

+#define DOCTEST_CHECK_THROWS_MESSAGE(expr, ...) DOCTEST_FUNC_EMPTY

+#define DOCTEST_REQUIRE_THROWS_MESSAGE(expr, ...) DOCTEST_FUNC_EMPTY

+#define DOCTEST_WARN_THROWS_AS_MESSAGE(expr, ex, ...) DOCTEST_FUNC_EMPTY

+#define DOCTEST_CHECK_THROWS_AS_MESSAGE(expr, ex, ...) DOCTEST_FUNC_EMPTY

+#define DOCTEST_REQUIRE_THROWS_AS_MESSAGE(expr, ex, ...) DOCTEST_FUNC_EMPTY

+#define DOCTEST_WARN_THROWS_WITH_MESSAGE(expr, with, ...) DOCTEST_FUNC_EMPTY

+#define DOCTEST_CHECK_THROWS_WITH_MESSAGE(expr, with, ...) DOCTEST_FUNC_EMPTY

+#define DOCTEST_REQUIRE_THROWS_WITH_MESSAGE(expr, with, ...) DOCTEST_FUNC_EMPTY

+#define DOCTEST_WARN_THROWS_WITH_AS_MESSAGE(expr, with, ex, ...) DOCTEST_FUNC_EMPTY

+#define DOCTEST_CHECK_THROWS_WITH_AS_MESSAGE(expr, with, ex, ...) DOCTEST_FUNC_EMPTY

+#define DOCTEST_REQUIRE_THROWS_WITH_AS_MESSAGE(expr, with, ex, ...) DOCTEST_FUNC_EMPTY

+#define DOCTEST_WARN_NOTHROW_MESSAGE(expr, ...) DOCTEST_FUNC_EMPTY

+#define DOCTEST_CHECK_NOTHROW_MESSAGE(expr, ...) DOCTEST_FUNC_EMPTY

+#define DOCTEST_REQUIRE_NOTHROW_MESSAGE(expr, ...) DOCTEST_FUNC_EMPTY

+

+#endif // DOCTEST_CONFIG_NO_EXCEPTIONS

+

+#endif // DOCTEST_CONFIG_EVALUATE_ASSERTS_EVEN_WHEN_DISABLED

+

+#endif // DOCTEST_CONFIG_DISABLE

+

+#ifdef DOCTEST_CONFIG_NO_EXCEPTIONS

+

+#ifdef DOCTEST_CONFIG_NO_EXCEPTIONS_BUT_WITH_ALL_ASSERTS

+#define DOCTEST_EXCEPTION_EMPTY_FUNC DOCTEST_FUNC_EMPTY

+#else // DOCTEST_CONFIG_NO_EXCEPTIONS_BUT_WITH_ALL_ASSERTS

+#define DOCTEST_EXCEPTION_EMPTY_FUNC [] { static_assert(false, "Exceptions are disabled! " \

+    "Use DOCTEST_CONFIG_NO_EXCEPTIONS_BUT_WITH_ALL_ASSERTS if you want to compile with exceptions disabled."); return false; }()

+

+#undef DOCTEST_REQUIRE

+#undef DOCTEST_REQUIRE_FALSE

+#undef DOCTEST_REQUIRE_MESSAGE

+#undef DOCTEST_REQUIRE_FALSE_MESSAGE

+#undef DOCTEST_REQUIRE_EQ

+#undef DOCTEST_REQUIRE_NE

+#undef DOCTEST_REQUIRE_GT

+#undef DOCTEST_REQUIRE_LT

+#undef DOCTEST_REQUIRE_GE

+#undef DOCTEST_REQUIRE_LE

+#undef DOCTEST_REQUIRE_UNARY

+#undef DOCTEST_REQUIRE_UNARY_FALSE

+

+#define DOCTEST_REQUIRE DOCTEST_EXCEPTION_EMPTY_FUNC

+#define DOCTEST_REQUIRE_FALSE DOCTEST_EXCEPTION_EMPTY_FUNC

+#define DOCTEST_REQUIRE_MESSAGE DOCTEST_EXCEPTION_EMPTY_FUNC

+#define DOCTEST_REQUIRE_FALSE_MESSAGE DOCTEST_EXCEPTION_EMPTY_FUNC

+#define DOCTEST_REQUIRE_EQ DOCTEST_EXCEPTION_EMPTY_FUNC

+#define DOCTEST_REQUIRE_NE DOCTEST_EXCEPTION_EMPTY_FUNC

+#define DOCTEST_REQUIRE_GT DOCTEST_EXCEPTION_EMPTY_FUNC

+#define DOCTEST_REQUIRE_LT DOCTEST_EXCEPTION_EMPTY_FUNC

+#define DOCTEST_REQUIRE_GE DOCTEST_EXCEPTION_EMPTY_FUNC

+#define DOCTEST_REQUIRE_LE DOCTEST_EXCEPTION_EMPTY_FUNC

+#define DOCTEST_REQUIRE_UNARY DOCTEST_EXCEPTION_EMPTY_FUNC

+#define DOCTEST_REQUIRE_UNARY_FALSE DOCTEST_EXCEPTION_EMPTY_FUNC

+

+#endif // DOCTEST_CONFIG_NO_EXCEPTIONS_BUT_WITH_ALL_ASSERTS

+

+#define DOCTEST_WARN_THROWS(...) DOCTEST_EXCEPTION_EMPTY_FUNC

+#define DOCTEST_CHECK_THROWS(...) DOCTEST_EXCEPTION_EMPTY_FUNC

+#define DOCTEST_REQUIRE_THROWS(...) DOCTEST_EXCEPTION_EMPTY_FUNC

+#define DOCTEST_WARN_THROWS_AS(expr, ...) DOCTEST_EXCEPTION_EMPTY_FUNC

+#define DOCTEST_CHECK_THROWS_AS(expr, ...) DOCTEST_EXCEPTION_EMPTY_FUNC

+#define DOCTEST_REQUIRE_THROWS_AS(expr, ...) DOCTEST_EXCEPTION_EMPTY_FUNC

+#define DOCTEST_WARN_THROWS_WITH(expr, ...) DOCTEST_EXCEPTION_EMPTY_FUNC

+#define DOCTEST_CHECK_THROWS_WITH(expr, ...) DOCTEST_EXCEPTION_EMPTY_FUNC

+#define DOCTEST_REQUIRE_THROWS_WITH(expr, ...) DOCTEST_EXCEPTION_EMPTY_FUNC

+#define DOCTEST_WARN_THROWS_WITH_AS(expr, with, ...) DOCTEST_EXCEPTION_EMPTY_FUNC

+#define DOCTEST_CHECK_THROWS_WITH_AS(expr, with, ...) DOCTEST_EXCEPTION_EMPTY_FUNC

+#define DOCTEST_REQUIRE_THROWS_WITH_AS(expr, with, ...) DOCTEST_EXCEPTION_EMPTY_FUNC

+#define DOCTEST_WARN_NOTHROW(...) DOCTEST_EXCEPTION_EMPTY_FUNC

+#define DOCTEST_CHECK_NOTHROW(...) DOCTEST_EXCEPTION_EMPTY_FUNC

+#define DOCTEST_REQUIRE_NOTHROW(...) DOCTEST_EXCEPTION_EMPTY_FUNC

+

+#define DOCTEST_WARN_THROWS_MESSAGE(expr, ...) DOCTEST_EXCEPTION_EMPTY_FUNC

+#define DOCTEST_CHECK_THROWS_MESSAGE(expr, ...) DOCTEST_EXCEPTION_EMPTY_FUNC

+#define DOCTEST_REQUIRE_THROWS_MESSAGE(expr, ...) DOCTEST_EXCEPTION_EMPTY_FUNC

+#define DOCTEST_WARN_THROWS_AS_MESSAGE(expr, ex, ...) DOCTEST_EXCEPTION_EMPTY_FUNC

+#define DOCTEST_CHECK_THROWS_AS_MESSAGE(expr, ex, ...) DOCTEST_EXCEPTION_EMPTY_FUNC

+#define DOCTEST_REQUIRE_THROWS_AS_MESSAGE(expr, ex, ...) DOCTEST_EXCEPTION_EMPTY_FUNC

+#define DOCTEST_WARN_THROWS_WITH_MESSAGE(expr, with, ...) DOCTEST_EXCEPTION_EMPTY_FUNC

+#define DOCTEST_CHECK_THROWS_WITH_MESSAGE(expr, with, ...) DOCTEST_EXCEPTION_EMPTY_FUNC

+#define DOCTEST_REQUIRE_THROWS_WITH_MESSAGE(expr, with, ...) DOCTEST_EXCEPTION_EMPTY_FUNC

+#define DOCTEST_WARN_THROWS_WITH_AS_MESSAGE(expr, with, ex, ...) DOCTEST_EXCEPTION_EMPTY_FUNC

+#define DOCTEST_CHECK_THROWS_WITH_AS_MESSAGE(expr, with, ex, ...) DOCTEST_EXCEPTION_EMPTY_FUNC

+#define DOCTEST_REQUIRE_THROWS_WITH_AS_MESSAGE(expr, with, ex, ...) DOCTEST_EXCEPTION_EMPTY_FUNC

+#define DOCTEST_WARN_NOTHROW_MESSAGE(expr, ...) DOCTEST_EXCEPTION_EMPTY_FUNC

+#define DOCTEST_CHECK_NOTHROW_MESSAGE(expr, ...) DOCTEST_EXCEPTION_EMPTY_FUNC

+#define DOCTEST_REQUIRE_NOTHROW_MESSAGE(expr, ...) DOCTEST_EXCEPTION_EMPTY_FUNC

+

+#endif // DOCTEST_CONFIG_NO_EXCEPTIONS

+

+// clang-format off

+// KEPT FOR BACKWARDS COMPATIBILITY - FORWARDING TO THE RIGHT MACROS

+#define DOCTEST_FAST_WARN_EQ             DOCTEST_WARN_EQ

+#define DOCTEST_FAST_CHECK_EQ            DOCTEST_CHECK_EQ

+#define DOCTEST_FAST_REQUIRE_EQ          DOCTEST_REQUIRE_EQ

+#define DOCTEST_FAST_WARN_NE             DOCTEST_WARN_NE

+#define DOCTEST_FAST_CHECK_NE            DOCTEST_CHECK_NE

+#define DOCTEST_FAST_REQUIRE_NE          DOCTEST_REQUIRE_NE

+#define DOCTEST_FAST_WARN_GT             DOCTEST_WARN_GT

+#define DOCTEST_FAST_CHECK_GT            DOCTEST_CHECK_GT

+#define DOCTEST_FAST_REQUIRE_GT          DOCTEST_REQUIRE_GT

+#define DOCTEST_FAST_WARN_LT             DOCTEST_WARN_LT

+#define DOCTEST_FAST_CHECK_LT            DOCTEST_CHECK_LT

+#define DOCTEST_FAST_REQUIRE_LT          DOCTEST_REQUIRE_LT

+#define DOCTEST_FAST_WARN_GE             DOCTEST_WARN_GE

+#define DOCTEST_FAST_CHECK_GE            DOCTEST_CHECK_GE

+#define DOCTEST_FAST_REQUIRE_GE          DOCTEST_REQUIRE_GE

+#define DOCTEST_FAST_WARN_LE             DOCTEST_WARN_LE

+#define DOCTEST_FAST_CHECK_LE            DOCTEST_CHECK_LE

+#define DOCTEST_FAST_REQUIRE_LE          DOCTEST_REQUIRE_LE

+

+#define DOCTEST_FAST_WARN_UNARY          DOCTEST_WARN_UNARY

+#define DOCTEST_FAST_CHECK_UNARY         DOCTEST_CHECK_UNARY

+#define DOCTEST_FAST_REQUIRE_UNARY       DOCTEST_REQUIRE_UNARY

+#define DOCTEST_FAST_WARN_UNARY_FALSE    DOCTEST_WARN_UNARY_FALSE

+#define DOCTEST_FAST_CHECK_UNARY_FALSE   DOCTEST_CHECK_UNARY_FALSE

+#define DOCTEST_FAST_REQUIRE_UNARY_FALSE DOCTEST_REQUIRE_UNARY_FALSE

+

+#define DOCTEST_TEST_CASE_TEMPLATE_INSTANTIATE(id, ...) DOCTEST_TEST_CASE_TEMPLATE_INVOKE(id,__VA_ARGS__)

+// clang-format on

+

+// BDD style macros

+// clang-format off

+#define DOCTEST_SCENARIO(name) DOCTEST_TEST_CASE("  Scenario: " name)

+#define DOCTEST_SCENARIO_CLASS(name) DOCTEST_TEST_CASE_CLASS("  Scenario: " name)

+#define DOCTEST_SCENARIO_TEMPLATE(name, T, ...)  DOCTEST_TEST_CASE_TEMPLATE("  Scenario: " name, T, __VA_ARGS__)

+#define DOCTEST_SCENARIO_TEMPLATE_DEFINE(name, T, id) DOCTEST_TEST_CASE_TEMPLATE_DEFINE("  Scenario: " name, T, id)

+

+#define DOCTEST_GIVEN(name)     DOCTEST_SUBCASE("   Given: " name)

+#define DOCTEST_WHEN(name)      DOCTEST_SUBCASE("    When: " name)

+#define DOCTEST_AND_WHEN(name)  DOCTEST_SUBCASE("And when: " name)

+#define DOCTEST_THEN(name)      DOCTEST_SUBCASE("    Then: " name)

+#define DOCTEST_AND_THEN(name)  DOCTEST_SUBCASE("     And: " name)

+// clang-format on

+

+// == SHORT VERSIONS OF THE MACROS

+#ifndef DOCTEST_CONFIG_NO_SHORT_MACRO_NAMES

+

+#define TEST_CASE(name) DOCTEST_TEST_CASE(name)

+#define TEST_CASE_CLASS(name) DOCTEST_TEST_CASE_CLASS(name)

+#define TEST_CASE_FIXTURE(x, name) DOCTEST_TEST_CASE_FIXTURE(x, name)

+#define TYPE_TO_STRING_AS(str, ...) DOCTEST_TYPE_TO_STRING_AS(str, __VA_ARGS__)

+#define TYPE_TO_STRING(...) DOCTEST_TYPE_TO_STRING(__VA_ARGS__)

+#define TEST_CASE_TEMPLATE(name, T, ...) DOCTEST_TEST_CASE_TEMPLATE(name, T, __VA_ARGS__)

+#define TEST_CASE_TEMPLATE_DEFINE(name, T, id) DOCTEST_TEST_CASE_TEMPLATE_DEFINE(name, T, id)

+#define TEST_CASE_TEMPLATE_INVOKE(id, ...) DOCTEST_TEST_CASE_TEMPLATE_INVOKE(id, __VA_ARGS__)

+#define TEST_CASE_TEMPLATE_APPLY(id, ...) DOCTEST_TEST_CASE_TEMPLATE_APPLY(id, __VA_ARGS__)

+#define SUBCASE(name) DOCTEST_SUBCASE(name)

+#define TEST_SUITE(decorators) DOCTEST_TEST_SUITE(decorators)

+#define TEST_SUITE_BEGIN(name) DOCTEST_TEST_SUITE_BEGIN(name)

+#define TEST_SUITE_END DOCTEST_TEST_SUITE_END

+#define REGISTER_EXCEPTION_TRANSLATOR(signature) DOCTEST_REGISTER_EXCEPTION_TRANSLATOR(signature)

+#define REGISTER_REPORTER(name, priority, reporter) DOCTEST_REGISTER_REPORTER(name, priority, reporter)

+#define REGISTER_LISTENER(name, priority, reporter) DOCTEST_REGISTER_LISTENER(name, priority, reporter)

+#define INFO(...) DOCTEST_INFO(__VA_ARGS__)

+#define CAPTURE(x) DOCTEST_CAPTURE(x)

+#define ADD_MESSAGE_AT(file, line, ...) DOCTEST_ADD_MESSAGE_AT(file, line, __VA_ARGS__)

+#define ADD_FAIL_CHECK_AT(file, line, ...) DOCTEST_ADD_FAIL_CHECK_AT(file, line, __VA_ARGS__)

+#define ADD_FAIL_AT(file, line, ...) DOCTEST_ADD_FAIL_AT(file, line, __VA_ARGS__)

+#define MESSAGE(...) DOCTEST_MESSAGE(__VA_ARGS__)

+#define FAIL_CHECK(...) DOCTEST_FAIL_CHECK(__VA_ARGS__)

+#define FAIL(...) DOCTEST_FAIL(__VA_ARGS__)

+#define TO_LVALUE(...) DOCTEST_TO_LVALUE(__VA_ARGS__)

+

+#define WARN(...) DOCTEST_WARN(__VA_ARGS__)

+#define WARN_FALSE(...) DOCTEST_WARN_FALSE(__VA_ARGS__)

+#define WARN_THROWS(...) DOCTEST_WARN_THROWS(__VA_ARGS__)

+#define WARN_THROWS_AS(expr, ...) DOCTEST_WARN_THROWS_AS(expr, __VA_ARGS__)

+#define WARN_THROWS_WITH(expr, ...) DOCTEST_WARN_THROWS_WITH(expr, __VA_ARGS__)

+#define WARN_THROWS_WITH_AS(expr, with, ...) DOCTEST_WARN_THROWS_WITH_AS(expr, with, __VA_ARGS__)

+#define WARN_NOTHROW(...) DOCTEST_WARN_NOTHROW(__VA_ARGS__)

+#define CHECK(...) DOCTEST_CHECK(__VA_ARGS__)

+#define CHECK_FALSE(...) DOCTEST_CHECK_FALSE(__VA_ARGS__)

+#define CHECK_THROWS(...) DOCTEST_CHECK_THROWS(__VA_ARGS__)

+#define CHECK_THROWS_AS(expr, ...) DOCTEST_CHECK_THROWS_AS(expr, __VA_ARGS__)

+#define CHECK_THROWS_WITH(expr, ...) DOCTEST_CHECK_THROWS_WITH(expr, __VA_ARGS__)

+#define CHECK_THROWS_WITH_AS(expr, with, ...) DOCTEST_CHECK_THROWS_WITH_AS(expr, with, __VA_ARGS__)

+#define CHECK_NOTHROW(...) DOCTEST_CHECK_NOTHROW(__VA_ARGS__)

+#define REQUIRE(...) DOCTEST_REQUIRE(__VA_ARGS__)

+#define REQUIRE_FALSE(...) DOCTEST_REQUIRE_FALSE(__VA_ARGS__)

+#define REQUIRE_THROWS(...) DOCTEST_REQUIRE_THROWS(__VA_ARGS__)

+#define REQUIRE_THROWS_AS(expr, ...) DOCTEST_REQUIRE_THROWS_AS(expr, __VA_ARGS__)

+#define REQUIRE_THROWS_WITH(expr, ...) DOCTEST_REQUIRE_THROWS_WITH(expr, __VA_ARGS__)

+#define REQUIRE_THROWS_WITH_AS(expr, with, ...) DOCTEST_REQUIRE_THROWS_WITH_AS(expr, with, __VA_ARGS__)

+#define REQUIRE_NOTHROW(...) DOCTEST_REQUIRE_NOTHROW(__VA_ARGS__)

+

+#define WARN_MESSAGE(cond, ...) DOCTEST_WARN_MESSAGE(cond, __VA_ARGS__)

+#define WARN_FALSE_MESSAGE(cond, ...) DOCTEST_WARN_FALSE_MESSAGE(cond, __VA_ARGS__)

+#define WARN_THROWS_MESSAGE(expr, ...) DOCTEST_WARN_THROWS_MESSAGE(expr, __VA_ARGS__)

+#define WARN_THROWS_AS_MESSAGE(expr, ex, ...) DOCTEST_WARN_THROWS_AS_MESSAGE(expr, ex, __VA_ARGS__)

+#define WARN_THROWS_WITH_MESSAGE(expr, with, ...) DOCTEST_WARN_THROWS_WITH_MESSAGE(expr, with, __VA_ARGS__)

+#define WARN_THROWS_WITH_AS_MESSAGE(expr, with, ex, ...) DOCTEST_WARN_THROWS_WITH_AS_MESSAGE(expr, with, ex, __VA_ARGS__)

+#define WARN_NOTHROW_MESSAGE(expr, ...) DOCTEST_WARN_NOTHROW_MESSAGE(expr, __VA_ARGS__)

+#define CHECK_MESSAGE(cond, ...) DOCTEST_CHECK_MESSAGE(cond, __VA_ARGS__)

+#define CHECK_FALSE_MESSAGE(cond, ...) DOCTEST_CHECK_FALSE_MESSAGE(cond, __VA_ARGS__)

+#define CHECK_THROWS_MESSAGE(expr, ...) DOCTEST_CHECK_THROWS_MESSAGE(expr, __VA_ARGS__)

+#define CHECK_THROWS_AS_MESSAGE(expr, ex, ...) DOCTEST_CHECK_THROWS_AS_MESSAGE(expr, ex, __VA_ARGS__)

+#define CHECK_THROWS_WITH_MESSAGE(expr, with, ...) DOCTEST_CHECK_THROWS_WITH_MESSAGE(expr, with, __VA_ARGS__)

+#define CHECK_THROWS_WITH_AS_MESSAGE(expr, with, ex, ...) DOCTEST_CHECK_THROWS_WITH_AS_MESSAGE(expr, with, ex, __VA_ARGS__)

+#define CHECK_NOTHROW_MESSAGE(expr, ...) DOCTEST_CHECK_NOTHROW_MESSAGE(expr, __VA_ARGS__)

+#define REQUIRE_MESSAGE(cond, ...) DOCTEST_REQUIRE_MESSAGE(cond, __VA_ARGS__)

+#define REQUIRE_FALSE_MESSAGE(cond, ...) DOCTEST_REQUIRE_FALSE_MESSAGE(cond, __VA_ARGS__)

+#define REQUIRE_THROWS_MESSAGE(expr, ...) DOCTEST_REQUIRE_THROWS_MESSAGE(expr, __VA_ARGS__)

+#define REQUIRE_THROWS_AS_MESSAGE(expr, ex, ...) DOCTEST_REQUIRE_THROWS_AS_MESSAGE(expr, ex, __VA_ARGS__)

+#define REQUIRE_THROWS_WITH_MESSAGE(expr, with, ...) DOCTEST_REQUIRE_THROWS_WITH_MESSAGE(expr, with, __VA_ARGS__)

+#define REQUIRE_THROWS_WITH_AS_MESSAGE(expr, with, ex, ...) DOCTEST_REQUIRE_THROWS_WITH_AS_MESSAGE(expr, with, ex, __VA_ARGS__)

+#define REQUIRE_NOTHROW_MESSAGE(expr, ...) DOCTEST_REQUIRE_NOTHROW_MESSAGE(expr, __VA_ARGS__)

+

+#define SCENARIO(name) DOCTEST_SCENARIO(name)

+#define SCENARIO_CLASS(name) DOCTEST_SCENARIO_CLASS(name)

+#define SCENARIO_TEMPLATE(name, T, ...) DOCTEST_SCENARIO_TEMPLATE(name, T, __VA_ARGS__)

+#define SCENARIO_TEMPLATE_DEFINE(name, T, id) DOCTEST_SCENARIO_TEMPLATE_DEFINE(name, T, id)

+#define GIVEN(name) DOCTEST_GIVEN(name)

+#define WHEN(name) DOCTEST_WHEN(name)

+#define AND_WHEN(name) DOCTEST_AND_WHEN(name)

+#define THEN(name) DOCTEST_THEN(name)

+#define AND_THEN(name) DOCTEST_AND_THEN(name)

+

+#define WARN_EQ(...) DOCTEST_WARN_EQ(__VA_ARGS__)

+#define CHECK_EQ(...) DOCTEST_CHECK_EQ(__VA_ARGS__)

+#define REQUIRE_EQ(...) DOCTEST_REQUIRE_EQ(__VA_ARGS__)

+#define WARN_NE(...) DOCTEST_WARN_NE(__VA_ARGS__)

+#define CHECK_NE(...) DOCTEST_CHECK_NE(__VA_ARGS__)

+#define REQUIRE_NE(...) DOCTEST_REQUIRE_NE(__VA_ARGS__)

+#define WARN_GT(...) DOCTEST_WARN_GT(__VA_ARGS__)

+#define CHECK_GT(...) DOCTEST_CHECK_GT(__VA_ARGS__)

+#define REQUIRE_GT(...) DOCTEST_REQUIRE_GT(__VA_ARGS__)

+#define WARN_LT(...) DOCTEST_WARN_LT(__VA_ARGS__)

+#define CHECK_LT(...) DOCTEST_CHECK_LT(__VA_ARGS__)

+#define REQUIRE_LT(...) DOCTEST_REQUIRE_LT(__VA_ARGS__)

+#define WARN_GE(...) DOCTEST_WARN_GE(__VA_ARGS__)

+#define CHECK_GE(...) DOCTEST_CHECK_GE(__VA_ARGS__)

+#define REQUIRE_GE(...) DOCTEST_REQUIRE_GE(__VA_ARGS__)

+#define WARN_LE(...) DOCTEST_WARN_LE(__VA_ARGS__)

+#define CHECK_LE(...) DOCTEST_CHECK_LE(__VA_ARGS__)

+#define REQUIRE_LE(...) DOCTEST_REQUIRE_LE(__VA_ARGS__)

+#define WARN_UNARY(...) DOCTEST_WARN_UNARY(__VA_ARGS__)

+#define CHECK_UNARY(...) DOCTEST_CHECK_UNARY(__VA_ARGS__)

+#define REQUIRE_UNARY(...) DOCTEST_REQUIRE_UNARY(__VA_ARGS__)

+#define WARN_UNARY_FALSE(...) DOCTEST_WARN_UNARY_FALSE(__VA_ARGS__)

+#define CHECK_UNARY_FALSE(...) DOCTEST_CHECK_UNARY_FALSE(__VA_ARGS__)

+#define REQUIRE_UNARY_FALSE(...) DOCTEST_REQUIRE_UNARY_FALSE(__VA_ARGS__)

+

+// KEPT FOR BACKWARDS COMPATIBILITY

+#define FAST_WARN_EQ(...) DOCTEST_FAST_WARN_EQ(__VA_ARGS__)

+#define FAST_CHECK_EQ(...) DOCTEST_FAST_CHECK_EQ(__VA_ARGS__)

+#define FAST_REQUIRE_EQ(...) DOCTEST_FAST_REQUIRE_EQ(__VA_ARGS__)

+#define FAST_WARN_NE(...) DOCTEST_FAST_WARN_NE(__VA_ARGS__)

+#define FAST_CHECK_NE(...) DOCTEST_FAST_CHECK_NE(__VA_ARGS__)

+#define FAST_REQUIRE_NE(...) DOCTEST_FAST_REQUIRE_NE(__VA_ARGS__)

+#define FAST_WARN_GT(...) DOCTEST_FAST_WARN_GT(__VA_ARGS__)

+#define FAST_CHECK_GT(...) DOCTEST_FAST_CHECK_GT(__VA_ARGS__)

+#define FAST_REQUIRE_GT(...) DOCTEST_FAST_REQUIRE_GT(__VA_ARGS__)

+#define FAST_WARN_LT(...) DOCTEST_FAST_WARN_LT(__VA_ARGS__)

+#define FAST_CHECK_LT(...) DOCTEST_FAST_CHECK_LT(__VA_ARGS__)

+#define FAST_REQUIRE_LT(...) DOCTEST_FAST_REQUIRE_LT(__VA_ARGS__)

+#define FAST_WARN_GE(...) DOCTEST_FAST_WARN_GE(__VA_ARGS__)

+#define FAST_CHECK_GE(...) DOCTEST_FAST_CHECK_GE(__VA_ARGS__)

+#define FAST_REQUIRE_GE(...) DOCTEST_FAST_REQUIRE_GE(__VA_ARGS__)

+#define FAST_WARN_LE(...) DOCTEST_FAST_WARN_LE(__VA_ARGS__)

+#define FAST_CHECK_LE(...) DOCTEST_FAST_CHECK_LE(__VA_ARGS__)

+#define FAST_REQUIRE_LE(...) DOCTEST_FAST_REQUIRE_LE(__VA_ARGS__)

+

+#define FAST_WARN_UNARY(...) DOCTEST_FAST_WARN_UNARY(__VA_ARGS__)

+#define FAST_CHECK_UNARY(...) DOCTEST_FAST_CHECK_UNARY(__VA_ARGS__)

+#define FAST_REQUIRE_UNARY(...) DOCTEST_FAST_REQUIRE_UNARY(__VA_ARGS__)

+#define FAST_WARN_UNARY_FALSE(...) DOCTEST_FAST_WARN_UNARY_FALSE(__VA_ARGS__)

+#define FAST_CHECK_UNARY_FALSE(...) DOCTEST_FAST_CHECK_UNARY_FALSE(__VA_ARGS__)

+#define FAST_REQUIRE_UNARY_FALSE(...) DOCTEST_FAST_REQUIRE_UNARY_FALSE(__VA_ARGS__)

+

+#define TEST_CASE_TEMPLATE_INSTANTIATE(id, ...) DOCTEST_TEST_CASE_TEMPLATE_INSTANTIATE(id, __VA_ARGS__)

+

+#endif // DOCTEST_CONFIG_NO_SHORT_MACRO_NAMES

+

+#ifndef DOCTEST_CONFIG_DISABLE

+

+// this is here to clear the 'current test suite' for the current translation unit - at the top

+DOCTEST_TEST_SUITE_END();

+

+#endif // DOCTEST_CONFIG_DISABLE

+

+DOCTEST_CLANG_SUPPRESS_WARNING_POP

+DOCTEST_MSVC_SUPPRESS_WARNING_POP

+DOCTEST_GCC_SUPPRESS_WARNING_POP

+

+DOCTEST_SUPPRESS_COMMON_WARNINGS_POP

+

+#endif // DOCTEST_LIBRARY_INCLUDED

+

+#ifndef DOCTEST_SINGLE_HEADER

+#define DOCTEST_SINGLE_HEADER

+#endif // DOCTEST_SINGLE_HEADER

+

+#if defined(DOCTEST_CONFIG_IMPLEMENT) || !defined(DOCTEST_SINGLE_HEADER)

+

+#ifndef DOCTEST_SINGLE_HEADER

+#include "doctest_fwd.h"

+#endif // DOCTEST_SINGLE_HEADER

+

+DOCTEST_CLANG_SUPPRESS_WARNING_WITH_PUSH("-Wunused-macros")

+

+#ifndef DOCTEST_LIBRARY_IMPLEMENTATION

+#define DOCTEST_LIBRARY_IMPLEMENTATION

+

+DOCTEST_CLANG_SUPPRESS_WARNING_POP

+

+DOCTEST_SUPPRESS_COMMON_WARNINGS_PUSH

+

+DOCTEST_CLANG_SUPPRESS_WARNING_PUSH

+DOCTEST_CLANG_SUPPRESS_WARNING("-Wglobal-constructors")

+DOCTEST_CLANG_SUPPRESS_WARNING("-Wexit-time-destructors")

+DOCTEST_CLANG_SUPPRESS_WARNING("-Wsign-conversion")

+DOCTEST_CLANG_SUPPRESS_WARNING("-Wshorten-64-to-32")

+DOCTEST_CLANG_SUPPRESS_WARNING("-Wmissing-variable-declarations")

+DOCTEST_CLANG_SUPPRESS_WARNING("-Wswitch")

+DOCTEST_CLANG_SUPPRESS_WARNING("-Wswitch-enum")

+DOCTEST_CLANG_SUPPRESS_WARNING("-Wcovered-switch-default")

+DOCTEST_CLANG_SUPPRESS_WARNING("-Wmissing-noreturn")

+DOCTEST_CLANG_SUPPRESS_WARNING("-Wdisabled-macro-expansion")

+DOCTEST_CLANG_SUPPRESS_WARNING("-Wmissing-braces")

+DOCTEST_CLANG_SUPPRESS_WARNING("-Wmissing-field-initializers")

+DOCTEST_CLANG_SUPPRESS_WARNING("-Wunused-member-function")

+DOCTEST_CLANG_SUPPRESS_WARNING("-Wnonportable-system-include-path")

+

+DOCTEST_GCC_SUPPRESS_WARNING_PUSH

+DOCTEST_GCC_SUPPRESS_WARNING("-Wconversion")

+DOCTEST_GCC_SUPPRESS_WARNING("-Wsign-conversion")

+DOCTEST_GCC_SUPPRESS_WARNING("-Wmissing-field-initializers")

+DOCTEST_GCC_SUPPRESS_WARNING("-Wmissing-braces")

+DOCTEST_GCC_SUPPRESS_WARNING("-Wswitch")

+DOCTEST_GCC_SUPPRESS_WARNING("-Wswitch-enum")

+DOCTEST_GCC_SUPPRESS_WARNING("-Wswitch-default")

+DOCTEST_GCC_SUPPRESS_WARNING("-Wunsafe-loop-optimizations")

+DOCTEST_GCC_SUPPRESS_WARNING("-Wold-style-cast")

+DOCTEST_GCC_SUPPRESS_WARNING("-Wunused-function")

+DOCTEST_GCC_SUPPRESS_WARNING("-Wmultiple-inheritance")

+DOCTEST_GCC_SUPPRESS_WARNING("-Wsuggest-attribute")

+

+DOCTEST_MSVC_SUPPRESS_WARNING_PUSH

+DOCTEST_MSVC_SUPPRESS_WARNING(4267) // 'var' : conversion from 'x' to 'y', possible loss of data

+DOCTEST_MSVC_SUPPRESS_WARNING(4530) // C++ exception handler used, but unwind semantics not enabled

+DOCTEST_MSVC_SUPPRESS_WARNING(4577) // 'noexcept' used with no exception handling mode specified

+DOCTEST_MSVC_SUPPRESS_WARNING(4774) // format string expected in argument is not a string literal

+DOCTEST_MSVC_SUPPRESS_WARNING(4365) // conversion from 'int' to 'unsigned', signed/unsigned mismatch

+DOCTEST_MSVC_SUPPRESS_WARNING(5039) // pointer to potentially throwing function passed to extern C

+DOCTEST_MSVC_SUPPRESS_WARNING(4800) // forcing value to bool 'true' or 'false' (performance warning)

+DOCTEST_MSVC_SUPPRESS_WARNING(5245) // unreferenced function with internal linkage has been removed

+

+DOCTEST_MAKE_STD_HEADERS_CLEAN_FROM_WARNINGS_ON_WALL_BEGIN

+

+// required includes - will go only in one translation unit!

+#include <ctime>

+#include <cmath>

+#include <climits>

+// borland (Embarcadero) compiler requires math.h and not cmath - https://github.com/doctest/doctest/pull/37

+#ifdef __BORLANDC__

+#include <math.h>

+#endif // __BORLANDC__

+#include <new>

+#include <cstdio>

+#include <cstdlib>

+#include <cstring>

+#include <limits>

+#include <utility>

+#include <fstream>

+#include <sstream>

+#include <iostream>

+#include <algorithm>

+#include <iomanip>

+#include <vector>

+#ifndef DOCTEST_CONFIG_NO_MULTITHREADING

+#include <atomic>

+#include <mutex>

+#define DOCTEST_DECLARE_MUTEX(name) std::mutex name;

+#define DOCTEST_DECLARE_STATIC_MUTEX(name) static DOCTEST_DECLARE_MUTEX(name)

+#define DOCTEST_LOCK_MUTEX(name) std::lock_guard<std::mutex> DOCTEST_ANONYMOUS(DOCTEST_ANON_LOCK_)(name);

+#else // DOCTEST_CONFIG_NO_MULTITHREADING

+#define DOCTEST_DECLARE_MUTEX(name)

+#define DOCTEST_DECLARE_STATIC_MUTEX(name)

+#define DOCTEST_LOCK_MUTEX(name)

+#endif // DOCTEST_CONFIG_NO_MULTITHREADING

+#include <set>

+#include <map>

+#include <unordered_set>

+#include <exception>

+#include <stdexcept>

+#include <csignal>

+#include <cfloat>

+#include <cctype>

+#include <cstdint>

+#include <string>

+

+#ifdef DOCTEST_PLATFORM_MAC

+#include <sys/types.h>

+#include <unistd.h>

+#include <sys/sysctl.h>

+#endif // DOCTEST_PLATFORM_MAC

+

+#ifdef DOCTEST_PLATFORM_WINDOWS

+

+// defines for a leaner windows.h

+#ifndef WIN32_LEAN_AND_MEAN

+#define WIN32_LEAN_AND_MEAN

+#endif // WIN32_LEAN_AND_MEAN

+#ifndef NOMINMAX

+#define NOMINMAX

+#endif // NOMINMAX

+

+// not sure what AfxWin.h is for - here I do what Catch does

+#ifdef __AFXDLL

+#include <AfxWin.h>

+#else

+#include <windows.h>

+#endif

+#include <io.h>

+

+#else // DOCTEST_PLATFORM_WINDOWS

+

+#include <sys/time.h>

+#include <unistd.h>

+

+#endif // DOCTEST_PLATFORM_WINDOWS

+

+// this is a fix for https://github.com/doctest/doctest/issues/348

+// https://mail.gnome.org/archives/xml/2012-January/msg00000.html

+#if !defined(HAVE_UNISTD_H) && !defined(STDOUT_FILENO)

+#define STDOUT_FILENO fileno(stdout)

+#endif // HAVE_UNISTD_H

+

+DOCTEST_MAKE_STD_HEADERS_CLEAN_FROM_WARNINGS_ON_WALL_END

+

+// counts the number of elements in a C array

+#define DOCTEST_COUNTOF(x) (sizeof(x) / sizeof(x[0]))

+

+#ifdef DOCTEST_CONFIG_DISABLE

+#define DOCTEST_BRANCH_ON_DISABLED(if_disabled, if_not_disabled) if_disabled

+#else // DOCTEST_CONFIG_DISABLE

+#define DOCTEST_BRANCH_ON_DISABLED(if_disabled, if_not_disabled) if_not_disabled

+#endif // DOCTEST_CONFIG_DISABLE

+

+#ifndef DOCTEST_CONFIG_OPTIONS_PREFIX

+#define DOCTEST_CONFIG_OPTIONS_PREFIX "dt-"

+#endif

+

+#ifndef DOCTEST_THREAD_LOCAL

+#if defined(DOCTEST_CONFIG_NO_MULTITHREADING) || DOCTEST_MSVC && (DOCTEST_MSVC < DOCTEST_COMPILER(19, 0, 0))

+#define DOCTEST_THREAD_LOCAL

+#else // DOCTEST_MSVC

+#define DOCTEST_THREAD_LOCAL thread_local

+#endif // DOCTEST_MSVC

+#endif // DOCTEST_THREAD_LOCAL

+

+#ifndef DOCTEST_MULTI_LANE_ATOMICS_THREAD_LANES

+#define DOCTEST_MULTI_LANE_ATOMICS_THREAD_LANES 32

+#endif

+

+#ifndef DOCTEST_MULTI_LANE_ATOMICS_CACHE_LINE_SIZE

+#define DOCTEST_MULTI_LANE_ATOMICS_CACHE_LINE_SIZE 64

+#endif

+

+#ifdef DOCTEST_CONFIG_NO_UNPREFIXED_OPTIONS

+#define DOCTEST_OPTIONS_PREFIX_DISPLAY DOCTEST_CONFIG_OPTIONS_PREFIX

+#else

+#define DOCTEST_OPTIONS_PREFIX_DISPLAY ""

+#endif

+

+#if defined(WINAPI_FAMILY) && (WINAPI_FAMILY == WINAPI_FAMILY_APP)

+#define DOCTEST_CONFIG_NO_MULTI_LANE_ATOMICS

+#endif

+

+#ifndef DOCTEST_CDECL

+#define DOCTEST_CDECL __cdecl

+#endif

+

+namespace doctest {

+

+bool is_running_in_test = false;

+

+namespace {

+    using namespace detail;

+

+    template <typename Ex>

+    DOCTEST_NORETURN void throw_exception(Ex const& e) {

+#ifndef DOCTEST_CONFIG_NO_EXCEPTIONS

+        throw e;

+#else  // DOCTEST_CONFIG_NO_EXCEPTIONS

+        std::cerr << "doctest will terminate because it needed to throw an exception.\n"

+                  << "The message was: " << e.what() << '\n';

+        std::terminate();

+#endif // DOCTEST_CONFIG_NO_EXCEPTIONS

+    }

+

+#ifndef DOCTEST_INTERNAL_ERROR

+#define DOCTEST_INTERNAL_ERROR(msg)                                                                \

+    throw_exception(std::logic_error(                                                              \

+            __FILE__ ":" DOCTEST_TOSTR(__LINE__) ": Internal doctest error: " msg))

+#endif // DOCTEST_INTERNAL_ERROR

+

+    // case insensitive strcmp

+    int stricmp(const char* a, const char* b) {

+        for(;; a++, b++) {

+            const int d = tolower(*a) - tolower(*b);

+            if(d != 0 || !*a)

+                return d;

+        }

+    }

+

+    struct Endianness

+    {

+        enum Arch

+        {

+            Big,

+            Little

+        };

+

+        static Arch which() {

+            int x = 1;

+            // casting any data pointer to char* is allowed

+            auto ptr = reinterpret_cast<char*>(&x);

+            if(*ptr)

+                return Little;

+            return Big;

+        }

+    };

+} // namespace

+

+namespace detail {

+    DOCTEST_THREAD_LOCAL class

+    {

+        std::vector<std::streampos> stack;

+        std::stringstream           ss;

+

+    public:

+        std::ostream* push() {

+            stack.push_back(ss.tellp());

+            return &ss;

+        }

+

+        String pop() {

+            if (stack.empty())

+                DOCTEST_INTERNAL_ERROR("TLSS was empty when trying to pop!");

+

+            std::streampos pos = stack.back();

+            stack.pop_back();

+            unsigned sz = static_cast<unsigned>(ss.tellp() - pos);

+            ss.rdbuf()->pubseekpos(pos, std::ios::in | std::ios::out);

+            return String(ss, sz);

+        }

+    } g_oss;

+

+    std::ostream* tlssPush() {

+        return g_oss.push();

+    }

+

+    String tlssPop() {

+        return g_oss.pop();

+    }

+

+#ifndef DOCTEST_CONFIG_DISABLE

+

+namespace timer_large_integer

+{

+    

+#if defined(DOCTEST_PLATFORM_WINDOWS)

+    using type = ULONGLONG;

+#else // DOCTEST_PLATFORM_WINDOWS

+    using type = std::uint64_t;

+#endif // DOCTEST_PLATFORM_WINDOWS

+}

+

+using ticks_t = timer_large_integer::type;

+

+#ifdef DOCTEST_CONFIG_GETCURRENTTICKS

+    ticks_t getCurrentTicks() { return DOCTEST_CONFIG_GETCURRENTTICKS(); }

+#elif defined(DOCTEST_PLATFORM_WINDOWS)

+    ticks_t getCurrentTicks() {

+        static LARGE_INTEGER hz = { {0} }, hzo = { {0} };

+        if(!hz.QuadPart) {

+            QueryPerformanceFrequency(&hz);

+            QueryPerformanceCounter(&hzo);

+        }

+        LARGE_INTEGER t;

+        QueryPerformanceCounter(&t);

+        return ((t.QuadPart - hzo.QuadPart) * LONGLONG(1000000)) / hz.QuadPart;

+    }

+#else  // DOCTEST_PLATFORM_WINDOWS

+    ticks_t getCurrentTicks() {

+        timeval t;

+        gettimeofday(&t, nullptr);

+        return static_cast<ticks_t>(t.tv_sec) * 1000000 + static_cast<ticks_t>(t.tv_usec);

+    }

+#endif // DOCTEST_PLATFORM_WINDOWS

+

+    struct Timer

+    {

+        void         start() { m_ticks = getCurrentTicks(); }

+        unsigned int getElapsedMicroseconds() const {

+            return static_cast<unsigned int>(getCurrentTicks() - m_ticks);

+        }

+        //unsigned int getElapsedMilliseconds() const {

+        //    return static_cast<unsigned int>(getElapsedMicroseconds() / 1000);

+        //}

+        double getElapsedSeconds() const { return static_cast<double>(getCurrentTicks() - m_ticks) / 1000000.0; }

+

+    private:

+        ticks_t m_ticks = 0;

+    };

+

+#ifdef DOCTEST_CONFIG_NO_MULTITHREADING

+    template <typename T>

+    using Atomic = T;

+#else // DOCTEST_CONFIG_NO_MULTITHREADING

+    template <typename T>

+    using Atomic = std::atomic<T>;

+#endif // DOCTEST_CONFIG_NO_MULTITHREADING

+

+#if defined(DOCTEST_CONFIG_NO_MULTI_LANE_ATOMICS) || defined(DOCTEST_CONFIG_NO_MULTITHREADING)

+    template <typename T>

+    using MultiLaneAtomic = Atomic<T>;

+#else // DOCTEST_CONFIG_NO_MULTI_LANE_ATOMICS

+    // Provides a multilane implementation of an atomic variable that supports add, sub, load,

+    // store. Instead of using a single atomic variable, this splits up into multiple ones,

+    // each sitting on a separate cache line. The goal is to provide a speedup when most

+    // operations are modifying. It achieves this with two properties:

+    //

+    // * Multiple atomics are used, so chance of congestion from the same atomic is reduced.

+    // * Each atomic sits on a separate cache line, so false sharing is reduced.

+    //

+    // The disadvantage is that there is a small overhead due to the use of TLS, and load/store

+    // is slower because all atomics have to be accessed.

+    template <typename T>

+    class MultiLaneAtomic

+    {

+        struct CacheLineAlignedAtomic

+        {

+            Atomic<T> atomic{};

+            char padding[DOCTEST_MULTI_LANE_ATOMICS_CACHE_LINE_SIZE - sizeof(Atomic<T>)];

+        };

+        CacheLineAlignedAtomic m_atomics[DOCTEST_MULTI_LANE_ATOMICS_THREAD_LANES];

+

+        static_assert(sizeof(CacheLineAlignedAtomic) == DOCTEST_MULTI_LANE_ATOMICS_CACHE_LINE_SIZE,

+                      "guarantee one atomic takes exactly one cache line");

+

+    public:

+        T operator++() DOCTEST_NOEXCEPT { return fetch_add(1) + 1; }

+

+        T operator++(int) DOCTEST_NOEXCEPT { return fetch_add(1); }

+

+        T fetch_add(T arg, std::memory_order order = std::memory_order_seq_cst) DOCTEST_NOEXCEPT {

+            return myAtomic().fetch_add(arg, order);

+        }

+

+        T fetch_sub(T arg, std::memory_order order = std::memory_order_seq_cst) DOCTEST_NOEXCEPT {

+            return myAtomic().fetch_sub(arg, order);

+        }

+

+        operator T() const DOCTEST_NOEXCEPT { return load(); }

+

+        T load(std::memory_order order = std::memory_order_seq_cst) const DOCTEST_NOEXCEPT {

+            auto result = T();

+            for(auto const& c : m_atomics) {

+                result += c.atomic.load(order);

+            }

+            return result;

+        }

+

+        T operator=(T desired) DOCTEST_NOEXCEPT { // lgtm [cpp/assignment-does-not-return-this]

+            store(desired);

+            return desired;

+        }

+

+        void store(T desired, std::memory_order order = std::memory_order_seq_cst) DOCTEST_NOEXCEPT {

+            // first value becomes desired", all others become 0.

+            for(auto& c : m_atomics) {

+                c.atomic.store(desired, order);

+                desired = {};

+            }

+        }

+

+    private:

+        // Each thread has a different atomic that it operates on. If more than NumLanes threads

+        // use this, some will use the same atomic. So performance will degrade a bit, but still

+        // everything will work.

+        //

+        // The logic here is a bit tricky. The call should be as fast as possible, so that there

+        // is minimal to no overhead in determining the correct atomic for the current thread.

+        //

+        // 1. A global static counter laneCounter counts continuously up.

+        // 2. Each successive thread will use modulo operation of that counter so it gets an atomic

+        //    assigned in a round-robin fashion.

+        // 3. This tlsLaneIdx is stored in the thread local data, so it is directly available with

+        //    little overhead.

+        Atomic<T>& myAtomic() DOCTEST_NOEXCEPT {

+            static Atomic<size_t> laneCounter;

+            DOCTEST_THREAD_LOCAL size_t tlsLaneIdx =

+                    laneCounter++ % DOCTEST_MULTI_LANE_ATOMICS_THREAD_LANES;

+

+            return m_atomics[tlsLaneIdx].atomic;

+        }

+    };

+#endif // DOCTEST_CONFIG_NO_MULTI_LANE_ATOMICS

+

+    // this holds both parameters from the command line and runtime data for tests

+    struct ContextState : ContextOptions, TestRunStats, CurrentTestCaseStats

+    {

+        MultiLaneAtomic<int> numAssertsCurrentTest_atomic;

+        MultiLaneAtomic<int> numAssertsFailedCurrentTest_atomic;

+

+        std::vector<std::vector<String>> filters = decltype(filters)(9); // 9 different filters

+

+        std::vector<IReporter*> reporters_currently_used;

+

+        assert_handler ah = nullptr;

+

+        Timer timer;

+

+        std::vector<String> stringifiedContexts; // logging from INFO() due to an exception

+

+        // stuff for subcases

+        bool reachedLeaf;

+        std::vector<SubcaseSignature> subcaseStack;

+        std::vector<SubcaseSignature> nextSubcaseStack;

+        std::unordered_set<unsigned long long> fullyTraversedSubcases;

+        size_t currentSubcaseDepth;

+        Atomic<bool> shouldLogCurrentException;

+

+        void resetRunData() {

+            numTestCases                = 0;

+            numTestCasesPassingFilters  = 0;

+            numTestSuitesPassingFilters = 0;

+            numTestCasesFailed          = 0;

+            numAsserts                  = 0;

+            numAssertsFailed            = 0;

+            numAssertsCurrentTest       = 0;

+            numAssertsFailedCurrentTest = 0;

+        }

+

+        void finalizeTestCaseData() {

+            seconds = timer.getElapsedSeconds();

+

+            // update the non-atomic counters

+            numAsserts += numAssertsCurrentTest_atomic;

+            numAssertsFailed += numAssertsFailedCurrentTest_atomic;

+            numAssertsCurrentTest       = numAssertsCurrentTest_atomic;

+            numAssertsFailedCurrentTest = numAssertsFailedCurrentTest_atomic;

+

+            if(numAssertsFailedCurrentTest)

+                failure_flags |= TestCaseFailureReason::AssertFailure;

+

+            if(Approx(currentTest->m_timeout).epsilon(DBL_EPSILON) != 0 &&

+               Approx(seconds).epsilon(DBL_EPSILON) > currentTest->m_timeout)

+                failure_flags |= TestCaseFailureReason::Timeout;

+

+            if(currentTest->m_should_fail) {

+                if(failure_flags) {

+                    failure_flags |= TestCaseFailureReason::ShouldHaveFailedAndDid;

+                } else {

+                    failure_flags |= TestCaseFailureReason::ShouldHaveFailedButDidnt;

+                }

+            } else if(failure_flags && currentTest->m_may_fail) {

+                failure_flags |= TestCaseFailureReason::CouldHaveFailedAndDid;

+            } else if(currentTest->m_expected_failures > 0) {

+                if(numAssertsFailedCurrentTest == currentTest->m_expected_failures) {

+                    failure_flags |= TestCaseFailureReason::FailedExactlyNumTimes;

+                } else {

+                    failure_flags |= TestCaseFailureReason::DidntFailExactlyNumTimes;

+                }

+            }

+

+            bool ok_to_fail = (TestCaseFailureReason::ShouldHaveFailedAndDid & failure_flags) ||

+                              (TestCaseFailureReason::CouldHaveFailedAndDid & failure_flags) ||

+                              (TestCaseFailureReason::FailedExactlyNumTimes & failure_flags);

+

+            // if any subcase has failed - the whole test case has failed

+            testCaseSuccess = !(failure_flags && !ok_to_fail);

+            if(!testCaseSuccess)

+                numTestCasesFailed++;

+        }

+    };

+

+    ContextState* g_cs = nullptr;

+

+    // used to avoid locks for the debug output

+    // TODO: figure out if this is indeed necessary/correct - seems like either there still

+    // could be a race or that there wouldn't be a race even if using the context directly

+    DOCTEST_THREAD_LOCAL bool g_no_colors;

+

+#endif // DOCTEST_CONFIG_DISABLE

+} // namespace detail

+

+char* String::allocate(size_type sz) {

+    if (sz <= last) {

+        buf[sz] = '\0';

+        setLast(last - sz);

+        return buf;

+    } else {

+        setOnHeap();

+        data.size = sz;

+        data.capacity = data.size + 1;

+        data.ptr = new char[data.capacity];

+        data.ptr[sz] = '\0';

+        return data.ptr;

+    }

+}

+

+void String::setOnHeap() noexcept { *reinterpret_cast<unsigned char*>(&buf[last]) = 128; }

+void String::setLast(size_type in) noexcept { buf[last] = char(in); }

+void String::setSize(size_type sz) noexcept {

+    if (isOnStack()) { buf[sz] = '\0'; setLast(last - sz); }

+    else { data.ptr[sz] = '\0'; data.size = sz; }

+}

+

+void String::copy(const String& other) {

+    if(other.isOnStack()) {

+        memcpy(buf, other.buf, len);

+    } else {

+        memcpy(allocate(other.data.size), other.data.ptr, other.data.size);

+    }

+}

+

+String::String() noexcept {

+    buf[0] = '\0';

+    setLast();

+}

+

+String::~String() {

+    if(!isOnStack())

+        delete[] data.ptr;

+} // NOLINT(clang-analyzer-cplusplus.NewDeleteLeaks)

+

+String::String(const char* in)

+        : String(in, strlen(in)) {}

+

+String::String(const char* in, size_type in_size) {

+    memcpy(allocate(in_size), in, in_size);

+}

+

+String::String(std::istream& in, size_type in_size) {

+    in.read(allocate(in_size), in_size);

+}

+

+String::String(const String& other) { copy(other); }

+

+String& String::operator=(const String& other) {

+    if(this != &other) {

+        if(!isOnStack())

+            delete[] data.ptr;

+

+        copy(other);

+    }

+

+    return *this;

+}

+

+String& String::operator+=(const String& other) {

+    const size_type my_old_size = size();

+    const size_type other_size  = other.size();

+    const size_type total_size  = my_old_size + other_size;

+    if(isOnStack()) {

+        if(total_size < len) {

+            // append to the current stack space

+            memcpy(buf + my_old_size, other.c_str(), other_size + 1);

+            // NOLINTNEXTLINE(clang-analyzer-cplusplus.NewDeleteLeaks)

+            setLast(last - total_size);

+        } else {

+            // alloc new chunk

+            char* temp = new char[total_size + 1];

+            // copy current data to new location before writing in the union

+            memcpy(temp, buf, my_old_size); // skip the +1 ('\0') for speed

+            // update data in union

+            setOnHeap();

+            data.size     = total_size;

+            data.capacity = data.size + 1;

+            data.ptr      = temp;

+            // transfer the rest of the data

+            memcpy(data.ptr + my_old_size, other.c_str(), other_size + 1);

+        }

+    } else {

+        if(data.capacity > total_size) {

+            // append to the current heap block

+            data.size = total_size;

+            memcpy(data.ptr + my_old_size, other.c_str(), other_size + 1);

+        } else {

+            // resize

+            data.capacity *= 2;

+            if(data.capacity <= total_size)

+                data.capacity = total_size + 1;

+            // alloc new chunk

+            char* temp = new char[data.capacity];

+            // copy current data to new location before releasing it

+            memcpy(temp, data.ptr, my_old_size); // skip the +1 ('\0') for speed

+            // release old chunk

+            delete[] data.ptr;

+            // update the rest of the union members

+            data.size = total_size;

+            data.ptr  = temp;

+            // transfer the rest of the data

+            memcpy(data.ptr + my_old_size, other.c_str(), other_size + 1);

+        }

+    }

+

+    return *this;

+}

+

+String::String(String&& other) noexcept {

+    memcpy(buf, other.buf, len);

+    other.buf[0] = '\0';

+    other.setLast();

+}

+

+String& String::operator=(String&& other) noexcept {

+    if(this != &other) {

+        if(!isOnStack())

+            delete[] data.ptr;

+        memcpy(buf, other.buf, len);

+        other.buf[0] = '\0';

+        other.setLast();

+    }

+    return *this;

+}

+

+char String::operator[](size_type i) const {

+    return const_cast<String*>(this)->operator[](i);

+}

+

+char& String::operator[](size_type i) {

+    if(isOnStack())

+        return reinterpret_cast<char*>(buf)[i];

+    return data.ptr[i];

+}

+

+DOCTEST_GCC_SUPPRESS_WARNING_WITH_PUSH("-Wmaybe-uninitialized")

+String::size_type String::size() const {

+    if(isOnStack())

+        return last - (size_type(buf[last]) & 31); // using "last" would work only if "len" is 32

+    return data.size;

+}

+DOCTEST_GCC_SUPPRESS_WARNING_POP

+

+String::size_type String::capacity() const {

+    if(isOnStack())

+        return len;

+    return data.capacity;

+}

+

+String String::substr(size_type pos, size_type cnt) && {

+    cnt = std::min(cnt, size() - 1 - pos);

+    char* cptr = c_str();

+    memmove(cptr, cptr + pos, cnt);

+    setSize(cnt);

+    return std::move(*this);

+}

+

+String String::substr(size_type pos, size_type cnt) const & {

+    cnt = std::min(cnt, size() - 1 - pos);

+    return String{ c_str() + pos, cnt };

+}

+

+String::size_type String::find(char ch, size_type pos) const {

+    const char* begin = c_str();

+    const char* end = begin + size();

+    const char* it = begin + pos;

+    for (; it < end && *it != ch; it++);

+    if (it < end) { return static_cast<size_type>(it - begin); }

+    else { return npos; }

+}

+

+String::size_type String::rfind(char ch, size_type pos) const {

+    const char* begin = c_str();

+    const char* it = begin + std::min(pos, size() - 1);

+    for (; it >= begin && *it != ch; it--);

+    if (it >= begin) { return static_cast<size_type>(it - begin); }

+    else { return npos; }

+}

+

+int String::compare(const char* other, bool no_case) const {

+    if(no_case)

+        return doctest::stricmp(c_str(), other);

+    return std::strcmp(c_str(), other);

+}

+

+int String::compare(const String& other, bool no_case) const {

+    return compare(other.c_str(), no_case);

+}

+

+String operator+(const String& lhs, const String& rhs) { return  String(lhs) += rhs; }

+

+bool operator==(const String& lhs, const String& rhs) { return lhs.compare(rhs) == 0; }

+bool operator!=(const String& lhs, const String& rhs) { return lhs.compare(rhs) != 0; }

+bool operator< (const String& lhs, const String& rhs) { return lhs.compare(rhs) < 0; }

+bool operator> (const String& lhs, const String& rhs) { return lhs.compare(rhs) > 0; }

+bool operator<=(const String& lhs, const String& rhs) { return (lhs != rhs) ? lhs.compare(rhs) < 0 : true; }

+bool operator>=(const String& lhs, const String& rhs) { return (lhs != rhs) ? lhs.compare(rhs) > 0 : true; }

+

+std::ostream& operator<<(std::ostream& s, const String& in) { return s << in.c_str(); }

+

+Contains::Contains(const String& str) : string(str) { }

+

+bool Contains::checkWith(const String& other) const {

+    return strstr(other.c_str(), string.c_str()) != nullptr;

+}

+

+String toString(const Contains& in) {

+    return "Contains( " + in.string + " )";

+}

+

+bool operator==(const String& lhs, const Contains& rhs) { return rhs.checkWith(lhs); }

+bool operator==(const Contains& lhs, const String& rhs) { return lhs.checkWith(rhs); }

+bool operator!=(const String& lhs, const Contains& rhs) { return !rhs.checkWith(lhs); }

+bool operator!=(const Contains& lhs, const String& rhs) { return !lhs.checkWith(rhs); }

+

+namespace {

+    void color_to_stream(std::ostream&, Color::Enum) DOCTEST_BRANCH_ON_DISABLED({}, ;)

+} // namespace

+

+namespace Color {

+    std::ostream& operator<<(std::ostream& s, Color::Enum code) {

+        color_to_stream(s, code);

+        return s;

+    }

+} // namespace Color

+

+// clang-format off

+const char* assertString(assertType::Enum at) {

+    DOCTEST_MSVC_SUPPRESS_WARNING_WITH_PUSH(4061) // enum 'x' in switch of enum 'y' is not explicitely handled

+    #define DOCTEST_GENERATE_ASSERT_TYPE_CASE(assert_type) case assertType::DT_ ## assert_type: return #assert_type

+    #define DOCTEST_GENERATE_ASSERT_TYPE_CASES(assert_type) \

+        DOCTEST_GENERATE_ASSERT_TYPE_CASE(WARN_ ## assert_type); \

+        DOCTEST_GENERATE_ASSERT_TYPE_CASE(CHECK_ ## assert_type); \

+        DOCTEST_GENERATE_ASSERT_TYPE_CASE(REQUIRE_ ## assert_type)

+    switch(at) {

+        DOCTEST_GENERATE_ASSERT_TYPE_CASE(WARN);

+        DOCTEST_GENERATE_ASSERT_TYPE_CASE(CHECK);

+        DOCTEST_GENERATE_ASSERT_TYPE_CASE(REQUIRE);

+

+        DOCTEST_GENERATE_ASSERT_TYPE_CASES(FALSE);

+

+        DOCTEST_GENERATE_ASSERT_TYPE_CASES(THROWS);

+

+        DOCTEST_GENERATE_ASSERT_TYPE_CASES(THROWS_AS);

+

+        DOCTEST_GENERATE_ASSERT_TYPE_CASES(THROWS_WITH);

+

+        DOCTEST_GENERATE_ASSERT_TYPE_CASES(THROWS_WITH_AS);

+

+        DOCTEST_GENERATE_ASSERT_TYPE_CASES(NOTHROW);

+

+        DOCTEST_GENERATE_ASSERT_TYPE_CASES(EQ);

+        DOCTEST_GENERATE_ASSERT_TYPE_CASES(NE);

+        DOCTEST_GENERATE_ASSERT_TYPE_CASES(GT);

+        DOCTEST_GENERATE_ASSERT_TYPE_CASES(LT);

+        DOCTEST_GENERATE_ASSERT_TYPE_CASES(GE);

+        DOCTEST_GENERATE_ASSERT_TYPE_CASES(LE);

+

+        DOCTEST_GENERATE_ASSERT_TYPE_CASES(UNARY);

+        DOCTEST_GENERATE_ASSERT_TYPE_CASES(UNARY_FALSE);

+

+        default: DOCTEST_INTERNAL_ERROR("Tried stringifying invalid assert type!");

+    }

+    DOCTEST_MSVC_SUPPRESS_WARNING_POP

+}

+// clang-format on

+

+const char* failureString(assertType::Enum at) {

+    if(at & assertType::is_warn) //!OCLINT bitwise operator in conditional

+        return "WARNING";

+    if(at & assertType::is_check) //!OCLINT bitwise operator in conditional

+        return "ERROR";

+    if(at & assertType::is_require) //!OCLINT bitwise operator in conditional

+        return "FATAL ERROR";

+    return "";

+}

+

+DOCTEST_CLANG_SUPPRESS_WARNING_WITH_PUSH("-Wnull-dereference")

+DOCTEST_GCC_SUPPRESS_WARNING_WITH_PUSH("-Wnull-dereference")

+// depending on the current options this will remove the path of filenames

+const char* skipPathFromFilename(const char* file) {

+#ifndef DOCTEST_CONFIG_DISABLE

+    if(getContextOptions()->no_path_in_filenames) {

+        auto back    = std::strrchr(file, '\\');

+        auto forward = std::strrchr(file, '/');

+        if(back || forward) {

+            if(back > forward)

+                forward = back;

+            return forward + 1;

+        }

+    }

+#endif // DOCTEST_CONFIG_DISABLE

+    return file;

+}

+DOCTEST_CLANG_SUPPRESS_WARNING_POP

+DOCTEST_GCC_SUPPRESS_WARNING_POP

+

+bool SubcaseSignature::operator==(const SubcaseSignature& other) const {

+    return m_line == other.m_line

+        && std::strcmp(m_file, other.m_file) == 0

+        && m_name == other.m_name;

+}

+

+bool SubcaseSignature::operator<(const SubcaseSignature& other) const {

+    if(m_line != other.m_line)

+        return m_line < other.m_line;

+    if(std::strcmp(m_file, other.m_file) != 0)

+        return std::strcmp(m_file, other.m_file) < 0;

+    return m_name.compare(other.m_name) < 0;

+}

+

+DOCTEST_DEFINE_INTERFACE(IContextScope)

+

+namespace detail {

+    void filldata<const void*>::fill(std::ostream* stream, const void* in) {

+        if (in) { *stream << in; }

+        else { *stream << "nullptr"; }

+    }

+

+    template <typename T>

+    String toStreamLit(T t) {

+        std::ostream* os = tlssPush();

+        os->operator<<(t);

+        return tlssPop();

+    }

+}

+

+#ifdef DOCTEST_CONFIG_TREAT_CHAR_STAR_AS_STRING

+String toString(const char* in) { return String("\"") + (in ? in : "{null string}") + "\""; }

+#endif // DOCTEST_CONFIG_TREAT_CHAR_STAR_AS_STRING

+

+#if DOCTEST_MSVC >= DOCTEST_COMPILER(19, 20, 0)

+// see this issue on why this is needed: https://github.com/doctest/doctest/issues/183

+String toString(const std::string& in) { return in.c_str(); }

+#endif // VS 2019

+

+String toString(String in) { return in; }

+

+String toString(std::nullptr_t) { return "nullptr"; }

+

+String toString(bool in) { return in ? "true" : "false"; }

+

+String toString(float in) { return toStreamLit(in); }

+String toString(double in) { return toStreamLit(in); }

+String toString(double long in) { return toStreamLit(in); }

+

+String toString(char in) { return toStreamLit(static_cast<signed>(in)); }

+String toString(char signed in) { return toStreamLit(static_cast<signed>(in)); }

+String toString(char unsigned in) { return toStreamLit(static_cast<unsigned>(in)); }

+String toString(short in) { return toStreamLit(in); }

+String toString(short unsigned in) { return toStreamLit(in); }

+String toString(signed in) { return toStreamLit(in); }

+String toString(unsigned in) { return toStreamLit(in); }

+String toString(long in) { return toStreamLit(in); }

+String toString(long unsigned in) { return toStreamLit(in); }

+String toString(long long in) { return toStreamLit(in); }

+String toString(long long unsigned in) { return toStreamLit(in); }

+

+Approx::Approx(double value)

+        : m_epsilon(static_cast<double>(std::numeric_limits<float>::epsilon()) * 100)

+        , m_scale(1.0)

+        , m_value(value) {}

+

+Approx Approx::operator()(double value) const {

+    Approx approx(value);

+    approx.epsilon(m_epsilon);

+    approx.scale(m_scale);

+    return approx;

+}

+

+Approx& Approx::epsilon(double newEpsilon) {

+    m_epsilon = newEpsilon;

+    return *this;

+}

+Approx& Approx::scale(double newScale) {

+    m_scale = newScale;

+    return *this;

+}

+

+bool operator==(double lhs, const Approx& rhs) {

+    // Thanks to Richard Harris for his help refining this formula

+    return std::fabs(lhs - rhs.m_value) <

+           rhs.m_epsilon * (rhs.m_scale + std::max<double>(std::fabs(lhs), std::fabs(rhs.m_value)));

+}

+bool operator==(const Approx& lhs, double rhs) { return operator==(rhs, lhs); }

+bool operator!=(double lhs, const Approx& rhs) { return !operator==(lhs, rhs); }

+bool operator!=(const Approx& lhs, double rhs) { return !operator==(rhs, lhs); }

+bool operator<=(double lhs, const Approx& rhs) { return lhs < rhs.m_value || lhs == rhs; }

+bool operator<=(const Approx& lhs, double rhs) { return lhs.m_value < rhs || lhs == rhs; }

+bool operator>=(double lhs, const Approx& rhs) { return lhs > rhs.m_value || lhs == rhs; }

+bool operator>=(const Approx& lhs, double rhs) { return lhs.m_value > rhs || lhs == rhs; }

+bool operator<(double lhs, const Approx& rhs) { return lhs < rhs.m_value && lhs != rhs; }

+bool operator<(const Approx& lhs, double rhs) { return lhs.m_value < rhs && lhs != rhs; }

+bool operator>(double lhs, const Approx& rhs) { return lhs > rhs.m_value && lhs != rhs; }

+bool operator>(const Approx& lhs, double rhs) { return lhs.m_value > rhs && lhs != rhs; }

+

+String toString(const Approx& in) {

+    return "Approx( " + doctest::toString(in.m_value) + " )";

+}

+const ContextOptions* getContextOptions() { return DOCTEST_BRANCH_ON_DISABLED(nullptr, g_cs); }

+

+DOCTEST_MSVC_SUPPRESS_WARNING_WITH_PUSH(4738)

+template <typename F>

+IsNaN<F>::operator bool() const {

+    return std::isnan(value) ^ flipped;

+}

+DOCTEST_MSVC_SUPPRESS_WARNING_POP

+template struct DOCTEST_INTERFACE_DEF IsNaN<float>;

+template struct DOCTEST_INTERFACE_DEF IsNaN<double>;

+template struct DOCTEST_INTERFACE_DEF IsNaN<long double>;

+template <typename F>

+String toString(IsNaN<F> in) { return String(in.flipped ? "! " : "") + "IsNaN( " + doctest::toString(in.value) + " )"; }

+String toString(IsNaN<float> in) { return toString<float>(in); }

+String toString(IsNaN<double> in) { return toString<double>(in); }

+String toString(IsNaN<double long> in) { return toString<double long>(in); }

+

+} // namespace doctest

+

+#ifdef DOCTEST_CONFIG_DISABLE

+namespace doctest {

+Context::Context(int, const char* const*) {}

+Context::~Context() = default;

+void Context::applyCommandLine(int, const char* const*) {}

+void Context::addFilter(const char*, const char*) {}

+void Context::clearFilters() {}

+void Context::setOption(const char*, bool) {}

+void Context::setOption(const char*, int) {}

+void Context::setOption(const char*, const char*) {}

+bool Context::shouldExit() { return false; }

+void Context::setAsDefaultForAssertsOutOfTestCases() {}

+void Context::setAssertHandler(detail::assert_handler) {}

+void Context::setCout(std::ostream*) {}

+int  Context::run() { return 0; }

+

+int                         IReporter::get_num_active_contexts() { return 0; }

+const IContextScope* const* IReporter::get_active_contexts() { return nullptr; }

+int                         IReporter::get_num_stringified_contexts() { return 0; }

+const String*               IReporter::get_stringified_contexts() { return nullptr; }

+

+int registerReporter(const char*, int, IReporter*) { return 0; }

+

+} // namespace doctest

+#else // DOCTEST_CONFIG_DISABLE

+

+#if !defined(DOCTEST_CONFIG_COLORS_NONE)

+#if !defined(DOCTEST_CONFIG_COLORS_WINDOWS) && !defined(DOCTEST_CONFIG_COLORS_ANSI)

+#ifdef DOCTEST_PLATFORM_WINDOWS

+#define DOCTEST_CONFIG_COLORS_WINDOWS

+#else // linux

+#define DOCTEST_CONFIG_COLORS_ANSI

+#endif // platform

+#endif // DOCTEST_CONFIG_COLORS_WINDOWS && DOCTEST_CONFIG_COLORS_ANSI

+#endif // DOCTEST_CONFIG_COLORS_NONE

+

+namespace doctest_detail_test_suite_ns {

+// holds the current test suite

+doctest::detail::TestSuite& getCurrentTestSuite() {

+    static doctest::detail::TestSuite data{};

+    return data;

+}

+} // namespace doctest_detail_test_suite_ns

+

+namespace doctest {

+namespace {

+    // the int (priority) is part of the key for automatic sorting - sadly one can register a

+    // reporter with a duplicate name and a different priority but hopefully that won't happen often :|

+    using reporterMap = std::map<std::pair<int, String>, reporterCreatorFunc>;

+

+    reporterMap& getReporters() {

+        static reporterMap data;

+        return data;

+    }

+    reporterMap& getListeners() {

+        static reporterMap data;

+        return data;

+    }

+} // namespace

+namespace detail {

+#define DOCTEST_ITERATE_THROUGH_REPORTERS(function, ...)                                           \

+    for(auto& curr_rep : g_cs->reporters_currently_used)                                           \

+    curr_rep->function(__VA_ARGS__)

+

+    bool checkIfShouldThrow(assertType::Enum at) {

+        if(at & assertType::is_require) //!OCLINT bitwise operator in conditional

+            return true;

+

+        if((at & assertType::is_check) //!OCLINT bitwise operator in conditional

+           && getContextOptions()->abort_after > 0 &&

+           (g_cs->numAssertsFailed + g_cs->numAssertsFailedCurrentTest_atomic) >=

+                   getContextOptions()->abort_after)

+            return true;

+

+        return false;

+    }

+

+#ifndef DOCTEST_CONFIG_NO_EXCEPTIONS

+    DOCTEST_NORETURN void throwException() {

+        g_cs->shouldLogCurrentException = false;

+        throw TestFailureException(); // NOLINT(hicpp-exception-baseclass)

+    }

+#else // DOCTEST_CONFIG_NO_EXCEPTIONS

+    void throwException() {}

+#endif // DOCTEST_CONFIG_NO_EXCEPTIONS

+} // namespace detail

+

+namespace {

+    using namespace detail;

+    // matching of a string against a wildcard mask (case sensitivity configurable) taken from

+    // https://www.codeproject.com/Articles/1088/Wildcard-string-compare-globbing

+    int wildcmp(const char* str, const char* wild, bool caseSensitive) {

+        const char* cp = str;

+        const char* mp = wild;

+

+        while((*str) && (*wild != '*')) {

+            if((caseSensitive ? (*wild != *str) : (tolower(*wild) != tolower(*str))) &&

+               (*wild != '?')) {

+                return 0;

+            }

+            wild++;

+            str++;

+        }

+

+        while(*str) {

+            if(*wild == '*') {

+                if(!*++wild) {

+                    return 1;

+                }

+                mp = wild;

+                cp = str + 1;

+            } else if((caseSensitive ? (*wild == *str) : (tolower(*wild) == tolower(*str))) ||

+                      (*wild == '?')) {

+                wild++;

+                str++;

+            } else {

+                wild = mp;   //!OCLINT parameter reassignment

+                str  = cp++; //!OCLINT parameter reassignment

+            }

+        }

+

+        while(*wild == '*') {

+            wild++;

+        }

+        return !*wild;

+    }

+

+    // checks if the name matches any of the filters (and can be configured what to do when empty)

+    bool matchesAny(const char* name, const std::vector<String>& filters, bool matchEmpty,

+        bool caseSensitive) {

+        if (filters.empty() && matchEmpty)

+            return true;

+        for (auto& curr : filters)

+            if (wildcmp(name, curr.c_str(), caseSensitive))

+                return true;

+        return false;

+    }

+

+    unsigned long long hash(unsigned long long a, unsigned long long b) {

+        return (a << 5) + b;

+    }

+

+    // C string hash function (djb2) - taken from http://www.cse.yorku.ca/~oz/hash.html

+    unsigned long long hash(const char* str) {

+        unsigned long long hash = 5381;

+        char c;

+        while ((c = *str++))

+            hash = ((hash << 5) + hash) + c; // hash * 33 + c

+        return hash;

+    }

+

+    unsigned long long hash(const SubcaseSignature& sig) {

+        return hash(hash(hash(sig.m_file), hash(sig.m_name.c_str())), sig.m_line);

+    }

+

+    unsigned long long hash(const std::vector<SubcaseSignature>& sigs, size_t count) {

+        unsigned long long running = 0;

+        auto end = sigs.begin() + count;

+        for (auto it = sigs.begin(); it != end; it++) {

+            running = hash(running, hash(*it));

+        }

+        return running;

+    }

+

+    unsigned long long hash(const std::vector<SubcaseSignature>& sigs) {

+        unsigned long long running = 0;

+        for (const SubcaseSignature& sig : sigs) {

+            running = hash(running, hash(sig));

+        }

+        return running;

+    }

+} // namespace

+namespace detail {

+    bool Subcase::checkFilters() {

+        if (g_cs->subcaseStack.size() < size_t(g_cs->subcase_filter_levels)) {

+            if (!matchesAny(m_signature.m_name.c_str(), g_cs->filters[6], true, g_cs->case_sensitive))

+                return true;

+            if (matchesAny(m_signature.m_name.c_str(), g_cs->filters[7], false, g_cs->case_sensitive))

+                return true;

+        }

+        return false;

+    }

+

+    Subcase::Subcase(const String& name, const char* file, int line)

+            : m_signature({name, file, line}) {

+        if (!g_cs->reachedLeaf) {

+            if (g_cs->nextSubcaseStack.size() <= g_cs->subcaseStack.size()

+                || g_cs->nextSubcaseStack[g_cs->subcaseStack.size()] == m_signature) {

+                // Going down.

+                if (checkFilters()) { return; }

+

+                g_cs->subcaseStack.push_back(m_signature);

+                g_cs->currentSubcaseDepth++;

+                m_entered = true;

+                DOCTEST_ITERATE_THROUGH_REPORTERS(subcase_start, m_signature);

+            }

+        } else {

+            if (g_cs->subcaseStack[g_cs->currentSubcaseDepth] == m_signature) {

+                // This subcase is reentered via control flow.

+                g_cs->currentSubcaseDepth++;

+                m_entered = true;

+                DOCTEST_ITERATE_THROUGH_REPORTERS(subcase_start, m_signature);

+            } else if (g_cs->nextSubcaseStack.size() <= g_cs->currentSubcaseDepth

+                    && g_cs->fullyTraversedSubcases.find(hash(hash(g_cs->subcaseStack, g_cs->currentSubcaseDepth), hash(m_signature)))

+                    == g_cs->fullyTraversedSubcases.end()) {

+                if (checkFilters()) { return; }

+                // This subcase is part of the one to be executed next.

+                g_cs->nextSubcaseStack.clear();

+                g_cs->nextSubcaseStack.insert(g_cs->nextSubcaseStack.end(),

+                    g_cs->subcaseStack.begin(), g_cs->subcaseStack.begin() + g_cs->currentSubcaseDepth);

+                g_cs->nextSubcaseStack.push_back(m_signature);

+            }

+        }

+    }

+

+    DOCTEST_MSVC_SUPPRESS_WARNING_WITH_PUSH(4996) // std::uncaught_exception is deprecated in C++17

+    DOCTEST_GCC_SUPPRESS_WARNING_WITH_PUSH("-Wdeprecated-declarations")

+    DOCTEST_CLANG_SUPPRESS_WARNING_WITH_PUSH("-Wdeprecated-declarations")

+

+    Subcase::~Subcase() {

+        if (m_entered) {

+            g_cs->currentSubcaseDepth--;

+

+            if (!g_cs->reachedLeaf) {

+                // Leaf.

+                g_cs->fullyTraversedSubcases.insert(hash(g_cs->subcaseStack));

+                g_cs->nextSubcaseStack.clear();

+                g_cs->reachedLeaf = true;

+            } else if (g_cs->nextSubcaseStack.empty()) {

+                // All children are finished.

+                g_cs->fullyTraversedSubcases.insert(hash(g_cs->subcaseStack));

+            }

+

+#if defined(__cpp_lib_uncaught_exceptions) && __cpp_lib_uncaught_exceptions >= 201411L && (!defined(__MAC_OS_X_VERSION_MIN_REQUIRED) || __MAC_OS_X_VERSION_MIN_REQUIRED >= 101200)

+            if(std::uncaught_exceptions() > 0

+#else

+            if(std::uncaught_exception()

+#endif

+                && g_cs->shouldLogCurrentException) {

+                DOCTEST_ITERATE_THROUGH_REPORTERS(

+                        test_case_exception, {"exception thrown in subcase - will translate later "

+                                                "when the whole test case has been exited (cannot "

+                                                "translate while there is an active exception)",

+                                                false});

+                g_cs->shouldLogCurrentException = false;

+            }

+

+            DOCTEST_ITERATE_THROUGH_REPORTERS(subcase_end, DOCTEST_EMPTY);

+        }

+    }

+

+    DOCTEST_CLANG_SUPPRESS_WARNING_POP

+    DOCTEST_GCC_SUPPRESS_WARNING_POP

+    DOCTEST_MSVC_SUPPRESS_WARNING_POP

+

+    Subcase::operator bool() const { return m_entered; }

+

+    Result::Result(bool passed, const String& decomposition)

+            : m_passed(passed)

+            , m_decomp(decomposition) {}

+

+    ExpressionDecomposer::ExpressionDecomposer(assertType::Enum at)

+            : m_at(at) {}

+

+    TestSuite& TestSuite::operator*(const char* in) {

+        m_test_suite = in;

+        return *this;

+    }

+

+    TestCase::TestCase(funcType test, const char* file, unsigned line, const TestSuite& test_suite,

+                       const String& type, int template_id) {

+        m_file              = file;

+        m_line              = line;

+        m_name              = nullptr; // will be later overridden in operator*

+        m_test_suite        = test_suite.m_test_suite;

+        m_description       = test_suite.m_description;

+        m_skip              = test_suite.m_skip;

+        m_no_breaks         = test_suite.m_no_breaks;

+        m_no_output         = test_suite.m_no_output;

+        m_may_fail          = test_suite.m_may_fail;

+        m_should_fail       = test_suite.m_should_fail;

+        m_expected_failures = test_suite.m_expected_failures;

+        m_timeout           = test_suite.m_timeout;

+

+        m_test        = test;

+        m_type        = type;

+        m_template_id = template_id;

+    }

+

+    TestCase::TestCase(const TestCase& other)

+            : TestCaseData() {

+        *this = other;

+    }

+

+    DOCTEST_MSVC_SUPPRESS_WARNING_WITH_PUSH(26434) // hides a non-virtual function

+    TestCase& TestCase::operator=(const TestCase& other) {

+        TestCaseData::operator=(other);

+        m_test        = other.m_test;

+        m_type        = other.m_type;

+        m_template_id = other.m_template_id;

+        m_full_name   = other.m_full_name;

+

+        if(m_template_id != -1)

+            m_name = m_full_name.c_str();

+        return *this;

+    }

+    DOCTEST_MSVC_SUPPRESS_WARNING_POP

+

+    TestCase& TestCase::operator*(const char* in) {

+        m_name = in;

+        // make a new name with an appended type for templated test case

+        if(m_template_id != -1) {

+            m_full_name = String(m_name) + "<" + m_type + ">";

+            // redirect the name to point to the newly constructed full name

+            m_name = m_full_name.c_str();

+        }

+        return *this;

+    }

+

+    bool TestCase::operator<(const TestCase& other) const {

+        // this will be used only to differentiate between test cases - not relevant for sorting

+        if(m_line != other.m_line)

+            return m_line < other.m_line;

+        const int name_cmp = strcmp(m_name, other.m_name);

+        if(name_cmp != 0)

+            return name_cmp < 0;

+        const int file_cmp = m_file.compare(other.m_file);

+        if(file_cmp != 0)

+            return file_cmp < 0;

+        return m_template_id < other.m_template_id;

+    }

+

+    // all the registered tests

+    std::set<TestCase>& getRegisteredTests() {

+        static std::set<TestCase> data;

+        return data;

+    }

+} // namespace detail

+namespace {

+    using namespace detail;

+    // for sorting tests by file/line

+    bool fileOrderComparator(const TestCase* lhs, const TestCase* rhs) {

+        // this is needed because MSVC gives different case for drive letters

+        // for __FILE__ when evaluated in a header and a source file

+        const int res = lhs->m_file.compare(rhs->m_file, bool(DOCTEST_MSVC));

+        if(res != 0)

+            return res < 0;

+        if(lhs->m_line != rhs->m_line)

+            return lhs->m_line < rhs->m_line;

+        return lhs->m_template_id < rhs->m_template_id;

+    }

+

+    // for sorting tests by suite/file/line

+    bool suiteOrderComparator(const TestCase* lhs, const TestCase* rhs) {

+        const int res = std::strcmp(lhs->m_test_suite, rhs->m_test_suite);

+        if(res != 0)

+            return res < 0;

+        return fileOrderComparator(lhs, rhs);

+    }

+

+    // for sorting tests by name/suite/file/line

+    bool nameOrderComparator(const TestCase* lhs, const TestCase* rhs) {

+        const int res = std::strcmp(lhs->m_name, rhs->m_name);

+        if(res != 0)

+            return res < 0;

+        return suiteOrderComparator(lhs, rhs);

+    }

+

+    DOCTEST_CLANG_SUPPRESS_WARNING_WITH_PUSH("-Wdeprecated-declarations")

+    void color_to_stream(std::ostream& s, Color::Enum code) {

+        static_cast<void>(s);    // for DOCTEST_CONFIG_COLORS_NONE or DOCTEST_CONFIG_COLORS_WINDOWS

+        static_cast<void>(code); // for DOCTEST_CONFIG_COLORS_NONE

+#ifdef DOCTEST_CONFIG_COLORS_ANSI

+        if(g_no_colors ||

+           (isatty(STDOUT_FILENO) == false && getContextOptions()->force_colors == false))

+            return;

+

+        auto col = "";

+        // clang-format off

+            switch(code) { //!OCLINT missing break in switch statement / unnecessary default statement in covered switch statement

+                case Color::Red:         col = "[0;31m"; break;

+                case Color::Green:       col = "[0;32m"; break;

+                case Color::Blue:        col = "[0;34m"; break;

+                case Color::Cyan:        col = "[0;36m"; break;

+                case Color::Yellow:      col = "[0;33m"; break;

+                case Color::Grey:        col = "[1;30m"; break;

+                case Color::LightGrey:   col = "[0;37m"; break;

+                case Color::BrightRed:   col = "[1;31m"; break;

+                case Color::BrightGreen: col = "[1;32m"; break;

+                case Color::BrightWhite: col = "[1;37m"; break;

+                case Color::Bright: // invalid

+                case Color::None:

+                case Color::White:

+                default:                 col = "[0m";

+            }

+        // clang-format on

+        s << "\033" << col;

+#endif // DOCTEST_CONFIG_COLORS_ANSI

+

+#ifdef DOCTEST_CONFIG_COLORS_WINDOWS

+        if(g_no_colors ||

+           (_isatty(_fileno(stdout)) == false && getContextOptions()->force_colors == false))

+            return;

+

+        static struct ConsoleHelper {

+            HANDLE stdoutHandle;

+            WORD   origFgAttrs;

+            WORD   origBgAttrs;

+

+            ConsoleHelper() {

+                stdoutHandle = GetStdHandle(STD_OUTPUT_HANDLE);

+                CONSOLE_SCREEN_BUFFER_INFO csbiInfo;

+                GetConsoleScreenBufferInfo(stdoutHandle, &csbiInfo);

+                origFgAttrs = csbiInfo.wAttributes & ~(BACKGROUND_GREEN | BACKGROUND_RED |

+                    BACKGROUND_BLUE | BACKGROUND_INTENSITY);

+                origBgAttrs = csbiInfo.wAttributes & ~(FOREGROUND_GREEN | FOREGROUND_RED |

+                    FOREGROUND_BLUE | FOREGROUND_INTENSITY);

+            }

+        } ch;

+

+#define DOCTEST_SET_ATTR(x) SetConsoleTextAttribute(ch.stdoutHandle, x | ch.origBgAttrs)

+

+        // clang-format off

+        switch (code) {

+            case Color::White:       DOCTEST_SET_ATTR(FOREGROUND_GREEN | FOREGROUND_RED | FOREGROUND_BLUE); break;

+            case Color::Red:         DOCTEST_SET_ATTR(FOREGROUND_RED);                                      break;

+            case Color::Green:       DOCTEST_SET_ATTR(FOREGROUND_GREEN);                                    break;

+            case Color::Blue:        DOCTEST_SET_ATTR(FOREGROUND_BLUE);                                     break;

+            case Color::Cyan:        DOCTEST_SET_ATTR(FOREGROUND_BLUE | FOREGROUND_GREEN);                  break;

+            case Color::Yellow:      DOCTEST_SET_ATTR(FOREGROUND_RED | FOREGROUND_GREEN);                   break;

+            case Color::Grey:        DOCTEST_SET_ATTR(0);                                                   break;

+            case Color::LightGrey:   DOCTEST_SET_ATTR(FOREGROUND_INTENSITY);                                break;

+            case Color::BrightRed:   DOCTEST_SET_ATTR(FOREGROUND_INTENSITY | FOREGROUND_RED);               break;

+            case Color::BrightGreen: DOCTEST_SET_ATTR(FOREGROUND_INTENSITY | FOREGROUND_GREEN);             break;

+            case Color::BrightWhite: DOCTEST_SET_ATTR(FOREGROUND_INTENSITY | FOREGROUND_GREEN | FOREGROUND_RED | FOREGROUND_BLUE); break;

+            case Color::None:

+            case Color::Bright: // invalid

+            default:                 DOCTEST_SET_ATTR(ch.origFgAttrs);

+        }

+            // clang-format on

+#endif // DOCTEST_CONFIG_COLORS_WINDOWS

+    }

+    DOCTEST_CLANG_SUPPRESS_WARNING_POP

+

+    std::vector<const IExceptionTranslator*>& getExceptionTranslators() {

+        static std::vector<const IExceptionTranslator*> data;

+        return data;

+    }

+

+    String translateActiveException() {

+#ifndef DOCTEST_CONFIG_NO_EXCEPTIONS

+        String res;

+        auto&  translators = getExceptionTranslators();

+        for(auto& curr : translators)

+            if(curr->translate(res))

+                return res;

+        // clang-format off

+        DOCTEST_GCC_SUPPRESS_WARNING_WITH_PUSH("-Wcatch-value")

+        try {

+            throw;

+        } catch(std::exception& ex) {

+            return ex.what();

+        } catch(std::string& msg) {

+            return msg.c_str();

+        } catch(const char* msg) {

+            return msg;

+        } catch(...) {

+            return "unknown exception";

+        }

+        DOCTEST_GCC_SUPPRESS_WARNING_POP

+// clang-format on

+#else  // DOCTEST_CONFIG_NO_EXCEPTIONS

+        return "";

+#endif // DOCTEST_CONFIG_NO_EXCEPTIONS

+    }

+} // namespace

+

+namespace detail {

+    // used by the macros for registering tests

+    int regTest(const TestCase& tc) {

+        getRegisteredTests().insert(tc);

+        return 0;

+    }

+

+    // sets the current test suite

+    int setTestSuite(const TestSuite& ts) {

+        doctest_detail_test_suite_ns::getCurrentTestSuite() = ts;

+        return 0;

+    }

+

+#ifdef DOCTEST_IS_DEBUGGER_ACTIVE

+    bool isDebuggerActive() { return DOCTEST_IS_DEBUGGER_ACTIVE(); }

+#else // DOCTEST_IS_DEBUGGER_ACTIVE

+#ifdef DOCTEST_PLATFORM_LINUX

+    class ErrnoGuard {

+    public:

+        ErrnoGuard() : m_oldErrno(errno) {}

+        ~ErrnoGuard() { errno = m_oldErrno; }

+    private:

+        int m_oldErrno;

+    };

+    // See the comments in Catch2 for the reasoning behind this implementation:

+    // https://github.com/catchorg/Catch2/blob/v2.13.1/include/internal/catch_debugger.cpp#L79-L102

+    bool isDebuggerActive() {

+        ErrnoGuard guard;

+        std::ifstream in("/proc/self/status");

+        for(std::string line; std::getline(in, line);) {

+            static const int PREFIX_LEN = 11;

+            if(line.compare(0, PREFIX_LEN, "TracerPid:\t") == 0) {

+                return line.length() > PREFIX_LEN && line[PREFIX_LEN] != '0';

+            }

+        }

+        return false;

+    }

+#elif defined(DOCTEST_PLATFORM_MAC)

+    // The following function is taken directly from the following technical note:

+    // https://developer.apple.com/library/archive/qa/qa1361/_index.html

+    // Returns true if the current process is being debugged (either

+    // running under the debugger or has a debugger attached post facto).

+    bool isDebuggerActive() {

+        int        mib[4];

+        kinfo_proc info;

+        size_t     size;

+        // Initialize the flags so that, if sysctl fails for some bizarre

+        // reason, we get a predictable result.

+        info.kp_proc.p_flag = 0;

+        // Initialize mib, which tells sysctl the info we want, in this case

+        // we're looking for information about a specific process ID.

+        mib[0] = CTL_KERN;

+        mib[1] = KERN_PROC;

+        mib[2] = KERN_PROC_PID;

+        mib[3] = getpid();

+        // Call sysctl.

+        size = sizeof(info);

+        if(sysctl(mib, DOCTEST_COUNTOF(mib), &info, &size, 0, 0) != 0) {

+            std::cerr << "\nCall to sysctl failed - unable to determine if debugger is active **\n";

+            return false;

+        }

+        // We're being debugged if the P_TRACED flag is set.

+        return ((info.kp_proc.p_flag & P_TRACED) != 0);

+    }

+#elif DOCTEST_MSVC || defined(__MINGW32__) || defined(__MINGW64__)

+    bool isDebuggerActive() { return ::IsDebuggerPresent() != 0; }

+#else

+    bool isDebuggerActive() { return false; }

+#endif // Platform

+#endif // DOCTEST_IS_DEBUGGER_ACTIVE

+

+    void registerExceptionTranslatorImpl(const IExceptionTranslator* et) {

+        if(std::find(getExceptionTranslators().begin(), getExceptionTranslators().end(), et) ==

+           getExceptionTranslators().end())

+            getExceptionTranslators().push_back(et);

+    }

+

+    DOCTEST_THREAD_LOCAL std::vector<IContextScope*> g_infoContexts; // for logging with INFO()

+

+    ContextScopeBase::ContextScopeBase() {

+        g_infoContexts.push_back(this);

+    }

+

+    ContextScopeBase::ContextScopeBase(ContextScopeBase&& other) noexcept {

+        if (other.need_to_destroy) {

+            other.destroy();

+        }

+        other.need_to_destroy = false;

+        g_infoContexts.push_back(this);

+    }

+

+    DOCTEST_MSVC_SUPPRESS_WARNING_WITH_PUSH(4996) // std::uncaught_exception is deprecated in C++17

+    DOCTEST_GCC_SUPPRESS_WARNING_WITH_PUSH("-Wdeprecated-declarations")

+    DOCTEST_CLANG_SUPPRESS_WARNING_WITH_PUSH("-Wdeprecated-declarations")

+

+    // destroy cannot be inlined into the destructor because that would mean calling stringify after

+    // ContextScope has been destroyed (base class destructors run after derived class destructors).

+    // Instead, ContextScope calls this method directly from its destructor.

+    void ContextScopeBase::destroy() {

+#if defined(__cpp_lib_uncaught_exceptions) && __cpp_lib_uncaught_exceptions >= 201411L && (!defined(__MAC_OS_X_VERSION_MIN_REQUIRED) || __MAC_OS_X_VERSION_MIN_REQUIRED >= 101200)

+        if(std::uncaught_exceptions() > 0) {

+#else

+        if(std::uncaught_exception()) {

+#endif

+            std::ostringstream s;

+            this->stringify(&s);

+            g_cs->stringifiedContexts.push_back(s.str().c_str());

+        }

+        g_infoContexts.pop_back();

+    }

+

+    DOCTEST_CLANG_SUPPRESS_WARNING_POP

+    DOCTEST_GCC_SUPPRESS_WARNING_POP

+    DOCTEST_MSVC_SUPPRESS_WARNING_POP

+} // namespace detail

+namespace {

+    using namespace detail;

+

+#if !defined(DOCTEST_CONFIG_POSIX_SIGNALS) && !defined(DOCTEST_CONFIG_WINDOWS_SEH)

+    struct FatalConditionHandler

+    {

+        static void reset() {}

+        static void allocateAltStackMem() {}

+        static void freeAltStackMem() {}

+    };

+#else // DOCTEST_CONFIG_POSIX_SIGNALS || DOCTEST_CONFIG_WINDOWS_SEH

+

+    void reportFatal(const std::string&);

+

+#ifdef DOCTEST_PLATFORM_WINDOWS

+

+    struct SignalDefs

+    {

+        DWORD id;

+        const char* name;

+    };

+    // There is no 1-1 mapping between signals and windows exceptions.

+    // Windows can easily distinguish between SO and SigSegV,

+    // but SigInt, SigTerm, etc are handled differently.

+    SignalDefs signalDefs[] = {

+            {static_cast<DWORD>(EXCEPTION_ILLEGAL_INSTRUCTION),

+             "SIGILL - Illegal instruction signal"},

+            {static_cast<DWORD>(EXCEPTION_STACK_OVERFLOW), "SIGSEGV - Stack overflow"},

+            {static_cast<DWORD>(EXCEPTION_ACCESS_VIOLATION),

+             "SIGSEGV - Segmentation violation signal"},

+            {static_cast<DWORD>(EXCEPTION_INT_DIVIDE_BY_ZERO), "Divide by zero error"},

+    };

+

+    struct FatalConditionHandler

+    {

+        static LONG CALLBACK handleException(PEXCEPTION_POINTERS ExceptionInfo) {

+            // Multiple threads may enter this filter/handler at once. We want the error message to be printed on the

+            // console just once no matter how many threads have crashed.

+            DOCTEST_DECLARE_STATIC_MUTEX(mutex)

+            static bool execute = true;

+            {

+                DOCTEST_LOCK_MUTEX(mutex)

+                if(execute) {

+                    bool reported = false;

+                    for(size_t i = 0; i < DOCTEST_COUNTOF(signalDefs); ++i) {

+                        if(ExceptionInfo->ExceptionRecord->ExceptionCode == signalDefs[i].id) {

+                            reportFatal(signalDefs[i].name);

+                            reported = true;

+                            break;

+                        }

+                    }

+                    if(reported == false)

+                        reportFatal("Unhandled SEH exception caught");

+                    if(isDebuggerActive() && !g_cs->no_breaks)

+                        DOCTEST_BREAK_INTO_DEBUGGER();

+                }

+                execute = false;

+            }

+            std::exit(EXIT_FAILURE);

+        }

+

+        static void allocateAltStackMem() {}

+        static void freeAltStackMem() {}

+

+        FatalConditionHandler() {

+            isSet = true;

+            // 32k seems enough for doctest to handle stack overflow,

+            // but the value was found experimentally, so there is no strong guarantee

+            guaranteeSize = 32 * 1024;

+            // Register an unhandled exception filter

+            previousTop = SetUnhandledExceptionFilter(handleException);

+            // Pass in guarantee size to be filled

+            SetThreadStackGuarantee(&guaranteeSize);

+

+            // On Windows uncaught exceptions from another thread, exceptions from

+            // destructors, or calls to std::terminate are not a SEH exception

+

+            // The terminal handler gets called when:

+            // - std::terminate is called FROM THE TEST RUNNER THREAD

+            // - an exception is thrown from a destructor FROM THE TEST RUNNER THREAD

+            original_terminate_handler = std::get_terminate();

+            std::set_terminate([]() DOCTEST_NOEXCEPT {

+                reportFatal("Terminate handler called");

+                if(isDebuggerActive() && !g_cs->no_breaks)

+                    DOCTEST_BREAK_INTO_DEBUGGER();

+                std::exit(EXIT_FAILURE); // explicitly exit - otherwise the SIGABRT handler may be called as well

+            });

+

+            // SIGABRT is raised when:

+            // - std::terminate is called FROM A DIFFERENT THREAD

+            // - an exception is thrown from a destructor FROM A DIFFERENT THREAD

+            // - an uncaught exception is thrown FROM A DIFFERENT THREAD

+            prev_sigabrt_handler = std::signal(SIGABRT, [](int signal) DOCTEST_NOEXCEPT {

+                if(signal == SIGABRT) {

+                    reportFatal("SIGABRT - Abort (abnormal termination) signal");

+                    if(isDebuggerActive() && !g_cs->no_breaks)

+                        DOCTEST_BREAK_INTO_DEBUGGER();

+                    std::exit(EXIT_FAILURE);

+                }

+            });

+

+            // The following settings are taken from google test, and more

+            // specifically from UnitTest::Run() inside of gtest.cc

+

+            // the user does not want to see pop-up dialogs about crashes

+            prev_error_mode_1 = SetErrorMode(SEM_FAILCRITICALERRORS | SEM_NOALIGNMENTFAULTEXCEPT |

+                                             SEM_NOGPFAULTERRORBOX | SEM_NOOPENFILEERRORBOX);

+            // This forces the abort message to go to stderr in all circumstances.

+            prev_error_mode_2 = _set_error_mode(_OUT_TO_STDERR);

+            // In the debug version, Visual Studio pops up a separate dialog

+            // offering a choice to debug the aborted program - we want to disable that.

+            prev_abort_behavior = _set_abort_behavior(0x0, _WRITE_ABORT_MSG | _CALL_REPORTFAULT);

+            // In debug mode, the Windows CRT can crash with an assertion over invalid

+            // input (e.g. passing an invalid file descriptor). The default handling

+            // for these assertions is to pop up a dialog and wait for user input.

+            // Instead ask the CRT to dump such assertions to stderr non-interactively.

+            prev_report_mode = _CrtSetReportMode(_CRT_ASSERT, _CRTDBG_MODE_FILE | _CRTDBG_MODE_DEBUG);

+            prev_report_file = _CrtSetReportFile(_CRT_ASSERT, _CRTDBG_FILE_STDERR);

+        }

+

+        static void reset() {

+            if(isSet) {

+                // Unregister handler and restore the old guarantee

+                SetUnhandledExceptionFilter(previousTop);

+                SetThreadStackGuarantee(&guaranteeSize);

+                std::set_terminate(original_terminate_handler);

+                std::signal(SIGABRT, prev_sigabrt_handler);

+                SetErrorMode(prev_error_mode_1);

+                _set_error_mode(prev_error_mode_2);

+                _set_abort_behavior(prev_abort_behavior, _WRITE_ABORT_MSG | _CALL_REPORTFAULT);

+                static_cast<void>(_CrtSetReportMode(_CRT_ASSERT, prev_report_mode));

+                static_cast<void>(_CrtSetReportFile(_CRT_ASSERT, prev_report_file));

+                isSet = false;

+            }

+        }

+

+        ~FatalConditionHandler() { reset(); }

+

+    private:

+        static UINT         prev_error_mode_1;

+        static int          prev_error_mode_2;

+        static unsigned int prev_abort_behavior;

+        static int          prev_report_mode;

+        static _HFILE       prev_report_file;

+        static void (DOCTEST_CDECL *prev_sigabrt_handler)(int);

+        static std::terminate_handler original_terminate_handler;

+        static bool isSet;

+        static ULONG guaranteeSize;

+        static LPTOP_LEVEL_EXCEPTION_FILTER previousTop;

+    };

+

+    UINT         FatalConditionHandler::prev_error_mode_1;

+    int          FatalConditionHandler::prev_error_mode_2;

+    unsigned int FatalConditionHandler::prev_abort_behavior;

+    int          FatalConditionHandler::prev_report_mode;

+    _HFILE       FatalConditionHandler::prev_report_file;

+    void (DOCTEST_CDECL *FatalConditionHandler::prev_sigabrt_handler)(int);

+    std::terminate_handler FatalConditionHandler::original_terminate_handler;

+    bool FatalConditionHandler::isSet = false;

+    ULONG FatalConditionHandler::guaranteeSize = 0;

+    LPTOP_LEVEL_EXCEPTION_FILTER FatalConditionHandler::previousTop = nullptr;

+

+#else // DOCTEST_PLATFORM_WINDOWS

+

+    struct SignalDefs

+    {

+        int         id;

+        const char* name;

+    };

+    SignalDefs signalDefs[] = {{SIGINT, "SIGINT - Terminal interrupt signal"},

+                               {SIGILL, "SIGILL - Illegal instruction signal"},

+                               {SIGFPE, "SIGFPE - Floating point error signal"},

+                               {SIGSEGV, "SIGSEGV - Segmentation violation signal"},

+                               {SIGTERM, "SIGTERM - Termination request signal"},

+                               {SIGABRT, "SIGABRT - Abort (abnormal termination) signal"}};

+

+    struct FatalConditionHandler

+    {

+        static bool             isSet;

+        static struct sigaction oldSigActions[DOCTEST_COUNTOF(signalDefs)];

+        static stack_t          oldSigStack;

+        static size_t           altStackSize;

+        static char*            altStackMem;

+

+        static void handleSignal(int sig) {

+            const char* name = "<unknown signal>";

+            for(std::size_t i = 0; i < DOCTEST_COUNTOF(signalDefs); ++i) {

+                SignalDefs& def = signalDefs[i];

+                if(sig == def.id) {

+                    name = def.name;

+                    break;

+                }

+            }

+            reset();

+            reportFatal(name);

+            raise(sig);

+        }

+

+        static void allocateAltStackMem() {

+            altStackMem = new char[altStackSize];

+        }

+

+        static void freeAltStackMem() {

+            delete[] altStackMem;

+        }

+

+        FatalConditionHandler() {

+            isSet = true;

+            stack_t sigStack;

+            sigStack.ss_sp    = altStackMem;

+            sigStack.ss_size  = altStackSize;

+            sigStack.ss_flags = 0;

+            sigaltstack(&sigStack, &oldSigStack);

+            struct sigaction sa = {};

+            sa.sa_handler       = handleSignal;

+            sa.sa_flags         = SA_ONSTACK;

+            for(std::size_t i = 0; i < DOCTEST_COUNTOF(signalDefs); ++i) {

+                sigaction(signalDefs[i].id, &sa, &oldSigActions[i]);

+            }

+        }

+

+        ~FatalConditionHandler() { reset(); }

+        static void reset() {

+            if(isSet) {

+                // Set signals back to previous values -- hopefully nobody overwrote them in the meantime

+                for(std::size_t i = 0; i < DOCTEST_COUNTOF(signalDefs); ++i) {

+                    sigaction(signalDefs[i].id, &oldSigActions[i], nullptr);

+                }

+                // Return the old stack

+                sigaltstack(&oldSigStack, nullptr);

+                isSet = false;

+            }

+        }

+    };

+

+    bool             FatalConditionHandler::isSet = false;

+    struct sigaction FatalConditionHandler::oldSigActions[DOCTEST_COUNTOF(signalDefs)] = {};

+    stack_t          FatalConditionHandler::oldSigStack = {};

+    size_t           FatalConditionHandler::altStackSize = 4 * SIGSTKSZ;

+    char*            FatalConditionHandler::altStackMem = nullptr;

+

+#endif // DOCTEST_PLATFORM_WINDOWS

+#endif // DOCTEST_CONFIG_POSIX_SIGNALS || DOCTEST_CONFIG_WINDOWS_SEH

+

+} // namespace

+

+namespace {

+    using namespace detail;

+

+#ifdef DOCTEST_PLATFORM_WINDOWS

+#define DOCTEST_OUTPUT_DEBUG_STRING(text) ::OutputDebugStringA(text)

+#else

+    // TODO: integration with XCode and other IDEs

+#define DOCTEST_OUTPUT_DEBUG_STRING(text)

+#endif // Platform

+

+    void addAssert(assertType::Enum at) {

+        if((at & assertType::is_warn) == 0) //!OCLINT bitwise operator in conditional

+            g_cs->numAssertsCurrentTest_atomic++;

+    }

+

+    void addFailedAssert(assertType::Enum at) {

+        if((at & assertType::is_warn) == 0) //!OCLINT bitwise operator in conditional

+            g_cs->numAssertsFailedCurrentTest_atomic++;

+    }

+

+#if defined(DOCTEST_CONFIG_POSIX_SIGNALS) || defined(DOCTEST_CONFIG_WINDOWS_SEH)

+    void reportFatal(const std::string& message) {

+        g_cs->failure_flags |= TestCaseFailureReason::Crash;

+

+        DOCTEST_ITERATE_THROUGH_REPORTERS(test_case_exception, {message.c_str(), true});

+

+        while (g_cs->subcaseStack.size()) {

+            g_cs->subcaseStack.pop_back();

+            DOCTEST_ITERATE_THROUGH_REPORTERS(subcase_end, DOCTEST_EMPTY);

+        }

+

+        g_cs->finalizeTestCaseData();

+

+        DOCTEST_ITERATE_THROUGH_REPORTERS(test_case_end, *g_cs);

+

+        DOCTEST_ITERATE_THROUGH_REPORTERS(test_run_end, *g_cs);

+    }

+#endif // DOCTEST_CONFIG_POSIX_SIGNALS || DOCTEST_CONFIG_WINDOWS_SEH

+} // namespace

+

+AssertData::AssertData(assertType::Enum at, const char* file, int line, const char* expr,

+    const char* exception_type, const StringContains& exception_string)

+    : m_test_case(g_cs->currentTest), m_at(at), m_file(file), m_line(line), m_expr(expr),

+    m_failed(true), m_threw(false), m_threw_as(false), m_exception_type(exception_type),

+    m_exception_string(exception_string) {

+#if DOCTEST_MSVC

+    if (m_expr[0] == ' ') // this happens when variadic macros are disabled under MSVC

+        ++m_expr;

+#endif // MSVC

+}

+

+namespace detail {

+    ResultBuilder::ResultBuilder(assertType::Enum at, const char* file, int line, const char* expr,

+                                 const char* exception_type, const String& exception_string)

+        : AssertData(at, file, line, expr, exception_type, exception_string) { }

+

+    ResultBuilder::ResultBuilder(assertType::Enum at, const char* file, int line, const char* expr,

+        const char* exception_type, const Contains& exception_string)

+        : AssertData(at, file, line, expr, exception_type, exception_string) { }

+

+    void ResultBuilder::setResult(const Result& res) {

+        m_decomp = res.m_decomp;

+        m_failed = !res.m_passed;

+    }

+

+    void ResultBuilder::translateException() {

+        m_threw     = true;

+        m_exception = translateActiveException();

+    }

+

+    bool ResultBuilder::log() {

+        if(m_at & assertType::is_throws) { //!OCLINT bitwise operator in conditional

+            m_failed = !m_threw;

+        } else if((m_at & assertType::is_throws_as) && (m_at & assertType::is_throws_with)) { //!OCLINT

+            m_failed = !m_threw_as || !m_exception_string.check(m_exception);

+        } else if(m_at & assertType::is_throws_as) { //!OCLINT bitwise operator in conditional

+            m_failed = !m_threw_as;

+        } else if(m_at & assertType::is_throws_with) { //!OCLINT bitwise operator in conditional

+            m_failed = !m_exception_string.check(m_exception);

+        } else if(m_at & assertType::is_nothrow) { //!OCLINT bitwise operator in conditional

+            m_failed = m_threw;

+        }

+

+        if(m_exception.size())

+            m_exception = "\"" + m_exception + "\"";

+

+        if(is_running_in_test) {

+            addAssert(m_at);

+            DOCTEST_ITERATE_THROUGH_REPORTERS(log_assert, *this);

+

+            if(m_failed)

+                addFailedAssert(m_at);

+        } else if(m_failed) {

+            failed_out_of_a_testing_context(*this);

+        }

+

+        return m_failed && isDebuggerActive() && !getContextOptions()->no_breaks &&

+            (g_cs->currentTest == nullptr || !g_cs->currentTest->m_no_breaks); // break into debugger

+    }

+

+    void ResultBuilder::react() const {

+        if(m_failed && checkIfShouldThrow(m_at))

+            throwException();

+    }

+

+    void failed_out_of_a_testing_context(const AssertData& ad) {

+        if(g_cs->ah)

+            g_cs->ah(ad);

+        else

+            std::abort();

+    }

+

+    bool decomp_assert(assertType::Enum at, const char* file, int line, const char* expr,

+                       const Result& result) {

+        bool failed = !result.m_passed;

+

+        // ###################################################################################

+        // IF THE DEBUGGER BREAKS HERE - GO 1 LEVEL UP IN THE CALLSTACK FOR THE FAILING ASSERT

+        // THIS IS THE EFFECT OF HAVING 'DOCTEST_CONFIG_SUPER_FAST_ASSERTS' DEFINED

+        // ###################################################################################

+        DOCTEST_ASSERT_OUT_OF_TESTS(result.m_decomp);

+        DOCTEST_ASSERT_IN_TESTS(result.m_decomp);

+        return !failed;

+    }

+

+    MessageBuilder::MessageBuilder(const char* file, int line, assertType::Enum severity) {

+        m_stream   = tlssPush();

+        m_file     = file;

+        m_line     = line;

+        m_severity = severity;

+    }

+

+    MessageBuilder::~MessageBuilder() {

+        if (!logged)

+            tlssPop();

+    }

+

+    DOCTEST_DEFINE_INTERFACE(IExceptionTranslator)

+

+    bool MessageBuilder::log() {

+        if (!logged) {

+            m_string = tlssPop();

+            logged = true;

+        }

+        

+        DOCTEST_ITERATE_THROUGH_REPORTERS(log_message, *this);

+

+        const bool isWarn = m_severity & assertType::is_warn;

+

+        // warn is just a message in this context so we don't treat it as an assert

+        if(!isWarn) {

+            addAssert(m_severity);

+            addFailedAssert(m_severity);

+        }

+

+        return isDebuggerActive() && !getContextOptions()->no_breaks && !isWarn &&

+            (g_cs->currentTest == nullptr || !g_cs->currentTest->m_no_breaks); // break into debugger

+    }

+

+    void MessageBuilder::react() {

+        if(m_severity & assertType::is_require) //!OCLINT bitwise operator in conditional

+            throwException();

+    }

+} // namespace detail

+namespace {

+    using namespace detail;

+

+    // clang-format off

+

+// =================================================================================================

+// The following code has been taken verbatim from Catch2/include/internal/catch_xmlwriter.h/cpp

+// This is done so cherry-picking bug fixes is trivial - even the style/formatting is untouched.

+// =================================================================================================

+

+    class XmlEncode {

+    public:

+        enum ForWhat { ForTextNodes, ForAttributes };

+

+        XmlEncode( std::string const& str, ForWhat forWhat = ForTextNodes );

+

+        void encodeTo( std::ostream& os ) const;

+

+        friend std::ostream& operator << ( std::ostream& os, XmlEncode const& xmlEncode );

+

+    private:

+        std::string m_str;

+        ForWhat m_forWhat;

+    };

+

+    class XmlWriter {

+    public:

+

+        class ScopedElement {

+        public:

+            ScopedElement( XmlWriter* writer );

+

+            ScopedElement( ScopedElement&& other ) DOCTEST_NOEXCEPT;

+            ScopedElement& operator=( ScopedElement&& other ) DOCTEST_NOEXCEPT;

+

+            ~ScopedElement();

+

+            ScopedElement& writeText( std::string const& text, bool indent = true );

+

+            template<typename T>

+            ScopedElement& writeAttribute( std::string const& name, T const& attribute ) {

+                m_writer->writeAttribute( name, attribute );

+                return *this;

+            }

+

+        private:

+            mutable XmlWriter* m_writer = nullptr;

+        };

+

+        XmlWriter( std::ostream& os = std::cout );

+        ~XmlWriter();

+

+        XmlWriter( XmlWriter const& ) = delete;

+        XmlWriter& operator=( XmlWriter const& ) = delete;

+

+        XmlWriter& startElement( std::string const& name );

+

+        ScopedElement scopedElement( std::string const& name );

+

+        XmlWriter& endElement();

+

+        XmlWriter& writeAttribute( std::string const& name, std::string const& attribute );

+

+        XmlWriter& writeAttribute( std::string const& name, const char* attribute );

+

+        XmlWriter& writeAttribute( std::string const& name, bool attribute );

+

+        template<typename T>

+        XmlWriter& writeAttribute( std::string const& name, T const& attribute ) {

+        std::stringstream rss;

+            rss << attribute;

+            return writeAttribute( name, rss.str() );

+        }

+

+        XmlWriter& writeText( std::string const& text, bool indent = true );

+

+        //XmlWriter& writeComment( std::string const& text );

+

+        //void writeStylesheetRef( std::string const& url );

+

+        //XmlWriter& writeBlankLine();

+

+        void ensureTagClosed();

+

+        void writeDeclaration();

+

+    private:

+

+        void newlineIfNecessary();

+

+        bool m_tagIsOpen = false;

+        bool m_needsNewline = false;

+        std::vector<std::string> m_tags;

+        std::string m_indent;

+        std::ostream& m_os;

+    };

+

+// =================================================================================================

+// The following code has been taken verbatim from Catch2/include/internal/catch_xmlwriter.h/cpp

+// This is done so cherry-picking bug fixes is trivial - even the style/formatting is untouched.

+// =================================================================================================

+

+using uchar = unsigned char;

+

+namespace {

+

+    size_t trailingBytes(unsigned char c) {

+        if ((c & 0xE0) == 0xC0) {

+            return 2;

+        }

+        if ((c & 0xF0) == 0xE0) {

+            return 3;

+        }

+        if ((c & 0xF8) == 0xF0) {

+            return 4;

+        }

+        DOCTEST_INTERNAL_ERROR("Invalid multibyte utf-8 start byte encountered");

+    }

+

+    uint32_t headerValue(unsigned char c) {

+        if ((c & 0xE0) == 0xC0) {

+            return c & 0x1F;

+        }

+        if ((c & 0xF0) == 0xE0) {

+            return c & 0x0F;

+        }

+        if ((c & 0xF8) == 0xF0) {

+            return c & 0x07;

+        }

+        DOCTEST_INTERNAL_ERROR("Invalid multibyte utf-8 start byte encountered");

+    }

+

+    void hexEscapeChar(std::ostream& os, unsigned char c) {

+        std::ios_base::fmtflags f(os.flags());

+        os << "\\x"

+            << std::uppercase << std::hex << std::setfill('0') << std::setw(2)

+            << static_cast<int>(c);

+        os.flags(f);

+    }

+

+} // anonymous namespace

+

+    XmlEncode::XmlEncode( std::string const& str, ForWhat forWhat )

+    :   m_str( str ),

+        m_forWhat( forWhat )

+    {}

+

+    void XmlEncode::encodeTo( std::ostream& os ) const {

+        // Apostrophe escaping not necessary if we always use " to write attributes

+        // (see: https://www.w3.org/TR/xml/#syntax)

+

+        for( std::size_t idx = 0; idx < m_str.size(); ++ idx ) {

+            uchar c = m_str[idx];

+            switch (c) {

+            case '<':   os << "&lt;"; break;

+            case '&':   os << "&amp;"; break;

+

+            case '>':

+                // See: https://www.w3.org/TR/xml/#syntax

+                if (idx > 2 && m_str[idx - 1] == ']' && m_str[idx - 2] == ']')

+                    os << "&gt;";

+                else

+                    os << c;

+                break;

+

+            case '\"':

+                if (m_forWhat == ForAttributes)

+                    os << "&quot;";

+                else

+                    os << c;

+                break;

+

+            default:

+                // Check for control characters and invalid utf-8

+

+                // Escape control characters in standard ascii

+                // see https://stackoverflow.com/questions/404107/why-are-control-characters-illegal-in-xml-1-0

+                if (c < 0x09 || (c > 0x0D && c < 0x20) || c == 0x7F) {

+                    hexEscapeChar(os, c);

+                    break;

+                }

+

+                // Plain ASCII: Write it to stream

+                if (c < 0x7F) {

+                    os << c;

+                    break;

+                }

+

+                // UTF-8 territory

+                // Check if the encoding is valid and if it is not, hex escape bytes.

+                // Important: We do not check the exact decoded values for validity, only the encoding format

+                // First check that this bytes is a valid lead byte:

+                // This means that it is not encoded as 1111 1XXX

+                // Or as 10XX XXXX

+                if (c <  0xC0 ||

+                    c >= 0xF8) {

+                    hexEscapeChar(os, c);

+                    break;

+                }

+

+                auto encBytes = trailingBytes(c);

+                // Are there enough bytes left to avoid accessing out-of-bounds memory?

+                if (idx + encBytes - 1 >= m_str.size()) {

+                    hexEscapeChar(os, c);

+                    break;

+                }

+                // The header is valid, check data

+                // The next encBytes bytes must together be a valid utf-8

+                // This means: bitpattern 10XX XXXX and the extracted value is sane (ish)

+                bool valid = true;

+                uint32_t value = headerValue(c);

+                for (std::size_t n = 1; n < encBytes; ++n) {

+                    uchar nc = m_str[idx + n];

+                    valid &= ((nc & 0xC0) == 0x80);

+                    value = (value << 6) | (nc & 0x3F);

+                }

+

+                if (

+                    // Wrong bit pattern of following bytes

+                    (!valid) ||

+                    // Overlong encodings

+                    (value < 0x80) ||

+                    (                 value < 0x800   && encBytes > 2) || // removed "0x80 <= value &&" because redundant

+                    (0x800 < value && value < 0x10000 && encBytes > 3) ||

+                    // Encoded value out of range

+                    (value >= 0x110000)

+                    ) {

+                    hexEscapeChar(os, c);

+                    break;

+                }

+

+                // If we got here, this is in fact a valid(ish) utf-8 sequence

+                for (std::size_t n = 0; n < encBytes; ++n) {

+                    os << m_str[idx + n];

+                }

+                idx += encBytes - 1;

+                break;

+            }

+        }

+    }

+

+    std::ostream& operator << ( std::ostream& os, XmlEncode const& xmlEncode ) {

+        xmlEncode.encodeTo( os );

+        return os;

+    }

+

+    XmlWriter::ScopedElement::ScopedElement( XmlWriter* writer )

+    :   m_writer( writer )

+    {}

+

+    XmlWriter::ScopedElement::ScopedElement( ScopedElement&& other ) DOCTEST_NOEXCEPT

+    :   m_writer( other.m_writer ){

+        other.m_writer = nullptr;

+    }

+    XmlWriter::ScopedElement& XmlWriter::ScopedElement::operator=( ScopedElement&& other ) DOCTEST_NOEXCEPT {

+        if ( m_writer ) {

+            m_writer->endElement();

+        }

+        m_writer = other.m_writer;

+        other.m_writer = nullptr;

+        return *this;

+    }

+

+

+    XmlWriter::ScopedElement::~ScopedElement() {

+        if( m_writer )

+            m_writer->endElement();

+    }

+

+    XmlWriter::ScopedElement& XmlWriter::ScopedElement::writeText( std::string const& text, bool indent ) {

+        m_writer->writeText( text, indent );

+        return *this;

+    }

+

+    XmlWriter::XmlWriter( std::ostream& os ) : m_os( os )

+    {

+        // writeDeclaration(); // called explicitly by the reporters that use the writer class - see issue #627

+    }

+

+    XmlWriter::~XmlWriter() {

+        while( !m_tags.empty() )

+            endElement();

+    }

+

+    XmlWriter& XmlWriter::startElement( std::string const& name ) {

+        ensureTagClosed();

+        newlineIfNecessary();

+        m_os << m_indent << '<' << name;

+        m_tags.push_back( name );

+        m_indent += "  ";

+        m_tagIsOpen = true;

+        return *this;

+    }

+

+    XmlWriter::ScopedElement XmlWriter::scopedElement( std::string const& name ) {

+        ScopedElement scoped( this );

+        startElement( name );

+        return scoped;

+    }

+

+    XmlWriter& XmlWriter::endElement() {

+        newlineIfNecessary();

+        m_indent = m_indent.substr( 0, m_indent.size()-2 );

+        if( m_tagIsOpen ) {

+            m_os << "/>";

+            m_tagIsOpen = false;

+        }

+        else {

+            m_os << m_indent << "</" << m_tags.back() << ">";

+        }

+        m_os << std::endl;

+        m_tags.pop_back();

+        return *this;

+    }

+

+    XmlWriter& XmlWriter::writeAttribute( std::string const& name, std::string const& attribute ) {

+        if( !name.empty() && !attribute.empty() )

+            m_os << ' ' << name << "=\"" << XmlEncode( attribute, XmlEncode::ForAttributes ) << '"';

+        return *this;

+    }

+

+    XmlWriter& XmlWriter::writeAttribute( std::string const& name, const char* attribute ) {

+        if( !name.empty() && attribute && attribute[0] != '\0' )

+            m_os << ' ' << name << "=\"" << XmlEncode( attribute, XmlEncode::ForAttributes ) << '"';

+        return *this;

+    }

+

+    XmlWriter& XmlWriter::writeAttribute( std::string const& name, bool attribute ) {

+        m_os << ' ' << name << "=\"" << ( attribute ? "true" : "false" ) << '"';

+        return *this;

+    }

+

+    XmlWriter& XmlWriter::writeText( std::string const& text, bool indent ) {

+        if( !text.empty() ){

+            bool tagWasOpen = m_tagIsOpen;

+            ensureTagClosed();

+            if( tagWasOpen && indent )

+                m_os << m_indent;

+            m_os << XmlEncode( text );

+            m_needsNewline = true;

+        }

+        return *this;

+    }

+

+    //XmlWriter& XmlWriter::writeComment( std::string const& text ) {

+    //    ensureTagClosed();

+    //    m_os << m_indent << "<!--" << text << "-->";

+    //    m_needsNewline = true;

+    //    return *this;

+    //}

+

+    //void XmlWriter::writeStylesheetRef( std::string const& url ) {

+    //    m_os << "<?xml-stylesheet type=\"text/xsl\" href=\"" << url << "\"?>\n";

+    //}

+

+    //XmlWriter& XmlWriter::writeBlankLine() {

+    //    ensureTagClosed();

+    //    m_os << '\n';

+    //    return *this;

+    //}

+

+    void XmlWriter::ensureTagClosed() {

+        if( m_tagIsOpen ) {

+            m_os << ">" << std::endl;

+            m_tagIsOpen = false;

+        }

+    }

+

+    void XmlWriter::writeDeclaration() {

+        m_os << "<?xml version=\"1.0\" encoding=\"UTF-8\"?>\n";

+    }

+

+    void XmlWriter::newlineIfNecessary() {

+        if( m_needsNewline ) {

+            m_os << std::endl;

+            m_needsNewline = false;

+        }

+    }

+

+// =================================================================================================

+// End of copy-pasted code from Catch

+// =================================================================================================

+

+    // clang-format on

+

+    struct XmlReporter : public IReporter

+    {

+        XmlWriter xml;

+        DOCTEST_DECLARE_MUTEX(mutex)

+

+        // caching pointers/references to objects of these types - safe to do

+        const ContextOptions& opt;

+        const TestCaseData*   tc = nullptr;

+

+        XmlReporter(const ContextOptions& co)

+                : xml(*co.cout)

+                , opt(co) {}

+

+        void log_contexts() {

+            int num_contexts = get_num_active_contexts();

+            if(num_contexts) {

+                auto              contexts = get_active_contexts();

+                std::stringstream ss;

+                for(int i = 0; i < num_contexts; ++i) {

+                    contexts[i]->stringify(&ss);

+                    xml.scopedElement("Info").writeText(ss.str());

+                    ss.str("");

+                }

+            }

+        }

+

+        unsigned line(unsigned l) const { return opt.no_line_numbers ? 0 : l; }

+

+        void test_case_start_impl(const TestCaseData& in) {

+            bool open_ts_tag = false;

+            if(tc != nullptr) { // we have already opened a test suite

+                if(std::strcmp(tc->m_test_suite, in.m_test_suite) != 0) {

+                    xml.endElement();

+                    open_ts_tag = true;

+                }

+            }

+            else {

+                open_ts_tag = true; // first test case ==> first test suite

+            }

+

+            if(open_ts_tag) {

+                xml.startElement("TestSuite");

+                xml.writeAttribute("name", in.m_test_suite);

+            }

+

+            tc = &in;

+            xml.startElement("TestCase")

+                    .writeAttribute("name", in.m_name)

+                    .writeAttribute("filename", skipPathFromFilename(in.m_file.c_str()))

+                    .writeAttribute("line", line(in.m_line))

+                    .writeAttribute("description", in.m_description);

+

+            if(Approx(in.m_timeout) != 0)

+                xml.writeAttribute("timeout", in.m_timeout);

+            if(in.m_may_fail)

+                xml.writeAttribute("may_fail", true);

+            if(in.m_should_fail)

+                xml.writeAttribute("should_fail", true);

+        }

+

+        // =========================================================================================

+        // WHAT FOLLOWS ARE OVERRIDES OF THE VIRTUAL METHODS OF THE REPORTER INTERFACE

+        // =========================================================================================

+

+        void report_query(const QueryData& in) override {

+            test_run_start();

+            if(opt.list_reporters) {

+                for(auto& curr : getListeners())

+                    xml.scopedElement("Listener")

+                            .writeAttribute("priority", curr.first.first)

+                            .writeAttribute("name", curr.first.second);

+                for(auto& curr : getReporters())

+                    xml.scopedElement("Reporter")

+                            .writeAttribute("priority", curr.first.first)

+                            .writeAttribute("name", curr.first.second);

+            } else if(opt.count || opt.list_test_cases) {

+                for(unsigned i = 0; i < in.num_data; ++i) {

+                    xml.scopedElement("TestCase").writeAttribute("name", in.data[i]->m_name)

+                        .writeAttribute("testsuite", in.data[i]->m_test_suite)

+                        .writeAttribute("filename", skipPathFromFilename(in.data[i]->m_file.c_str()))

+                        .writeAttribute("line", line(in.data[i]->m_line))

+                        .writeAttribute("skipped", in.data[i]->m_skip);

+                }

+                xml.scopedElement("OverallResultsTestCases")

+                        .writeAttribute("unskipped", in.run_stats->numTestCasesPassingFilters);

+            } else if(opt.list_test_suites) {

+                for(unsigned i = 0; i < in.num_data; ++i)

+                    xml.scopedElement("TestSuite").writeAttribute("name", in.data[i]->m_test_suite);

+                xml.scopedElement("OverallResultsTestCases")

+                        .writeAttribute("unskipped", in.run_stats->numTestCasesPassingFilters);

+                xml.scopedElement("OverallResultsTestSuites")

+                        .writeAttribute("unskipped", in.run_stats->numTestSuitesPassingFilters);

+            }

+            xml.endElement();

+        }

+

+        void test_run_start() override {

+            xml.writeDeclaration();

+

+            // remove .exe extension - mainly to have the same output on UNIX and Windows

+            std::string binary_name = skipPathFromFilename(opt.binary_name.c_str());

+#ifdef DOCTEST_PLATFORM_WINDOWS

+            if(binary_name.rfind(".exe") != std::string::npos)

+                binary_name = binary_name.substr(0, binary_name.length() - 4);

+#endif // DOCTEST_PLATFORM_WINDOWS

+

+            xml.startElement("doctest").writeAttribute("binary", binary_name);

+            if(opt.no_version == false)

+                xml.writeAttribute("version", DOCTEST_VERSION_STR);

+

+            // only the consequential ones (TODO: filters)

+            xml.scopedElement("Options")

+                    .writeAttribute("order_by", opt.order_by.c_str())

+                    .writeAttribute("rand_seed", opt.rand_seed)

+                    .writeAttribute("first", opt.first)

+                    .writeAttribute("last", opt.last)

+                    .writeAttribute("abort_after", opt.abort_after)

+                    .writeAttribute("subcase_filter_levels", opt.subcase_filter_levels)

+                    .writeAttribute("case_sensitive", opt.case_sensitive)

+                    .writeAttribute("no_throw", opt.no_throw)

+                    .writeAttribute("no_skip", opt.no_skip);

+        }

+

+        void test_run_end(const TestRunStats& p) override {

+            if(tc) // the TestSuite tag - only if there has been at least 1 test case

+                xml.endElement();

+

+            xml.scopedElement("OverallResultsAsserts")

+                    .writeAttribute("successes", p.numAsserts - p.numAssertsFailed)

+                    .writeAttribute("failures", p.numAssertsFailed);

+

+            xml.startElement("OverallResultsTestCases")

+                    .writeAttribute("successes",

+                                    p.numTestCasesPassingFilters - p.numTestCasesFailed)

+                    .writeAttribute("failures", p.numTestCasesFailed);

+            if(opt.no_skipped_summary == false)

+                xml.writeAttribute("skipped", p.numTestCases - p.numTestCasesPassingFilters);

+            xml.endElement();

+

+            xml.endElement();

+        }

+

+        void test_case_start(const TestCaseData& in) override {

+            test_case_start_impl(in);

+            xml.ensureTagClosed();

+        }

+        

+        void test_case_reenter(const TestCaseData&) override {}

+

+        void test_case_end(const CurrentTestCaseStats& st) override {

+            xml.startElement("OverallResultsAsserts")

+                    .writeAttribute("successes",

+                                    st.numAssertsCurrentTest - st.numAssertsFailedCurrentTest)

+                    .writeAttribute("failures", st.numAssertsFailedCurrentTest)

+                    .writeAttribute("test_case_success", st.testCaseSuccess);

+            if(opt.duration)

+                xml.writeAttribute("duration", st.seconds);

+            if(tc->m_expected_failures)

+                xml.writeAttribute("expected_failures", tc->m_expected_failures);

+            xml.endElement();

+

+            xml.endElement();

+        }

+

+        void test_case_exception(const TestCaseException& e) override {

+            DOCTEST_LOCK_MUTEX(mutex)

+

+            xml.scopedElement("Exception")

+                    .writeAttribute("crash", e.is_crash)

+                    .writeText(e.error_string.c_str());

+        }

+

+        void subcase_start(const SubcaseSignature& in) override {

+            xml.startElement("SubCase")

+                    .writeAttribute("name", in.m_name)

+                    .writeAttribute("filename", skipPathFromFilename(in.m_file))

+                    .writeAttribute("line", line(in.m_line));

+            xml.ensureTagClosed();

+        }

+

+        void subcase_end() override { xml.endElement(); }

+

+        void log_assert(const AssertData& rb) override {

+            if(!rb.m_failed && !opt.success)

+                return;

+

+            DOCTEST_LOCK_MUTEX(mutex)

+

+            xml.startElement("Expression")

+                    .writeAttribute("success", !rb.m_failed)

+                    .writeAttribute("type", assertString(rb.m_at))

+                    .writeAttribute("filename", skipPathFromFilename(rb.m_file))

+                    .writeAttribute("line", line(rb.m_line));

+

+            xml.scopedElement("Original").writeText(rb.m_expr);

+

+            if(rb.m_threw)

+                xml.scopedElement("Exception").writeText(rb.m_exception.c_str());

+

+            if(rb.m_at & assertType::is_throws_as)

+                xml.scopedElement("ExpectedException").writeText(rb.m_exception_type);

+            if(rb.m_at & assertType::is_throws_with)

+                xml.scopedElement("ExpectedExceptionString").writeText(rb.m_exception_string.c_str());

+            if((rb.m_at & assertType::is_normal) && !rb.m_threw)

+                xml.scopedElement("Expanded").writeText(rb.m_decomp.c_str());

+

+            log_contexts();

+

+            xml.endElement();

+        }

+

+        void log_message(const MessageData& mb) override {

+            DOCTEST_LOCK_MUTEX(mutex)

+

+            xml.startElement("Message")

+                    .writeAttribute("type", failureString(mb.m_severity))

+                    .writeAttribute("filename", skipPathFromFilename(mb.m_file))

+                    .writeAttribute("line", line(mb.m_line));

+

+            xml.scopedElement("Text").writeText(mb.m_string.c_str());

+

+            log_contexts();

+

+            xml.endElement();

+        }

+

+        void test_case_skipped(const TestCaseData& in) override {

+            if(opt.no_skipped_summary == false) {

+                test_case_start_impl(in);

+                xml.writeAttribute("skipped", "true");

+                xml.endElement();

+            }

+        }

+    };

+

+    DOCTEST_REGISTER_REPORTER("xml", 0, XmlReporter);

+

+    void fulltext_log_assert_to_stream(std::ostream& s, const AssertData& rb) {

+        if((rb.m_at & (assertType::is_throws_as | assertType::is_throws_with)) ==

+            0) //!OCLINT bitwise operator in conditional

+            s << Color::Cyan << assertString(rb.m_at) << "( " << rb.m_expr << " ) "

+                << Color::None;

+

+        if(rb.m_at & assertType::is_throws) { //!OCLINT bitwise operator in conditional

+            s << (rb.m_threw ? "threw as expected!" : "did NOT throw at all!") << "\n";

+        } else if((rb.m_at & assertType::is_throws_as) &&

+                    (rb.m_at & assertType::is_throws_with)) { //!OCLINT

+            s << Color::Cyan << assertString(rb.m_at) << "( " << rb.m_expr << ", \""

+                << rb.m_exception_string.c_str()

+                << "\", " << rb.m_exception_type << " ) " << Color::None;

+            if(rb.m_threw) {

+                if(!rb.m_failed) {

+                    s << "threw as expected!\n";

+                } else {

+                    s << "threw a DIFFERENT exception! (contents: " << rb.m_exception << ")\n";

+                }

+            } else {

+                s << "did NOT throw at all!\n";

+            }

+        } else if(rb.m_at &

+                    assertType::is_throws_as) { //!OCLINT bitwise operator in conditional

+            s << Color::Cyan << assertString(rb.m_at) << "( " << rb.m_expr << ", "

+                << rb.m_exception_type << " ) " << Color::None

+                << (rb.m_threw ? (rb.m_threw_as ? "threw as expected!" :

+                                                "threw a DIFFERENT exception: ") :

+                                "did NOT throw at all!")

+                << Color::Cyan << rb.m_exception << "\n";

+        } else if(rb.m_at &

+                    assertType::is_throws_with) { //!OCLINT bitwise operator in conditional

+            s << Color::Cyan << assertString(rb.m_at) << "( " << rb.m_expr << ", \""

+                << rb.m_exception_string.c_str()

+                << "\" ) " << Color::None

+                << (rb.m_threw ? (!rb.m_failed ? "threw as expected!" :

+                                                "threw a DIFFERENT exception: ") :

+                                "did NOT throw at all!")

+                << Color::Cyan << rb.m_exception << "\n";

+        } else if(rb.m_at & assertType::is_nothrow) { //!OCLINT bitwise operator in conditional

+            s << (rb.m_threw ? "THREW exception: " : "didn't throw!") << Color::Cyan

+                << rb.m_exception << "\n";

+        } else {

+            s << (rb.m_threw ? "THREW exception: " :

+                                (!rb.m_failed ? "is correct!\n" : "is NOT correct!\n"));

+            if(rb.m_threw)

+                s << rb.m_exception << "\n";

+            else

+                s << "  values: " << assertString(rb.m_at) << "( " << rb.m_decomp << " )\n";

+        }

+    }

+

+    // TODO:

+    // - log_message()

+    // - respond to queries

+    // - honor remaining options

+    // - more attributes in tags

+    struct JUnitReporter : public IReporter

+    {

+        XmlWriter xml;

+        DOCTEST_DECLARE_MUTEX(mutex)

+        Timer timer;

+        std::vector<String> deepestSubcaseStackNames;

+

+        struct JUnitTestCaseData

+        {

+            static std::string getCurrentTimestamp() {

+                // Beware, this is not reentrant because of backward compatibility issues

+                // Also, UTC only, again because of backward compatibility (%z is C++11)

+                time_t rawtime;

+                std::time(&rawtime);

+                auto const timeStampSize = sizeof("2017-01-16T17:06:45Z");

+

+                std::tm timeInfo;

+#ifdef DOCTEST_PLATFORM_WINDOWS

+                gmtime_s(&timeInfo, &rawtime);

+#else // DOCTEST_PLATFORM_WINDOWS

+                gmtime_r(&rawtime, &timeInfo);

+#endif // DOCTEST_PLATFORM_WINDOWS

+

+                char timeStamp[timeStampSize];

+                const char* const fmt = "%Y-%m-%dT%H:%M:%SZ";

+

+                std::strftime(timeStamp, timeStampSize, fmt, &timeInfo);

+                return std::string(timeStamp);

+            }

+

+            struct JUnitTestMessage

+            {

+                JUnitTestMessage(const std::string& _message, const std::string& _type, const std::string& _details)

+                    : message(_message), type(_type), details(_details) {}

+

+                JUnitTestMessage(const std::string& _message, const std::string& _details)

+                    : message(_message), type(), details(_details) {}

+

+                std::string message, type, details;

+            };

+

+            struct JUnitTestCase

+            {

+                JUnitTestCase(const std::string& _classname, const std::string& _name)

+                    : classname(_classname), name(_name), time(0), failures() {}

+

+                std::string classname, name;

+                double time;

+                std::vector<JUnitTestMessage> failures, errors;

+            };

+

+            void add(const std::string& classname, const std::string& name) {

+                testcases.emplace_back(classname, name);

+            }

+

+            void appendSubcaseNamesToLastTestcase(std::vector<String> nameStack) {

+                for(auto& curr: nameStack)

+                    if(curr.size())

+                        testcases.back().name += std::string("/") + curr.c_str();

+            }

+

+            void addTime(double time) {

+                if(time < 1e-4)

+                    time = 0;

+                testcases.back().time = time;

+                totalSeconds += time;

+            }

+

+            void addFailure(const std::string& message, const std::string& type, const std::string& details) {

+                testcases.back().failures.emplace_back(message, type, details);

+                ++totalFailures;

+            }

+

+            void addError(const std::string& message, const std::string& details) {

+                testcases.back().errors.emplace_back(message, details);

+                ++totalErrors;

+            }

+

+            std::vector<JUnitTestCase> testcases;

+            double totalSeconds = 0;

+            int totalErrors = 0, totalFailures = 0;

+        };

+

+        JUnitTestCaseData testCaseData;

+

+        // caching pointers/references to objects of these types - safe to do

+        const ContextOptions& opt;

+        const TestCaseData*   tc = nullptr;

+

+        JUnitReporter(const ContextOptions& co)

+                : xml(*co.cout)

+                , opt(co) {}

+

+        unsigned line(unsigned l) const { return opt.no_line_numbers ? 0 : l; }

+

+        // =========================================================================================

+        // WHAT FOLLOWS ARE OVERRIDES OF THE VIRTUAL METHODS OF THE REPORTER INTERFACE

+        // =========================================================================================

+

+        void report_query(const QueryData&) override {

+            xml.writeDeclaration();

+        }

+

+        void test_run_start() override {

+            xml.writeDeclaration();

+        }

+

+        void test_run_end(const TestRunStats& p) override {

+            // remove .exe extension - mainly to have the same output on UNIX and Windows

+            std::string binary_name = skipPathFromFilename(opt.binary_name.c_str());

+#ifdef DOCTEST_PLATFORM_WINDOWS

+            if(binary_name.rfind(".exe") != std::string::npos)

+                binary_name = binary_name.substr(0, binary_name.length() - 4);

+#endif // DOCTEST_PLATFORM_WINDOWS

+            xml.startElement("testsuites");

+            xml.startElement("testsuite").writeAttribute("name", binary_name)

+                    .writeAttribute("errors", testCaseData.totalErrors)

+                    .writeAttribute("failures", testCaseData.totalFailures)

+                    .writeAttribute("tests", p.numAsserts);

+            if(opt.no_time_in_output == false) {

+                xml.writeAttribute("time", testCaseData.totalSeconds);

+                xml.writeAttribute("timestamp", JUnitTestCaseData::getCurrentTimestamp());

+            }

+            if(opt.no_version == false)

+                xml.writeAttribute("doctest_version", DOCTEST_VERSION_STR);

+

+            for(const auto& testCase : testCaseData.testcases) {

+                xml.startElement("testcase")

+                    .writeAttribute("classname", testCase.classname)

+                    .writeAttribute("name", testCase.name);

+                if(opt.no_time_in_output == false)

+                    xml.writeAttribute("time", testCase.time);

+                // This is not ideal, but it should be enough to mimic gtest's junit output.

+                xml.writeAttribute("status", "run");

+

+                for(const auto& failure : testCase.failures) {

+                    xml.scopedElement("failure")

+                        .writeAttribute("message", failure.message)

+                        .writeAttribute("type", failure.type)

+                        .writeText(failure.details, false);

+                }

+

+                for(const auto& error : testCase.errors) {

+                    xml.scopedElement("error")

+                        .writeAttribute("message", error.message)

+                        .writeText(error.details);

+                }

+

+                xml.endElement();

+            }

+            xml.endElement();

+            xml.endElement();

+        }

+

+        void test_case_start(const TestCaseData& in) override {

+            testCaseData.add(skipPathFromFilename(in.m_file.c_str()), in.m_name);

+            timer.start();

+        }

+

+        void test_case_reenter(const TestCaseData& in) override {

+            testCaseData.addTime(timer.getElapsedSeconds());

+            testCaseData.appendSubcaseNamesToLastTestcase(deepestSubcaseStackNames);

+            deepestSubcaseStackNames.clear();

+

+            timer.start();

+            testCaseData.add(skipPathFromFilename(in.m_file.c_str()), in.m_name);

+        }

+

+        void test_case_end(const CurrentTestCaseStats&) override {

+            testCaseData.addTime(timer.getElapsedSeconds());

+            testCaseData.appendSubcaseNamesToLastTestcase(deepestSubcaseStackNames);

+            deepestSubcaseStackNames.clear();

+        }

+

+        void test_case_exception(const TestCaseException& e) override {

+            DOCTEST_LOCK_MUTEX(mutex)

+            testCaseData.addError("exception", e.error_string.c_str());

+        }

+

+        void subcase_start(const SubcaseSignature& in) override {

+            deepestSubcaseStackNames.push_back(in.m_name);

+        }

+

+        void subcase_end() override {}

+

+        void log_assert(const AssertData& rb) override {

+            if(!rb.m_failed) // report only failures & ignore the `success` option

+                return;

+

+            DOCTEST_LOCK_MUTEX(mutex)

+

+            std::ostringstream os;

+            os << skipPathFromFilename(rb.m_file) << (opt.gnu_file_line ? ":" : "(")

+              << line(rb.m_line) << (opt.gnu_file_line ? ":" : "):") << std::endl;

+

+            fulltext_log_assert_to_stream(os, rb);

+            log_contexts(os);

+            testCaseData.addFailure(rb.m_decomp.c_str(), assertString(rb.m_at), os.str());

+        }

+

+        void log_message(const MessageData&) override {}

+

+        void test_case_skipped(const TestCaseData&) override {}

+

+        void log_contexts(std::ostringstream& s) {

+            int num_contexts = get_num_active_contexts();

+            if(num_contexts) {

+                auto contexts = get_active_contexts();

+

+                s << "  logged: ";

+                for(int i = 0; i < num_contexts; ++i) {

+                    s << (i == 0 ? "" : "          ");

+                    contexts[i]->stringify(&s);

+                    s << std::endl;

+                }

+            }

+        }

+    };

+

+    DOCTEST_REGISTER_REPORTER("junit", 0, JUnitReporter);

+

+    struct Whitespace

+    {

+        int nrSpaces;

+        explicit Whitespace(int nr)

+                : nrSpaces(nr) {}

+    };

+

+    std::ostream& operator<<(std::ostream& out, const Whitespace& ws) {

+        if(ws.nrSpaces != 0)

+            out << std::setw(ws.nrSpaces) << ' ';

+        return out;

+    }

+

+    struct ConsoleReporter : public IReporter

+    {

+        std::ostream&                 s;

+        bool                          hasLoggedCurrentTestStart;

+        std::vector<SubcaseSignature> subcasesStack;

+        size_t                        currentSubcaseLevel;

+        DOCTEST_DECLARE_MUTEX(mutex)

+

+        // caching pointers/references to objects of these types - safe to do

+        const ContextOptions& opt;

+        const TestCaseData*   tc;

+

+        ConsoleReporter(const ContextOptions& co)

+                : s(*co.cout)

+                , opt(co) {}

+

+        ConsoleReporter(const ContextOptions& co, std::ostream& ostr)

+                : s(ostr)

+                , opt(co) {}

+

+        // =========================================================================================

+        // WHAT FOLLOWS ARE HELPERS USED BY THE OVERRIDES OF THE VIRTUAL METHODS OF THE INTERFACE

+        // =========================================================================================

+

+        void separator_to_stream() {

+            s << Color::Yellow

+              << "==============================================================================="

+                 "\n";

+        }

+

+        const char* getSuccessOrFailString(bool success, assertType::Enum at,

+                                           const char* success_str) {

+            if(success)

+                return success_str;

+            return failureString(at);

+        }

+

+        Color::Enum getSuccessOrFailColor(bool success, assertType::Enum at) {

+            return success ? Color::BrightGreen :

+                             (at & assertType::is_warn) ? Color::Yellow : Color::Red;

+        }

+

+        void successOrFailColoredStringToStream(bool success, assertType::Enum at,

+                                                const char* success_str = "SUCCESS") {

+            s << getSuccessOrFailColor(success, at)

+              << getSuccessOrFailString(success, at, success_str) << ": ";

+        }

+

+        void log_contexts() {

+            int num_contexts = get_num_active_contexts();

+            if(num_contexts) {

+                auto contexts = get_active_contexts();

+

+                s << Color::None << "  logged: ";

+                for(int i = 0; i < num_contexts; ++i) {

+                    s << (i == 0 ? "" : "          ");

+                    contexts[i]->stringify(&s);

+                    s << "\n";

+                }

+            }

+

+            s << "\n";

+        }

+

+        // this was requested to be made virtual so users could override it

+        virtual void file_line_to_stream(const char* file, int line,

+                                        const char* tail = "") {

+            s << Color::LightGrey << skipPathFromFilename(file) << (opt.gnu_file_line ? ":" : "(")

+            << (opt.no_line_numbers ? 0 : line) // 0 or the real num depending on the option

+            << (opt.gnu_file_line ? ":" : "):") << tail;

+        }

+

+        void logTestStart() {

+            if(hasLoggedCurrentTestStart)

+                return;

+

+            separator_to_stream();

+            file_line_to_stream(tc->m_file.c_str(), tc->m_line, "\n");

+            if(tc->m_description)

+                s << Color::Yellow << "DESCRIPTION: " << Color::None << tc->m_description << "\n";

+            if(tc->m_test_suite && tc->m_test_suite[0] != '\0')

+                s << Color::Yellow << "TEST SUITE: " << Color::None << tc->m_test_suite << "\n";

+            if(strncmp(tc->m_name, "  Scenario:", 11) != 0)

+                s << Color::Yellow << "TEST CASE:  ";

+            s << Color::None << tc->m_name << "\n";

+

+            for(size_t i = 0; i < currentSubcaseLevel; ++i) {

+                if(subcasesStack[i].m_name[0] != '\0')

+                    s << "  " << subcasesStack[i].m_name << "\n";

+            }

+

+            if(currentSubcaseLevel != subcasesStack.size()) {

+                s << Color::Yellow << "\nDEEPEST SUBCASE STACK REACHED (DIFFERENT FROM THE CURRENT ONE):\n" << Color::None;

+                for(size_t i = 0; i < subcasesStack.size(); ++i) {

+                    if(subcasesStack[i].m_name[0] != '\0')

+                        s << "  " << subcasesStack[i].m_name << "\n";

+                }

+            }

+

+            s << "\n";

+

+            hasLoggedCurrentTestStart = true;

+        }

+

+        void printVersion() {

+            if(opt.no_version == false)

+                s << Color::Cyan << "[doctest] " << Color::None << "doctest version is \""

+                  << DOCTEST_VERSION_STR << "\"\n";

+        }

+

+        void printIntro() {

+            if(opt.no_intro == false) {

+                printVersion();

+                s << Color::Cyan << "[doctest] " << Color::None

+                  << "run with \"--" DOCTEST_OPTIONS_PREFIX_DISPLAY "help\" for options\n";

+            }

+        }

+

+        void printHelp() {

+            int sizePrefixDisplay = static_cast<int>(strlen(DOCTEST_OPTIONS_PREFIX_DISPLAY));

+            printVersion();

+            // clang-format off

+            s << Color::Cyan << "[doctest]\n" << Color::None;

+            s << Color::Cyan << "[doctest] " << Color::None;

+            s << "boolean values: \"1/on/yes/true\" or \"0/off/no/false\"\n";

+            s << Color::Cyan << "[doctest] " << Color::None;

+            s << "filter  values: \"str1,str2,str3\" (comma separated strings)\n";

+            s << Color::Cyan << "[doctest]\n" << Color::None;

+            s << Color::Cyan << "[doctest] " << Color::None;

+            s << "filters use wildcards for matching strings\n";

+            s << Color::Cyan << "[doctest] " << Color::None;

+            s << "something passes a filter if any of the strings in a filter matches\n";

+#ifndef DOCTEST_CONFIG_NO_UNPREFIXED_OPTIONS

+            s << Color::Cyan << "[doctest]\n" << Color::None;

+            s << Color::Cyan << "[doctest] " << Color::None;

+            s << "ALL FLAGS, OPTIONS AND FILTERS ALSO AVAILABLE WITH A \"" DOCTEST_CONFIG_OPTIONS_PREFIX "\" PREFIX!!!\n";

+#endif

+            s << Color::Cyan << "[doctest]\n" << Color::None;

+            s << Color::Cyan << "[doctest] " << Color::None;

+            s << "Query flags - the program quits after them. Available:\n\n";

+            s << " -" DOCTEST_OPTIONS_PREFIX_DISPLAY "?,   --" DOCTEST_OPTIONS_PREFIX_DISPLAY "help, -" DOCTEST_OPTIONS_PREFIX_DISPLAY "h                      "

+              << Whitespace(sizePrefixDisplay*0) <<  "prints this message\n";

+            s << " -" DOCTEST_OPTIONS_PREFIX_DISPLAY "v,   --" DOCTEST_OPTIONS_PREFIX_DISPLAY "version                       "

+              << Whitespace(sizePrefixDisplay*1) << "prints the version\n";

+            s << " -" DOCTEST_OPTIONS_PREFIX_DISPLAY "c,   --" DOCTEST_OPTIONS_PREFIX_DISPLAY "count                         "

+              << Whitespace(sizePrefixDisplay*1) << "prints the number of matching tests\n";

+            s << " -" DOCTEST_OPTIONS_PREFIX_DISPLAY "ltc, --" DOCTEST_OPTIONS_PREFIX_DISPLAY "list-test-cases               "

+              << Whitespace(sizePrefixDisplay*1) << "lists all matching tests by name\n";

+            s << " -" DOCTEST_OPTIONS_PREFIX_DISPLAY "lts, --" DOCTEST_OPTIONS_PREFIX_DISPLAY "list-test-suites              "

+              << Whitespace(sizePrefixDisplay*1) << "lists all matching test suites\n";

+            s << " -" DOCTEST_OPTIONS_PREFIX_DISPLAY "lr,  --" DOCTEST_OPTIONS_PREFIX_DISPLAY "list-reporters                "

+              << Whitespace(sizePrefixDisplay*1) << "lists all registered reporters\n\n";

+            // ================================================================================== << 79

+            s << Color::Cyan << "[doctest] " << Color::None;

+            s << "The available <int>/<string> options/filters are:\n\n";

+            s << " -" DOCTEST_OPTIONS_PREFIX_DISPLAY "tc,  --" DOCTEST_OPTIONS_PREFIX_DISPLAY "test-case=<filters>           "

+              << Whitespace(sizePrefixDisplay*1) << "filters     tests by their name\n";

+            s << " -" DOCTEST_OPTIONS_PREFIX_DISPLAY "tce, --" DOCTEST_OPTIONS_PREFIX_DISPLAY "test-case-exclude=<filters>   "

+              << Whitespace(sizePrefixDisplay*1) << "filters OUT tests by their name\n";

+            s << " -" DOCTEST_OPTIONS_PREFIX_DISPLAY "sf,  --" DOCTEST_OPTIONS_PREFIX_DISPLAY "source-file=<filters>         "

+              << Whitespace(sizePrefixDisplay*1) << "filters     tests by their file\n";

+            s << " -" DOCTEST_OPTIONS_PREFIX_DISPLAY "sfe, --" DOCTEST_OPTIONS_PREFIX_DISPLAY "source-file-exclude=<filters> "

+              << Whitespace(sizePrefixDisplay*1) << "filters OUT tests by their file\n";

+            s << " -" DOCTEST_OPTIONS_PREFIX_DISPLAY "ts,  --" DOCTEST_OPTIONS_PREFIX_DISPLAY "test-suite=<filters>          "

+              << Whitespace(sizePrefixDisplay*1) << "filters     tests by their test suite\n";

+            s << " -" DOCTEST_OPTIONS_PREFIX_DISPLAY "tse, --" DOCTEST_OPTIONS_PREFIX_DISPLAY "test-suite-exclude=<filters>  "

+              << Whitespace(sizePrefixDisplay*1) << "filters OUT tests by their test suite\n";

+            s << " -" DOCTEST_OPTIONS_PREFIX_DISPLAY "sc,  --" DOCTEST_OPTIONS_PREFIX_DISPLAY "subcase=<filters>             "

+              << Whitespace(sizePrefixDisplay*1) << "filters     subcases by their name\n";

+            s << " -" DOCTEST_OPTIONS_PREFIX_DISPLAY "sce, --" DOCTEST_OPTIONS_PREFIX_DISPLAY "subcase-exclude=<filters>     "

+              << Whitespace(sizePrefixDisplay*1) << "filters OUT subcases by their name\n";

+            s << " -" DOCTEST_OPTIONS_PREFIX_DISPLAY "r,   --" DOCTEST_OPTIONS_PREFIX_DISPLAY "reporters=<filters>           "

+              << Whitespace(sizePrefixDisplay*1) << "reporters to use (console is default)\n";

+            s << " -" DOCTEST_OPTIONS_PREFIX_DISPLAY "o,   --" DOCTEST_OPTIONS_PREFIX_DISPLAY "out=<string>                  "

+              << Whitespace(sizePrefixDisplay*1) << "output filename\n";

+            s << " -" DOCTEST_OPTIONS_PREFIX_DISPLAY "ob,  --" DOCTEST_OPTIONS_PREFIX_DISPLAY "order-by=<string>             "

+              << Whitespace(sizePrefixDisplay*1) << "how the tests should be ordered\n";

+            s << Whitespace(sizePrefixDisplay*3) << "                                       <string> - [file/suite/name/rand/none]\n";

+            s << " -" DOCTEST_OPTIONS_PREFIX_DISPLAY "rs,  --" DOCTEST_OPTIONS_PREFIX_DISPLAY "rand-seed=<int>               "

+              << Whitespace(sizePrefixDisplay*1) << "seed for random ordering\n";

+            s << " -" DOCTEST_OPTIONS_PREFIX_DISPLAY "f,   --" DOCTEST_OPTIONS_PREFIX_DISPLAY "first=<int>                   "

+              << Whitespace(sizePrefixDisplay*1) << "the first test passing the filters to\n";

+            s << Whitespace(sizePrefixDisplay*3) << "                                       execute - for range-based execution\n";

+            s << " -" DOCTEST_OPTIONS_PREFIX_DISPLAY "l,   --" DOCTEST_OPTIONS_PREFIX_DISPLAY "last=<int>                    "

+              << Whitespace(sizePrefixDisplay*1) << "the last test passing the filters to\n";

+            s << Whitespace(sizePrefixDisplay*3) << "                                       execute - for range-based execution\n";

+            s << " -" DOCTEST_OPTIONS_PREFIX_DISPLAY "aa,  --" DOCTEST_OPTIONS_PREFIX_DISPLAY "abort-after=<int>             "

+              << Whitespace(sizePrefixDisplay*1) << "stop after <int> failed assertions\n";

+            s << " -" DOCTEST_OPTIONS_PREFIX_DISPLAY "scfl,--" DOCTEST_OPTIONS_PREFIX_DISPLAY "subcase-filter-levels=<int>   "

+              << Whitespace(sizePrefixDisplay*1) << "apply filters for the first <int> levels\n";

+            s << Color::Cyan << "\n[doctest] " << Color::None;

+            s << "Bool options - can be used like flags and true is assumed. Available:\n\n";

+            s << " -" DOCTEST_OPTIONS_PREFIX_DISPLAY "s,   --" DOCTEST_OPTIONS_PREFIX_DISPLAY "success=<bool>                "

+              << Whitespace(sizePrefixDisplay*1) << "include successful assertions in output\n";

+            s << " -" DOCTEST_OPTIONS_PREFIX_DISPLAY "cs,  --" DOCTEST_OPTIONS_PREFIX_DISPLAY "case-sensitive=<bool>         "

+              << Whitespace(sizePrefixDisplay*1) << "filters being treated as case sensitive\n";

+            s << " -" DOCTEST_OPTIONS_PREFIX_DISPLAY "e,   --" DOCTEST_OPTIONS_PREFIX_DISPLAY "exit=<bool>                   "

+              << Whitespace(sizePrefixDisplay*1) << "exits after the tests finish\n";

+            s << " -" DOCTEST_OPTIONS_PREFIX_DISPLAY "d,   --" DOCTEST_OPTIONS_PREFIX_DISPLAY "duration=<bool>               "

+              << Whitespace(sizePrefixDisplay*1) << "prints the time duration of each test\n";

+            s << " -" DOCTEST_OPTIONS_PREFIX_DISPLAY "m,   --" DOCTEST_OPTIONS_PREFIX_DISPLAY "minimal=<bool>                "

+              << Whitespace(sizePrefixDisplay*1) << "minimal console output (only failures)\n";

+            s << " -" DOCTEST_OPTIONS_PREFIX_DISPLAY "q,   --" DOCTEST_OPTIONS_PREFIX_DISPLAY "quiet=<bool>                  "

+              << Whitespace(sizePrefixDisplay*1) << "no console output\n";

+            s << " -" DOCTEST_OPTIONS_PREFIX_DISPLAY "nt,  --" DOCTEST_OPTIONS_PREFIX_DISPLAY "no-throw=<bool>               "

+              << Whitespace(sizePrefixDisplay*1) << "skips exceptions-related assert checks\n";

+            s << " -" DOCTEST_OPTIONS_PREFIX_DISPLAY "ne,  --" DOCTEST_OPTIONS_PREFIX_DISPLAY "no-exitcode=<bool>            "

+              << Whitespace(sizePrefixDisplay*1) << "returns (or exits) always with success\n";

+            s << " -" DOCTEST_OPTIONS_PREFIX_DISPLAY "nr,  --" DOCTEST_OPTIONS_PREFIX_DISPLAY "no-run=<bool>                 "

+              << Whitespace(sizePrefixDisplay*1) << "skips all runtime doctest operations\n";

+            s << " -" DOCTEST_OPTIONS_PREFIX_DISPLAY "ni,  --" DOCTEST_OPTIONS_PREFIX_DISPLAY "no-intro=<bool>               "

+              << Whitespace(sizePrefixDisplay*1) << "omit the framework intro in the output\n";

+            s << " -" DOCTEST_OPTIONS_PREFIX_DISPLAY "nv,  --" DOCTEST_OPTIONS_PREFIX_DISPLAY "no-version=<bool>             "

+              << Whitespace(sizePrefixDisplay*1) << "omit the framework version in the output\n";

+            s << " -" DOCTEST_OPTIONS_PREFIX_DISPLAY "nc,  --" DOCTEST_OPTIONS_PREFIX_DISPLAY "no-colors=<bool>              "

+              << Whitespace(sizePrefixDisplay*1) << "disables colors in output\n";

+            s << " -" DOCTEST_OPTIONS_PREFIX_DISPLAY "fc,  --" DOCTEST_OPTIONS_PREFIX_DISPLAY "force-colors=<bool>           "

+              << Whitespace(sizePrefixDisplay*1) << "use colors even when not in a tty\n";

+            s << " -" DOCTEST_OPTIONS_PREFIX_DISPLAY "nb,  --" DOCTEST_OPTIONS_PREFIX_DISPLAY "no-breaks=<bool>              "

+              << Whitespace(sizePrefixDisplay*1) << "disables breakpoints in debuggers\n";

+            s << " -" DOCTEST_OPTIONS_PREFIX_DISPLAY "ns,  --" DOCTEST_OPTIONS_PREFIX_DISPLAY "no-skip=<bool>                "

+              << Whitespace(sizePrefixDisplay*1) << "don't skip test cases marked as skip\n";

+            s << " -" DOCTEST_OPTIONS_PREFIX_DISPLAY "gfl, --" DOCTEST_OPTIONS_PREFIX_DISPLAY "gnu-file-line=<bool>          "

+              << Whitespace(sizePrefixDisplay*1) << ":n: vs (n): for line numbers in output\n";

+            s << " -" DOCTEST_OPTIONS_PREFIX_DISPLAY "npf, --" DOCTEST_OPTIONS_PREFIX_DISPLAY "no-path-filenames=<bool>      "

+              << Whitespace(sizePrefixDisplay*1) << "only filenames and no paths in output\n";

+            s << " -" DOCTEST_OPTIONS_PREFIX_DISPLAY "nln, --" DOCTEST_OPTIONS_PREFIX_DISPLAY "no-line-numbers=<bool>        "

+              << Whitespace(sizePrefixDisplay*1) << "0 instead of real line numbers in output\n";

+            // ================================================================================== << 79

+            // clang-format on

+

+            s << Color::Cyan << "\n[doctest] " << Color::None;

+            s << "for more information visit the project documentation\n\n";

+        }

+

+        void printRegisteredReporters() {

+            printVersion();

+            auto printReporters = [this] (const reporterMap& reporters, const char* type) {

+                if(reporters.size()) {

+                    s << Color::Cyan << "[doctest] " << Color::None << "listing all registered " << type << "\n";

+                    for(auto& curr : reporters)

+                        s << "priority: " << std::setw(5) << curr.first.first

+                          << " name: " << curr.first.second << "\n";

+                }

+            };

+            printReporters(getListeners(), "listeners");

+            printReporters(getReporters(), "reporters");

+        }

+

+        // =========================================================================================

+        // WHAT FOLLOWS ARE OVERRIDES OF THE VIRTUAL METHODS OF THE REPORTER INTERFACE

+        // =========================================================================================

+

+        void report_query(const QueryData& in) override {

+            if(opt.version) {

+                printVersion();

+            } else if(opt.help) {

+                printHelp();

+            } else if(opt.list_reporters) {

+                printRegisteredReporters();

+            } else if(opt.count || opt.list_test_cases) {

+                if(opt.list_test_cases) {

+                    s << Color::Cyan << "[doctest] " << Color::None

+                      << "listing all test case names\n";

+                    separator_to_stream();

+                }

+

+                for(unsigned i = 0; i < in.num_data; ++i)

+                    s << Color::None << in.data[i]->m_name << "\n";

+

+                separator_to_stream();

+

+                s << Color::Cyan << "[doctest] " << Color::None

+                  << "unskipped test cases passing the current filters: "

+                  << g_cs->numTestCasesPassingFilters << "\n";

+

+            } else if(opt.list_test_suites) {

+                s << Color::Cyan << "[doctest] " << Color::None << "listing all test suites\n";

+                separator_to_stream();

+

+                for(unsigned i = 0; i < in.num_data; ++i)

+                    s << Color::None << in.data[i]->m_test_suite << "\n";

+

+                separator_to_stream();

+

+                s << Color::Cyan << "[doctest] " << Color::None

+                  << "unskipped test cases passing the current filters: "

+                  << g_cs->numTestCasesPassingFilters << "\n";

+                s << Color::Cyan << "[doctest] " << Color::None

+                  << "test suites with unskipped test cases passing the current filters: "

+                  << g_cs->numTestSuitesPassingFilters << "\n";

+            }

+        }

+

+        void test_run_start() override {

+            if(!opt.minimal)

+                printIntro();

+        }

+

+        void test_run_end(const TestRunStats& p) override {

+            if(opt.minimal && p.numTestCasesFailed == 0)

+                return;

+

+            separator_to_stream();

+            s << std::dec;

+

+            auto totwidth = int(std::ceil(log10((std::max(p.numTestCasesPassingFilters, static_cast<unsigned>(p.numAsserts))) + 1)));

+            auto passwidth = int(std::ceil(log10((std::max(p.numTestCasesPassingFilters - p.numTestCasesFailed, static_cast<unsigned>(p.numAsserts - p.numAssertsFailed))) + 1)));

+            auto failwidth = int(std::ceil(log10((std::max(p.numTestCasesFailed, static_cast<unsigned>(p.numAssertsFailed))) + 1)));

+            const bool anythingFailed = p.numTestCasesFailed > 0 || p.numAssertsFailed > 0;

+            s << Color::Cyan << "[doctest] " << Color::None << "test cases: " << std::setw(totwidth)

+              << p.numTestCasesPassingFilters << " | "

+              << ((p.numTestCasesPassingFilters == 0 || anythingFailed) ? Color::None :

+                                                                          Color::Green)

+              << std::setw(passwidth) << p.numTestCasesPassingFilters - p.numTestCasesFailed << " passed"

+              << Color::None << " | " << (p.numTestCasesFailed > 0 ? Color::Red : Color::None)

+              << std::setw(failwidth) << p.numTestCasesFailed << " failed" << Color::None << " |";

+            if(opt.no_skipped_summary == false) {

+                const int numSkipped = p.numTestCases - p.numTestCasesPassingFilters;

+                s << " " << (numSkipped == 0 ? Color::None : Color::Yellow) << numSkipped

+                  << " skipped" << Color::None;

+            }

+            s << "\n";

+            s << Color::Cyan << "[doctest] " << Color::None << "assertions: " << std::setw(totwidth)

+              << p.numAsserts << " | "

+              << ((p.numAsserts == 0 || anythingFailed) ? Color::None : Color::Green)

+              << std::setw(passwidth) << (p.numAsserts - p.numAssertsFailed) << " passed" << Color::None

+              << " | " << (p.numAssertsFailed > 0 ? Color::Red : Color::None) << std::setw(failwidth)

+              << p.numAssertsFailed << " failed" << Color::None << " |\n";

+            s << Color::Cyan << "[doctest] " << Color::None

+              << "Status: " << (p.numTestCasesFailed > 0 ? Color::Red : Color::Green)

+              << ((p.numTestCasesFailed > 0) ? "FAILURE!" : "SUCCESS!") << Color::None << std::endl;

+        }

+

+        void test_case_start(const TestCaseData& in) override {

+            hasLoggedCurrentTestStart = false;

+            tc                        = &in;

+            subcasesStack.clear();

+            currentSubcaseLevel = 0;

+        }

+        

+        void test_case_reenter(const TestCaseData&) override {

+            subcasesStack.clear();

+        }

+

+        void test_case_end(const CurrentTestCaseStats& st) override {

+            if(tc->m_no_output)

+                return;

+

+            // log the preamble of the test case only if there is something

+            // else to print - something other than that an assert has failed

+            if(opt.duration ||

+               (st.failure_flags && st.failure_flags != static_cast<int>(TestCaseFailureReason::AssertFailure)))

+                logTestStart();

+

+            if(opt.duration)

+                s << Color::None << std::setprecision(6) << std::fixed << st.seconds

+                  << " s: " << tc->m_name << "\n";

+

+            if(st.failure_flags & TestCaseFailureReason::Timeout)

+                s << Color::Red << "Test case exceeded time limit of " << std::setprecision(6)

+                  << std::fixed << tc->m_timeout << "!\n";

+

+            if(st.failure_flags & TestCaseFailureReason::ShouldHaveFailedButDidnt) {

+                s << Color::Red << "Should have failed but didn't! Marking it as failed!\n";

+            } else if(st.failure_flags & TestCaseFailureReason::ShouldHaveFailedAndDid) {

+                s << Color::Yellow << "Failed as expected so marking it as not failed\n";

+            } else if(st.failure_flags & TestCaseFailureReason::CouldHaveFailedAndDid) {

+                s << Color::Yellow << "Allowed to fail so marking it as not failed\n";

+            } else if(st.failure_flags & TestCaseFailureReason::DidntFailExactlyNumTimes) {

+                s << Color::Red << "Didn't fail exactly " << tc->m_expected_failures

+                  << " times so marking it as failed!\n";

+            } else if(st.failure_flags & TestCaseFailureReason::FailedExactlyNumTimes) {

+                s << Color::Yellow << "Failed exactly " << tc->m_expected_failures

+                  << " times as expected so marking it as not failed!\n";

+            }

+            if(st.failure_flags & TestCaseFailureReason::TooManyFailedAsserts) {

+                s << Color::Red << "Aborting - too many failed asserts!\n";

+            }

+            s << Color::None; // lgtm [cpp/useless-expression]

+        }

+

+        void test_case_exception(const TestCaseException& e) override {

+            DOCTEST_LOCK_MUTEX(mutex)

+            if(tc->m_no_output)

+                return;

+

+            logTestStart();

+

+            file_line_to_stream(tc->m_file.c_str(), tc->m_line, " ");

+            successOrFailColoredStringToStream(false, e.is_crash ? assertType::is_require :

+                                                                   assertType::is_check);

+            s << Color::Red << (e.is_crash ? "test case CRASHED: " : "test case THREW exception: ")

+              << Color::Cyan << e.error_string << "\n";

+

+            int num_stringified_contexts = get_num_stringified_contexts();

+            if(num_stringified_contexts) {

+                auto stringified_contexts = get_stringified_contexts();

+                s << Color::None << "  logged: ";

+                for(int i = num_stringified_contexts; i > 0; --i) {

+                    s << (i == num_stringified_contexts ? "" : "          ")

+                      << stringified_contexts[i - 1] << "\n";

+                }

+            }

+            s << "\n" << Color::None;

+        }

+

+        void subcase_start(const SubcaseSignature& subc) override {

+            subcasesStack.push_back(subc);

+            ++currentSubcaseLevel;

+            hasLoggedCurrentTestStart = false;

+        }

+

+        void subcase_end() override {

+            --currentSubcaseLevel;

+            hasLoggedCurrentTestStart = false;

+        }

+

+        void log_assert(const AssertData& rb) override {

+            if((!rb.m_failed && !opt.success) || tc->m_no_output)

+                return;

+

+            DOCTEST_LOCK_MUTEX(mutex)

+

+            logTestStart();

+

+            file_line_to_stream(rb.m_file, rb.m_line, " ");

+            successOrFailColoredStringToStream(!rb.m_failed, rb.m_at);

+

+            fulltext_log_assert_to_stream(s, rb);

+

+            log_contexts();

+        }

+

+        void log_message(const MessageData& mb) override {

+            if(tc->m_no_output)

+                return;

+

+            DOCTEST_LOCK_MUTEX(mutex)

+

+            logTestStart();

+

+            file_line_to_stream(mb.m_file, mb.m_line, " ");

+            s << getSuccessOrFailColor(false, mb.m_severity)

+              << getSuccessOrFailString(mb.m_severity & assertType::is_warn, mb.m_severity,

+                                        "MESSAGE") << ": ";

+            s << Color::None << mb.m_string << "\n";

+            log_contexts();

+        }

+

+        void test_case_skipped(const TestCaseData&) override {}

+    };

+

+    DOCTEST_REGISTER_REPORTER("console", 0, ConsoleReporter);

+

+#ifdef DOCTEST_PLATFORM_WINDOWS

+    struct DebugOutputWindowReporter : public ConsoleReporter

+    {

+        DOCTEST_THREAD_LOCAL static std::ostringstream oss;

+

+        DebugOutputWindowReporter(const ContextOptions& co)

+                : ConsoleReporter(co, oss) {}

+

+#define DOCTEST_DEBUG_OUTPUT_REPORTER_OVERRIDE(func, type, arg)                                    \

+    void func(type arg) override {                                                                 \

+        bool with_col = g_no_colors;                                                               \

+        g_no_colors   = false;                                                                     \

+        ConsoleReporter::func(arg);                                                                \

+        if(oss.tellp() != std::streampos{}) {                                                      \

+            DOCTEST_OUTPUT_DEBUG_STRING(oss.str().c_str());                                        \

+            oss.str("");                                                                           \

+        }                                                                                          \

+        g_no_colors = with_col;                                                                    \

+    }

+

+        DOCTEST_DEBUG_OUTPUT_REPORTER_OVERRIDE(test_run_start, DOCTEST_EMPTY, DOCTEST_EMPTY)

+        DOCTEST_DEBUG_OUTPUT_REPORTER_OVERRIDE(test_run_end, const TestRunStats&, in)

+        DOCTEST_DEBUG_OUTPUT_REPORTER_OVERRIDE(test_case_start, const TestCaseData&, in)

+        DOCTEST_DEBUG_OUTPUT_REPORTER_OVERRIDE(test_case_reenter, const TestCaseData&, in)

+        DOCTEST_DEBUG_OUTPUT_REPORTER_OVERRIDE(test_case_end, const CurrentTestCaseStats&, in)

+        DOCTEST_DEBUG_OUTPUT_REPORTER_OVERRIDE(test_case_exception, const TestCaseException&, in)

+        DOCTEST_DEBUG_OUTPUT_REPORTER_OVERRIDE(subcase_start, const SubcaseSignature&, in)

+        DOCTEST_DEBUG_OUTPUT_REPORTER_OVERRIDE(subcase_end, DOCTEST_EMPTY, DOCTEST_EMPTY)

+        DOCTEST_DEBUG_OUTPUT_REPORTER_OVERRIDE(log_assert, const AssertData&, in)

+        DOCTEST_DEBUG_OUTPUT_REPORTER_OVERRIDE(log_message, const MessageData&, in)

+        DOCTEST_DEBUG_OUTPUT_REPORTER_OVERRIDE(test_case_skipped, const TestCaseData&, in)

+    };

+

+    DOCTEST_THREAD_LOCAL std::ostringstream DebugOutputWindowReporter::oss;

+#endif // DOCTEST_PLATFORM_WINDOWS

+

+    // the implementation of parseOption()

+    bool parseOptionImpl(int argc, const char* const* argv, const char* pattern, String* value) {

+        // going from the end to the beginning and stopping on the first occurrence from the end

+        for(int i = argc; i > 0; --i) {

+            auto index = i - 1;

+            auto temp = std::strstr(argv[index], pattern);

+            if(temp && (value || strlen(temp) == strlen(pattern))) { //!OCLINT prefer early exits and continue

+                // eliminate matches in which the chars before the option are not '-'

+                bool noBadCharsFound = true;

+                auto curr            = argv[index];

+                while(curr != temp) {

+                    if(*curr++ != '-') {

+                        noBadCharsFound = false;

+                        break;

+                    }

+                }

+                if(noBadCharsFound && argv[index][0] == '-') {

+                    if(value) {

+                        // parsing the value of an option

+                        temp += strlen(pattern);

+                        const unsigned len = strlen(temp);

+                        if(len) {

+                            *value = temp;

+                            return true;

+                        }

+                    } else {

+                        // just a flag - no value

+                        return true;

+                    }

+                }

+            }

+        }

+        return false;

+    }

+

+    // parses an option and returns the string after the '=' character

+    bool parseOption(int argc, const char* const* argv, const char* pattern, String* value = nullptr,

+                     const String& defaultVal = String()) {

+        if(value)

+            *value = defaultVal;

+#ifndef DOCTEST_CONFIG_NO_UNPREFIXED_OPTIONS

+        // offset (normally 3 for "dt-") to skip prefix

+        if(parseOptionImpl(argc, argv, pattern + strlen(DOCTEST_CONFIG_OPTIONS_PREFIX), value))

+            return true;

+#endif // DOCTEST_CONFIG_NO_UNPREFIXED_OPTIONS

+        return parseOptionImpl(argc, argv, pattern, value);

+    }

+

+    // locates a flag on the command line

+    bool parseFlag(int argc, const char* const* argv, const char* pattern) {

+        return parseOption(argc, argv, pattern);

+    }

+

+    // parses a comma separated list of words after a pattern in one of the arguments in argv

+    bool parseCommaSepArgs(int argc, const char* const* argv, const char* pattern,

+                           std::vector<String>& res) {

+        String filtersString;

+        if(parseOption(argc, argv, pattern, &filtersString)) {

+            // tokenize with "," as a separator, unless escaped with backslash

+            std::ostringstream s;

+            auto flush = [&s, &res]() {

+                auto string = s.str();

+                if(string.size() > 0) {

+                    res.push_back(string.c_str());

+                }

+                s.str("");

+            };

+

+            bool seenBackslash = false;

+            const char* current = filtersString.c_str();

+            const char* end = current + strlen(current);

+            while(current != end) {

+                char character = *current++;

+                if(seenBackslash) {

+                    seenBackslash = false;

+                    if(character == ',' || character == '\\') {

+                        s.put(character);

+                        continue;

+                    }

+                    s.put('\\');

+                }

+                if(character == '\\') {

+                    seenBackslash = true;

+                } else if(character == ',') {

+                    flush();

+                } else {

+                    s.put(character);

+                }

+            }

+

+            if(seenBackslash) {

+                s.put('\\');

+            }

+            flush();

+            return true;

+        }

+        return false;

+    }

+

+    enum optionType

+    {

+        option_bool,

+        option_int

+    };

+

+    // parses an int/bool option from the command line

+    bool parseIntOption(int argc, const char* const* argv, const char* pattern, optionType type,

+                        int& res) {

+        String parsedValue;

+        if(!parseOption(argc, argv, pattern, &parsedValue))

+            return false;

+

+        if(type) {

+            // integer

+            // TODO: change this to use std::stoi or something else! currently it uses undefined behavior - assumes '0' on failed parse...

+            int theInt = std::atoi(parsedValue.c_str());

+            if (theInt != 0) {

+                res = theInt; //!OCLINT parameter reassignment

+                return true;

+            }

+        } else {

+            // boolean

+            const char positive[][5] = { "1", "true", "on", "yes" };  // 5 - strlen("true") + 1

+            const char negative[][6] = { "0", "false", "off", "no" }; // 6 - strlen("false") + 1

+

+            // if the value matches any of the positive/negative possibilities

+            for (unsigned i = 0; i < 4; i++) {

+                if (parsedValue.compare(positive[i], true) == 0) {

+                    res = 1; //!OCLINT parameter reassignment

+                    return true;

+                }

+                if (parsedValue.compare(negative[i], true) == 0) {

+                    res = 0; //!OCLINT parameter reassignment

+                    return true;

+                }

+            }

+        }

+        return false;

+    }

+} // namespace

+

+Context::Context(int argc, const char* const* argv)

+        : p(new detail::ContextState) {

+    parseArgs(argc, argv, true);

+    if(argc)

+        p->binary_name = argv[0];

+}

+

+Context::~Context() {

+    if(g_cs == p)

+        g_cs = nullptr;

+    delete p;

+}

+

+void Context::applyCommandLine(int argc, const char* const* argv) {

+    parseArgs(argc, argv);

+    if(argc)

+        p->binary_name = argv[0];

+}

+

+// parses args

+void Context::parseArgs(int argc, const char* const* argv, bool withDefaults) {

+    using namespace detail;

+

+    // clang-format off

+    parseCommaSepArgs(argc, argv, DOCTEST_CONFIG_OPTIONS_PREFIX "source-file=",        p->filters[0]);

+    parseCommaSepArgs(argc, argv, DOCTEST_CONFIG_OPTIONS_PREFIX "sf=",                 p->filters[0]);

+    parseCommaSepArgs(argc, argv, DOCTEST_CONFIG_OPTIONS_PREFIX "source-file-exclude=",p->filters[1]);

+    parseCommaSepArgs(argc, argv, DOCTEST_CONFIG_OPTIONS_PREFIX "sfe=",                p->filters[1]);

+    parseCommaSepArgs(argc, argv, DOCTEST_CONFIG_OPTIONS_PREFIX "test-suite=",         p->filters[2]);

+    parseCommaSepArgs(argc, argv, DOCTEST_CONFIG_OPTIONS_PREFIX "ts=",                 p->filters[2]);

+    parseCommaSepArgs(argc, argv, DOCTEST_CONFIG_OPTIONS_PREFIX "test-suite-exclude=", p->filters[3]);

+    parseCommaSepArgs(argc, argv, DOCTEST_CONFIG_OPTIONS_PREFIX "tse=",                p->filters[3]);

+    parseCommaSepArgs(argc, argv, DOCTEST_CONFIG_OPTIONS_PREFIX "test-case=",          p->filters[4]);

+    parseCommaSepArgs(argc, argv, DOCTEST_CONFIG_OPTIONS_PREFIX "tc=",                 p->filters[4]);

+    parseCommaSepArgs(argc, argv, DOCTEST_CONFIG_OPTIONS_PREFIX "test-case-exclude=",  p->filters[5]);

+    parseCommaSepArgs(argc, argv, DOCTEST_CONFIG_OPTIONS_PREFIX "tce=",                p->filters[5]);

+    parseCommaSepArgs(argc, argv, DOCTEST_CONFIG_OPTIONS_PREFIX "subcase=",            p->filters[6]);

+    parseCommaSepArgs(argc, argv, DOCTEST_CONFIG_OPTIONS_PREFIX "sc=",                 p->filters[6]);

+    parseCommaSepArgs(argc, argv, DOCTEST_CONFIG_OPTIONS_PREFIX "subcase-exclude=",    p->filters[7]);

+    parseCommaSepArgs(argc, argv, DOCTEST_CONFIG_OPTIONS_PREFIX "sce=",                p->filters[7]);

+    parseCommaSepArgs(argc, argv, DOCTEST_CONFIG_OPTIONS_PREFIX "reporters=",          p->filters[8]);

+    parseCommaSepArgs(argc, argv, DOCTEST_CONFIG_OPTIONS_PREFIX "r=",                  p->filters[8]);

+    // clang-format on

+

+    int    intRes = 0;

+    String strRes;

+

+#define DOCTEST_PARSE_AS_BOOL_OR_FLAG(name, sname, var, default)                                   \

+    if(parseIntOption(argc, argv, DOCTEST_CONFIG_OPTIONS_PREFIX name "=", option_bool, intRes) ||  \

+       parseIntOption(argc, argv, DOCTEST_CONFIG_OPTIONS_PREFIX sname "=", option_bool, intRes))   \

+        p->var = static_cast<bool>(intRes);                                                        \

+    else if(parseFlag(argc, argv, DOCTEST_CONFIG_OPTIONS_PREFIX name) ||                           \

+            parseFlag(argc, argv, DOCTEST_CONFIG_OPTIONS_PREFIX sname))                            \

+        p->var = true;                                                                             \

+    else if(withDefaults)                                                                          \

+    p->var = default

+

+#define DOCTEST_PARSE_INT_OPTION(name, sname, var, default)                                        \

+    if(parseIntOption(argc, argv, DOCTEST_CONFIG_OPTIONS_PREFIX name "=", option_int, intRes) ||   \

+       parseIntOption(argc, argv, DOCTEST_CONFIG_OPTIONS_PREFIX sname "=", option_int, intRes))    \

+        p->var = intRes;                                                                           \

+    else if(withDefaults)                                                                          \

+    p->var = default

+

+#define DOCTEST_PARSE_STR_OPTION(name, sname, var, default)                                        \

+    if(parseOption(argc, argv, DOCTEST_CONFIG_OPTIONS_PREFIX name "=", &strRes, default) ||        \

+       parseOption(argc, argv, DOCTEST_CONFIG_OPTIONS_PREFIX sname "=", &strRes, default) ||       \

+       withDefaults)                                                                               \

+    p->var = strRes

+

+    // clang-format off

+    DOCTEST_PARSE_STR_OPTION("out", "o", out, "");

+    DOCTEST_PARSE_STR_OPTION("order-by", "ob", order_by, "file");

+    DOCTEST_PARSE_INT_OPTION("rand-seed", "rs", rand_seed, 0);

+

+    DOCTEST_PARSE_INT_OPTION("first", "f", first, 0);

+    DOCTEST_PARSE_INT_OPTION("last", "l", last, UINT_MAX);

+

+    DOCTEST_PARSE_INT_OPTION("abort-after", "aa", abort_after, 0);

+    DOCTEST_PARSE_INT_OPTION("subcase-filter-levels", "scfl", subcase_filter_levels, INT_MAX);

+

+    DOCTEST_PARSE_AS_BOOL_OR_FLAG("success", "s", success, false);

+    DOCTEST_PARSE_AS_BOOL_OR_FLAG("case-sensitive", "cs", case_sensitive, false);

+    DOCTEST_PARSE_AS_BOOL_OR_FLAG("exit", "e", exit, false);

+    DOCTEST_PARSE_AS_BOOL_OR_FLAG("duration", "d", duration, false);

+    DOCTEST_PARSE_AS_BOOL_OR_FLAG("minimal", "m", minimal, false);

+    DOCTEST_PARSE_AS_BOOL_OR_FLAG("quiet", "q", quiet, false);

+    DOCTEST_PARSE_AS_BOOL_OR_FLAG("no-throw", "nt", no_throw, false);

+    DOCTEST_PARSE_AS_BOOL_OR_FLAG("no-exitcode", "ne", no_exitcode, false);

+    DOCTEST_PARSE_AS_BOOL_OR_FLAG("no-run", "nr", no_run, false);

+    DOCTEST_PARSE_AS_BOOL_OR_FLAG("no-intro", "ni", no_intro, false);

+    DOCTEST_PARSE_AS_BOOL_OR_FLAG("no-version", "nv", no_version, false);

+    DOCTEST_PARSE_AS_BOOL_OR_FLAG("no-colors", "nc", no_colors, false);

+    DOCTEST_PARSE_AS_BOOL_OR_FLAG("force-colors", "fc", force_colors, false);

+    DOCTEST_PARSE_AS_BOOL_OR_FLAG("no-breaks", "nb", no_breaks, false);

+    DOCTEST_PARSE_AS_BOOL_OR_FLAG("no-skip", "ns", no_skip, false);

+    DOCTEST_PARSE_AS_BOOL_OR_FLAG("gnu-file-line", "gfl", gnu_file_line, !bool(DOCTEST_MSVC));

+    DOCTEST_PARSE_AS_BOOL_OR_FLAG("no-path-filenames", "npf", no_path_in_filenames, false);

+    DOCTEST_PARSE_AS_BOOL_OR_FLAG("no-line-numbers", "nln", no_line_numbers, false);

+    DOCTEST_PARSE_AS_BOOL_OR_FLAG("no-debug-output", "ndo", no_debug_output, false);

+    DOCTEST_PARSE_AS_BOOL_OR_FLAG("no-skipped-summary", "nss", no_skipped_summary, false);

+    DOCTEST_PARSE_AS_BOOL_OR_FLAG("no-time-in-output", "ntio", no_time_in_output, false);

+    // clang-format on

+

+    if(withDefaults) {

+        p->help             = false;

+        p->version          = false;

+        p->count            = false;

+        p->list_test_cases  = false;

+        p->list_test_suites = false;

+        p->list_reporters   = false;

+    }

+    if(parseFlag(argc, argv, DOCTEST_CONFIG_OPTIONS_PREFIX "help") ||

+       parseFlag(argc, argv, DOCTEST_CONFIG_OPTIONS_PREFIX "h") ||

+       parseFlag(argc, argv, DOCTEST_CONFIG_OPTIONS_PREFIX "?")) {

+        p->help = true;

+        p->exit = true;

+    }

+    if(parseFlag(argc, argv, DOCTEST_CONFIG_OPTIONS_PREFIX "version") ||

+       parseFlag(argc, argv, DOCTEST_CONFIG_OPTIONS_PREFIX "v")) {

+        p->version = true;

+        p->exit    = true;

+    }

+    if(parseFlag(argc, argv, DOCTEST_CONFIG_OPTIONS_PREFIX "count") ||

+       parseFlag(argc, argv, DOCTEST_CONFIG_OPTIONS_PREFIX "c")) {

+        p->count = true;

+        p->exit  = true;

+    }

+    if(parseFlag(argc, argv, DOCTEST_CONFIG_OPTIONS_PREFIX "list-test-cases") ||

+       parseFlag(argc, argv, DOCTEST_CONFIG_OPTIONS_PREFIX "ltc")) {

+        p->list_test_cases = true;

+        p->exit            = true;

+    }

+    if(parseFlag(argc, argv, DOCTEST_CONFIG_OPTIONS_PREFIX "list-test-suites") ||

+       parseFlag(argc, argv, DOCTEST_CONFIG_OPTIONS_PREFIX "lts")) {

+        p->list_test_suites = true;

+        p->exit             = true;

+    }

+    if(parseFlag(argc, argv, DOCTEST_CONFIG_OPTIONS_PREFIX "list-reporters") ||

+       parseFlag(argc, argv, DOCTEST_CONFIG_OPTIONS_PREFIX "lr")) {

+        p->list_reporters = true;

+        p->exit           = true;

+    }

+}

+

+// allows the user to add procedurally to the filters from the command line

+void Context::addFilter(const char* filter, const char* value) { setOption(filter, value); }

+

+// allows the user to clear all filters from the command line

+void Context::clearFilters() {

+    for(auto& curr : p->filters)

+        curr.clear();

+}

+

+// allows the user to override procedurally the bool options from the command line

+void Context::setOption(const char* option, bool value) {

+    setOption(option, value ? "true" : "false");

+}

+

+// allows the user to override procedurally the int options from the command line

+void Context::setOption(const char* option, int value) {

+    setOption(option, toString(value).c_str());

+}

+

+// allows the user to override procedurally the string options from the command line

+void Context::setOption(const char* option, const char* value) {

+    auto argv   = String("-") + option + "=" + value;

+    auto lvalue = argv.c_str();

+    parseArgs(1, &lvalue);

+}

+

+// users should query this in their main() and exit the program if true

+bool Context::shouldExit() { return p->exit; }

+

+void Context::setAsDefaultForAssertsOutOfTestCases() { g_cs = p; }

+

+void Context::setAssertHandler(detail::assert_handler ah) { p->ah = ah; }

+

+void Context::setCout(std::ostream* out) { p->cout = out; }

+

+static class DiscardOStream : public std::ostream

+{

+private:

+    class : public std::streambuf

+    {

+    private:

+        // allowing some buffering decreases the amount of calls to overflow

+        char buf[1024];

+

+    protected:

+        std::streamsize xsputn(const char_type*, std::streamsize count) override { return count; }

+

+        int_type overflow(int_type ch) override {

+            setp(std::begin(buf), std::end(buf));

+            return traits_type::not_eof(ch);

+        }

+    } discardBuf;

+

+public:

+    DiscardOStream()

+            : std::ostream(&discardBuf) {}

+} discardOut;

+

+// the main function that does all the filtering and test running

+int Context::run() {

+    using namespace detail;

+

+    // save the old context state in case such was setup - for using asserts out of a testing context

+    auto old_cs = g_cs;

+    // this is the current contest

+    g_cs               = p;

+    is_running_in_test = true;

+

+    g_no_colors = p->no_colors;

+    p->resetRunData();

+

+    std::fstream fstr;

+    if(p->cout == nullptr) {

+        if(p->quiet) {

+            p->cout = &discardOut;

+        } else if(p->out.size()) {

+            // to a file if specified

+            fstr.open(p->out.c_str(), std::fstream::out);

+            p->cout = &fstr;

+        } else {

+            // stdout by default

+            p->cout = &std::cout;

+        }

+    }

+

+    FatalConditionHandler::allocateAltStackMem();

+

+    auto cleanup_and_return = [&]() {

+        FatalConditionHandler::freeAltStackMem();

+

+        if(fstr.is_open())

+            fstr.close();

+

+        // restore context

+        g_cs               = old_cs;

+        is_running_in_test = false;

+

+        // we have to free the reporters which were allocated when the run started

+        for(auto& curr : p->reporters_currently_used)

+            delete curr;

+        p->reporters_currently_used.clear();

+

+        if(p->numTestCasesFailed && !p->no_exitcode)

+            return EXIT_FAILURE;

+        return EXIT_SUCCESS;

+    };

+

+    // setup default reporter if none is given through the command line

+    if(p->filters[8].empty())

+        p->filters[8].push_back("console");

+

+    // check to see if any of the registered reporters has been selected

+    for(auto& curr : getReporters()) {

+        if(matchesAny(curr.first.second.c_str(), p->filters[8], false, p->case_sensitive))

+            p->reporters_currently_used.push_back(curr.second(*g_cs));

+    }

+

+    // TODO: check if there is nothing in reporters_currently_used

+

+    // prepend all listeners

+    for(auto& curr : getListeners())

+        p->reporters_currently_used.insert(p->reporters_currently_used.begin(), curr.second(*g_cs));

+

+#ifdef DOCTEST_PLATFORM_WINDOWS

+    if(isDebuggerActive() && p->no_debug_output == false)

+        p->reporters_currently_used.push_back(new DebugOutputWindowReporter(*g_cs));

+#endif // DOCTEST_PLATFORM_WINDOWS

+

+    // handle version, help and no_run

+    if(p->no_run || p->version || p->help || p->list_reporters) {

+        DOCTEST_ITERATE_THROUGH_REPORTERS(report_query, QueryData());

+

+        return cleanup_and_return();

+    }

+

+    std::vector<const TestCase*> testArray;

+    for(auto& curr : getRegisteredTests())

+        testArray.push_back(&curr);

+    p->numTestCases = testArray.size();

+

+    // sort the collected records

+    if(!testArray.empty()) {

+        if(p->order_by.compare("file", true) == 0) {

+            std::sort(testArray.begin(), testArray.end(), fileOrderComparator);

+        } else if(p->order_by.compare("suite", true) == 0) {

+            std::sort(testArray.begin(), testArray.end(), suiteOrderComparator);

+        } else if(p->order_by.compare("name", true) == 0) {

+            std::sort(testArray.begin(), testArray.end(), nameOrderComparator);

+        } else if(p->order_by.compare("rand", true) == 0) {

+            std::srand(p->rand_seed);

+

+            // random_shuffle implementation

+            const auto first = &testArray[0];

+            for(size_t i = testArray.size() - 1; i > 0; --i) {

+                int idxToSwap = std::rand() % (i + 1);

+

+                const auto temp = first[i];

+

+                first[i]         = first[idxToSwap];

+                first[idxToSwap] = temp;

+            }

+        } else if(p->order_by.compare("none", true) == 0) {

+            // means no sorting - beneficial for death tests which call into the executable

+            // with a specific test case in mind - we don't want to slow down the startup times

+        }

+    }

+

+    std::set<String> testSuitesPassingFilt;

+

+    bool                             query_mode = p->count || p->list_test_cases || p->list_test_suites;

+    std::vector<const TestCaseData*> queryResults;

+

+    if(!query_mode)

+        DOCTEST_ITERATE_THROUGH_REPORTERS(test_run_start, DOCTEST_EMPTY);

+

+    // invoke the registered functions if they match the filter criteria (or just count them)

+    for(auto& curr : testArray) {

+        const auto& tc = *curr;

+

+        bool skip_me = false;

+        if(tc.m_skip && !p->no_skip)

+            skip_me = true;

+

+        if(!matchesAny(tc.m_file.c_str(), p->filters[0], true, p->case_sensitive))

+            skip_me = true;

+        if(matchesAny(tc.m_file.c_str(), p->filters[1], false, p->case_sensitive))

+            skip_me = true;

+        if(!matchesAny(tc.m_test_suite, p->filters[2], true, p->case_sensitive))

+            skip_me = true;

+        if(matchesAny(tc.m_test_suite, p->filters[3], false, p->case_sensitive))

+            skip_me = true;

+        if(!matchesAny(tc.m_name, p->filters[4], true, p->case_sensitive))

+            skip_me = true;

+        if(matchesAny(tc.m_name, p->filters[5], false, p->case_sensitive))

+            skip_me = true;

+

+        if(!skip_me)

+            p->numTestCasesPassingFilters++;

+

+        // skip the test if it is not in the execution range

+        if((p->last < p->numTestCasesPassingFilters && p->first <= p->last) ||

+           (p->first > p->numTestCasesPassingFilters))

+            skip_me = true;

+

+        if(skip_me) {

+            if(!query_mode)

+                DOCTEST_ITERATE_THROUGH_REPORTERS(test_case_skipped, tc);

+            continue;

+        }

+

+        // do not execute the test if we are to only count the number of filter passing tests

+        if(p->count)

+            continue;

+

+        // print the name of the test and don't execute it

+        if(p->list_test_cases) {

+            queryResults.push_back(&tc);

+            continue;

+        }

+

+        // print the name of the test suite if not done already and don't execute it

+        if(p->list_test_suites) {

+            if((testSuitesPassingFilt.count(tc.m_test_suite) == 0) && tc.m_test_suite[0] != '\0') {

+                queryResults.push_back(&tc);

+                testSuitesPassingFilt.insert(tc.m_test_suite);

+                p->numTestSuitesPassingFilters++;

+            }

+            continue;

+        }

+

+        // execute the test if it passes all the filtering

+        {

+            p->currentTest = &tc;

+

+            p->failure_flags = TestCaseFailureReason::None;

+            p->seconds       = 0;

+

+            // reset atomic counters

+            p->numAssertsFailedCurrentTest_atomic = 0;

+            p->numAssertsCurrentTest_atomic       = 0;

+

+            p->fullyTraversedSubcases.clear();

+

+            DOCTEST_ITERATE_THROUGH_REPORTERS(test_case_start, tc);

+

+            p->timer.start();

+            

+            bool run_test = true;

+

+            do {

+                // reset some of the fields for subcases (except for the set of fully passed ones)

+                p->reachedLeaf = false;

+                // May not be empty if previous subcase exited via exception.

+                p->subcaseStack.clear();

+                p->currentSubcaseDepth = 0;

+

+                p->shouldLogCurrentException = true;

+

+                // reset stuff for logging with INFO()

+                p->stringifiedContexts.clear();

+

+#ifndef DOCTEST_CONFIG_NO_EXCEPTIONS

+                try {

+#endif // DOCTEST_CONFIG_NO_EXCEPTIONS

+// MSVC 2015 diagnoses fatalConditionHandler as unused (because reset() is a static method)

+DOCTEST_MSVC_SUPPRESS_WARNING_WITH_PUSH(4101) // unreferenced local variable

+                    FatalConditionHandler fatalConditionHandler; // Handle signals

+                    // execute the test

+                    tc.m_test();

+                    fatalConditionHandler.reset();

+DOCTEST_MSVC_SUPPRESS_WARNING_POP

+#ifndef DOCTEST_CONFIG_NO_EXCEPTIONS

+                } catch(const TestFailureException&) {

+                    p->failure_flags |= TestCaseFailureReason::AssertFailure;

+                } catch(...) {

+                    DOCTEST_ITERATE_THROUGH_REPORTERS(test_case_exception,

+                                                      {translateActiveException(), false});

+                    p->failure_flags |= TestCaseFailureReason::Exception;

+                }

+#endif // DOCTEST_CONFIG_NO_EXCEPTIONS

+

+                // exit this loop if enough assertions have failed - even if there are more subcases

+                if(p->abort_after > 0 &&

+                   p->numAssertsFailed + p->numAssertsFailedCurrentTest_atomic >= p->abort_after) {

+                    run_test = false;

+                    p->failure_flags |= TestCaseFailureReason::TooManyFailedAsserts;

+                }

+                

+                if(!p->nextSubcaseStack.empty() && run_test)

+                    DOCTEST_ITERATE_THROUGH_REPORTERS(test_case_reenter, tc);

+                if(p->nextSubcaseStack.empty())

+                    run_test = false;

+            } while(run_test);

+

+            p->finalizeTestCaseData();

+

+            DOCTEST_ITERATE_THROUGH_REPORTERS(test_case_end, *g_cs);

+

+            p->currentTest = nullptr;

+

+            // stop executing tests if enough assertions have failed

+            if(p->abort_after > 0 && p->numAssertsFailed >= p->abort_after)

+                break;

+        }

+    }

+

+    if(!query_mode) {

+        DOCTEST_ITERATE_THROUGH_REPORTERS(test_run_end, *g_cs);

+    } else {

+        QueryData qdata;

+        qdata.run_stats = g_cs;

+        qdata.data      = queryResults.data();

+        qdata.num_data  = unsigned(queryResults.size());

+        DOCTEST_ITERATE_THROUGH_REPORTERS(report_query, qdata);

+    }

+

+    return cleanup_and_return();

+}

+

+DOCTEST_DEFINE_INTERFACE(IReporter)

+

+int IReporter::get_num_active_contexts() { return detail::g_infoContexts.size(); }

+const IContextScope* const* IReporter::get_active_contexts() {

+    return get_num_active_contexts() ? &detail::g_infoContexts[0] : nullptr;

+}

+

+int IReporter::get_num_stringified_contexts() { return detail::g_cs->stringifiedContexts.size(); }

+const String* IReporter::get_stringified_contexts() {

+    return get_num_stringified_contexts() ? &detail::g_cs->stringifiedContexts[0] : nullptr;

+}

+

+namespace detail {

+    void registerReporterImpl(const char* name, int priority, reporterCreatorFunc c, bool isReporter) {

+        if(isReporter)

+            getReporters().insert(reporterMap::value_type(reporterMap::key_type(priority, name), c));

+        else

+            getListeners().insert(reporterMap::value_type(reporterMap::key_type(priority, name), c));

+    }

+} // namespace detail

+

+} // namespace doctest

+

+#endif // DOCTEST_CONFIG_DISABLE

+

+#ifdef DOCTEST_CONFIG_IMPLEMENT_WITH_MAIN

+DOCTEST_MSVC_SUPPRESS_WARNING_WITH_PUSH(4007) // 'function' : must be 'attribute' - see issue #182

+int main(int argc, char** argv) { return doctest::Context(argc, argv).run(); }

+DOCTEST_MSVC_SUPPRESS_WARNING_POP

+#endif // DOCTEST_CONFIG_IMPLEMENT_WITH_MAIN

+

+DOCTEST_CLANG_SUPPRESS_WARNING_POP

+DOCTEST_MSVC_SUPPRESS_WARNING_POP

+DOCTEST_GCC_SUPPRESS_WARNING_POP

+

+DOCTEST_SUPPRESS_COMMON_WARNINGS_POP

+

+#endif // DOCTEST_LIBRARY_IMPLEMENTATION

+#endif // DOCTEST_CONFIG_IMPLEMENT

diff --git a/tests/doctest_main.cc b/tests/doctest_main.cc
new file mode 100644
index 0000000..5e90be1
--- /dev/null
+++ b/tests/doctest_main.cc
@@ -0,0 +1,12 @@
+#ifdef _MSC_VER
+  #pragma warning(disable:5246) // initialization of subobject needs braces
+  #pragma warning(disable:5262) // implicit switch fall-through
+  #pragma warning(disable:5264) // unused const variable
+#endif
+
+#if defined(__clang__) && !defined(__APPLE__)
+  #pragma GCC diagnostic ignored "-Wunsafe-buffer-usage"
+#endif
+
+#define DOCTEST_CONFIG_IMPLEMENT_WITH_MAIN
+#include "doctest.h"
diff --git a/tests/include_multiple.c b/tests/include_multiple.c
new file mode 100644
index 0000000..563b861
--- /dev/null
+++ b/tests/include_multiple.c
@@ -0,0 +1,39 @@
+// Confirm that nanoprintf can be included and then implemented.
+
+#ifdef _MSC_VER
+  #pragma warning(disable:4464) // relative include uses ..
+  #pragma warning(disable:4710) // inline
+  #pragma warning(disable:4711) // inline
+#endif
+
+// Include the interface multiple times to make sure it guards against itself.
+#include "../nanoprintf.h"
+#include "../nanoprintf.h"
+#include "../nanoprintf.h"
+#include "../nanoprintf.h"
+#include "../nanoprintf.h"
+
+#define NANOPRINTF_USE_FIELD_WIDTH_FORMAT_SPECIFIERS 1
+#define NANOPRINTF_USE_PRECISION_FORMAT_SPECIFIERS 1
+#define NANOPRINTF_USE_FLOAT_FORMAT_SPECIFIERS 1
+#define NANOPRINTF_USE_LARGE_FORMAT_SPECIFIERS 1
+#define NANOPRINTF_USE_BINARY_FORMAT_SPECIFIERS 1
+#define NANOPRINTF_USE_WRITEBACK_FORMAT_SPECIFIERS 1
+#define NANOPRINTF_IMPLEMENTATION
+
+// Include the implementation multiple times to make sure it guards against itself.
+#include "../nanoprintf.h"
+#include "../nanoprintf.h"
+#include "../nanoprintf.h"
+#include "../nanoprintf.h"
+#include "../nanoprintf.h"
+
+#include <stdio.h>
+
+int main(int c, char const *v[]) {
+  char buf[64];
+  (void)c;
+  (void)v;
+  npf_snprintf(buf, sizeof(buf), "%s", "hello");
+  printf("%s\n", buf);
+}
diff --git a/tests/mpaland-conformance b/tests/mpaland-conformance
new file mode 160000
index 0000000..38f318f
--- /dev/null
+++ b/tests/mpaland-conformance
@@ -0,0 +1 @@
+Subproject commit 38f318ff21fc44a97f81506b2419a03ef90b1413
diff --git a/tests/size_report.py b/tests/size_report.py
new file mode 100644
index 0000000..82e0483
--- /dev/null
+++ b/tests/size_report.py
@@ -0,0 +1,137 @@
+"""Compile and analyze nanoprintf for different architectures."""
+import argparse
+import pathlib
+import subprocess
+import sys
+import tempfile
+
+
+def parse_args():
+    """Parse and validate command-line arguments."""
+    parser = argparse.ArgumentParser()
+    parser.add_argument('-p',
+                        '--platform',
+                        required=True,
+                        choices=('cm0', 'cm4', 'host'),
+                        help='target platform')
+    return parser.parse_args()
+
+
+def git_root():
+    """Return the root of the current file git repository."""
+    cur = pathlib.Path(__file__).resolve()
+    while cur != cur.parent:
+        if (cur / '.git').is_dir():
+            return cur
+        cur = cur.parent
+
+    raise ValueError(f'{__file__} not in git repo')
+
+
+def build(platform, flags):
+    """Build a nanoprintf implementation object for platform + flags."""
+    cc_exe = 'cc' if platform == 'host' else 'arm-none-eabi-gcc'
+    cc_cmd = [cc_exe, '-c', '-x', 'c', '-Os', f'-I{git_root()}', '-o', 'npf.o']
+    cc_cmd += {'cm0': ['-mcpu=cortex-m0'],
+               'cm4': ['-mcpu=cortex-m4', '-mfloat-abi=hard'],
+               'host': []}[platform]
+    cc_cmd += ['-DNANOPRINTF_IMPLEMENTATION'] + flags + ['-']
+    nm_exe = 'nm' if platform == 'host' else 'arm-none-eabi-nm'
+    nm_cmd = [nm_exe, '--print-size', '--size-sort', 'npf.o']
+
+    print(' '.join(cc_cmd))
+    print(' '.join(nm_cmd), flush=True)
+
+    with tempfile.TemporaryDirectory() as temp_dir:
+        subprocess.run(
+            cc_cmd,
+            check=True,
+            cwd=temp_dir,
+            input=rb'#include "nanoprintf.h"')
+        return subprocess.run(
+            nm_cmd,
+            check=True,
+            cwd=temp_dir,
+            stdout=subprocess.PIPE).stdout.decode()
+
+
+def measure(build_output):
+    """Parse the results of nm to accumulate and print a total size."""
+    total = 0
+    for line in build_output.split('\n'):
+        parts = [l for l in line.split() if l.strip()]
+        if not parts:
+            continue
+        print(line)
+        if parts[0] in ('u', 'U'):
+            continue
+        if len(parts) >= 3 and parts[2] not in ('t', 'T'):
+            continue
+        total += int(line.split()[1], 16)
+    print(f'Total size: 0x{total:x} ({total}) bytes')
+
+
+_CONFIGS = [
+    {'name': 'Minimal', 'flags': [
+        '-DNANOPRINTF_USE_FIELD_WIDTH_FORMAT_SPECIFIERS=0',
+        '-DNANOPRINTF_USE_PRECISION_FORMAT_SPECIFIERS=0',
+        '-DNANOPRINTF_USE_FLOAT_FORMAT_SPECIFIERS=0',
+        '-DNANOPRINTF_USE_LARGE_FORMAT_SPECIFIERS=0',
+        '-DNANOPRINTF_USE_BINARY_FORMAT_SPECIFIERS=0',
+        '-DNANOPRINTF_USE_WRITEBACK_FORMAT_SPECIFIERS=0',
+    ]},
+    {'name': 'Binary', 'flags': [
+        '-DNANOPRINTF_USE_FIELD_WIDTH_FORMAT_SPECIFIERS=0',
+        '-DNANOPRINTF_USE_PRECISION_FORMAT_SPECIFIERS=0',
+        '-DNANOPRINTF_USE_FLOAT_FORMAT_SPECIFIERS=0',
+        '-DNANOPRINTF_USE_LARGE_FORMAT_SPECIFIERS=0',
+        '-DNANOPRINTF_USE_BINARY_FORMAT_SPECIFIERS=1',
+        '-DNANOPRINTF_USE_WRITEBACK_FORMAT_SPECIFIERS=0',
+    ]},
+    {'name': 'Field Width + Precision', 'flags': [
+        '-DNANOPRINTF_USE_FIELD_WIDTH_FORMAT_SPECIFIERS=1',
+        '-DNANOPRINTF_USE_PRECISION_FORMAT_SPECIFIERS=1',
+        '-DNANOPRINTF_USE_FLOAT_FORMAT_SPECIFIERS=0',
+        '-DNANOPRINTF_USE_LARGE_FORMAT_SPECIFIERS=0',
+        '-DNANOPRINTF_USE_BINARY_FORMAT_SPECIFIERS=0',
+        '-DNANOPRINTF_USE_WRITEBACK_FORMAT_SPECIFIERS=0',
+    ]},
+    {'name': 'Field Width + Precision + Binary', 'flags': [
+        '-DNANOPRINTF_USE_FIELD_WIDTH_FORMAT_SPECIFIERS=1',
+        '-DNANOPRINTF_USE_PRECISION_FORMAT_SPECIFIERS=1',
+        '-DNANOPRINTF_USE_FLOAT_FORMAT_SPECIFIERS=0',
+        '-DNANOPRINTF_USE_LARGE_FORMAT_SPECIFIERS=0',
+        '-DNANOPRINTF_USE_BINARY_FORMAT_SPECIFIERS=1',
+        '-DNANOPRINTF_USE_WRITEBACK_FORMAT_SPECIFIERS=0',
+    ]},
+    {'name': 'Float', 'flags': [
+        '-DNANOPRINTF_USE_FIELD_WIDTH_FORMAT_SPECIFIERS=0',
+        '-DNANOPRINTF_USE_PRECISION_FORMAT_SPECIFIERS=1',
+        '-DNANOPRINTF_USE_FLOAT_FORMAT_SPECIFIERS=1',
+        '-DNANOPRINTF_USE_LARGE_FORMAT_SPECIFIERS=0',
+        '-DNANOPRINTF_USE_BINARY_FORMAT_SPECIFIERS=0',
+        '-DNANOPRINTF_USE_WRITEBACK_FORMAT_SPECIFIERS=0',
+    ]},
+    {'name': 'Everything', 'flags': [
+        '-DNANOPRINTF_USE_FIELD_WIDTH_FORMAT_SPECIFIERS=1',
+        '-DNANOPRINTF_USE_PRECISION_FORMAT_SPECIFIERS=1',
+        '-DNANOPRINTF_USE_FLOAT_FORMAT_SPECIFIERS=1',
+        '-DNANOPRINTF_USE_LARGE_FORMAT_SPECIFIERS=1',
+        '-DNANOPRINTF_USE_BINARY_FORMAT_SPECIFIERS=1',
+        '-DNANOPRINTF_USE_WRITEBACK_FORMAT_SPECIFIERS=1',
+    ]},
+]
+
+
+def main():
+    """Entry point"""
+    args = parse_args()
+    for cfg in _CONFIGS:
+        print(f'Configuration "{cfg["name"]}":')
+        measure(build(args.platform, cfg['flags']))
+        print()
+    return 0
+
+
+if __name__ == '__main__':
+    sys.exit(main())
diff --git a/tests/static_main.c b/tests/static_main.c
new file mode 100644
index 0000000..25eda03
--- /dev/null
+++ b/tests/static_main.c
@@ -0,0 +1,39 @@
+#include "../nanoprintf.h"
+
+int npf_snprintf(char *buffer, size_t bufsz, const char *format, ...) {
+    (void)buffer;
+    (void)bufsz;
+    (void)format;
+    return 0;
+}
+
+int npf_vsnprintf(char *buffer, size_t bufsz, char const *format,
+                  va_list vlist) {
+    (void)buffer;
+    (void)bufsz;
+    (void)format;
+    (void)vlist;
+    return 0;
+}
+
+int npf_pprintf(npf_putc pc, void *pc_ctx, char const *format, ...) {
+    (void)pc;
+    (void)pc_ctx;
+    (void)format;
+    return 0;
+}
+
+int npf_vpprintf(npf_putc pc, void *pc_ctx, char const *format, va_list vlist) {
+    (void)pc;
+    (void)pc_ctx;
+    (void)format;
+    (void)vlist;
+    return 0;
+}
+
+extern void do_private_nanoprintf_stuff();
+
+int main(void) {
+    do_private_nanoprintf_stuff();
+    return 0;
+}
diff --git a/tests/static_nanoprintf.c b/tests/static_nanoprintf.c
new file mode 100644
index 0000000..96ddaae
--- /dev/null
+++ b/tests/static_nanoprintf.c
@@ -0,0 +1,7 @@
+#define NANOPRINTF_VISIBILITY_STATIC
+#define NANOPRINTF_IMPLEMENTATION
+#include "../nanoprintf.h"
+
+int do_private_nanoprintf_stuff() {
+    return npf_snprintf(NULL, 0, "%s", "hello world");
+}
diff --git a/tests/unit_binary.cc b/tests/unit_binary.cc
new file mode 100644
index 0000000..dd458c5
--- /dev/null
+++ b/tests/unit_binary.cc
@@ -0,0 +1,149 @@
+#include "unit_nanoprintf.h"
+
+#include <cmath>
+#include <limits>
+#include <string>
+
+#if NANOPRINTF_HAVE_GCC_WARNING_PRAGMAS
+  #pragma GCC diagnostic push
+  #if NANOPRINTF_CLANG
+    #pragma GCC diagnostic ignored "-Wformat-pedantic"
+    #pragma GCC diagnostic ignored "-Wformat-nonliteral"
+  #endif
+#endif
+
+TEST_CASE("npf_bin_len") {
+  CHECK(npf_bin_len(0) == 1);
+  CHECK(npf_bin_len(1) == 1);
+  CHECK(npf_bin_len(0b10) == 2);
+  CHECK(npf_bin_len(0b100) == 3);
+  CHECK(npf_bin_len(0b1000) == 4);
+  CHECK(npf_bin_len(0b10000) == 5);
+  CHECK(npf_bin_len(0b100000) == 6);
+  CHECK(npf_bin_len(0b1000000) == 7);
+  CHECK(npf_bin_len(0b10000000) == 8);
+  CHECK(npf_bin_len(0b100000001) == 9);
+  CHECK(npf_bin_len(0b1000000001) == 10);
+  CHECK(npf_bin_len(0b10000000001) == 11);
+  CHECK(npf_bin_len(0b100000000001) == 12);
+  CHECK(npf_bin_len(0b1000000000000) == 13);
+  CHECK(npf_bin_len(0b10000000000000) == 14);
+  CHECK(npf_bin_len(0b100000000000000) == 15);
+  CHECK(npf_bin_len(0b1000000000000000) == 16);
+  CHECK(npf_bin_len(0x80000000UL) == 32);
+
+#if NANOPRINTF_USE_LARGE_FORMAT_SPECIFIERS == 1
+  CHECK(npf_bin_len(0x8000000000ULL) == 40);
+  CHECK(npf_bin_len(0x800000000000ULL) == 48);
+  CHECK(npf_bin_len(0x80000000000000ULL) == 56);
+  CHECK(npf_bin_len(0x8000000000000000ULL) == 64);
+#endif
+}
+
+namespace {
+void require_equal(char const *expected, char const *fmt, ...) {
+  char buf[256];
+
+  std::string npf_result; {
+    va_list args;
+    va_start(args, fmt);
+    npf_vsnprintf(buf, sizeof(buf), fmt, args);
+    va_end(args);
+    buf[sizeof(buf)-1] = '\0';
+    npf_result = buf;
+  }
+
+  REQUIRE(npf_result == std::string{expected});
+}
+}
+
+TEST_CASE("binary") {
+  SUBCASE("plain") {
+    require_equal(   "0", "%b", 0);
+    require_equal(   "1", "%b", 1);
+    require_equal(  "10", "%b", 0b10);
+    require_equal(  "11", "%b", 0b11);
+    require_equal( "100", "%b", 0b100);
+    require_equal( "101", "%b", 0b101);
+    require_equal( "110", "%b", 0b110);
+    require_equal( "110", "%b", 0b110);
+    require_equal( "111", "%b", 0b111);
+    require_equal("1000", "%b", 0b1000);
+    require_equal("10000", "%b", 0b10000);
+    require_equal("100000", "%b", 0b100000);
+    require_equal("1000000", "%b", 0b1000000);
+    require_equal("10000000", "%b", 0b10000000);
+    require_equal(   "10010001101000101011001111000", "%b", 0x12345678);
+    require_equal( "1010101010101010101010101010101", "%b", 0x55555555);
+    require_equal("10101010101010101010101010101010", "%b", 0xAAAAAAAA);
+    require_equal("11111111111111111111111111111111", "%b", 0xFFFFFFFF);
+  }
+
+  SUBCASE("length") {
+    require_equal("11111111", "%hhb", 0xFFFFFFFF); // char
+    require_equal("1111111111111111", "%hb", 0xFFFFFFFF); // short
+  }
+
+#if NANOPRINTF_USE_PRECISION_FORMAT_SPECIFIERS == 1
+  SUBCASE("precision") {
+    require_equal(                "", "%.0b", 0);
+    require_equal(               "0", "%.1b", 0);
+    require_equal(              "00", "%.2b", 0);
+    require_equal(             "000", "%.3b", 0);
+    require_equal(            "0000", "%.4b", 0);
+    require_equal(           "00000", "%.5b", 0);
+    require_equal(          "000000", "%.6b", 0);
+    require_equal(         "0000000", "%.7b", 0);
+    require_equal(        "00000000", "%.8b", 0);
+    require_equal(       "000000000", "%.9b", 0);
+    require_equal(      "0000000000", "%.10b", 0);
+    require_equal(     "00000000000", "%.11b", 0);
+    require_equal(    "000000000000", "%.12b", 0);
+    require_equal(   "0000000000000", "%.13b", 0);
+    require_equal(  "00000000000000", "%.14b", 0);
+    require_equal( "000000000000000", "%.15b", 0);
+    require_equal("0000000000000000", "%.16b", 0);
+    require_equal("00001111", "%.8b", 0b1111);
+
+    require_equal("00000000000000000000000000000000", "%.32b", 0);
+  }
+#endif
+
+#if NANOPRINTF_USE_FIELD_WIDTH_FORMAT_SPECIFIERS == 1
+  SUBCASE("field width") {
+    require_equal(   "0", "%1b", 0);
+    require_equal("   0", "%4b", 0);
+    require_equal("  11", "%4b", 0b11);
+  }
+#endif
+
+#if NANOPRINTF_USE_LARGE_FORMAT_SPECIFIERS == 1
+  SUBCASE("large") {
+    require_equal("100000000000000000000000000000000", "%llb", 0x100000000ULL);
+    require_equal("100000000000000000000000000000001", "%llb", 0x100000001ULL);
+    require_equal("111111111111111111111111111111111111", "%llb", 0xFFFFFFFFFULL);
+
+    require_equal("1000000000000000000000000000000000000000000000000000000000000000",
+                  "%llb",
+                  std::numeric_limits<long long int>::min());
+
+    require_equal( "111111111111111111111111111111111111111111111111111111111111111",
+                  "%llb",
+                  std::numeric_limits<long long int>::max());
+  }
+#endif
+
+  SUBCASE("alternate form") {
+    require_equal("0", "%#b", 0);
+    require_equal("0b1", "%#b", 1);
+    require_equal("0B1", "%#B", 1);
+    require_equal("0b110101", "%#b", 0b110101);
+#if NANOPRINTF_USE_FIELD_WIDTH_FORMAT_SPECIFIERS == 1
+    require_equal("       0", "%#8b", 0);
+    require_equal("    0b11", "%#8b", 0b11);
+#if NANOPRINTF_USE_PRECISION_FORMAT_SPECIFIERS == 1
+    require_equal("    0b0001", "%#10.4b", 1);
+#endif
+#endif
+  }
+}
diff --git a/tests/unit_bufputc.cc b/tests/unit_bufputc.cc
new file mode 100644
index 0000000..903dc40
--- /dev/null
+++ b/tests/unit_bufputc.cc
@@ -0,0 +1,48 @@
+#include "unit_nanoprintf.h"
+
+#include <string>
+
+TEST_CASE("npf_bufputc") {
+  npf_bufputc_ctx_t bpc;
+  char buf[32];
+  bpc.cur = 0;
+  bpc.len = sizeof(buf);
+  bpc.dst = buf;
+
+  SUBCASE("Writes start at beginning of buffer") {
+    npf_bufputc('A', &bpc);
+    REQUIRE(bpc.dst[0] == 'A');
+  }
+
+  SUBCASE("Increments cur after write") {
+    npf_bufputc('A', &bpc);
+    REQUIRE(bpc.cur == 1);
+  }
+
+  SUBCASE("Writes to final byte of buffer") {
+    buf[sizeof(buf) - 1] = '*';
+    bpc.cur = bpc.len - 1;
+    npf_bufputc('A', &bpc);
+    REQUIRE(buf[sizeof(buf) - 1] == 'A');
+  }
+
+  SUBCASE("Doesn't write past final byte of buffer") {
+    buf[3] = '*';
+    bpc.len = 3;
+    bpc.cur = 3;
+    npf_bufputc('A', &bpc);
+    REQUIRE(buf[3] == '*');
+  }
+
+  SUBCASE("Multiple calls write sequential bytes") {
+    npf_bufputc('A', &bpc);
+    npf_bufputc('B', &bpc);
+    npf_bufputc('C', &bpc);
+    npf_bufputc('D', &bpc);
+    npf_bufputc('E', &bpc);
+    npf_bufputc('F', &bpc);
+    REQUIRE(bpc.cur == 6);
+    bpc.dst[6] = '\0';
+    REQUIRE(std::string(bpc.dst) == "ABCDEF");
+  }
+}
diff --git a/tests/unit_fsplit_abs.cc b/tests/unit_fsplit_abs.cc
new file mode 100644
index 0000000..cde8fbf
--- /dev/null
+++ b/tests/unit_fsplit_abs.cc
@@ -0,0 +1,73 @@
+#include "unit_nanoprintf.h"
+
+#include <cmath>
+#include <string>
+
+#if NANOPRINTF_HAVE_GCC_WARNING_PRAGMAS
+  #pragma GCC diagnostic push
+  #if NANOPRINTF_CLANG
+    #pragma GCC diagnostic ignored "-Wformat-pedantic"
+    #pragma GCC diagnostic ignored "-Wmissing-prototypes"
+  #endif
+#endif
+
+void require_fsplit_abs(float f,
+                        uint64_t expected_int_part,
+                        uint64_t expected_frac_part,
+                        int expected_frac_base10_neg_e) {
+  uint64_t int_part, frac_part;
+  int frac_neg_exp;
+  REQUIRE(npf_fsplit_abs(f, &int_part, &frac_part, &frac_neg_exp));
+  REQUIRE(int_part == expected_int_part);
+  REQUIRE(frac_part == expected_frac_part);
+  REQUIRE(frac_neg_exp == expected_frac_base10_neg_e);
+}
+
+TEST_CASE("npf_fsplit_abs") {
+  require_fsplit_abs(0.f, 0, 0, 0);
+  require_fsplit_abs(1.f, 1, 0, 0);
+  require_fsplit_abs(-1.f, 1, 0, 0);
+  require_fsplit_abs(123456.f, 123456, 0, 0);
+  require_fsplit_abs(-123456.f, 123456, 0, 0);
+  require_fsplit_abs(powf(2.0f, 63.f), 9223372036854775808ULL, 0, 0);
+
+  SUBCASE("exponent too large") {
+    uint64_t i, f;
+    int f_neg_exp;
+    REQUIRE(!npf_fsplit_abs(powf(2.0f, 64.f), &i, &f, &f_neg_exp));
+  }
+
+  // fractional base-10 negative exponent
+  require_fsplit_abs(0.03125f, 0, 3125, 1);
+  require_fsplit_abs(0.0078125f, 0, 78125, 2);
+  require_fsplit_abs(2.4414062E-4f, 0, 244140625, 3);
+  require_fsplit_abs(3.8146973E-6f, 0, 381469726, 5);
+
+  // perfectly-representable fractions, adding 1 bit to mantissa each time.
+  require_fsplit_abs(1.5f, 1, 5, 0);
+  require_fsplit_abs(1.625f, 1, 625, 0);
+  require_fsplit_abs(1.875f, 1, 875, 0);
+  require_fsplit_abs(1.9375f, 1, 9375, 0);
+  require_fsplit_abs(1.96875f, 1, 96875, 0);
+  require_fsplit_abs(1.984375f, 1, 984375, 0);
+  require_fsplit_abs(1.9921875f, 1, 9921875, 0);
+
+  require_fsplit_abs(1.9960938f, 1, 99609375, 0); // first truncation divergence.
+
+  // truncations, but continue adding mantissa bits
+  require_fsplit_abs(1.9980469f, 1, 998046875, 0); // 1.998046875 is stored.
+  require_fsplit_abs(1.9990234f, 1, 999023437, 0); // 1.9990234375 is stored.
+  require_fsplit_abs(1.9995117f, 1, 999511718, 0); // 1.99951171875 is stored.
+  require_fsplit_abs(1.9997559f, 1, 999755859, 0); // 1.999755859375 is stored.
+  require_fsplit_abs(1.9998779f, 1, 999877929, 0); // 1.9998779296875 is stored.
+  require_fsplit_abs(1.999939f,  1, 999938964, 0); // 1.99993896484375 is stored.
+  require_fsplit_abs(1.9999695f, 1, 999969482, 0); // 1.999969482421875 is stored.
+  require_fsplit_abs(1.9999847f, 1, 999984741, 0); // 1.9999847412109375 is stored.
+  require_fsplit_abs(1.9999924f, 1, 999992370, 0); // 1.99999237060546875 is stored.
+  require_fsplit_abs(1.9999962f, 1, 999996185, 0); // 1.999996185302734375 is stored.
+  require_fsplit_abs(1.9999981f, 1, 999998092, 0); // 1.9999980926513671875 is stored.
+  require_fsplit_abs(1.999999f,  1, 999999046, 0); // 1.99999904632568359375 is stored.
+  require_fsplit_abs(1.9999995f, 1, 999999523, 0); // 1.999999523162841796875 is stored.
+  require_fsplit_abs(1.9999998f, 1, 999999761, 0); // 1.9999997615814208984375 is stored.
+  require_fsplit_abs(1.9999999f, 1, 999999880, 0); // 1.99999988079071044921875 is stored.
+}
diff --git a/tests/unit_ftoa_rev.cc b/tests/unit_ftoa_rev.cc
new file mode 100644
index 0000000..f5cc575
--- /dev/null
+++ b/tests/unit_ftoa_rev.cc
@@ -0,0 +1,25 @@
+#include "unit_nanoprintf.h"
+
+#include <cstring>
+#include <string>
+
+#if NANOPRINTF_HAVE_GCC_WARNING_PRAGMAS
+  #pragma GCC diagnostic push
+  #if NANOPRINTF_CLANG
+    #pragma GCC diagnostic ignored "-Wformat-pedantic"
+  #endif
+#endif
+
+TEST_CASE("ftoa_rev") {
+  char buf[64];
+  npf_format_spec_t spec;
+  int frac_bytes;
+  memset(buf, 0, sizeof(buf));
+  memset(&spec, 0, sizeof(spec));
+  spec.prec = 1;
+
+  SUBCASE("zero") {
+    REQUIRE(npf_ftoa_rev(buf, 0.f, &spec, &frac_bytes) == 2);
+    REQUIRE(std::string{".0"} == buf);
+  }
+}
diff --git a/tests/unit_itoa_rev.cc b/tests/unit_itoa_rev.cc
new file mode 100644
index 0000000..76e0974
--- /dev/null
+++ b/tests/unit_itoa_rev.cc
@@ -0,0 +1,69 @@
+#include "unit_nanoprintf.h"
+
+#include <climits>
+#include <cstring>
+#include <string>
+
+#if NANOPRINTF_HAVE_GCC_WARNING_PRAGMAS
+  #pragma GCC diagnostic push
+  #if NANOPRINTF_CLANG
+    #pragma GCC diagnostic ignored "-Wformat-pedantic"
+    #pragma GCC diagnostic ignored "-Wmissing-prototypes"
+    #pragma GCC diagnostic ignored "-Wold-style-cast"
+  #endif
+#endif
+
+void require_npf_itoa(char const *expected, npf_int_t val) {
+  char buf[64];
+  int const n = npf_itoa_rev(buf, val);
+  buf[n] = '\0';
+  REQUIRE(n == (int)strlen(expected));
+  REQUIRE(std::string{buf} == std::string{expected});
+}
+
+TEST_CASE("npf_itoa_rev") {
+  require_npf_itoa("0", 0);
+  require_npf_itoa("1", 1);
+  require_npf_itoa("9", 9);
+  require_npf_itoa("01", 10);
+  require_npf_itoa("24", 42);
+  require_npf_itoa("99", 99);
+  require_npf_itoa("001", 100);
+  require_npf_itoa("321", 123);
+  require_npf_itoa("999", 999);
+  require_npf_itoa("0123456", 6543210);
+
+  SUBCASE("max values") {
+#if NANOPRINTF_USE_LARGE_FORMAT_SPECIFIERS == 1
+#if INTMAX_MAX == 9223372036854775807ll
+    require_npf_itoa("7085774586302733229", INTMAX_MAX);
+#else
+#error Unknown INTMAX_MAX here, please add another branch.
+#endif
+#else
+#if INT_MAX == 2147483647
+    require_npf_itoa("7463847412", INT_MAX);
+#else
+#error Unknown INT_MAX here, please add another branch.
+#endif
+#endif
+  }
+
+  SUBCASE("min values") {
+#if NANOPRINTF_USE_LARGE_FORMAT_SPECIFIERS == 1
+    require_npf_itoa("8085774586302733229", INTMAX_MIN);
+#else
+#if INT_MIN == -2147483648
+    require_npf_itoa("8463847412", INT_MIN);
+#else
+#error Unknown INT_MIN here, please add another branch.
+#endif
+#endif
+  }
+
+  SUBCASE("negative values have minus sign stripped") {
+    require_npf_itoa("1", -1);
+    require_npf_itoa("5987987", -7897895);
+    require_npf_itoa("12345", -54321);
+  }
+}
diff --git a/tests/unit_nanoprintf.h b/tests/unit_nanoprintf.h
new file mode 100644
index 0000000..b2e5d5d
--- /dev/null
+++ b/tests/unit_nanoprintf.h
@@ -0,0 +1,38 @@
+#pragma once
+
+// Each unit test file compiles nanoprintf privately for access to helper functions.
+#define NANOPRINTF_USE_FIELD_WIDTH_FORMAT_SPECIFIERS 1
+#define NANOPRINTF_USE_PRECISION_FORMAT_SPECIFIERS 1
+#define NANOPRINTF_USE_FLOAT_FORMAT_SPECIFIERS 1
+#define NANOPRINTF_USE_BINARY_FORMAT_SPECIFIERS 1
+#define NANOPRINTF_USE_WRITEBACK_FORMAT_SPECIFIERS 1
+#define NANOPRINTF_VISIBILITY_STATIC
+#define NANOPRINTF_IMPLEMENTATION
+
+#ifdef _MSC_VER
+  #pragma warning(disable:4464) // relative include uses ..
+#endif
+
+#include "../nanoprintf.h"
+
+#if NANOPRINTF_HAVE_GCC_WARNING_PRAGMAS
+  #pragma GCC diagnostic push
+  #if NANOPRINTF_CLANG
+    #pragma GCC diagnostic ignored "-Wc++98-compat-pedantic"
+    #ifndef __APPLE__
+      #pragma GCC diagnostic ignored "-Wreserved-identifier"
+      #pragma GCC diagnostic ignored "-Wunsafe-buffer-usage"
+    #endif
+  #endif
+#endif
+
+#ifdef _MSC_VER
+  #pragma warning(disable:4710) // function wasn't inlined
+  #pragma warning(disable:4711) // function was inlined
+  #pragma warning(disable:4514) // unreferenced inline function has been removed
+  #pragma warning(disable:5039) // could throw inside extern c function
+  #pragma warning(disable:5264) // const variable not used (shut up doctest)
+#endif
+
+#include "doctest.h"
+
diff --git a/tests/unit_parse_format_spec.cc b/tests/unit_parse_format_spec.cc
new file mode 100644
index 0000000..420d177
--- /dev/null
+++ b/tests/unit_parse_format_spec.cc
@@ -0,0 +1,492 @@
+#include "unit_nanoprintf.h"
+
+#include <cstring>
+#include <string>
+
+TEST_CASE("npf_parse_format_spec") {
+  npf_format_spec_t spec;
+  memset(&spec, 0xCD, sizeof(spec));
+
+  SUBCASE("Optional flags") {
+    // Printf behavior is specified in ISO/IEC 9899:201x 7.21.6.1
+    // Optional flags are defined in 7.21.6.1.6
+
+    /*
+        '-' flag: The result of the conversion is left-justified within the field.
+       (It is right-justified if this flag is not specified.)
+    */
+
+    REQUIRE(!npf_parse_format_spec("%-", &spec)); // left-justify alone
+
+    SUBCASE("left-justify off by default") {
+      REQUIRE(npf_parse_format_spec("%u", &spec) == 2);
+      REQUIRE(!spec.left_justified);
+    }
+
+    SUBCASE("left-justify specified") {
+      REQUIRE(npf_parse_format_spec("%-u", &spec) == 3);
+      REQUIRE(spec.left_justified);
+    }
+
+    SUBCASE("left-justify specified multiple times") {
+      REQUIRE(npf_parse_format_spec("%-----u", &spec) == 7);
+      REQUIRE(spec.left_justified);
+    }
+
+    /*
+        '+': The result of a signed conversion always begins with a plus or minus
+       sign. (It begins with a sign only when a negative value is converted if this
+       flag is not specified.) The results of all floating conversions of a negative
+       zero, and of negative values that round to zero, include a minus sign.
+    */
+
+    REQUIRE(!npf_parse_format_spec("%+", &spec)); // prepend sign alone
+
+    SUBCASE("prepend sign off by default") {
+      REQUIRE(npf_parse_format_spec("%u", &spec) == 2);
+      REQUIRE(!spec.prepend);
+    }
+
+    SUBCASE("prepend sign specified") {
+      REQUIRE(npf_parse_format_spec("%+u", &spec) == 3);
+      REQUIRE(spec.prepend == '+');
+    }
+
+    SUBCASE("prepend sign specified multiple times") {
+      REQUIRE(npf_parse_format_spec("%+++++u", &spec) == 7);
+      REQUIRE(spec.prepend == '+');
+    }
+
+    /*
+        ' ': If the first character of a signed conversion is not a sign, or if a
+       signed conversion results in no characters, a space is prefixed to the
+       result. If the space and + flags both appear, the space flag is ignored.
+    */
+
+    REQUIRE(!npf_parse_format_spec("% ", &spec));  // space flag alone
+
+    SUBCASE("prepend space off by default") {
+      REQUIRE(npf_parse_format_spec("%u", &spec) == 2);
+      REQUIRE(!spec.prepend);
+    }
+
+    SUBCASE("prepend space specified") {
+      REQUIRE(npf_parse_format_spec("% u", &spec) == 3);
+      REQUIRE(spec.prepend == ' ');
+    }
+
+    SUBCASE("prepend space specified multiple times") {
+      REQUIRE(npf_parse_format_spec("%     u", &spec) == 7);
+      REQUIRE(spec.prepend == ' ');
+    }
+
+    SUBCASE("prepend space ignored if prepend sign flag is present") {
+      REQUIRE(npf_parse_format_spec("%+ u", &spec) == 4);
+      REQUIRE(spec.prepend == '+');
+
+      REQUIRE(npf_parse_format_spec("% +u", &spec) == 4);
+      REQUIRE(spec.prepend == '+');
+
+      REQUIRE(npf_parse_format_spec("% + + u", &spec) == 7);
+      REQUIRE(spec.prepend == '+');
+    }
+
+    /*
+        '#': The result is converted to an ‘‘alternative form’’. For o conversion,
+       it increases the precision, if and only if necessary, to force the first
+       digit of the result to be a zero (if the value and precision are both 0, a
+       single 0 is printed). For x (or X) conversion, a nonzero result has 0x (or
+       0X) prefixed to it. For a, A, e, E, f, F, g, and G conversions, the result of
+       converting a floating-point number always contains a decimal-point character,
+       even if no digits follow it. (Normally, a decimal-point character appears in
+       the result of these conversions only if a digit follows it.) For g and G
+       conversions, trailing zeros are not removed from the result. For other
+       conversions, the behavior is undefined.
+    */
+
+    REQUIRE(!npf_parse_format_spec("%#", &spec)); // alternative form alone
+
+    SUBCASE("alternative form off by default") {
+      REQUIRE(npf_parse_format_spec("%u", &spec) == 2);
+      REQUIRE(!spec.alt_form);
+    }
+
+    SUBCASE("alternative form specified") {
+      REQUIRE(npf_parse_format_spec("%#u", &spec) == 3);
+      REQUIRE(spec.alt_form);
+    }
+
+    SUBCASE("alternative form specified multiple times") {
+      REQUIRE(npf_parse_format_spec("%#####u", &spec) == 7);
+      REQUIRE(spec.alt_form);
+    }
+
+    /*
+        '0': For d, i, o, u, x, X, a, A, e, E, f, F, g, and G conversions, leading
+       zeros (following any indication of sign or base) are used to pad to the field
+       width rather than performing space padding, except when converting an
+       infinity or NaN. If the 0 and - flags both appear, the 0 flag is ignored. For
+       d, i, o, u, x, and X conversions, if a precision is specified, the 0 flag is
+       ignored. For other conversions, the behavior is undefined.
+    */
+
+    REQUIRE(!npf_parse_format_spec("%0", &spec)); // leading zero alone
+
+    SUBCASE("leading zero off by default") {
+      REQUIRE(npf_parse_format_spec("%u", &spec) == 2);
+      REQUIRE(!spec.leading_zero_pad);
+    }
+
+    SUBCASE("leading zero specified") {
+      REQUIRE(npf_parse_format_spec("%0u", &spec) == 3);
+      REQUIRE(spec.leading_zero_pad == 1);
+    }
+
+    SUBCASE("leading zero specified multiple times") {
+      REQUIRE(npf_parse_format_spec("%00000u", &spec) == 7);
+      REQUIRE(spec.leading_zero_pad == 1);
+    }
+
+    SUBCASE("leading zero ignored when also left-justified") {
+      REQUIRE(npf_parse_format_spec("%0-u", &spec) == 4);
+      REQUIRE(spec.left_justified);
+      REQUIRE(!spec.leading_zero_pad);
+
+      REQUIRE(npf_parse_format_spec("%-0u", &spec) == 4);
+      REQUIRE(spec.left_justified);
+      REQUIRE(!spec.leading_zero_pad);
+
+      REQUIRE(npf_parse_format_spec("%0-0-0-u", &spec) == 8);
+      REQUIRE(spec.left_justified);
+      REQUIRE(!spec.leading_zero_pad);
+    }
+
+    SUBCASE("0 flag is ignored when precision is specified") {
+      REQUIRE(npf_parse_format_spec("%0.1u", &spec) == 5);
+      REQUIRE(!spec.leading_zero_pad);
+    }
+  }
+
+  SUBCASE("field width") {
+    /*
+       An optional minimum field width. If the converted value has fewer characters
+       than the field width, it is padded with spaces (by default) on the left (or
+       right, if the left adjustment flag, described later, has been given) to the
+       field width. The field width takes the form of an asterisk * (described
+       later) or a nonnegative decimal integer. Note that 0 is taken as a flag, not
+       as the beginning of a field width.
+    */
+
+    SUBCASE("field width is none if not specified") {
+      REQUIRE(npf_parse_format_spec("%u", &spec) == 2);
+      REQUIRE(spec.field_width_opt == NPF_FMT_SPEC_OPT_NONE);
+    }
+
+    SUBCASE("field width star is captured") {
+      REQUIRE(npf_parse_format_spec("%*u", &spec) == 3);
+      REQUIRE(spec.field_width_opt == NPF_FMT_SPEC_OPT_STAR);
+    }
+
+    SUBCASE("field width is literal") {
+      REQUIRE(npf_parse_format_spec("%123u", &spec) == 5);
+      REQUIRE(spec.field_width_opt == NPF_FMT_SPEC_OPT_LITERAL);
+      REQUIRE(spec.field_width == 123);
+    }
+  }
+
+  SUBCASE("precision") {
+    /*
+       An optional precision that gives the minimum number of digits to appear for
+       the d, i, o, u, x, and X conversions, the number of digits to appear after
+       the decimal-point character for a, A, e, E, f, and F conversions, the maximum
+       number of significant digits for the g and G conversions, or the maximum
+       number of bytes to be written for s conversions. The precision takes the form
+       of a period (.) followed either by an asterisk * (described later) or by an
+       optional decimal integer; if only the period is specified, the precision is
+       taken as zero. If a precision appears with any other conversion specifier,
+       the behavior is undefined.
+    */
+
+    SUBCASE("precision default is 6 for float types") {
+      REQUIRE(npf_parse_format_spec("%f", &spec) == 2);
+      REQUIRE(spec.prec_opt == NPF_FMT_SPEC_OPT_NONE);
+      REQUIRE(spec.prec == 6);
+        /*
+            Not supported yet
+
+            CHECK_EQUAL(2, npf_parse_format_spec("%g", &spec));
+            CHECK_EQUAL(NPF_FMT_SPEC_OPT_NONE, spec.prec_opt);
+            CHECK_EQUAL(6, spec.prec);
+            CHECK_EQUAL(2, npf_parse_format_spec("%e", &spec));
+            CHECK_EQUAL(NPF_FMT_SPEC_OPT_NONE, spec.prec_opt);
+            CHECK_EQUAL(6, spec.prec);
+        */
+    }
+
+    SUBCASE("precision captures star") {
+      REQUIRE(npf_parse_format_spec("%.*u", &spec) == 4);
+      REQUIRE(spec.prec_opt == NPF_FMT_SPEC_OPT_STAR);
+    }
+
+    SUBCASE("precision is literal zero if only a period is specified") {
+      REQUIRE(npf_parse_format_spec("%.u", &spec) == 3);
+      REQUIRE(spec.prec_opt == NPF_FMT_SPEC_OPT_LITERAL);
+      REQUIRE(!spec.prec);
+    }
+
+    SUBCASE("precision is literal value if period followed by number") {
+      REQUIRE(npf_parse_format_spec("%.12345u", &spec) == 8);
+      REQUIRE(spec.prec_opt == NPF_FMT_SPEC_OPT_LITERAL);
+      REQUIRE(spec.prec == 12345);
+    }
+
+    SUBCASE("precision is none when a negative literal is provided") {
+      REQUIRE(npf_parse_format_spec("%.-34u", &spec) == 6);
+      REQUIRE(spec.prec_opt == NPF_FMT_SPEC_OPT_NONE);
+    }
+  }
+
+  SUBCASE("length modifiers") {
+    /*
+       The length modifiers and their meanings are:
+
+       hh Specifies that a following d, i, o, u, x, or X conversion specifier
+       applies to a signed char or unsigned char argument (the argument will have
+       been promoted according to the integer promotions, but its value shall be
+       converted to signed char or unsigned char before printing); or that a
+       following n conversion specifier applies to a pointer to a signed char
+       argument.
+
+       h Specifies that a following d, i, o, u, x, or X conversion specifier applies
+       to a short int or unsigned short int argument (the argument will have been
+       promoted according to the integer promotions, but its value shall be
+       converted to short int or unsigned short int before printing); or that a
+       following n conversion specifier applies to a pointer to a short int
+       argument.
+
+       l (ell) Specifies that a following d, i, o, u, x, or X conversion specifier
+       applies to a long int or unsigned long int argument; that a following n
+       conversion specifier applies to a pointer to a long int argument; that a
+       following c conversion specifier applies to a wint_t argument; that a
+       following s conversion specifier applies to a pointer to a wchar_t argument;
+       or has no effect on a following a, A, e, E, f, F, g, or G conversion
+       specifier.
+
+       ll (ell-ell) Specifies that a following d, i, o, u, x, or X
+       conversion specifier applies to a long long int or unsigned long long int
+       argument; or that a following n conversion specifier applies to a pointer to
+       a long long int argument.
+
+       j Specifies that a following d, i, o, u, x, or X
+       conversion specifier applies to an intmax_t or uintmax_t argument; or that a
+       following n conversion specifier applies to a pointer to an intmax_t
+       argument.
+
+       z Specifies that a following d, i, o, u, x, or X conversion specifier applies
+       to a size_t or the corresponding signed integer type argument; or that a
+       following n conversion specifier applies to a pointer to a signed integer
+       type corresponding to size_t argument
+
+       t Specifies that a following d, i, o, u, x, or X conversion specifier applies
+       to a ptrdiff_t or the corresponding unsigned integer type argument; or that a
+       following n conversion specifier applies to a pointer to a ptrdiff_t
+       argument.
+
+       L Specifies that a following a, A, e, E, f, F, g, or G conversion specifier
+       applies to a long double argument.
+    */
+
+    REQUIRE(!npf_parse_format_spec("%hh", &spec));  // length mod w/o coversion spec.
+
+    SUBCASE("hh") {
+      REQUIRE(npf_parse_format_spec("%hhu", &spec) == 4);
+      REQUIRE(spec.length_modifier == NPF_FMT_SPEC_LEN_MOD_CHAR);
+    }
+
+    SUBCASE("h") {
+      REQUIRE(npf_parse_format_spec("%hu", &spec) == 3);
+      REQUIRE(spec.length_modifier == NPF_FMT_SPEC_LEN_MOD_SHORT);
+    }
+
+    SUBCASE("l") {
+      REQUIRE(npf_parse_format_spec("%lu", &spec) == 3);
+      REQUIRE(spec.length_modifier == NPF_FMT_SPEC_LEN_MOD_LONG);
+    }
+
+#if NANOPRINTF_USE_LARGE_FORMAT_SPECIFIERS == 1
+    SUBCASE("ll") {
+      REQUIRE(npf_parse_format_spec("%llu", &spec) == 4);
+      REQUIRE(spec.length_modifier == NPF_FMT_SPEC_LEN_MOD_LARGE_LONG_LONG);
+    }
+
+    SUBCASE("j") {
+      REQUIRE(npf_parse_format_spec("%ju", &spec) == 3);
+      REQUIRE(spec.length_modifier == NPF_FMT_SPEC_LEN_MOD_LARGE_INTMAX);
+    }
+
+    SUBCASE("z") {
+      REQUIRE(npf_parse_format_spec("%zu", &spec) == 3);
+      REQUIRE(spec.length_modifier == NPF_FMT_SPEC_LEN_MOD_LARGE_SIZET);
+    }
+
+    SUBCASE("t") {
+      REQUIRE(npf_parse_format_spec("%tu", &spec) == 3);
+      REQUIRE(spec.length_modifier == NPF_FMT_SPEC_LEN_MOD_LARGE_PTRDIFFT);
+    }
+
+    SUBCASE("L") {
+      REQUIRE(npf_parse_format_spec("%Lu", &spec) == 3);
+      REQUIRE(spec.length_modifier == NPF_FMT_SPEC_LEN_MOD_LONG_DOUBLE);
+    }
+#endif
+  }
+
+  SUBCASE("conversion specifiers") {
+    // All conversion specifiers are defined in 7.21.6.1.8
+
+    SUBCASE("% literal") {
+      REQUIRE(npf_parse_format_spec("%%", &spec) == 2);
+      REQUIRE(spec.conv_spec == NPF_FMT_SPEC_CONV_PERCENT);
+    }
+
+    SUBCASE("% clears precision") {
+      REQUIRE(npf_parse_format_spec("%.9%", &spec) == 4);
+      REQUIRE(spec.prec_opt == NPF_FMT_SPEC_OPT_NONE);
+    }
+
+    SUBCASE("c") {
+      REQUIRE(npf_parse_format_spec("%c", &spec) == 2);
+      REQUIRE(spec.conv_spec == NPF_FMT_SPEC_CONV_CHAR);
+    }
+
+    SUBCASE("c clears precision") {
+      REQUIRE(npf_parse_format_spec("%.9c", &spec) == 4);
+      REQUIRE(spec.prec_opt == NPF_FMT_SPEC_OPT_NONE);
+    }
+
+    SUBCASE("s") {
+      REQUIRE(npf_parse_format_spec("%s", &spec) == 2);
+      REQUIRE(spec.conv_spec == NPF_FMT_SPEC_CONV_STRING);
+    }
+
+    SUBCASE("s clears leading 0") {
+      REQUIRE(npf_parse_format_spec("%0s", &spec) == 3);
+      REQUIRE(!spec.leading_zero_pad);
+    }
+
+    SUBCASE("string negative left-justify field width") {
+      REQUIRE(npf_parse_format_spec("%-15s", &spec) == 5);
+      REQUIRE(spec.conv_spec == NPF_FMT_SPEC_CONV_STRING);
+      REQUIRE(spec.left_justified);
+      REQUIRE(spec.field_width == 15);
+    }
+
+    SUBCASE("i") {
+      REQUIRE(npf_parse_format_spec("%i", &spec) == 2);
+      REQUIRE(spec.conv_spec == NPF_FMT_SPEC_CONV_SIGNED_INT);
+    }
+
+    SUBCASE("d") {
+      REQUIRE(npf_parse_format_spec("%d", &spec) == 2);
+      REQUIRE(spec.conv_spec == NPF_FMT_SPEC_CONV_SIGNED_INT);
+    }
+
+    SUBCASE("o") {
+      REQUIRE(npf_parse_format_spec("%o", &spec) == 2);
+      REQUIRE(spec.conv_spec == NPF_FMT_SPEC_CONV_OCTAL);
+    }
+
+    SUBCASE("x") {
+      REQUIRE(npf_parse_format_spec("%x", &spec) == 2);
+      REQUIRE(spec.conv_spec == NPF_FMT_SPEC_CONV_HEX_INT);
+      REQUIRE(spec.case_adjust == 'a' - 'A');
+    }
+
+    SUBCASE("X") {
+      REQUIRE(npf_parse_format_spec("%X", &spec) == 2);
+      REQUIRE(spec.conv_spec == NPF_FMT_SPEC_CONV_HEX_INT);
+      REQUIRE(spec.case_adjust == 0);
+    }
+
+    SUBCASE("u") {
+      REQUIRE(npf_parse_format_spec("%u", &spec) == 2);
+      REQUIRE(spec.conv_spec == NPF_FMT_SPEC_CONV_UNSIGNED_INT);
+    }
+
+    SUBCASE("n") {
+      REQUIRE(npf_parse_format_spec("%n", &spec) == 2);
+      REQUIRE(spec.conv_spec == NPF_FMT_SPEC_CONV_WRITEBACK);
+    }
+
+    SUBCASE("n clears precision") {
+      REQUIRE(npf_parse_format_spec("%.4n", &spec) == 4);
+      REQUIRE(spec.prec_opt == NPF_FMT_SPEC_OPT_NONE);
+    }
+
+    SUBCASE("p") {
+      REQUIRE(npf_parse_format_spec("%p", &spec) == 2);
+      REQUIRE(spec.conv_spec == NPF_FMT_SPEC_CONV_POINTER);
+    }
+
+    SUBCASE("p clears precision") {
+      REQUIRE(npf_parse_format_spec("%.4p", &spec) == 4);
+      REQUIRE(spec.prec_opt == NPF_FMT_SPEC_OPT_NONE);
+    }
+
+    SUBCASE("f") {
+      REQUIRE(npf_parse_format_spec("%f", &spec) == 2);
+      REQUIRE(spec.conv_spec == NPF_FMT_SPEC_CONV_FLOAT_DEC);
+      REQUIRE(spec.case_adjust == 'a' - 'A');
+    }
+
+    SUBCASE("F") {
+      REQUIRE(npf_parse_format_spec("%F", &spec) == 2);
+      REQUIRE(spec.conv_spec == NPF_FMT_SPEC_CONV_FLOAT_DEC);
+      REQUIRE(spec.case_adjust == 0);
+    }
+
+    SUBCASE("e") {
+      REQUIRE(npf_parse_format_spec("%e", &spec) == 2);
+      REQUIRE(spec.conv_spec == NPF_FMT_SPEC_CONV_FLOAT_SCI);
+      REQUIRE(spec.case_adjust == 'a' - 'A');
+    }
+
+    SUBCASE("E") {
+      REQUIRE(npf_parse_format_spec("%E", &spec) == 2);
+      REQUIRE(spec.conv_spec == NPF_FMT_SPEC_CONV_FLOAT_SCI);
+      REQUIRE(spec.case_adjust == 0);
+    }
+
+    SUBCASE("g") {
+      REQUIRE(npf_parse_format_spec("%g", &spec) == 2);
+      REQUIRE(spec.conv_spec == NPF_FMT_SPEC_CONV_FLOAT_SHORTEST);
+      REQUIRE(spec.case_adjust == 'a' - 'A');
+    }
+
+    SUBCASE("G") {
+      REQUIRE(npf_parse_format_spec("%G", &spec) == 2);
+      REQUIRE(spec.conv_spec == NPF_FMT_SPEC_CONV_FLOAT_SHORTEST);
+      REQUIRE(spec.case_adjust == 0);
+    }
+
+    SUBCASE("a") {
+      REQUIRE(npf_parse_format_spec("%a", &spec) == 2);
+      REQUIRE(spec.conv_spec == NPF_FMT_SPEC_CONV_FLOAT_HEX);
+      REQUIRE(spec.case_adjust == 'a' - 'A');
+    }
+
+    SUBCASE("A") {
+      REQUIRE(npf_parse_format_spec("%A", &spec) == 2);
+      REQUIRE(spec.conv_spec == NPF_FMT_SPEC_CONV_FLOAT_HEX);
+      REQUIRE(spec.case_adjust == 0);
+    }
+
+#if NANOPRINTF_USE_BINARY_FORMAT_SPECIFIERS == 1
+    SUBCASE("b") {
+      REQUIRE(npf_parse_format_spec("%b", &spec) == 2);
+      REQUIRE(spec.conv_spec == NPF_FMT_SPEC_CONV_BINARY);
+    }
+#endif
+  }
+}
+
diff --git a/tests/unit_snprintf.cc b/tests/unit_snprintf.cc
new file mode 100644
index 0000000..74a85ab
--- /dev/null
+++ b/tests/unit_snprintf.cc
@@ -0,0 +1,119 @@
+#include "unit_nanoprintf.h"
+
+#include <cstring>
+#include <string>
+
+#if NANOPRINTF_HAVE_GCC_WARNING_PRAGMAS
+  #pragma GCC diagnostic push
+  #if NANOPRINTF_CLANG
+    #pragma GCC diagnostic ignored "-Wformat-pedantic"
+  #endif
+  #pragma GCC diagnostic ignored "-Wformat-zero-length"
+#endif
+
+TEST_CASE("npf_snprintf") {
+  char buf[128];
+  buf[0] = '$';
+
+  SUBCASE("zero-sized buffer") {
+    REQUIRE(npf_snprintf(buf, 0, "abcd") == 4);
+    REQUIRE(buf[0] == '$');
+  }
+
+  SUBCASE("empty string has null terminator") {
+    memset(buf, 0xFF, sizeof(buf));
+    npf_snprintf(buf, sizeof(buf), "");
+    REQUIRE(buf[0] == '\0');
+  }
+
+  SUBCASE("string has null terminator") {
+    memset(buf, 0xFF, sizeof(buf));
+    npf_snprintf(buf, sizeof(buf), "Hello");
+    REQUIRE(buf[5] == '\0');
+    REQUIRE(std::string{buf} == "Hello");
+  }
+
+  SUBCASE("returns number of printed characters without null terminator") {
+    REQUIRE(!npf_snprintf(buf, sizeof(buf), ""));
+    REQUIRE(npf_snprintf(buf, sizeof(buf), "a") == 1);
+    REQUIRE(npf_snprintf(buf, sizeof(buf), "%s", "abcdefg") == 7);
+  }
+
+  SUBCASE("prints string to buffer") {
+    REQUIRE(npf_snprintf(buf, sizeof(buf), "hello %s", "world") == 11);
+    REQUIRE(std::string{buf} == std::string{"hello world"});
+  }
+
+  SUBCASE("returns len if buf is null") {
+    REQUIRE(npf_snprintf(nullptr, 0, "hello %s", "world") == 11);
+  }
+
+  SUBCASE("positive and negative integers in the same format string") {
+    npf_snprintf(buf, sizeof(buf), "%i %u", -100, 100u);
+    REQUIRE(std::string{"-100 100"} == std::string{buf});
+  }
+
+  SUBCASE("fills buffer fully") {
+    REQUIRE(npf_snprintf(buf, 5, "abcd") == 4);
+    REQUIRE(buf[0] == 'a');
+    REQUIRE(buf[1] == 'b');
+    REQUIRE(buf[2] == 'c');
+    REQUIRE(buf[3] == 'd');
+    REQUIRE(buf[4] == '\0');
+  }
+
+  SUBCASE("last byte is null terminator") {
+    REQUIRE(npf_snprintf(buf, 4, "abcd") == 4);
+    REQUIRE(buf[0] == 'a');
+    REQUIRE(buf[1] == 'b');
+    REQUIRE(buf[2] == 'c');
+    REQUIRE(buf[3] == '\0');
+  }
+
+  SUBCASE("terminates before end of buffer") {
+    buf[3] = '*';
+    REQUIRE(npf_snprintf(buf, 3, "abcd") == 4);
+    REQUIRE(buf[0] == 'a');
+    REQUIRE(buf[1] == 'b');
+    REQUIRE(buf[2] == '\0');
+    REQUIRE(buf[3] == '*');
+  }
+
+  SUBCASE("string trimming") {
+    buf[0] = '@';
+    buf[7] = '*';
+    buf[8] = '!';
+
+    SUBCASE("zero-sized buffer") {
+      REQUIRE(npf_snprintf(buf, 0, "abc") == 3);
+      REQUIRE(buf[0] == '@');
+    }
+
+    SUBCASE("small string") {
+      REQUIRE(npf_snprintf(buf, 8, "abc") == 3);
+      REQUIRE(std::string{buf} == "abc");
+    }
+
+    SUBCASE("exact fit string") {
+      REQUIRE(npf_snprintf(buf, 8, "1234567") == 7);
+      REQUIRE(buf[7] == '\0');
+      REQUIRE(std::string{buf} == "1234567");
+    }
+
+    SUBCASE("if the null terminator doesn't fit, the string is trimmed") {
+      REQUIRE(npf_snprintf(buf, 8, "12345678") == 8);
+      REQUIRE(std::string{buf} == "1234567");
+      REQUIRE(buf[8] == '!');
+    }
+
+    SUBCASE("if the string contents are too long, the string is trimmed") {
+      REQUIRE(npf_snprintf(buf, 8, "123456789") == 9);
+      REQUIRE(std::string{buf} == "1234567");
+      REQUIRE(buf[8] == '!');
+    }
+
+    SUBCASE("null buffer with non-null length doesn't get terminated") {
+      npf_snprintf(nullptr, 4, "abcd");
+    }
+  }
+}
diff --git a/tests/unit_snprintf_safe_empty.cc b/tests/unit_snprintf_safe_empty.cc
new file mode 100644
index 0000000..a3b6feb
--- /dev/null
+++ b/tests/unit_snprintf_safe_empty.cc
@@ -0,0 +1,39 @@
+#define NANOPRINTF_SNPRINTF_SAFE_EMPTY_STRING_ON_OVERFLOW
+#include "unit_nanoprintf.h"
+
+#include <string>
+
+TEST_CASE("snprintf safety: empty string") {
+  char buf[9];
+  buf[0] = '@';
+  buf[7] = '*';
+  buf[8] = '!';
+
+  SUBCASE("zero-sized buffer") {
+    REQUIRE(npf_snprintf(buf, 0, "abc") == 3);
+    REQUIRE(buf[0] == '@');
+  }
+
+  SUBCASE("small string") {
+    REQUIRE(npf_snprintf(buf, 8, "abc") == 3);
+    REQUIRE(std::string{buf} == "abc");
+  }
+
+  SUBCASE("exact fit string") {
+    REQUIRE(npf_snprintf(buf, 8, "1234567") == 7);
+    REQUIRE(buf[7] == '\0');
+    REQUIRE(std::string{buf} == "1234567");
+  }
+
+  SUBCASE("if the null terminator doesn't fit, the string is empty") {
+    REQUIRE(npf_snprintf(buf, 8, "12345678") == 8);
+    REQUIRE(buf[0] == '\0');
+    REQUIRE(buf[8] == '!');
+  }
+
+  SUBCASE("if the string contents are too long, the string is empty") {
+    REQUIRE(npf_snprintf(buf, 8, "123456789") == 9);
+    REQUIRE(buf[0] == '\0');
+    REQUIRE(buf[8] == '!');
+  }
+}
diff --git a/tests/unit_utoa_rev.cc b/tests/unit_utoa_rev.cc
new file mode 100644
index 0000000..a925d75
--- /dev/null
+++ b/tests/unit_utoa_rev.cc
@@ -0,0 +1,138 @@
+#include "unit_nanoprintf.h"
+
+#include <climits>
+#include <string>
+
+#if NANOPRINTF_HAVE_GCC_WARNING_PRAGMAS
+  #pragma GCC diagnostic push
+  #if NANOPRINTF_CLANG
+    #pragma GCC diagnostic ignored "-Wformat-pedantic"
+    #pragma GCC diagnostic ignored "-Wold-style-cast"
+    #pragma GCC diagnostic ignored "-Wmissing-prototypes"
+  #endif
+#endif
+
+void require_npf_utoa(
+    std::string const &expected,
+    npf_uint_t val,
+    unsigned base,
+    unsigned case_adjust = 'a' - 'A') {
+  char buf[64];
+  int const n = npf_utoa_rev(buf, val, base, case_adjust);
+  buf[n] = '\0';
+  REQUIRE(n == (int)expected.size());
+  REQUIRE(std::string{buf} == expected);
+}
+
+TEST_CASE("npf_utoa_rev") {
+  SUBCASE("base 10") {
+    require_npf_utoa("0", 0, 10);
+    require_npf_utoa("1", 1, 10);
+    require_npf_utoa("9", 9, 10);
+    require_npf_utoa("01", 10, 10);
+    require_npf_utoa("31", 13, 10);
+    require_npf_utoa("89", 98, 10);
+    require_npf_utoa("99", 99, 10);
+    require_npf_utoa("001", 100, 10);
+    require_npf_utoa("321", 123, 10);
+    require_npf_utoa("999", 999, 10);
+    require_npf_utoa("0001", 1000, 10);
+    require_npf_utoa("4321", 1234, 10);
+    require_npf_utoa("9999", 9999, 10);
+    require_npf_utoa("00001", 10000, 10);
+    require_npf_utoa("54321", 12345, 10);
+    require_npf_utoa("99999", 99999, 10);
+    require_npf_utoa("000001", 100000, 10);
+  }
+
+  SUBCASE("base 10 maxima") {
+#if NANOPRINTF_USE_LARGE_FORMAT_SPECIFIERS == 1
+#if UINTMAX_MAX == 18446744073709551615u
+    require_npf_utoa("51615590737044764481", UINTMAX_MAX, 10);
+#else
+#error Unknown UINTMAX_MAX here, please add another branch.
+#endif
+#else
+#if UINT_MAX == 4294967295
+    require_npf_utoa("5927694924", UINT_MAX, 10);
+#else
+#error Unknown UINT_MAX here, please add another branch.
+#endif
+#endif
+  }
+
+  SUBCASE("base 8") {
+    require_npf_utoa("0", 0, 8);
+    require_npf_utoa("1", 1, 8);
+    require_npf_utoa("7", 7, 8);
+    require_npf_utoa("01", 010, 8);
+    require_npf_utoa("31", 013, 8);
+    require_npf_utoa("71", 017, 8);
+    require_npf_utoa("02", 020, 8);
+    require_npf_utoa("72", 027, 8);
+    require_npf_utoa("03", 030, 8);
+    require_npf_utoa("77", 077, 8);
+    require_npf_utoa("001", 0100, 8);
+    require_npf_utoa("777", 0777, 8);
+    require_npf_utoa("0001", 01000, 8);
+    require_npf_utoa("7777", 07777, 8);
+    require_npf_utoa("00001", 010000, 8);
+    require_npf_utoa("77777", 077777, 8);
+    require_npf_utoa("000001", 0100000, 8);
+    require_npf_utoa("7654321", 01234567, 8);
+  }
+
+  SUBCASE("base 8 maxima") {
+#if NANOPRINTF_USE_LARGE_FORMAT_SPECIFIERS == 1
+#if UINTMAX_MAX == 18446744073709551615u
+    require_npf_utoa("7777777777777777777771", UINTMAX_MAX, 8);
+#else
+#error Unknown UINTMAX_MAX here, please add another branch.
+#endif
+#else
+#if UINT_MAX == 4294967295
+    require_npf_utoa("77777777773", UINT_MAX, 8);
+#else
+#error Unknown UINT_MAX here, please add another branch.
+#endif
+#endif
+  }
+
+  SUBCASE("base 16 lowercase") {
+    require_npf_utoa("0", 0, 16);
+    require_npf_utoa("1", 1, 16);
+    require_npf_utoa("f", 0xf, 16);
+    require_npf_utoa("01", 0x10, 16);
+    require_npf_utoa("c3", 0x3c, 16);
+    require_npf_utoa("ff", 0xff, 16);
+    require_npf_utoa("001", 0x100, 16);
+    require_npf_utoa("fff", 0xfff, 16);
+    require_npf_utoa("0001", 0x1000, 16);
+    require_npf_utoa("ffff", 0xffff, 16);
+    require_npf_utoa("00001", 0x10000, 16);
+    require_npf_utoa("fffff", 0xfffff, 16);
+    require_npf_utoa("000001", 0x100000, 16);
+    require_npf_utoa("4d3c2b1a", 0xa1b2c3d4, 16);
+  }
+
+  SUBCASE("base 16 uppercase") {
+    require_npf_utoa("12345", 0x54321, 16, 0);
+    require_npf_utoa("FEDCBA98", 0x89abcdef, 16, 0);
+  }
+
+  SUBCASE("base 16 maxima") {
+#if NANOPRINTF_USE_LARGE_FORMAT_SPECIFIERS == 1
+#if UINTMAX_MAX == 18446744073709551615u
+    require_npf_utoa("ffffffffffffffff", UINTMAX_MAX, 16);
+#else
+#error Unknown UINTMAX_MAX here, please add another branch.
+#endif
+#else
+#if UINT_MAX == 4294967295
+    require_npf_utoa("ffffffff", UINT_MAX, 16);
+#else
+#error Unknown UINT_MAX here, please add another branch.
+#endif
+#endif
+  }
+}
diff --git a/tests/unit_vpprintf.cc b/tests/unit_vpprintf.cc
new file mode 100644
index 0000000..ac029ec
--- /dev/null
+++ b/tests/unit_vpprintf.cc
@@ -0,0 +1,288 @@
+#include "unit_nanoprintf.h"
+
+#include <climits>
+#include <cstring>
+#include <string>
+#include <vector>
+
+#if NANOPRINTF_HAVE_GCC_WARNING_PRAGMAS
+  #pragma GCC diagnostic push
+  #if NANOPRINTF_CLANG
+    #pragma GCC diagnostic ignored "-Wformat-pedantic"
+    #pragma GCC diagnostic ignored "-Wold-style-cast"
+  #else
+    #pragma GCC diagnostic ignored "-Wformat-overflow"
+  #endif
+  #pragma GCC diagnostic ignored "-Wformat"
+  #pragma GCC diagnostic ignored "-Wformat-zero-length"
+  #pragma GCC diagnostic ignored "-Wformat-security"
+#endif
+
+struct Recorder {
+  static void PutC(int c, void *ctx) {
+    static_cast<Recorder*>(ctx)->calls.push_back((char)c);
+  }
+
+  std::string String() const {
+    return calls.empty() ? std::string() : std::string(calls.begin(), calls.end());
+  }
+
+  std::vector<char> calls;
+};
+
+TEST_CASE("npf_vpprintf") {
+  Recorder r;
+
+  SUBCASE("empty string never calls callback") {
+    REQUIRE(npf_pprintf(r.PutC, &r, "") == 0);
+    REQUIRE(r.calls.empty());
+  }
+
+  SUBCASE("single character string") {
+    REQUIRE(npf_pprintf(r.PutC, &r, "A") == 1);
+    REQUIRE(r.calls.size() == 1);
+    REQUIRE(r.calls[0] == 'A');
+  }
+
+  SUBCASE("string without format specifiers") {
+    std::string const s{"Hello from nanoprintf!"};
+    REQUIRE(npf_pprintf(r.PutC, &r, s.c_str()) == (int)s.size());
+    REQUIRE(s == r.String());
+  }
+
+  SUBCASE("percent literal") {
+    REQUIRE(npf_pprintf(r.PutC, &r, "%%") == 1);
+    REQUIRE(r.String() == std::string{"%"});
+  }
+
+  SUBCASE("character single") {
+    REQUIRE(npf_pprintf(r.PutC, &r, "%c", 'A') == 1);
+    REQUIRE(r.String() == std::string{"A"});
+  }
+
+  SUBCASE("character multiple") {
+    REQUIRE(npf_pprintf(r.PutC, &r, "%c%c%c%c", 'A', 'B', 'C', 'D') == 4);
+    REQUIRE(r.String() == std::string{"ABCD"});
+  }
+
+  SUBCASE("string single") {
+    REQUIRE(npf_pprintf(r.PutC, &r, "%s", "abcd") == 4);
+    REQUIRE(r.String() == std::string{"abcd"});
+  }
+
+  SUBCASE("string empty") {
+    REQUIRE(npf_pprintf(r.PutC, &r, "%s", "") == 0);
+    REQUIRE(r.String().empty());
+  }
+
+  SUBCASE("string multiple") {
+    REQUIRE(npf_pprintf(r.PutC, &r, "%s%s%s", "abcd", "e", "fgh") == 8);
+    REQUIRE(r.String() == std::string{"abcdefgh"});
+  }
+
+  SUBCASE("string precision zero null pointer") {
+    REQUIRE(npf_pprintf(r.PutC, &r, "%.0s", nullptr) == 0);
+    REQUIRE(r.String() == std::string{""});
+  }
+
+  SUBCASE("signed int zero") {
+    REQUIRE(npf_pprintf(r.PutC, &r, "%i", 0) == 1);
+    REQUIRE(r.String() == std::string{"0"});
+  }
+
+  SUBCASE("signed int positive") {
+    REQUIRE(npf_pprintf(r.PutC, &r, "%i", 123) == 3);
+    REQUIRE(r.String() == std::string{"123"});
+  }
+
+  SUBCASE("signed int negative") {
+    REQUIRE(npf_pprintf(r.PutC, &r, "%i", -456) == 4);
+    REQUIRE(r.String() == std::string{"-456"});
+  }
+
+  SUBCASE("signed int max") {
+    REQUIRE(npf_pprintf(r.PutC, &r, "%d", INT_MAX) == 10);
+    REQUIRE(r.String() == std::string{"2147483647"});
+  }
+
+  SUBCASE("signed int min") {
+    REQUIRE(npf_pprintf(r.PutC, &r, "%d", INT_MIN) == 11);
+    REQUIRE(r.String() == std::string{"-2147483648"});
+  }
+
+  SUBCASE("unsigned int zero") {
+    REQUIRE(npf_pprintf(r.PutC, &r, "%u", 0) == 1);
+    REQUIRE(r.String() == std::string{"0"});
+  }
+
+  SUBCASE("unsigned int positive") {
+    REQUIRE(npf_pprintf(r.PutC, &r, "%u", 45678) == 5);
+    REQUIRE(r.String() == std::string{"45678"});
+  }
+
+  SUBCASE("unsigned int max u32") {
+    REQUIRE(npf_pprintf(r.PutC, &r, "%u", 4294967295u) == 10);
+    REQUIRE(r.String() == std::string{"4294967295"});
+  }
+
+  SUBCASE("octal zero") {
+    REQUIRE(npf_pprintf(r.PutC, &r, "%o", 0) == 1);
+    REQUIRE(r.String() == std::string{"0"});
+  }
+
+  SUBCASE("octal positive") {
+    REQUIRE(npf_pprintf(r.PutC, &r, "%o", 1234) == 4);
+    REQUIRE(r.String() == std::string{"2322"});
+  }
+
+  SUBCASE("octal max u32") {
+    REQUIRE(npf_pprintf(r.PutC, &r, "%o", 4294967295u) == 11);
+    REQUIRE(r.String() == std::string{"37777777777"});
+  }
+
+  SUBCASE("hex zero") {
+    REQUIRE(npf_pprintf(r.PutC, &r, "%x", 0) == 1);
+    REQUIRE(r.String() == std::string{"0"});
+  }
+
+  SUBCASE("hex single digit numeric") {
+    REQUIRE(npf_pprintf(r.PutC, &r, "%x", 8) == 1);
+    REQUIRE(r.String() == std::string{"8"});
+  }
+
+  SUBCASE("hex single digit alpha") {
+    REQUIRE(npf_pprintf(r.PutC, &r, "%x", 0xc) == 1);
+    REQUIRE(r.String() == std::string{"c"});
+  }
+
+  SUBCASE("hex large") {
+    REQUIRE(npf_pprintf(r.PutC, &r, "%x", 0x9ABCDEF0) == 8);
+    REQUIRE(r.String() == std::string{"9abcdef0"});
+  }
+
+  SUBCASE("hex max u32") {
+    REQUIRE(npf_pprintf(r.PutC, &r, "%x", 0xFFFFFFFF) == 8);
+    REQUIRE(r.String() == std::string{"ffffffff"});
+  }
+
+  SUBCASE("hex uppercase") {
+    REQUIRE(npf_pprintf(r.PutC, &r, "%X", 0xabcdefab) == 8);
+    REQUIRE(r.String() == std::string{"ABCDEFAB"});
+  }
+
+  SUBCASE("pointer null") {
+    REQUIRE(npf_pprintf(r.PutC, &r, "%p", nullptr) == 3);
+    REQUIRE(r.String() == std::string{"0x0"});
+  }
+
+  SUBCASE("pointer") {
+    void *p;
+    uintptr_t const u = 1234;
+    memcpy(&p, &u, sizeof(p));
+    int const n = npf_pprintf(r.PutC, &r, "%p", p);
+
+    std::string const s = r.String();
+    char const *sb = s.c_str();
+
+    REQUIRE(n > 2);
+    REQUIRE(*sb == '0');
+    ++sb;
+    REQUIRE(*sb == 'x');
+    ++sb;
+
+    for (int i = 2; i < n - 1; ++i) {
+      char const c = *sb++;
+      REQUIRE([c]{ return (c >= '0' && c <= '9') || (c >= 'a' && c <= 'f'); }());
+    }
+  }
+
+  SUBCASE("float zero") {
+    REQUIRE(npf_pprintf(r.PutC, &r, "%f", (double)0.f) == 8);
+    REQUIRE(r.String() == std::string{"0.000000"});
+  }
+
+  SUBCASE("float one") {
+    REQUIRE(npf_pprintf(r.PutC, &r, "%f", 1.0) == 8);
+    REQUIRE(r.String() == std::string{"1.000000"});
+  }
+
+  SUBCASE("field width 1 has no effect") {
+    REQUIRE(npf_pprintf(r.PutC, &r, "%1c", 'A') == 1);
+    REQUIRE(r.String() == std::string{"A"});
+  }
+
+  SUBCASE("field width right justified by default") {
+    REQUIRE(npf_pprintf(r.PutC, &r, "%2c", 'A') == 2);
+    REQUIRE(r.String() == std::string{" A"});
+  }
+
+  SUBCASE("field width left justify flag") {
+    REQUIRE(npf_pprintf(r.PutC, &r, "%-2c", 'A') == 2);
+    REQUIRE(r.String() == std::string{"A "});
+  }
+
+  SUBCASE("prepend sign flag negative") {
+    REQUIRE(npf_pprintf(r.PutC, &r, "%+d", -2) == 2);
+    REQUIRE(r.String() == std::string{"-2"});
+  }
+
+  SUBCASE("prepend sign flag positive for signed conversion") {
+    REQUIRE(npf_pprintf(r.PutC, &r, "%+d", 2) == 2);
+    REQUIRE(r.String() == std::string{"+2"});
+  }
+
+  SUBCASE("prepend sign flag zero") {
+    REQUIRE(npf_pprintf(r.PutC, &r, "%+d", 0) == 2);
+    REQUIRE(r.String() == std::string{"+0"});
+  }
+
+  SUBCASE("prepend sign flag does nothing for unsigned conversion") {
+    REQUIRE(npf_pprintf(r.PutC, &r, "%+u", 1) == 1);
+    REQUIRE(r.String() == std::string{"1"});
+  }
+
+  SUBCASE("prepend space emits space instead of sign when positive") {
+    REQUIRE(npf_pprintf(r.PutC, &r, "% d", 1) == 2);
+    REQUIRE(r.String() == std::string{" 1"});
+  }
+
+  SUBCASE("prepend space emits minus sign when negative") {
+    REQUIRE(npf_pprintf(r.PutC, &r, "% d", -1) == 2);
+    REQUIRE(r.String() == std::string{"-1"});
+  }
+
+  SUBCASE("leading zero-pad flag does nothing on char (undefined)") {
+    REQUIRE(npf_pprintf(r.PutC, &r, "%010c", 'A') == 1);
+    REQUIRE(r.String() == std::string{"A"});
+  }
+
+  SUBCASE("leading zero-pad flag does nothing on string (undefined)") {
+    REQUIRE(npf_pprintf(r.PutC, &r, "%0s", "ABCD") == 4);
+    REQUIRE(r.String() == std::string{"ABCD"});
+  }
+
+  SUBCASE("alternative flag: hex doesn't prepend 0x if value is 0") {
+    REQUIRE(npf_pprintf(r.PutC, &r, "%#x", 0) == 1);
+    REQUIRE(r.String() == std::string{"0"});
+  }
+
+  SUBCASE("alternative flag: hex uppercase 0X") {
+    REQUIRE(npf_pprintf(r.PutC, &r, "%#X", 1) == 3);
+    REQUIRE(r.String() == std::string{"0X1"});
+  }
+
+  SUBCASE("alternative flag: hex lowercase 0x") {
+    REQUIRE(npf_pprintf(r.PutC, &r, "%#x", 1) == 3);
+    REQUIRE(r.String() == std::string{"0x1"});
+  }
+
+  SUBCASE("alternative flag: octal doesn't prepend 0 if value is 0") {
+    REQUIRE(npf_pprintf(r.PutC, &r, "%#o", 0) == 1);
+    REQUIRE(r.String() == std::string{"0"});
+  }
+
+  SUBCASE("alterinative flag: octal non-zero value") {
+    REQUIRE(npf_pprintf(r.PutC, &r, "%#o", 2) == 2);
+    REQUIRE(r.String() == std::string{"02"});
+  }
+}