mirror of
https://github.com/catchorg/Catch2.git
synced 2026-08-06 13:44:22 +02:00
Compare commits
68 Commits
| Author | SHA1 | Date | |
|---|---|---|---|
| 8b08d4d795 | |||
| 1079da4c5f | |||
| 60c8b87829 | |||
| 64a551e2e7 | |||
| 2b971368dd | |||
| f5db82a5c4 | |||
| dcbb2d5d84 | |||
| 1a9625a6e7 | |||
| bb8873ccd7 | |||
| 46bdf1a04c | |||
| c267251e70 | |||
| 0cc833ea95 | |||
| 8494e2dce4 | |||
| eb431764b4 | |||
| ae5d271da2 | |||
| a15f718c82 | |||
| 191fa38c9b | |||
| dd94b9a780 | |||
| 9f7c9f6872 | |||
| 919385f704 | |||
| 15d52830ee | |||
| 9ec44dd62b | |||
| 675f9eaeb1 | |||
| bcfb10e498 | |||
| b7e0310fbe | |||
| 6d4ea62200 | |||
| 195231c59a | |||
| 3a865e8c0f | |||
| 69e0473f6e | |||
| 6ee0826dca | |||
| 47ea57d74f | |||
| d838f88b9c | |||
| c267b6eb4d | |||
| 3cdae5faf0 | |||
| 651247c7f4 | |||
| 15b9393f0f | |||
| a18badd10f | |||
| 54af40652a | |||
| 5f47160bb2 | |||
| e83528c6eb | |||
| fd31a62547 | |||
| 4df8fee92d | |||
| 300c5d3eed | |||
| 11a96e186a | |||
| 10f62484bf | |||
| be2dfb45cc | |||
| 47200ddbee | |||
| e51dcdcc59 | |||
| 77eae8bd2a | |||
| 3ab0d7cef3 | |||
| e83218c2df | |||
| 548e14a8c8 | |||
| b670de4fe1 | |||
| 465e63dad7 | |||
| 34f4f81947 | |||
| 57f738b380 | |||
| 1df10d28ae | |||
| 9f1b48a94f | |||
| f4e83daa18 | |||
| 572f96b8fe | |||
| 8492fd444e | |||
| fe2a20ab55 | |||
| 51b0532d1f | |||
| 2ec64d12b1 | |||
| ccc49ba664 | |||
| 6f036244e9 | |||
| 50e9dbfc4e | |||
| a404f37cec |
@@ -32,6 +32,7 @@ Checks: >-
|
||||
-modernize-deprecated-headers,
|
||||
,# There's a lot of these and most of them are probably not useful,
|
||||
-modernize-pass-by-value,
|
||||
-modernize-use-string-view, # We support C++14,
|
||||
|
||||
performance-*,
|
||||
performance-enum-size,
|
||||
@@ -58,6 +59,8 @@ Checks: >-
|
||||
-readability-uppercase-literal-suffix,
|
||||
-readability-use-anyofallof,
|
||||
-readability-avoid-return-with-void-value,
|
||||
,# We prefer if defined(FOO) form because it is easier to extend later,
|
||||
-readability-use-concise-preprocessor-directives,
|
||||
|
||||
,# time hogs,
|
||||
-bugprone-throw-keyword-missing,
|
||||
|
||||
@@ -16,10 +16,10 @@ jobs:
|
||||
compilation_mode: [fastbuild, dbg, opt]
|
||||
|
||||
steps:
|
||||
- uses: actions/checkout@v4
|
||||
- uses: actions/checkout@v6
|
||||
|
||||
- name: Mount Bazel cache
|
||||
uses: actions/cache@v3
|
||||
uses: actions/cache@v5
|
||||
with:
|
||||
path: "/home/runner/.cache/bazel"
|
||||
key: bazel-ubuntu22-gcc11
|
||||
|
||||
@@ -23,7 +23,7 @@ jobs:
|
||||
other_pkgs: clang-11
|
||||
|
||||
steps:
|
||||
- uses: actions/checkout@v4
|
||||
- uses: actions/checkout@v6
|
||||
|
||||
- name: Prepare environment
|
||||
run: |
|
||||
|
||||
@@ -75,7 +75,7 @@ jobs:
|
||||
|
||||
|
||||
steps:
|
||||
- uses: actions/checkout@v4
|
||||
- uses: actions/checkout@v6
|
||||
|
||||
- name: Prepare environment
|
||||
run: |
|
||||
@@ -100,7 +100,7 @@ jobs:
|
||||
name: clang-tidy
|
||||
runs-on: ubuntu-22.04
|
||||
steps:
|
||||
- uses: actions/checkout@v4
|
||||
- uses: actions/checkout@v6
|
||||
|
||||
- name: Prepare environment
|
||||
run: |
|
||||
|
||||
@@ -83,7 +83,7 @@ jobs:
|
||||
other_pkgs: g++-11
|
||||
|
||||
steps:
|
||||
- uses: actions/checkout@v4
|
||||
- uses: actions/checkout@v6
|
||||
|
||||
- name: Add repositories for older compilers
|
||||
run: |
|
||||
|
||||
@@ -17,7 +17,7 @@ jobs:
|
||||
std: [14, 17]
|
||||
|
||||
steps:
|
||||
- uses: actions/checkout@v4
|
||||
- uses: actions/checkout@v6
|
||||
|
||||
- name: Configure
|
||||
run: |
|
||||
|
||||
@@ -25,7 +25,7 @@ jobs:
|
||||
filter:
|
||||
|
||||
steps:
|
||||
- uses: actions/checkout@v4
|
||||
- uses: actions/checkout@v6
|
||||
|
||||
- name: Configure
|
||||
|
||||
|
||||
@@ -18,7 +18,7 @@ jobs:
|
||||
profile_generate: 'false'
|
||||
|
||||
steps:
|
||||
- uses: actions/checkout@v4
|
||||
- uses: actions/checkout@v6
|
||||
|
||||
- name: Install conan
|
||||
run: pip install conan==${{matrix.conan_version}}
|
||||
|
||||
@@ -9,10 +9,10 @@ jobs:
|
||||
steps:
|
||||
|
||||
- name: Checkout source code
|
||||
uses: actions/checkout@v4
|
||||
uses: actions/checkout@v6
|
||||
|
||||
- name: Setup Dependencies
|
||||
uses: actions/setup-python@v2
|
||||
uses: actions/setup-python@v6
|
||||
with:
|
||||
python-version: '3.7'
|
||||
- name: Install checkguard
|
||||
|
||||
@@ -18,7 +18,7 @@ jobs:
|
||||
build_type: [Debug, Release]
|
||||
std: [14, 17]
|
||||
steps:
|
||||
- uses: actions/checkout@v4
|
||||
- uses: actions/checkout@v6
|
||||
|
||||
- name: Configure build
|
||||
run: |
|
||||
|
||||
+1
-1
@@ -35,7 +35,7 @@ if(CMAKE_BINARY_DIR STREQUAL CMAKE_CURRENT_SOURCE_DIR)
|
||||
endif()
|
||||
|
||||
project(Catch2
|
||||
VERSION 3.13.0 # CML version placeholder, don't delete
|
||||
VERSION 3.15.3 # CML version placeholder, don't delete
|
||||
LANGUAGES CXX
|
||||
HOMEPAGE_URL "https://github.com/catchorg/Catch2"
|
||||
DESCRIPTION "A modern, C++-native, unit test framework."
|
||||
|
||||
+2
-2
@@ -1,5 +1,5 @@
|
||||
module(name = "catch2")
|
||||
|
||||
bazel_dep(name = "bazel_skylib", version = "1.7.1")
|
||||
bazel_dep(name = "rules_cc", version = "0.1.1")
|
||||
bazel_dep(name = "bazel_skylib", version = "1.9.0")
|
||||
bazel_dep(name = "rules_cc", version = "0.2.16")
|
||||
bazel_dep(name = "rules_license", version = "1.0.0")
|
||||
|
||||
@@ -9,8 +9,13 @@ add_executable(AssertionsSlowPath
|
||||
assertion_listener.cpp
|
||||
)
|
||||
|
||||
add_executable(EmptyExecutable
|
||||
only_include.cpp
|
||||
)
|
||||
|
||||
target_link_libraries(AssertionsFastPath PRIVATE Catch2::Catch2WithMain)
|
||||
target_link_libraries(AssertionsSlowPath PRIVATE Catch2::Catch2WithMain)
|
||||
target_link_libraries(EmptyExecutable PRIVATE Catch2::Catch2WithMain)
|
||||
|
||||
list(APPEND CATCH_TEST_TARGETS AssertionsFastPath AssertionsSlowPath)
|
||||
list(APPEND CATCH_TEST_TARGETS AssertionsFastPath AssertionsSlowPath EmptyExecutable)
|
||||
set(CATCH_TEST_TARGETS ${CATCH_TEST_TARGETS} PARENT_SCOPE)
|
||||
|
||||
@@ -0,0 +1,200 @@
|
||||
#!/usr/bin/env python3
|
||||
|
||||
# Copyright Catch2 Authors
|
||||
# Distributed under the Boost Software License, Version 1.0.
|
||||
# (See accompanying file LICENSE.txt or copy at
|
||||
# https://www.boost.org/LICENSE_1_0.txt)
|
||||
|
||||
# SPDX-License-Identifier: BSL-1.0
|
||||
|
||||
import argparse
|
||||
import json
|
||||
import os
|
||||
import re
|
||||
import statistics
|
||||
import subprocess
|
||||
import sys
|
||||
import tempfile
|
||||
import time
|
||||
|
||||
HERE = os.path.dirname(os.path.abspath(__file__))
|
||||
TEMPLATE = os.path.join(HERE, "listing_template.json")
|
||||
SHIM = os.path.join(HERE, "copy_shim.cmake")
|
||||
COUNTS = [1, 10, 500, 1000, 2000, 4000, 8000]
|
||||
#COUNTS = [1, 10, 100]
|
||||
REPEATS = 5
|
||||
|
||||
def load_template(path: str) -> dict:
|
||||
with open(path, encoding="utf-8") as f:
|
||||
catch_out = json.load(f)
|
||||
if 'listings' not in catch_out or 'tests' not in catch_out['listings']:
|
||||
sys.exit(f"Template '{path}' does not contain the expected 'listings.tests' data")
|
||||
|
||||
return catch_out
|
||||
|
||||
|
||||
def synthesize_listing(template_doc: dict, count: int, file) -> str:
|
||||
"""Writes JSON listing with exactly `count` test cases into `file`.
|
||||
|
||||
The template entries are cycled through, and each name is made unique by
|
||||
appending an index, so that every registered CTest test has a distinct name.
|
||||
"""
|
||||
doc_copy = template_doc.copy()
|
||||
template_tests = template_doc['listings']['tests']
|
||||
num_original = len(template_tests)
|
||||
|
||||
out_tests = []
|
||||
for i in range(count):
|
||||
entry = template_tests[i % num_original].copy()
|
||||
entry["name"] = f"{entry['name']} #{i // num_original}"
|
||||
out_tests.append(entry)
|
||||
|
||||
doc_copy['listings']['tests'] = out_tests
|
||||
|
||||
# indent=2 matches Catch2's pretty printing
|
||||
json.dump(doc_copy, file, indent=2)
|
||||
file.write('\n')
|
||||
|
||||
|
||||
def build_command(args, listing_path: str, ctest_file: str, add_tags: bool) -> list[str]:
|
||||
# The executor needs to be a CMake list, so it has to be separated by ';'
|
||||
executor = ";".join(['cmake', f"-DBENCH_LISTING={listing_path}", "-P", SHIM, "--"])
|
||||
return [
|
||||
'cmake',
|
||||
"-DTEST_TARGET=benchmark",
|
||||
f"-DTEST_EXECUTABLE={listing_path}",
|
||||
f"-DTEST_EXECUTOR={executor}",
|
||||
f"-DTEST_WORKING_DIR={os.path.dirname(ctest_file)}",
|
||||
f"-DCTEST_FILE={ctest_file}",
|
||||
f"-DADD_TAGS_AS_LABELS={'TRUE' if add_tags else 'FALSE'}",
|
||||
"-P",
|
||||
args.script,
|
||||
]
|
||||
|
||||
|
||||
def run_once(cmd: list[str]) -> float:
|
||||
start = time.monotonic()
|
||||
result = subprocess.run(cmd, capture_output=True, text=True)
|
||||
elapsed = time.monotonic() - start
|
||||
if result.returncode != 0:
|
||||
sys.stderr.write("Benchmark invocation failed:\n")
|
||||
sys.stderr.write(f" cmd: {' '.join(cmd)}\n")
|
||||
sys.stderr.write(f" stdout: {result.stdout}\n")
|
||||
sys.stderr.write(f" stderr: {result.stderr}\n")
|
||||
sys.exit(1)
|
||||
return elapsed
|
||||
|
||||
|
||||
def verify_output(ctest_file: str, count: int, add_tags: bool):
|
||||
"""Sanity-check the generated CTest script and return its add_test count."""
|
||||
with open(ctest_file, encoding="utf-8") as f:
|
||||
content = f.read()
|
||||
add_test_lines = sum(1 for line in content.splitlines() if line.startswith("add_test("))
|
||||
if add_test_lines != count:
|
||||
sys.exit(
|
||||
f"Sanity check failed: expected {count} add_test() lines, "
|
||||
f"found {add_test_lines} in {ctest_file}"
|
||||
)
|
||||
if add_tags and count > 0 and "LABELS" not in content:
|
||||
sys.exit(
|
||||
f"Sanity check failed: ADD_TAGS_AS_LABELS was on but no LABELS found "
|
||||
f"in {ctest_file}"
|
||||
)
|
||||
|
||||
|
||||
def cmake_version() -> str:
|
||||
stdout = subprocess.run(
|
||||
['cmake', "--version"], capture_output=True, text=True, check=True
|
||||
).stdout
|
||||
match = re.match("cmake version (.+)", stdout)
|
||||
return match.group(1)
|
||||
|
||||
|
||||
def print_table(counts, tag_modes, results):
|
||||
timing_by_ct = {(count, tags): median_time for (count, tags, median_time) in results}
|
||||
|
||||
headers = ["N"]
|
||||
if False in tag_modes:
|
||||
headers.append("tags:off")
|
||||
if True in tag_modes:
|
||||
headers.append("tags:on")
|
||||
|
||||
def fmt_cell(count, tags_on):
|
||||
median_time = timing_by_ct[(count, tags_on)]
|
||||
return f"{median_time:8.1f} ms"
|
||||
|
||||
# Header
|
||||
row = f"{headers[0]:>6}"
|
||||
if False in tag_modes:
|
||||
row += f" {headers[1]:>14}"
|
||||
if True in tag_modes:
|
||||
row += f" {headers[2]:>14}"
|
||||
|
||||
print()
|
||||
print("-" * len(row))
|
||||
print(row)
|
||||
print("-" * len(row))
|
||||
|
||||
for count in counts:
|
||||
row = f"{count:>6}"
|
||||
if False in tag_modes:
|
||||
row += f" {fmt_cell(count, False):>14}"
|
||||
if True in tag_modes:
|
||||
row += f" {fmt_cell(count, True):>14}"
|
||||
print(row)
|
||||
|
||||
|
||||
def main():
|
||||
parser = argparse.ArgumentParser(
|
||||
description="Benchmark the CMake-side cost of catch_discover_tests."
|
||||
)
|
||||
parser.add_argument(
|
||||
"--script",
|
||||
required=True,
|
||||
help="Path to the CatchAddTests.cmake to benchmark.",
|
||||
)
|
||||
args = parser.parse_args()
|
||||
|
||||
if not os.path.exists(args.script):
|
||||
sys.exit(f"Script to benchmark '{args.script}' does not exist")
|
||||
if not os.path.exists(SHIM):
|
||||
sys.exit(f"Copy-shim '{SHIM}' does not exist")
|
||||
|
||||
tag_modes = [False, True]
|
||||
|
||||
template_doc = load_template(TEMPLATE)
|
||||
|
||||
print(f"Benchmarking: {os.path.abspath(args.script)}")
|
||||
print(f"CMake version: {cmake_version()}")
|
||||
print(f"Ns: {COUNTS}")
|
||||
print(f"Repeats: {REPEATS}")
|
||||
print()
|
||||
|
||||
|
||||
results = [] # (count, tags_on, median_ms)
|
||||
with tempfile.TemporaryDirectory() as workdir:
|
||||
os.makedirs(workdir, exist_ok=True)
|
||||
for count in COUNTS:
|
||||
listing_path = os.path.join(workdir, f"listing-{count}.json")
|
||||
with open(listing_path, "w", encoding="utf-8") as f:
|
||||
synthesize_listing(template_doc, count, f)
|
||||
|
||||
for add_tags in tag_modes:
|
||||
mode = "on" if add_tags else "off"
|
||||
ctest_file = os.path.join(workdir, f"ctest-{count}-{mode}.cmake")
|
||||
cmd = build_command(args, listing_path, ctest_file, add_tags)
|
||||
|
||||
# Untimed warmup run (populates any filesystem/OS caches).
|
||||
run_once(cmd)
|
||||
verify_output(ctest_file, count, add_tags)
|
||||
|
||||
timings = [run_once(cmd) for _ in range(REPEATS)]
|
||||
median_ms = statistics.median(timings) * 1000.0
|
||||
results.append((count, add_tags, median_ms))
|
||||
print(f'N = {count} done in ~{len(tag_modes) * sum(timings):.2f} s')
|
||||
|
||||
print_table(COUNTS, tag_modes, results)
|
||||
|
||||
|
||||
if __name__ == "__main__":
|
||||
main()
|
||||
@@ -0,0 +1,40 @@
|
||||
# SPDX-License-Identifier: BSL-1.0
|
||||
|
||||
# Fake Catch2 binary used by benchmark_discovery.py.
|
||||
#
|
||||
# catch_discover_tests_impl discovers tests by running
|
||||
#
|
||||
# ${TEST_EXECUTOR} "${TEST_EXECUTABLE}" ... --list-tests --reporter json --out <file>
|
||||
#
|
||||
# and then reading the JSON listing back from <file>. This script fakes
|
||||
# being Catch2 binary by taking the fake JSON listing prepared by
|
||||
# `benchmark_discovery.py` and copying it over to the file path specified
|
||||
# after the `--out` argument.
|
||||
#
|
||||
# The fake JSON listing is passed as BENCH_LISTING CMake argument.
|
||||
|
||||
if(NOT DEFINED BENCH_LISTING)
|
||||
message(FATAL_ERROR "copy_shim: BENCH_LISTING is not set")
|
||||
endif()
|
||||
if(NOT EXISTS "${BENCH_LISTING}")
|
||||
message(FATAL_ERROR "copy_shim: BENCH_LISTING '${BENCH_LISTING}' does not exist")
|
||||
endif()
|
||||
|
||||
|
||||
# FIXME: better handling of bad --out
|
||||
# Find the "--out <file>" argument pair among the forwarded arguments.
|
||||
set(_out_file "")
|
||||
math(EXPR _last "${CMAKE_ARGC} - 1")
|
||||
foreach(_i RANGE 0 ${_last})
|
||||
if("${CMAKE_ARGV${_i}}" STREQUAL "--out")
|
||||
math(EXPR _next "${_i} + 1")
|
||||
set(_out_file "${CMAKE_ARGV${_next}}")
|
||||
break()
|
||||
endif()
|
||||
endforeach()
|
||||
|
||||
if(_out_file STREQUAL "")
|
||||
message(FATAL_ERROR "copy_shim: could not find '--out <file>' in arguments")
|
||||
endif()
|
||||
|
||||
file(COPY_FILE "${BENCH_LISTING}" "${_out_file}")
|
||||
@@ -0,0 +1,134 @@
|
||||
{
|
||||
"version": 1,
|
||||
"metadata": {
|
||||
"name": "benchmark-template",
|
||||
"catch2-version": "3.15.2"
|
||||
},
|
||||
"listings": {
|
||||
"tests": [
|
||||
{
|
||||
"name": "Comparing function pointers",
|
||||
"class-name": "",
|
||||
"tags": [
|
||||
"function pointer",
|
||||
"Tricky"
|
||||
],
|
||||
"source-location": {
|
||||
"filename": "/mnt/c/ubuntu/Catch2/tests/SelfTest/UsageTests/Tricky.tests.cpp",
|
||||
"line": 266
|
||||
}
|
||||
},
|
||||
{
|
||||
"name": "Testing checked-if 4",
|
||||
"class-name": "",
|
||||
"tags": [
|
||||
"!shouldfail",
|
||||
"checked-if"
|
||||
],
|
||||
"source-location": {
|
||||
"filename": "/mnt/c/ubuntu/Catch2/tests/SelfTest/UsageTests/Misc.tests.cpp",
|
||||
"line": 216
|
||||
}
|
||||
},
|
||||
{
|
||||
"name": "count_equidistant_floats - double",
|
||||
"class-name": "",
|
||||
"tags": [
|
||||
"approvals",
|
||||
"distance",
|
||||
"floating-point"
|
||||
],
|
||||
"source-location": {
|
||||
"filename": "/mnt/c/ubuntu/Catch2/tests/SelfTest/IntrospectiveTests/FloatingPoint.tests.cpp",
|
||||
"line": 103
|
||||
}
|
||||
},
|
||||
{
|
||||
"name": "Usage of AllTrue range matcher",
|
||||
"class-name": "",
|
||||
"tags": [
|
||||
"matchers",
|
||||
"quantifiers",
|
||||
"templated"
|
||||
],
|
||||
"source-location": {
|
||||
"filename": "/mnt/c/ubuntu/Catch2/tests/SelfTest/UsageTests/MatchersRanges.tests.cpp",
|
||||
"line": 372
|
||||
}
|
||||
},
|
||||
{
|
||||
"name": "Exception matchers that succeed",
|
||||
"class-name": "",
|
||||
"tags": [
|
||||
"!throws",
|
||||
"exceptions",
|
||||
"matchers"
|
||||
],
|
||||
"source-location": {
|
||||
"filename": "/mnt/c/ubuntu/Catch2/tests/SelfTest/UsageTests/Matchers.tests.cpp",
|
||||
"line": 428
|
||||
}
|
||||
},
|
||||
{
|
||||
"name": "A TEMPLATE_TEST_CASE_METHOD based test run that succeeds - float",
|
||||
"class-name": "Template_Fixture",
|
||||
"tags": [
|
||||
"class",
|
||||
"template"
|
||||
],
|
||||
"source-location": {
|
||||
"filename": "/mnt/c/ubuntu/Catch2/tests/SelfTest/UsageTests/Class.tests.cpp",
|
||||
"line": 82
|
||||
}
|
||||
},
|
||||
{
|
||||
"name": "Approximate PI",
|
||||
"class-name": "",
|
||||
"tags": [
|
||||
"Approx",
|
||||
"PI"
|
||||
],
|
||||
"source-location": {
|
||||
"filename": "/mnt/c/ubuntu/Catch2/tests/SelfTest/UsageTests/Approx.tests.cpp",
|
||||
"line": 134
|
||||
}
|
||||
},
|
||||
{
|
||||
"name": "TextFlow::Column respects width setting",
|
||||
"class-name": "",
|
||||
"tags": [
|
||||
"approvals",
|
||||
"column",
|
||||
"TextFlow"
|
||||
],
|
||||
"source-location": {
|
||||
"filename": "/mnt/c/ubuntu/Catch2/tests/SelfTest/IntrospectiveTests/TextFlow.tests.cpp",
|
||||
"line": 38
|
||||
}
|
||||
},
|
||||
{
|
||||
"name": "Generators internals",
|
||||
"class-name": "",
|
||||
"tags": [
|
||||
"generators",
|
||||
"internals"
|
||||
],
|
||||
"source-location": {
|
||||
"filename": "/mnt/c/ubuntu/Catch2/tests/SelfTest/IntrospectiveTests/GeneratorsImpl.tests.cpp",
|
||||
"line": 24
|
||||
}
|
||||
},
|
||||
{
|
||||
"name": "Mayfail test case with nested sections",
|
||||
"class-name": "",
|
||||
"tags": [
|
||||
"!mayfail"
|
||||
],
|
||||
"source-location": {
|
||||
"filename": "/mnt/c/ubuntu/Catch2/tests/SelfTest/UsageTests/Condition.tests.cpp",
|
||||
"line": 83
|
||||
}
|
||||
}
|
||||
]
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,16 @@
|
||||
|
||||
// Copyright Catch2 Authors
|
||||
// Distributed under the Boost Software License, Version 1.0.
|
||||
// (See accompanying file LICENSE.txt or copy at
|
||||
// https://www.boost.org/LICENSE_1_0.txt)
|
||||
|
||||
// SPDX-License-Identifier: BSL-1.0
|
||||
|
||||
/**\file
|
||||
* "empty" include of catch_test_macros.hpp
|
||||
*
|
||||
* This file does only one thing: include `catch2/catch_test_macros.hpp`,
|
||||
* so that we can measure the compilation cost of the header.
|
||||
*/
|
||||
|
||||
#include <catch2/catch_test_macros.hpp>
|
||||
@@ -0,0 +1,117 @@
|
||||
<a id="top"></a>
|
||||
|
||||
# Benchmarks of Catch2
|
||||
|
||||
This folder holds benchmarks for Catch2. It should not be built for the
|
||||
small(er) test sets, as it is only relevant for running (mainly runtime)
|
||||
benchmarks.
|
||||
|
||||
Below you will find some practical examples using
|
||||
[hyperfine](https://github.com/sharkdp/hyperfine) to determine the
|
||||
performance of various scenarios. They assume two parallel checkouts, one
|
||||
named `Catch2-old` and the other `Catch2-new`. You will need to change
|
||||
the paths to work on your own machine.
|
||||
|
||||
|
||||
## Runtime benchmarks
|
||||
|
||||
The runtime benchmarks currently consist of few different `TEST_CASE`s,
|
||||
each with a simple loop over different assertion type. They are compiled
|
||||
into two binaries, one for assertion slow path and one for assertion fast
|
||||
path.
|
||||
|
||||
|
||||
### Assumptions and notes about real-world usage
|
||||
|
||||
The basic assertion macro, `REQUIRE` (and `CHECK`) is the most common one,
|
||||
by far. Thus, it is the most important one to run quickly. It is probably
|
||||
followed by the matcher macro, `REQUIRE_THAT`.
|
||||
|
||||
The most common result of an assertion is that it passes. Even if it is
|
||||
stringified and reported, that is most likely due to some listener/reporter
|
||||
that wants the string representation, not because it failed.
|
||||
|
||||
The performance of both Debug and Release builds are important; users
|
||||
will run tests in both. LTO runtime performance is not too important,
|
||||
but compile time perf is.
|
||||
|
||||
|
||||
### Examples
|
||||
|
||||
**Compare performance of `REQUIRE` in slow path, debug build**
|
||||
```text
|
||||
hyperfine --warmup 2 --shell none --parameter-list version old,new '/home/xarn/benches/Catch2-{version}/build-debug/benchmarks/AssertionsSlowPath -o /dev/null "REQUIRE"'
|
||||
```
|
||||
|
||||
**Compare performance of `REQUIRE_THAT` in fast path, release build**
|
||||
```text
|
||||
hyperfine --warmup 2 --shell none --parameter-list version old,new '/home/xarn/benches/Catch2-{version}/build-release/benchmarks/AssertionsFastPath -o /dev/null "REQUIRE_THAT"'
|
||||
```
|
||||
|
||||
**Compare performance of `REQUIRE` with stringification enabled, release build**
|
||||
```text
|
||||
hyperfine --warmup 2 --shell none --parameter-list version old,new '/home/xarn/benches/Catch2-{version}/build-release/benchmarks/AssertionsFastPath -s -o /dev/null "REQUIRE"'
|
||||
```
|
||||
_Note that we redirect the output to `/dev/null` to reduce the overhead of the actual output printing, to see just the impact of stringification._
|
||||
|
||||
TODO:
|
||||
* Start empty binary (set up cost base)
|
||||
* Section tracking
|
||||
|
||||
|
||||
## Compilation benchmarks
|
||||
|
||||
As tests are often iterated upon and relinked, the compilation cost of
|
||||
Catch2 is also important.
|
||||
|
||||
|
||||
### Examples
|
||||
|
||||
**Compare overhead of including `catch_test_macros.hpp`**
|
||||
```text
|
||||
hyperfine --warmup 2 --shell none --parameter-list version old,new '/usr/bin/c++ -I/home/xarn/benches/Catch2-{version}/src/catch2/.. -I/home/xarn/benches/Catch2-{version}/build-debug/generated-includes -g -o /dev/null -c /home/xarn/benches/Catch2-{version}/benchmarks/only_include.cpp'
|
||||
```
|
||||
|
||||
**Compare build time of Catch2's `SelfTest` test suite, Debug build**
|
||||
```text
|
||||
hyperfine --warmup 2 --parameter-list version old,vas --prepare 'find ~/benches/Catch2-{version}/tests/SelfTest -type f -name "*.cpp" -exec touch {} +' 'ninja -j 1 -C ~/benches/Catch2-{version}/build-debug'
|
||||
```
|
||||
|
||||
TODO:
|
||||
* Link-only recipe
|
||||
|
||||
|
||||
## Misc. benchmarks
|
||||
|
||||
### `catch_discover_tests`
|
||||
|
||||
The first JSON-based implementation of `catch_discover_tests` turned out
|
||||
to have quadratic complexity in number of tests, which meant that registering
|
||||
say 1k test cases took ~4s , and it became unusably slow with larger
|
||||
test suites.
|
||||
|
||||
To prevent backsliding, and enable future optimizations, there is now
|
||||
a benchmarking script for `catch_discover_tests` in the `discover_tests`
|
||||
directory.
|
||||
|
||||
`discover_tests/benchmark_discovery.py` runs the real `catch_discover_tests`
|
||||
implementation (through CMake script-mode) on a synthesized JSON listing
|
||||
(generated from `discover_tests/listing_template.json`) through an executor
|
||||
shim (`discover_tests/copy_shim.cmake`). This means it can run even without
|
||||
real test binary.
|
||||
|
||||
If the JSON output from Catch2 changes, the synthesized JSON listing
|
||||
can be updated by taking a real listing from `SelfTest` binary, and pruning
|
||||
it down to keep only ~10 entries.
|
||||
|
||||
|
||||
#### Examples
|
||||
|
||||
**Benchmark the current `catch_discover_tests`, with and without tags-as-labels**
|
||||
```text
|
||||
./benchmarks/discover_tests/benchmark_discovery.py --script ./extras/CatchAddTests.cmake
|
||||
```
|
||||
|
||||
Note that 8k test cases is unrealistically high, but still useful to see scaling.
|
||||
Benchmark for single test case is useful to provide estimate of the flat
|
||||
overhead from using `catch_discover_tests` at all.
|
||||
@@ -7,21 +7,48 @@
|
||||
// SPDX-License-Identifier: BSL-1.0
|
||||
|
||||
#include <catch2/catch_test_macros.hpp>
|
||||
#include <catch2/matchers/catch_matchers_templated.hpp>
|
||||
|
||||
TEST_CASE("Simple REQUIRE - 10M") {
|
||||
for (size_t i = 0; i < 10'000'000; ++i) {
|
||||
REQUIRE(true);
|
||||
namespace {
|
||||
|
||||
class MatchAllMatcher final : public Catch::Matchers::MatcherGenericBase {
|
||||
public:
|
||||
template <typename Any>
|
||||
bool match( Any&& ) const {
|
||||
return true;
|
||||
}
|
||||
|
||||
std::string describe() const override {
|
||||
using namespace std::string_literals;
|
||||
return "Long string that does not fit into SSO"s;
|
||||
}
|
||||
};
|
||||
|
||||
MatchAllMatcher MatchAll() { return MatchAllMatcher(); }
|
||||
|
||||
|
||||
TEST_CASE( "REQUIRE", "[assertions]" ) {
|
||||
for ( size_t i = 0; i < 10'000'000; ++i ) {
|
||||
REQUIRE( true );
|
||||
}
|
||||
}
|
||||
|
||||
TEST_CASE("Simple NOTHROW - 10M") {
|
||||
for (size_t i = 0; i < 10'000'000; ++i) {
|
||||
REQUIRE_NOTHROW([](){}());
|
||||
TEST_CASE( "REQUIRE_THAT", "[assertions][matchers]" ) {
|
||||
for ( size_t i = 0; i < 10'000'000; ++i ) {
|
||||
REQUIRE_THAT( 1, MatchAll() );
|
||||
}
|
||||
}
|
||||
|
||||
TEST_CASE("Simple THROWS - 10M") {
|
||||
for (size_t i = 0; i < 10'000'000; ++i) {
|
||||
REQUIRE_THROWS([]() { throw 1; }());
|
||||
TEST_CASE( "REQUIRE_NOTHROW", "[assertions][exceptions]" ) {
|
||||
for ( size_t i = 0; i < 10'000'000; ++i ) {
|
||||
REQUIRE_NOTHROW( []() {}() );
|
||||
}
|
||||
}
|
||||
|
||||
TEST_CASE( "REQUIRE_THROWS", "[assertions][exceptions]" ) {
|
||||
for ( size_t i = 0; i < 10'000'000; ++i ) {
|
||||
REQUIRE_THROWS( []() { throw 1; }() );
|
||||
}
|
||||
}
|
||||
|
||||
} // namespace
|
||||
|
||||
+1
-1
@@ -26,7 +26,7 @@ The ```CHECK``` family are equivalent but execution continues in the same test c
|
||||
Evaluates the expression and records the result. If an exception is thrown, it is caught, reported, and counted as a failure. These are the macros you will use most of the time.
|
||||
|
||||
Examples:
|
||||
```
|
||||
```cpp
|
||||
CHECK( str == "string value" );
|
||||
CHECK( thisReturnsTrue() );
|
||||
REQUIRE( i == 42 );
|
||||
|
||||
@@ -212,6 +212,17 @@ execution (useful e.g. in cross-compilation environments).
|
||||
calling ``catch_discover_tests``. This provides a mechanism for globally
|
||||
selecting a preferred test discovery behavior.
|
||||
|
||||
_Note that on Apple Silicon with the Xcode generator you must use `PRE_TEST`,
|
||||
e.g. `catch_discover_tests(tests DISCOVERY_MODE PRE_TEST)`. With the default
|
||||
`POST_BUILD` mode the build fails with `Result: Subprocess killed`, because
|
||||
macOS on Apple Silicon refuses to run unsigned binaries and Xcode code-signs
|
||||
the test executable only **after** the post-build script that `POST_BUILD`
|
||||
mode uses to run it for test discovery. `PRE_TEST` avoids this by delaying
|
||||
discovery until test time, when the executable is already signed. The same
|
||||
limitation affects CMake's `gtest_discover_tests`; see
|
||||
[Catch2 #2411](https://github.com/catchorg/Catch2/issues/2411) and
|
||||
[CMake #21845](https://gitlab.kitware.com/cmake/cmake/-/issues/21845)._
|
||||
|
||||
* `SKIP_IS_FAILURE`
|
||||
|
||||
Skipped tests will be marked as failed instead.
|
||||
|
||||
@@ -26,7 +26,7 @@
|
||||
[Disable statistical analysis of collected benchmark samples](#disable-statistical-analysis-of-collected-benchmark-samples)<br>
|
||||
[Specify the amount of time in milliseconds spent on warming up each test](#specify-the-amount-of-time-in-milliseconds-spent-on-warming-up-each-test)<br>
|
||||
[Usage](#usage)<br>
|
||||
[Specify the section to run](#specify-the-section-to-run)<br>
|
||||
[Specify the section/generator element to run](#specify-the-sectiongenerator-element-to-run)<br>
|
||||
[Filenames as tags](#filenames-as-tags)<br>
|
||||
[Override output colouring](#override-output-colouring)<br>
|
||||
[Test Sharding](#test-sharding)<br>
|
||||
@@ -289,9 +289,10 @@ as follows:
|
||||
| Option | `normal` (default) | `quiet` | `high` |
|
||||
|--------------------|---------------------------------|---------------------|-----------------------------------------|
|
||||
| `--list-tests` | Test names and tags | Test names only | Same as `normal`, plus source code line |
|
||||
| `--list-tags` | Tags and counts | Same as `normal` | Same as `normal` |
|
||||
| `--list-tags` | Tags and counts | Tags only | Same as `normal` |
|
||||
| `--list-reporters` | Reporter names and descriptions | Reporter names only | Same as `normal` |
|
||||
| `--list-listeners` | Listener names and descriptions | Same as `normal` | Same as `normal` |
|
||||
| `--list-listeners` | Listener names and descriptions | Listener names only | Same as `normal` |
|
||||
|
||||
|
||||
<a id="sending-output-to-a-file"></a>
|
||||
## Sending output to a file
|
||||
|
||||
@@ -7,6 +7,8 @@
|
||||
[Writing documentation](#writing-documentation)<br>
|
||||
[Writing code](#writing-code)<br>
|
||||
[CoC](#coc)<br>
|
||||
[Using LLMs when contributing](#using-llms-when-contributing)<br>
|
||||
|
||||
|
||||
So you want to contribute something to Catch2? That's great! Whether it's
|
||||
a bug fix, a new feature, support for additional compilers - or just
|
||||
@@ -332,6 +334,19 @@ When adding new `CATCH_CONFIG` option, there are multiple places to edit:
|
||||
This project has a [CoC](../CODE_OF_CONDUCT.md). Please adhere to it
|
||||
while contributing to Catch2.
|
||||
|
||||
|
||||
## Using LLMs when contributing
|
||||
|
||||
I do not care whether you used LLM for your contribution. What I care
|
||||
about is the quality of the contribution, not whether you made it through
|
||||
prompting LLM, letting GAs run wild, dictated it into the computer, wrote
|
||||
it using your nose or used butterflies to flip the correct bits.
|
||||
|
||||
The flipside of this is that I am also not going to iterate your LLM for
|
||||
you. If a PR looks LLM generated and does not pass the muster to be merged
|
||||
as-is, I am going to close it.
|
||||
|
||||
|
||||
-----------
|
||||
|
||||
_This documentation will always be in-progress as new information comes
|
||||
|
||||
@@ -6,6 +6,7 @@
|
||||
[Built-in matchers](#built-in-matchers)<br>
|
||||
[Writing custom matchers (old style)](#writing-custom-matchers-old-style)<br>
|
||||
[Writing custom matchers (new style)](#writing-custom-matchers-new-style)<br>
|
||||
[Constexpr matchers](#constexpr-matchers)<br>
|
||||
|
||||
Matchers, as popularized by the [Hamcrest](https://en.wikipedia.org/wiki/Hamcrest)
|
||||
framework are an alternative way to write assertions, useful for tests
|
||||
@@ -471,6 +472,42 @@ and new style matchers arbitrarily.
|
||||
> `MatcherGenericBase` lives in `catch2/matchers/catch_matchers_templated.hpp`
|
||||
|
||||
|
||||
## Constexpr matchers
|
||||
|
||||
> Support for constexpr matchers was introduced in Catch2 3.15.0
|
||||
|
||||
When compiled for C++20, the new-style matchers (can) support `constexpr`
|
||||
matching, albeit not `constexpr` stringification. The matcher combinators
|
||||
require C++26 (or at least P2738) to be `constexpr` compatible.
|
||||
|
||||
This can be used together with the `STATIC_REQUIRE_THAT` macro to write
|
||||
matcher-based static assertions like this:
|
||||
|
||||
```cpp
|
||||
TEST_CASE("Constexpr support for matchers", "[constexpr][matchers]") {
|
||||
STATIC_REQUIRE_THAT( 1, MatchAll() );
|
||||
STATIC_REQUIRE_THAT( 1, MatchAll() || MatchAll() );
|
||||
STATIC_REQUIRE_THAT( 1, !!MatchAll() );
|
||||
}
|
||||
```
|
||||
|
||||
### First party constexpr matchers
|
||||
|
||||
Some (but not all) of Catch2's generic matchers support `constexpr`
|
||||
matching. Currently, this includes:
|
||||
|
||||
* `IsEmpty()`
|
||||
* `SizeIs(size_t target_size)`, `SizeIs(Matcher size_matcher)`
|
||||
* `AllMatch(Matcher element_matcher)`
|
||||
* `AnyMatch(Matcher element_matcher)`
|
||||
* `NoneMatch(Matcher element_matcher)`
|
||||
* `AllTrue()`, `AnyTrue()`, `NoneTrue()`
|
||||
* `Contains(T&& target_element, Comparator = std::equal_to<>{})`
|
||||
* `Contains(Matcher element_matcher)`
|
||||
* `RangeEquals(TargetRangeLike&&, Comparator = std::equal_to<>{})`
|
||||
* `UnorderedRangeEquals(TargetRangeLike&&, Comparator = std::equal_to<>{})`
|
||||
|
||||
|
||||
---
|
||||
|
||||
[Home](Readme.md#top)
|
||||
|
||||
@@ -91,6 +91,30 @@ TEST_CASE("STATIC_CHECK showcase", "[traits]") {
|
||||
}
|
||||
```
|
||||
|
||||
* `STATIC_REQUIRE_THAT` and `STATIC_CHECK_THAT`
|
||||
|
||||
> `STATIC_REQUIRE_THAT` and `STATIC_CHECK_THAT` was introduced in Catch2 3.15.0
|
||||
|
||||
`STATIC_{REQUIRE,CHECK}_THAT` are analogous to `STATIC_{REQUIRE,CHECK}`,
|
||||
but for matchers. They are always defined, even if the current compiler
|
||||
does not support `constexpr` matchers, but in that case the compilation
|
||||
will always fail.
|
||||
|
||||
Just like `STATIC_{REQUIRE,CHECK}`, `STATIC_{REQUIRE,CHECK}_THAT` can be
|
||||
delayed into runtime through the `CATCH_CONFIG_RUNTIME_STATIC_REQUIRE`
|
||||
configuration option.
|
||||
|
||||
Example:
|
||||
```cpp
|
||||
TEST_CASE("Constexpr support for matchers", "[constexpr][matchers]") {
|
||||
STATIC_REQUIRE_THAT( 1, MatchAll() );
|
||||
STATIC_REQUIRE_THAT( 1, MatchAll() && MatchAll() );
|
||||
STATIC_REQUIRE_THAT( 1, MatchAll() || MatchAll() );
|
||||
STATIC_REQUIRE_THAT( 1, !!MatchAll() );
|
||||
}
|
||||
```
|
||||
|
||||
|
||||
## Test case related macros
|
||||
|
||||
* `REGISTER_TEST_CASE`
|
||||
|
||||
+76
-1
@@ -2,6 +2,11 @@
|
||||
|
||||
# Release notes
|
||||
**Contents**<br>
|
||||
[3.15.3](#3153)<br>
|
||||
[3.15.2](#3152)<br>
|
||||
[3.15.1](#3151)<br>
|
||||
[3.15.0](#3150)<br>
|
||||
[3.14.0](#3140)<br>
|
||||
[3.13.0](#3130)<br>
|
||||
[3.12.0](#3120)<br>
|
||||
[3.11.0](#3110)<br>
|
||||
@@ -73,6 +78,77 @@
|
||||
[Even Older versions](#even-older-versions)<br>
|
||||
|
||||
|
||||
|
||||
## 3.15.3
|
||||
|
||||
### Fixes
|
||||
* The JSON reporter's number handling is locale-independent. (#3176)
|
||||
* Removed leftover debug message from `catch_discover_tests` (#3177)
|
||||
* Fixed typo in the "Could not jump to the Nth element" exception message (#3181)
|
||||
|
||||
### Improvements
|
||||
* `catch_discover_tests` registers tests in deterministic (alphabetical) order.
|
||||
* `catch_discover_tests` has been rewritten to be massively faster.
|
||||
* Preparing the actual CTest script is significantly faster.
|
||||
* Parsing the JSON test array is significantly faster.
|
||||
* Running without `ADD_TAGS_AS_LABELS` set is 10-15% faster (as opposed to being the same speed) (#3169)
|
||||
* The new implementation is about 4x-5x faster, so registering 500 tests now takes ~350ms (down from 1.1s).
|
||||
* JSON writing is faster
|
||||
* Small improvement in writing non-string values
|
||||
* ~6-40% improvement in writing string values that do not need escaping
|
||||
* ~1-6% improvement in writing string values that do need escaping
|
||||
* Better support for infs and NaNs in JSON output
|
||||
|
||||
|
||||
## 3.15.2
|
||||
|
||||
### Fixes
|
||||
* Fixed `--warn InfiniteGenerators` triggering even if the generator was limited to specific element via path filtering.
|
||||
* Fixed `-Wunused-parameter` triggering in `-fnoexceptions` builds.
|
||||
|
||||
### Improvements
|
||||
* `catch_discover_tests` can handle cases where the binary prints out non-Catch2 output due to global constructors (#3162)
|
||||
|
||||
|
||||
## 3.15.1
|
||||
|
||||
### Fixes
|
||||
* Fixed potential OOB access when looking for start of broken UTF-8 sequence during linebreaking (#3096)
|
||||
* Fixed `TEMPLATE_LIST_TEST_CASE_METHOD` and `CATCH_TEMPLATE_PRODUCT_TEST_CASE` potentially causing ODR violations (#3161)
|
||||
|
||||
|
||||
## 3.15.0
|
||||
|
||||
### Fixes
|
||||
* Fixed ambiguous overload issue with `std::optional<T>` in C++26. (#3095)
|
||||
* Since `std::optional` was turned into a range, the partial specialization of `StringMaker` for range-like types and for `std::optional<T>` were in conflict.
|
||||
|
||||
### Improvements
|
||||
* Simplified `CATCH_REGISTER_ENUM` internals for faster compilation and retrieval.
|
||||
* Successful assertion are slightly (1-2%) faster.
|
||||
* Generic (new-style) matchers support constexpr matching in C++20.
|
||||
* Combining matchers requires C++26.
|
||||
* Catch2-provided generic matchers are all constexpr enabled.
|
||||
* Added `STATIC_REQUIRE_THAT` for compile-time matcher assertions.
|
||||
* This requires the compiler to support enough `constexpr` for matchers, see above.
|
||||
|
||||
|
||||
## 3.14.0
|
||||
|
||||
### Fixes
|
||||
* Added missing `<cstdint>` includes. (#3078)
|
||||
* Fixed suppression of empty variadic macro arguments warning on Clang <19. (#3085)
|
||||
* Fixed `catch_discover_tests` failing during `PRE_TEST` discovery if a target does not have discoverable tests. (#3075)
|
||||
* Fixed build of the main library failing with `CATCH_CONFIG_PREFIX_ALL` defined. (#3087)
|
||||
* JUnit reporter outputs single failed (errored/skipped) assertion per test case. (#1919)
|
||||
|
||||
### Improvements
|
||||
* The default implementation of `--list-tags` and `--list-listeners` has a quiet variant.
|
||||
* Suppressed the new Clang warning about `__COUNTER__` usage. (#3076)
|
||||
* Line-wrapping counts utf-8 codepoints instead of bytes. (#1022, #3086)
|
||||
* Combining character sequences are still miscounted, but Catch2 does not aim to fully support Unicode.
|
||||
|
||||
|
||||
## 3.13.0
|
||||
|
||||
### Fixes
|
||||
@@ -80,7 +156,6 @@
|
||||
* The CLI validation fails instead.
|
||||
* Fixed warning suppression macros being doubly defined when using Clang on Windows (#3060)
|
||||
|
||||
|
||||
### Improvements
|
||||
* Suppressed static analysis 26426 diagnostic for MSVC (#3057)
|
||||
* Renamed the internal deprecation macro from `DEPRECATED` to `CATCH_DEPRECATED` to avoid conflicts (#3058)
|
||||
|
||||
@@ -36,10 +36,12 @@ Tags allow an arbitrary number of additional strings to be associated with a tes
|
||||
|
||||
As an example - given the following test cases:
|
||||
|
||||
TEST_CASE( "A", "[widget]" ) { /* ... */ }
|
||||
TEST_CASE( "B", "[widget]" ) { /* ... */ }
|
||||
TEST_CASE( "C", "[gadget]" ) { /* ... */ }
|
||||
TEST_CASE( "D", "[widget][gadget]" ) { /* ... */ }
|
||||
```cpp
|
||||
TEST_CASE( "A", "[widget]" ) { /* ... */ }
|
||||
TEST_CASE( "B", "[widget]" ) { /* ... */ }
|
||||
TEST_CASE( "C", "[gadget]" ) { /* ... */ }
|
||||
TEST_CASE( "D", "[widget][gadget]" ) { /* ... */ }
|
||||
```
|
||||
|
||||
The tag expression, ```"[widget]"``` selects A, B & D. ```"[gadget]"``` selects C & D. ```"[widget][gadget]"``` selects just D and ```"[widget],[gadget]"``` selects all four test cases.
|
||||
|
||||
|
||||
+8
-2
@@ -44,7 +44,7 @@ same as the Catch name; see also ``TEST_PREFIX`` and ``TEST_SUFFIX``.
|
||||
|
||||
``catch_discover_tests`` sets up a post-build command on the test executable
|
||||
that generates the list of tests by parsing the output from running the test
|
||||
with the ``--list-test-names-only`` argument. This ensures that the full
|
||||
with the ``--list-tests --reporter json`` argument. This ensures that the full
|
||||
list of tests is obtained. Since test discovery occurs at build time, it is
|
||||
not necessary to re-run CMake when the list of tests changes.
|
||||
However, it requires that :prop_tgt:`CROSSCOMPILING_EMULATOR` is properly set
|
||||
@@ -67,7 +67,7 @@ same as the Catch name; see also ``TEST_PREFIX`` and ``TEST_SUFFIX``.
|
||||
|
||||
``TEST_SPEC arg1...``
|
||||
Specifies test cases, wildcarded test cases, tags and tag expressions to
|
||||
pass to the Catch executable with the ``--list-test-names-only`` argument.
|
||||
pass to the Catch executable when listing the tests.
|
||||
|
||||
``EXTRA_ARGS arg1...``
|
||||
Any extra arguments to pass on the command line to each test case.
|
||||
@@ -146,6 +146,12 @@ same as the Catch name; see also ``TEST_PREFIX`` and ``TEST_SUFFIX``.
|
||||
calling ``catch_discover_tests``. This provides a mechanism for globally selecting
|
||||
a preferred test discovery behavior without having to modify each call site.
|
||||
|
||||
On Apple Silicon with the Xcode generator you must use ``PRE_TEST``. With the
|
||||
default ``POST_BUILD`` mode the build fails with ``Result: Subprocess killed``,
|
||||
because macOS on Apple Silicon refuses to run unsigned binaries and Xcode
|
||||
code-signs the test executable only after the post-build script that
|
||||
``POST_BUILD`` mode uses to run it for test discovery. See Catch2 issue #2411.
|
||||
|
||||
``SKIP_IS_FAILURE``
|
||||
Disables skipped test detection.
|
||||
|
||||
|
||||
+230
-20
@@ -1,7 +1,146 @@
|
||||
# Distributed under the OSI-approved BSD 3-Clause License. See accompanying
|
||||
# file Copyright.txt or https://cmake.org/licensing for details.
|
||||
|
||||
function(add_command NAME)
|
||||
# Because natively using CMake's JSON processing for arrays leads to quadratic
|
||||
# running times, we do terrible hack and split JSON array by object boundary
|
||||
# + commas and try to reconstruct valid JSON objects. During this, we need
|
||||
# to replace CMake characters that could be in the test name/tags with
|
||||
# placeholder, so it doesn't affect CMake's processing of the strings/lists
|
||||
# we create during the parsing.
|
||||
#
|
||||
# We use 0x01, 0x02, 0x03, and 0x04 as placeholder bytes, as those cannot
|
||||
# exist in JSON unescaped.
|
||||
#
|
||||
# 0x01 <=> ';' (CMake list separator)
|
||||
# 0x02 == element boundary marker used while splitting the tests array
|
||||
# 0x03 <-> '[' (opens a CMake bracket-argument context)
|
||||
# 0x04 <-> ']' (closes a CMake bracket-argument context)
|
||||
#
|
||||
string(ASCII 1 _SemicolonEscape)
|
||||
string(ASCII 2 _BoundaryEscape)
|
||||
string(ASCII 3 _OpenBracketEscape)
|
||||
string(ASCII 4 _CloseBracketEscape)
|
||||
|
||||
|
||||
# Placeholder bytes in the listing would break our parsing hack, so
|
||||
# we check they don't exist. They shouldn't exist in valid JSON, but
|
||||
# the reporter might not be escaping them properly.
|
||||
function(validate_input_noescapes listing_var)
|
||||
foreach(byte "${_SemicolonEscape}" "${_BoundaryEscape}" "${_OpenBracketEscape}" "${_CloseBracketEscape}")
|
||||
string(FIND "${${listing_var}}" "${byte}" found)
|
||||
if(NOT found EQUAL -1)
|
||||
message(FATAL_ERROR
|
||||
"The test listing contains raw control byte (0x01-0x04) which should not "
|
||||
"be there. This means either bad escaping in JSON reporter, or corrupted file. "
|
||||
)
|
||||
endif()
|
||||
endforeach()
|
||||
endfunction()
|
||||
|
||||
|
||||
# Replaces relevant characters with their placeholders, see the top of this file.
|
||||
# Modifies argument `var` in place.
|
||||
function(magic_escape_chars var)
|
||||
set(value "${${var}}")
|
||||
string(REPLACE ";" "${_SemicolonEscape}" value "${value}")
|
||||
string(REPLACE "[" "${_OpenBracketEscape}" value "${value}")
|
||||
string(REPLACE "]" "${_CloseBracketEscape}" value "${value}")
|
||||
set(${var} "${value}" PARENT_SCOPE)
|
||||
endfunction()
|
||||
|
||||
|
||||
# Turns placeholders back into original characters, see the top of this file.
|
||||
# Modifies argument `var` in place.
|
||||
function(magic_unescape_chars var)
|
||||
set(value "${${var}}")
|
||||
string(REPLACE "${_SemicolonEscape}" ";" value "${value}")
|
||||
string(REPLACE "${_OpenBracketEscape}" "[" value "${value}")
|
||||
string(REPLACE "${_CloseBracketEscape}" "]" value "${value}")
|
||||
set(${var} "${value}" PARENT_SCOPE)
|
||||
endfunction()
|
||||
|
||||
|
||||
# Abuses knowledge of Catch2's JSON reporter output for listing tests to
|
||||
# split JSON array of the test listings into a CMake list of strings,
|
||||
# with each element being the JSON string of one array entry.
|
||||
#
|
||||
# This avoids the terrible quadratic running time of using CMake's JSON
|
||||
# support to parse the JSON reporter output "properly", where the whole
|
||||
# JSON array of tests is parsed again for every element. Instead, we can
|
||||
# use the CMake's API to only parse the individual test's objects, which
|
||||
# are usually small and only have to be reparsed fixed number of times
|
||||
# (once for test names, once for labels).
|
||||
#
|
||||
# We process the string representing the JSON array by splitting it on
|
||||
# `}<ws>*,<ws>*{` and then checking for each chunk whether it is a valid
|
||||
# JSON object representing Catch2's test. If not (e.g. because we split
|
||||
# on the presence of `}<ws>*,<ws>*{` inside a test name), then we append
|
||||
# the next chunk to the current one and check again. And again, until
|
||||
# we get back to a valid JSON.
|
||||
#
|
||||
# Note that to support passing the object strings back from the function,
|
||||
# they will still contain the placeholders and need to be unescaped before
|
||||
# further processing (e.g. sending them into CMake's JSON parsing API).
|
||||
function(split_json_array json_array_var out_var)
|
||||
# We have to pass the input by var name to avoid CMake processing
|
||||
# the input as an arg.
|
||||
set(json_in "${${json_array_var}}")
|
||||
|
||||
# Strip the array brackets at the start and end of the JSON array.
|
||||
# Must happen before we escape the other [] instances below from the
|
||||
# actual array data.
|
||||
string(REGEX REPLACE "^[ \t\r\n]*\\[" "" json_in "${json_in}")
|
||||
string(REGEX REPLACE "\\][ \t\r\n]*$" "" json_in "${json_in}")
|
||||
|
||||
magic_escape_chars(json_in)
|
||||
|
||||
# We need to keep the whitespace around comma around, so that if we
|
||||
# split inside the test object, we can reconstruct it losslessly.
|
||||
string(REGEX REPLACE "(}[ \t\r\n]*)[,]([ \t\r\n]*{)" "\\1${_BoundaryEscape}\\2" json_in "${json_in}")
|
||||
|
||||
# We escaped all list separators above, so now we can turn the JSON
|
||||
# string into a CMake list of fragments in single pass.
|
||||
string(REPLACE "${_BoundaryEscape}" ";" fragments "${json_in}")
|
||||
|
||||
# And now we have to reconstruct the actual JSON structure from fragments.
|
||||
set(array_elements "")
|
||||
set(accumulator "")
|
||||
foreach(next_fragment IN LISTS fragments)
|
||||
if(accumulator)
|
||||
set(accumulator "${accumulator},${next_fragment}")
|
||||
else()
|
||||
set(accumulator "${next_fragment}")
|
||||
endif()
|
||||
|
||||
# Because the fragments (might) contain invalid JSON characters due
|
||||
# to escaping, we have to unescape it before checking if we can parse it.
|
||||
set(maybe_json "${accumulator}")
|
||||
magic_unescape_chars(maybe_json)
|
||||
string(JSON unused ERROR_VARIABLE err GET "${maybe_json}" "name")
|
||||
if(err STREQUAL "NOTFOUND")
|
||||
list(APPEND array_elements "${accumulator}")
|
||||
set(accumulator "")
|
||||
endif()
|
||||
endforeach()
|
||||
|
||||
set(${out_var} "${array_elements}" PARENT_SCOPE)
|
||||
endfunction()
|
||||
|
||||
# TBD: Further possible optimization is that most arguments for per-test
|
||||
# `prepare_command` call are constant across one invocation of
|
||||
# `catch_discover_tests`, and thus need checking and escaping only
|
||||
# once, instead of for each test.
|
||||
# This would provide nice speed-up of the actual command preparation,
|
||||
# but it will make the script much harder to read, and it is utterly
|
||||
# dwarfed by the quadratic scaling of parsing JSON arrays in CMake.
|
||||
|
||||
|
||||
# Prepare command with escaped (bracketed) arguments and return it via `_Command` out variable.
|
||||
#
|
||||
# To avoid quadratic performance when concatenating all commands together,
|
||||
# the actual concatenation must be done by the caller, by appending it
|
||||
# into a string of all other commands.
|
||||
function(prepare_command NAME)
|
||||
set(_args "")
|
||||
# use ARGV* instead of ARGN, because ARGN splits arrays into multiple arguments
|
||||
math(EXPR _last_arg ${ARGC}-1)
|
||||
@@ -13,11 +152,45 @@ function(add_command NAME)
|
||||
set(_args "${_args} ${_arg}")
|
||||
endif()
|
||||
endforeach()
|
||||
set(script "${script}${NAME}(${_args})\n" PARENT_SCOPE)
|
||||
set(_Command "${NAME}(${_args})\n" PARENT_SCOPE)
|
||||
endfunction()
|
||||
|
||||
# Generates random filename in the temp folder.
|
||||
# Temp folder is retrieved by checking env vars from various platforms.
|
||||
function(make_temp_file_path OUT_VARIABLE FALLBACK_PATH)
|
||||
set(TEMP_DIR "")
|
||||
set(ENV_VARS
|
||||
# From XDG base dir specification
|
||||
XDG_RUNTIME_DIR
|
||||
# From POSIX standard
|
||||
TMPDIR
|
||||
# From Windows
|
||||
TMP
|
||||
TEMP
|
||||
)
|
||||
|
||||
foreach(var ${ENV_VARS})
|
||||
if(DEFINED ENV{${var}} AND NOT "$ENV{${var}}" STREQUAL "")
|
||||
set(TEMP_DIR "$ENV{${var}}")
|
||||
break()
|
||||
endif()
|
||||
endforeach()
|
||||
|
||||
# If all checks fail, we use the fallback path
|
||||
if(TEMP_DIR STREQUAL "")
|
||||
set(TEMP_DIR "${FALLBACK_PATH}")
|
||||
endif()
|
||||
|
||||
file(TO_CMAKE_PATH "${TEMP_DIR}" TEMP_DIR)
|
||||
|
||||
# Generate the random file name
|
||||
string(RANDOM LENGTH 8 RAND_ID)
|
||||
set(FINAL_TEMP_PATH "${TEMP_DIR}/Catch2-test-listing.${RAND_ID}.json")
|
||||
|
||||
set(${OUT_VARIABLE} "${FINAL_TEMP_PATH}" PARENT_SCOPE)
|
||||
endfunction()
|
||||
|
||||
function(catch_discover_tests_impl)
|
||||
|
||||
cmake_parse_arguments(
|
||||
""
|
||||
""
|
||||
@@ -26,6 +199,12 @@ function(catch_discover_tests_impl)
|
||||
${ARGN}
|
||||
)
|
||||
|
||||
# We periodically append to the output file below, so we have to ensure
|
||||
# that it is empty at the start, or we get duplicated test scripts.
|
||||
file(REMOVE "${_CTEST_FILE}")
|
||||
# Size (in Bytes) at which the intermediate `script` var is dumped to file.
|
||||
set(_WriteToFileThreshold 50000)
|
||||
|
||||
set(add_tags "${_ADD_TAGS_AS_LABELS}")
|
||||
set(prefix "${_TEST_PREFIX}")
|
||||
set(suffix "${_TEST_SUFFIX}")
|
||||
@@ -72,8 +251,14 @@ function(catch_discover_tests_impl)
|
||||
set(ENV{DYLD_FRAMEWORK_PATH} "${paths}")
|
||||
endif()
|
||||
|
||||
make_temp_file_path(listing_output_path "${_TEST_WORKING_DIR}")
|
||||
|
||||
execute_process(
|
||||
COMMAND ${_TEST_EXECUTOR} "${_TEST_EXECUTABLE}" ${spec} --list-tests --reporter json
|
||||
COMMAND ${_TEST_EXECUTOR} "${_TEST_EXECUTABLE}" ${spec}
|
||||
--list-tests
|
||||
--reporter json
|
||||
--out "${listing_output_path}"
|
||||
--order lex # Make sure the output order, and thus test registration order, is consistent across runs.
|
||||
OUTPUT_VARIABLE listing_output
|
||||
RESULT_VARIABLE result
|
||||
WORKING_DIRECTORY "${_TEST_WORKING_DIR}"
|
||||
@@ -86,6 +271,11 @@ function(catch_discover_tests_impl)
|
||||
)
|
||||
endif()
|
||||
|
||||
# Read the JSON output back from the output file and validate it.
|
||||
file(READ ${listing_output_path} listing_output)
|
||||
file(REMOVE ${listing_output_path})
|
||||
validate_input_noescapes(listing_output)
|
||||
|
||||
# Prepare reporter
|
||||
if(reporter)
|
||||
set(reporter_arg "--reporter ${reporter}")
|
||||
@@ -140,21 +330,34 @@ function(catch_discover_tests_impl)
|
||||
message(FATAL_ERROR "Unsupported catch output version: '${version}'")
|
||||
endif()
|
||||
|
||||
# Speed-up reparsing by cutting away unneeded parts of JSON.
|
||||
# Extract just the JSON array with tests and then split them into
|
||||
# individual objects.
|
||||
string(JSON test_listing GET "${listing_output}" "listings" "tests")
|
||||
string(JSON num_tests LENGTH "${test_listing}")
|
||||
split_json_array(test_listing tests)
|
||||
|
||||
# Exit early if no tests are detected
|
||||
if(num_tests STREQUAL "0")
|
||||
if(NOT tests)
|
||||
file(WRITE "${_CTEST_FILE}" "")
|
||||
return()
|
||||
endif()
|
||||
|
||||
# CMake's foreach-RANGE is inclusive, so we have to subtract 1
|
||||
math(EXPR num_tests "${num_tests} - 1")
|
||||
# Each element in the tests is JSON-string representing one test object.
|
||||
# We have to parse it and then turn it into CTest script commands.
|
||||
foreach(single_test IN LISTS tests)
|
||||
# Because appending to the same string in CMake has quadratic runtime,
|
||||
# we flush the script into the file periodically to avoid the worst case.
|
||||
string(LENGTH "${script}" script_len)
|
||||
if (script_len GREATER _WriteToFileThreshold)
|
||||
file(APPEND "${_CTEST_FILE}" "${script}")
|
||||
set(script "")
|
||||
endif()
|
||||
|
||||
foreach(idx RANGE ${num_tests})
|
||||
string(JSON single_test GET ${test_listing} ${idx})
|
||||
string(JSON test_tags GET "${single_test}" "tags")
|
||||
# The elements are still escaped and contain JSON-invalid characters,
|
||||
# they have to be unescaped before parsing them as JSON.
|
||||
magic_unescape_chars(single_test)
|
||||
if(add_tags)
|
||||
string(JSON test_tags GET "${single_test}" "tags")
|
||||
endif()
|
||||
string(JSON plain_name GET "${single_test}" "name")
|
||||
|
||||
# Escape characters in test case names that would be parsed by Catch2
|
||||
@@ -170,7 +373,7 @@ function(catch_discover_tests_impl)
|
||||
endif()
|
||||
|
||||
# ...and add to script
|
||||
add_command(add_test
|
||||
prepare_command(add_test
|
||||
"${prefix}${plain_name}${suffix}"
|
||||
${_TEST_EXECUTOR}
|
||||
"${_TEST_EXECUTABLE}"
|
||||
@@ -179,12 +382,14 @@ function(catch_discover_tests_impl)
|
||||
"${reporter_arg}"
|
||||
"${output_dir_arg}"
|
||||
)
|
||||
add_command(set_tests_properties
|
||||
string(APPEND script "${_Command}")
|
||||
prepare_command(set_tests_properties
|
||||
"${prefix}${plain_name}${suffix}"
|
||||
PROPERTIES
|
||||
WORKING_DIRECTORY "${_TEST_WORKING_DIR}"
|
||||
${properties}
|
||||
)
|
||||
string(APPEND script "${_Command}")
|
||||
|
||||
if(add_tags)
|
||||
string(JSON num_tags LENGTH "${test_tags}")
|
||||
@@ -204,19 +409,21 @@ function(catch_discover_tests_impl)
|
||||
list(APPEND tag_list "${a_tag}")
|
||||
endforeach()
|
||||
|
||||
add_command(set_tests_properties
|
||||
prepare_command(set_tests_properties
|
||||
"${prefix}${plain_name}${suffix}"
|
||||
PROPERTIES
|
||||
LABELS "${tag_list}"
|
||||
)
|
||||
string(APPEND script "${_Command}")
|
||||
endif()
|
||||
endif(add_tags)
|
||||
|
||||
if(environment_modifications)
|
||||
add_command(set_tests_properties
|
||||
prepare_command(set_tests_properties
|
||||
"${prefix}${plain_name}${suffix}"
|
||||
PROPERTIES
|
||||
ENVIRONMENT_MODIFICATION "${environment_modifications}")
|
||||
string(APPEND script "${_Command}")
|
||||
endif()
|
||||
|
||||
list(APPEND tests "${prefix}${plain_name}${suffix}")
|
||||
@@ -224,13 +431,16 @@ function(catch_discover_tests_impl)
|
||||
|
||||
# Create a list of all discovered tests, which users may use to e.g. set
|
||||
# properties on the tests
|
||||
add_command(set ${_TEST_LIST} ${tests})
|
||||
prepare_command(set ${_TEST_LIST} ${tests})
|
||||
string(APPEND script "${_Command}")
|
||||
|
||||
# Write CTest script
|
||||
file(WRITE "${_CTEST_FILE}" "${script}")
|
||||
# Write any script leftovers we have
|
||||
file(APPEND "${_CTEST_FILE}" "${script}")
|
||||
endfunction()
|
||||
|
||||
if(CMAKE_SCRIPT_MODE_FILE)
|
||||
# To enable `include`ing this file in the unit test scripts, we only run
|
||||
# the impl if an actual `TEST_EXECUTABLE` is provided.
|
||||
if(CMAKE_SCRIPT_MODE_FILE AND DEFINED TEST_EXECUTABLE)
|
||||
catch_discover_tests_impl(
|
||||
TEST_EXECUTABLE ${TEST_EXECUTABLE}
|
||||
TEST_EXECUTOR ${TEST_EXECUTOR}
|
||||
|
||||
+297
-190
@@ -6,8 +6,8 @@
|
||||
|
||||
// SPDX-License-Identifier: BSL-1.0
|
||||
|
||||
// Catch v3.13.0
|
||||
// Generated: 2026-02-15 22:55:00.269529
|
||||
// Catch v3.15.3
|
||||
// Generated: 2026-07-26 22:17:52.418168
|
||||
// ----------------------------------------------------------
|
||||
// This file is an amalgamation of multiple different files.
|
||||
// You probably shouldn't edit it directly.
|
||||
@@ -74,11 +74,11 @@ namespace Catch {
|
||||
samples.data(), samples.data() + samples.size() );
|
||||
|
||||
auto wrap_estimate = [](Estimate<double> e) {
|
||||
return Estimate<FDuration> {
|
||||
FDuration(e.point),
|
||||
FDuration(e.lower_bound),
|
||||
FDuration(e.upper_bound),
|
||||
e.confidence_interval,
|
||||
return Estimate<FDuration>{
|
||||
FDuration( e.point ),
|
||||
FDuration( e.lower_bound ),
|
||||
FDuration( e.upper_bound ),
|
||||
e.confidence_interval,
|
||||
};
|
||||
};
|
||||
std::vector<FDuration> samples2;
|
||||
@@ -147,7 +147,7 @@ namespace Catch {
|
||||
namespace Catch {
|
||||
namespace Benchmark {
|
||||
namespace Detail {
|
||||
struct optimized_away_error : std::exception {
|
||||
struct optimized_away_error final : std::exception {
|
||||
const char* what() const noexcept override;
|
||||
};
|
||||
|
||||
@@ -995,6 +995,7 @@ namespace Catch {
|
||||
|
||||
|
||||
|
||||
|
||||
#include <cassert>
|
||||
#include <stack>
|
||||
|
||||
@@ -1007,7 +1008,7 @@ namespace Catch {
|
||||
m_messageId( builder.m_info.sequence ) {
|
||||
MessageInfo info( CATCH_MOVE( builder.m_info ) );
|
||||
info.message = builder.m_stream.str();
|
||||
IResultCapture::pushScopedMessage( CATCH_MOVE( info ) );
|
||||
Detail::pushScopedMessage( CATCH_MOVE( info ) );
|
||||
}
|
||||
|
||||
ScopedMessage::ScopedMessage( ScopedMessage&& old ) noexcept:
|
||||
@@ -1016,7 +1017,7 @@ namespace Catch {
|
||||
}
|
||||
|
||||
ScopedMessage::~ScopedMessage() {
|
||||
if ( !m_moved ) { IResultCapture::popScopedMessage( m_messageId ); }
|
||||
if ( !m_moved ) { Detail::popScopedMessage( m_messageId ); }
|
||||
}
|
||||
|
||||
|
||||
@@ -1088,7 +1089,7 @@ namespace Catch {
|
||||
assert( m_captured == m_messages.size() );
|
||||
if ( m_isScoped ) {
|
||||
for ( auto const& message : m_messages ) {
|
||||
IResultCapture::popScopedMessage( message.sequence );
|
||||
Detail::popScopedMessage( message.sequence );
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -1097,9 +1098,9 @@ namespace Catch {
|
||||
assert( index < m_messages.size() );
|
||||
m_messages[index].message += value;
|
||||
if ( m_isScoped ) {
|
||||
IResultCapture::pushScopedMessage( CATCH_MOVE( m_messages[index] ) );
|
||||
Detail::pushScopedMessage( CATCH_MOVE( m_messages[index] ) );
|
||||
} else {
|
||||
IResultCapture::addUnscopedMessage( CATCH_MOVE( m_messages[index] ) );
|
||||
Detail::addUnscopedMessage( CATCH_MOVE( m_messages[index] ) );
|
||||
}
|
||||
m_captured++;
|
||||
}
|
||||
@@ -1160,9 +1161,6 @@ namespace Catch {
|
||||
CATCH_INTERNAL_ERROR("Attempted to register active exception under CATCH_CONFIG_DISABLE_EXCEPTIONS!");
|
||||
#endif
|
||||
}
|
||||
IMutableEnumValuesRegistry& getMutableEnumValuesRegistry() override {
|
||||
return m_enumValuesRegistry;
|
||||
}
|
||||
|
||||
private:
|
||||
TestRegistry m_testCaseRegistry;
|
||||
@@ -1170,7 +1168,6 @@ namespace Catch {
|
||||
ExceptionTranslatorRegistry m_exceptionTranslatorRegistry;
|
||||
TagAliasRegistry m_tagAliasRegistry;
|
||||
StartupExceptionRegistry m_exceptionRegistry;
|
||||
Detail::EnumValuesRegistry m_enumValuesRegistry;
|
||||
};
|
||||
}
|
||||
|
||||
@@ -2397,7 +2394,7 @@ namespace Catch {
|
||||
}
|
||||
|
||||
Version const& libraryVersion() {
|
||||
static Version version( 3, 13, 0, "", 0 );
|
||||
static Version version( 3, 15, 3, "", 0 );
|
||||
return version;
|
||||
}
|
||||
|
||||
@@ -2544,7 +2541,7 @@ namespace Catch {
|
||||
bool isValid = next();
|
||||
if ( !isValid ) {
|
||||
Detail::throw_generator_exception(
|
||||
"Coud not jump to Nth element: not enough elements" );
|
||||
"Could not jump to Nth element: not enough elements" );
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -2686,13 +2683,17 @@ namespace Catch {
|
||||
|
||||
namespace Catch {
|
||||
|
||||
void AssertionHandler::finishIncomplete() {
|
||||
m_resultCapture.handleIncomplete( m_assertionInfo );
|
||||
}
|
||||
|
||||
AssertionHandler::AssertionHandler
|
||||
( StringRef macroName,
|
||||
SourceLineInfo const& lineInfo,
|
||||
StringRef capturedExpression,
|
||||
ResultDisposition::Flags resultDisposition )
|
||||
: m_assertionInfo{ macroName, lineInfo, capturedExpression, resultDisposition },
|
||||
m_resultCapture( getResultCapture() )
|
||||
m_resultCapture( static_cast<RunContext&>(getResultCapture()) )
|
||||
{
|
||||
m_resultCapture.notifyAssertionStarted( m_assertionInfo );
|
||||
}
|
||||
@@ -4088,12 +4089,11 @@ namespace Catch {
|
||||
|
||||
|
||||
|
||||
|
||||
#include <cassert>
|
||||
|
||||
namespace Catch {
|
||||
|
||||
IMutableEnumValuesRegistry::~IMutableEnumValuesRegistry() = default;
|
||||
|
||||
namespace Detail {
|
||||
|
||||
namespace {
|
||||
@@ -4119,9 +4119,7 @@ namespace Catch {
|
||||
return parsed;
|
||||
}
|
||||
|
||||
EnumInfo::~EnumInfo() = default;
|
||||
|
||||
StringRef EnumInfo::lookup( int value ) const {
|
||||
StringRef EnumInfo::lookup( int64_t value ) const {
|
||||
for( auto const& valueToName : m_values ) {
|
||||
if( valueToName.first == value )
|
||||
return valueToName.second;
|
||||
@@ -4129,25 +4127,20 @@ namespace Catch {
|
||||
return "{** unexpected enum value **}"_sr;
|
||||
}
|
||||
|
||||
Catch::Detail::unique_ptr<EnumInfo> makeEnumInfo( StringRef enumName, StringRef allValueNames, std::vector<int> const& values ) {
|
||||
auto enumInfo = Catch::Detail::make_unique<EnumInfo>();
|
||||
enumInfo->m_name = enumName;
|
||||
enumInfo->m_values.reserve( values.size() );
|
||||
EnumInfo makeEnumInfo( StringRef enumName, StringRef allValueNames, std::vector<int64_t> const& values ) {
|
||||
EnumInfo enumInfo;
|
||||
enumInfo.m_name = enumName;
|
||||
enumInfo.m_values.reserve( values.size() );
|
||||
|
||||
const auto valueNames = Catch::Detail::parseEnums( allValueNames );
|
||||
assert( valueNames.size() == values.size() );
|
||||
std::size_t i = 0;
|
||||
for( auto value : values )
|
||||
enumInfo->m_values.emplace_back(value, valueNames[i++]);
|
||||
for (size_t i = 0; i < values.size(); ++i) {
|
||||
enumInfo.m_values.emplace_back( values[i], valueNames[i] );
|
||||
}
|
||||
|
||||
return enumInfo;
|
||||
}
|
||||
|
||||
EnumInfo const& EnumValuesRegistry::registerEnum( StringRef enumName, StringRef allValueNames, std::vector<int> const& values ) {
|
||||
m_enumInfos.push_back(makeEnumInfo(enumName, allValueNames, values));
|
||||
return *m_enumInfos.back();
|
||||
}
|
||||
|
||||
} // Detail
|
||||
} // Catch
|
||||
|
||||
@@ -4685,12 +4678,28 @@ namespace Detail {
|
||||
|
||||
|
||||
|
||||
#include <cmath>
|
||||
#include <locale>
|
||||
|
||||
namespace Catch {
|
||||
|
||||
namespace {
|
||||
static bool needsEscape( char c ) {
|
||||
return c == '"' || c == '\\' || c == '\b' || c == '\f' ||
|
||||
c == '\n' || c == '\r' || c == '\t';
|
||||
struct EscapeLUT {
|
||||
bool escape[256] = {};
|
||||
constexpr EscapeLUT() {
|
||||
escape[static_cast<unsigned char>( '"' )] = true;
|
||||
escape[static_cast<unsigned char>( '\\' )] = true;
|
||||
escape[static_cast<unsigned char>( '\b' )] = true;
|
||||
escape[static_cast<unsigned char>( '\f' )] = true;
|
||||
escape[static_cast<unsigned char>( '\n' )] = true;
|
||||
escape[static_cast<unsigned char>( '\r' )] = true;
|
||||
escape[static_cast<unsigned char>( '\t' )] = true;
|
||||
}
|
||||
};
|
||||
static constexpr EscapeLUT escapeLUT{};
|
||||
|
||||
static constexpr bool needsEscape( char c ) {
|
||||
return escapeLUT.escape[static_cast<unsigned char>( c )];
|
||||
}
|
||||
|
||||
static Catch::StringRef makeEscapeStringRef( char c ) {
|
||||
@@ -4802,7 +4811,10 @@ namespace Catch {
|
||||
|
||||
JsonValueWriter::JsonValueWriter( std::ostream& os,
|
||||
std::uint64_t indent_level ):
|
||||
m_os{ os }, m_indent_level{ indent_level } {}
|
||||
m_os{ os }, m_indent_level{ indent_level } {
|
||||
// We use C locale so that writing of numerical values is locale-independent.
|
||||
m_sstream.imbue( std::locale::classic() );
|
||||
}
|
||||
|
||||
JsonObjectWriter JsonValueWriter::writeObject() && {
|
||||
return JsonObjectWriter{ m_os, m_indent_level };
|
||||
@@ -4816,26 +4828,55 @@ namespace Catch {
|
||||
writeImpl( value, true );
|
||||
}
|
||||
|
||||
void JsonValueWriter::write( float value ) && {
|
||||
writeFloatingPoint( value );
|
||||
}
|
||||
|
||||
void JsonValueWriter::write( double value ) && {
|
||||
writeFloatingPoint( value );
|
||||
}
|
||||
|
||||
void JsonValueWriter::write( bool value ) && {
|
||||
writeImpl( value ? "true"_sr : "false"_sr, false );
|
||||
}
|
||||
|
||||
template <typename T>
|
||||
void JsonValueWriter::writeFloatingPoint( T value ) {
|
||||
if ( Catch::isnan( value ) ) {
|
||||
writeImpl( "NaN"_sr, false );
|
||||
} else if ( std::isinf( value ) ) {
|
||||
writeImpl( value < 0 ? "-Infinity"_sr : "Infinity"_sr, false );
|
||||
} else {
|
||||
m_sstream << value;
|
||||
writeImpl( m_sstream.str(), false );
|
||||
}
|
||||
}
|
||||
|
||||
void JsonValueWriter::writeImpl( Catch::StringRef value, bool quote ) {
|
||||
if ( quote ) { m_os << '"'; }
|
||||
size_t current_start = 0;
|
||||
for ( size_t i = 0; i < value.size(); ++i ) {
|
||||
if ( needsEscape( value[i] ) ) {
|
||||
if ( current_start < i ) {
|
||||
m_os << value.substr( current_start, i - current_start );
|
||||
// Escaping only makes sense for actual strings, which are passed
|
||||
// with `quote == true`. Non-quoted values are things like bools
|
||||
// and numbers, which cannot create inputs that need escaping.
|
||||
if ( !quote ) {
|
||||
m_os << value;
|
||||
} else {
|
||||
m_os << '"';
|
||||
size_t current_start = 0;
|
||||
for ( size_t i = 0; i < value.size(); ++i ) {
|
||||
if ( needsEscape( value[i] ) ) {
|
||||
if ( current_start < i ) {
|
||||
m_os
|
||||
<< value.substr( current_start, i - current_start );
|
||||
}
|
||||
m_os << makeEscapeStringRef( value[i] );
|
||||
current_start = i + 1;
|
||||
}
|
||||
m_os << makeEscapeStringRef( value[i] );
|
||||
current_start = i + 1;
|
||||
}
|
||||
if ( current_start < value.size() ) {
|
||||
m_os << value.substr( current_start,
|
||||
value.size() - current_start );
|
||||
}
|
||||
m_os << '"';
|
||||
}
|
||||
if ( current_start < value.size() ) {
|
||||
m_os << value.substr( current_start, value.size() - current_start );
|
||||
}
|
||||
if ( quote ) { m_os << '"'; }
|
||||
}
|
||||
|
||||
} // namespace Catch
|
||||
@@ -5072,7 +5113,7 @@ namespace Catch {
|
||||
namespace {
|
||||
//! A no-op implementation, used if no reporter wants output
|
||||
//! redirection.
|
||||
class NoopRedirect : public OutputRedirect {
|
||||
class NoopRedirect final : public OutputRedirect {
|
||||
void activateImpl() override {}
|
||||
void deactivateImpl() override {}
|
||||
std::string getStdout() override { return {}; }
|
||||
@@ -5109,7 +5150,7 @@ namespace Catch {
|
||||
* Redirects the `std::cout`, `std::cerr`, `std::clog` streams,
|
||||
* but does not touch the actual `stdout`/`stderr` file descriptors.
|
||||
*/
|
||||
class StreamRedirect : public OutputRedirect {
|
||||
class StreamRedirect final : public OutputRedirect {
|
||||
ReusableStringStream m_redirectedOut, m_redirectedErr;
|
||||
RedirectedStreamNew m_cout, m_cerr, m_clog;
|
||||
|
||||
@@ -5220,7 +5261,7 @@ namespace Catch {
|
||||
* Works by replacing the file descriptors numbered 1 and 2
|
||||
* with an open temporary file.
|
||||
*/
|
||||
class FileRedirect : public OutputRedirect {
|
||||
class FileRedirect final : public OutputRedirect {
|
||||
TempFile m_outFile, m_errFile;
|
||||
int m_originalOut = -1;
|
||||
int m_originalErr = -1;
|
||||
@@ -5888,9 +5929,14 @@ namespace Catch {
|
||||
// can be, so the tracker has to throw for a wrong
|
||||
// filter to stop the execution flow.
|
||||
if (filter.type == PathFilter::For::Section) {
|
||||
// TBD: Explicit SKIP, or new exception that says
|
||||
// "don't continue", but doesn't show in totals?
|
||||
SKIP();
|
||||
// We want the semantics of `SKIP()`, but we inline it
|
||||
// to avoid issues with conditionally prefixed macros
|
||||
INTERNAL_CATCH_MSG(
|
||||
"SKIP",
|
||||
Catch::ResultWas::ExplicitSkip,
|
||||
Catch::ResultDisposition::Normal,
|
||||
"" );
|
||||
Catch::Detail::Unreachable();
|
||||
}
|
||||
// '*' is the wildcard for "all elements in generator"
|
||||
// used for filtering sections below the generator, but
|
||||
@@ -6018,6 +6064,8 @@ namespace Catch {
|
||||
auto getGenerator() const -> GeneratorBasePtr const& override {
|
||||
return m_generator;
|
||||
}
|
||||
|
||||
bool isFilteredImpl() const override { return m_isFiltered; }
|
||||
};
|
||||
} // namespace
|
||||
}
|
||||
@@ -6131,6 +6179,25 @@ namespace Catch {
|
||||
}
|
||||
CATCH_INTERNAL_STOP_WARNINGS_SUPPRESSION
|
||||
|
||||
|
||||
void pushScopedMessage( MessageInfo&& message ) {
|
||||
Detail::g_messageHolder().addScopedMessage( CATCH_MOVE( message ) );
|
||||
}
|
||||
|
||||
void popScopedMessage( unsigned int messageId ) {
|
||||
Detail::g_messageHolder().removeMessage( messageId );
|
||||
}
|
||||
|
||||
void emplaceUnscopedMessage( MessageBuilder&& builder ) {
|
||||
Detail::g_messageHolder().addUnscopedMessage( CATCH_MOVE( builder ) );
|
||||
}
|
||||
|
||||
void addUnscopedMessage( MessageInfo&& message ) {
|
||||
Detail::g_messageHolder().addUnscopedMessage( CATCH_MOVE( message ) );
|
||||
}
|
||||
|
||||
bool lastAssertionPassed() { return Detail::g_lastAssertionPassed; }
|
||||
|
||||
} // namespace Detail
|
||||
|
||||
RunContext::RunContext(IConfig const* _config, IEventListenerPtr&& reporter)
|
||||
@@ -6264,14 +6331,13 @@ namespace Catch {
|
||||
Detail::g_lastAssertionPassed = true;
|
||||
} else if (!result.succeeded()) {
|
||||
Detail::g_lastAssertionPassed = false;
|
||||
if (result.isOk()) {
|
||||
}
|
||||
else if( m_activeTestCase->getTestCaseInfo().okToFail() ) // Read from a shared state established before the threads could start, this is fine
|
||||
if (result.isOk()) {}
|
||||
else if( m_activeTestCase->getTestCaseInfo().okToFail() ) { // Read from a shared state established before the threads could start, this is fine
|
||||
m_atomicAssertionCount.failedButOk++;
|
||||
else
|
||||
} else {
|
||||
m_atomicAssertionCount.failed++;
|
||||
}
|
||||
else {
|
||||
}
|
||||
} else {
|
||||
Detail::g_lastAssertionPassed = true;
|
||||
}
|
||||
|
||||
@@ -6344,13 +6410,6 @@ namespace Catch {
|
||||
SourceLineInfo lineInfo,
|
||||
Generators::GeneratorBasePtr&& generator ) {
|
||||
|
||||
// TBD: Do we want to avoid the warning if the generator is filtered?
|
||||
if ( m_config->warnAboutInfiniteGenerators() &&
|
||||
!generator->isFinite() ) {
|
||||
// TBD: Would it be better to expand this macro inline?
|
||||
FAIL( "GENERATE() would run infinitely" );
|
||||
}
|
||||
|
||||
auto nameAndLoc = TestCaseTracking::NameAndLocation( static_cast<std::string>( generatorName ), lineInfo );
|
||||
auto& currentTracker = m_trackerContext.currentTracker();
|
||||
assert(
|
||||
@@ -6363,11 +6422,24 @@ namespace Catch {
|
||||
m_trackerContext,
|
||||
¤tTracker,
|
||||
CATCH_MOVE( generator ) );
|
||||
auto ret = newTracker.get();
|
||||
|
||||
// The warning shouldn't fire if the generator is infinite, **but** filtered down.
|
||||
if ( m_config->warnAboutInfiniteGenerators() &&
|
||||
!newTracker->m_generator->isFinite() &&
|
||||
!newTracker->isFiltered() ) {
|
||||
// We want the semantics of `FAIL()`, but we inline it
|
||||
// to avoid issues with conditionally prefixed macros
|
||||
INTERNAL_CATCH_MSG( "FAIL",
|
||||
Catch::ResultWas::ExplicitFailure,
|
||||
Catch::ResultDisposition::Normal,
|
||||
"GENERATE() would run infinitely" );
|
||||
}
|
||||
|
||||
auto returnPtr = newTracker.get();
|
||||
currentTracker.addChild( CATCH_MOVE( newTracker ) );
|
||||
|
||||
ret->open();
|
||||
return ret;
|
||||
returnPtr->open();
|
||||
return returnPtr;
|
||||
}
|
||||
|
||||
bool RunContext::testForMissingAssertions(Counts& assertions) {
|
||||
@@ -6449,7 +6521,7 @@ namespace Catch {
|
||||
// and since IResultCapture::getLastResult is deprecated,
|
||||
// we will leave it as is, until it is finally removed.
|
||||
Detail::LockGuard _( m_assertionMutex );
|
||||
return &(*m_lastResult);
|
||||
return &*m_lastResult;
|
||||
}
|
||||
|
||||
void RunContext::exceptionEarlyReported() {
|
||||
@@ -6523,10 +6595,6 @@ namespace Catch {
|
||||
m_reporter->testRunEnded(TestRunStats(m_runInfo, m_totals, false));
|
||||
}
|
||||
|
||||
bool RunContext::lastAssertionPassed() {
|
||||
return Detail::g_lastAssertionPassed;
|
||||
}
|
||||
|
||||
void RunContext::assertionPassedFastPath(SourceLineInfo lineInfo) {
|
||||
// We want to save the line info for better experience with unexpected assertions
|
||||
Detail::g_lastKnownLineInfo = lineInfo;
|
||||
@@ -6705,7 +6773,7 @@ namespace Catch {
|
||||
}
|
||||
|
||||
void RunContext::populateReaction( AssertionReaction& reaction,
|
||||
bool has_normal_disposition ) {
|
||||
bool has_normal_disposition ) const {
|
||||
reaction.shouldDebugBreak = m_shouldDebugBreak;
|
||||
reaction.shouldThrow = aborting() || has_normal_disposition;
|
||||
}
|
||||
@@ -6756,22 +6824,6 @@ namespace Catch {
|
||||
}
|
||||
}
|
||||
|
||||
void IResultCapture::pushScopedMessage( MessageInfo&& message ) {
|
||||
Detail::g_messageHolder().addScopedMessage( CATCH_MOVE( message ) );
|
||||
}
|
||||
|
||||
void IResultCapture::popScopedMessage( unsigned int messageId ) {
|
||||
Detail::g_messageHolder().removeMessage( messageId );
|
||||
}
|
||||
|
||||
void IResultCapture::emplaceUnscopedMessage( MessageBuilder&& builder ) {
|
||||
Detail::g_messageHolder().addUnscopedMessage( CATCH_MOVE( builder ) );
|
||||
}
|
||||
|
||||
void IResultCapture::addUnscopedMessage( MessageInfo&& message ) {
|
||||
Detail::g_messageHolder().addUnscopedMessage( CATCH_MOVE( message ) );
|
||||
}
|
||||
|
||||
void seedRng(IConfig const& config) {
|
||||
sharedRng().seed(config.rngSeed());
|
||||
}
|
||||
@@ -7103,10 +7155,11 @@ namespace Catch {
|
||||
|
||||
TagAlias const* TagAliasRegistry::find( std::string const& alias ) const {
|
||||
auto it = m_registry.find( alias );
|
||||
if( it != m_registry.end() )
|
||||
return &(it->second);
|
||||
else
|
||||
if ( it != m_registry.end() ) {
|
||||
return &it->second;
|
||||
} else {
|
||||
return nullptr;
|
||||
}
|
||||
}
|
||||
|
||||
std::string TagAliasRegistry::expandAliases( std::string const& unexpandedTestSpec ) const {
|
||||
@@ -7469,6 +7522,28 @@ namespace TestCaseTracking {
|
||||
m_ctx.setCurrentTracker( this );
|
||||
}
|
||||
|
||||
bool SectionTracker::isFilteredImpl() const {
|
||||
// TBD: This is currently _very_ similar to the block in `isComplete`.
|
||||
// Is this neccessarily that way, or just accident of current semantics?
|
||||
const size_t filterIndex =
|
||||
m_newStyleFilters ? m_allTrackerDepth : m_sectionOnlyDepth;
|
||||
|
||||
if ( filterIndex < m_filterRef->size() ) {
|
||||
// 1) New style filter must explicitly target section
|
||||
if ( m_newStyleFilters && ( *m_filterRef )[filterIndex].type !=
|
||||
PathFilter::For::Section ) {
|
||||
return true;
|
||||
}
|
||||
// 2) Both style filters must match the trimmed name exactly
|
||||
if ( m_trimmed_name !=
|
||||
StringRef( ( *m_filterRef )[filterIndex].filter ) ) {
|
||||
return true;
|
||||
}
|
||||
}
|
||||
|
||||
return false;
|
||||
}
|
||||
|
||||
SectionTracker::SectionTracker( NameAndLocation&& nameAndLocation, TrackerContext& ctx, ITracker* parent )
|
||||
: TrackerBase( CATCH_MOVE(nameAndLocation), ctx, parent ),
|
||||
m_trimmed_name(trim(StringRef(ITracker::nameAndLocation().name)))
|
||||
@@ -7553,7 +7628,7 @@ namespace Catch {
|
||||
|
||||
void throw_test_failure_exception() {
|
||||
#if !defined( CATCH_CONFIG_DISABLE_EXCEPTIONS )
|
||||
throw TestFailureException{};
|
||||
throw TestFailureException{}; //NOLINT(bugprone-std-exception-baseclass)
|
||||
#else
|
||||
CATCH_ERROR( "Test failure requires aborting test!" );
|
||||
#endif
|
||||
@@ -7561,7 +7636,7 @@ namespace Catch {
|
||||
|
||||
void throw_test_skip_exception() {
|
||||
#if !defined( CATCH_CONFIG_DISABLE_EXCEPTIONS )
|
||||
throw Catch::TestSkipException();
|
||||
throw Catch::TestSkipException(); //NOLINT(bugprone-std-exception-baseclass)
|
||||
#else
|
||||
CATCH_ERROR( "Explicitly skipping tests during runtime requires exceptions" );
|
||||
#endif
|
||||
@@ -7893,6 +7968,10 @@ namespace {
|
||||
return std::memchr( chars, c, sizeof( chars ) - 1 ) != nullptr;
|
||||
}
|
||||
|
||||
bool isUtf8ContinuationByte( char c ) {
|
||||
return ( static_cast<unsigned char>( c ) & 0xC0 ) == 0x80;
|
||||
}
|
||||
|
||||
} // namespace
|
||||
|
||||
namespace Catch {
|
||||
@@ -7919,6 +7998,11 @@ namespace Catch {
|
||||
if ( it != m_string.end() ) {
|
||||
++m_size;
|
||||
++it;
|
||||
// Skip UTF-8 continuation bytes
|
||||
while ( it != m_string.end() &&
|
||||
isUtf8ContinuationByte( *it ) ) {
|
||||
++it;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -7981,6 +8065,11 @@ namespace Catch {
|
||||
void AnsiSkippingString::const_iterator::advance() {
|
||||
assert( m_it != m_string->end() );
|
||||
m_it++;
|
||||
// Skip UTF-8 continuation bytes
|
||||
while ( m_it != m_string->end() &&
|
||||
isUtf8ContinuationByte( *m_it ) ) {
|
||||
m_it++;
|
||||
}
|
||||
tryParseAnsiEscapes();
|
||||
}
|
||||
|
||||
@@ -8000,6 +8089,11 @@ namespace Catch {
|
||||
assert( *m_it == '\033' );
|
||||
m_it--;
|
||||
}
|
||||
// Skip back over UTF-8 continuation bytes to the leading byte
|
||||
while ( m_it != m_string->begin() &&
|
||||
isUtf8ContinuationByte( *m_it ) ) {
|
||||
m_it--;
|
||||
}
|
||||
}
|
||||
|
||||
static bool isBoundary( AnsiSkippingString const& line,
|
||||
@@ -8646,13 +8740,13 @@ namespace Catch {
|
||||
namespace Matchers {
|
||||
|
||||
std::string MatcherUntypedBase::toString() const {
|
||||
if (m_cachedToString.empty()) {
|
||||
m_cachedToString = describe();
|
||||
}
|
||||
return m_cachedToString;
|
||||
return describe();
|
||||
}
|
||||
|
||||
MatcherUntypedBase::~MatcherUntypedBase() = default;
|
||||
std::string MatcherUntypedBase::describe() const {
|
||||
using namespace std::string_literals;
|
||||
return "Undescribed matcher"s;
|
||||
}
|
||||
|
||||
} // namespace Matchers
|
||||
} // namespace Catch
|
||||
@@ -8673,14 +8767,6 @@ namespace Matchers {
|
||||
return sstr.str();
|
||||
}
|
||||
|
||||
IsEmptyMatcher IsEmpty() {
|
||||
return {};
|
||||
}
|
||||
|
||||
HasSizeMatcher SizeIs(std::size_t sz) {
|
||||
return HasSizeMatcher{ sz };
|
||||
}
|
||||
|
||||
} // end namespace Matchers
|
||||
} // end namespace Catch
|
||||
|
||||
@@ -8934,17 +9020,13 @@ std::string Catch::Matchers::Detail::finalizeDescription(const std::string& desc
|
||||
|
||||
namespace Catch {
|
||||
namespace Matchers {
|
||||
std::string AllTrueMatcher::describe() const { return "contains only true"; }
|
||||
|
||||
AllTrueMatcher AllTrue() { return AllTrueMatcher{}; }
|
||||
std::string AllTrueMatcher::describe() const { return "contains only true"; }
|
||||
|
||||
std::string NoneTrueMatcher::describe() const { return "contains no true"; }
|
||||
|
||||
NoneTrueMatcher NoneTrue() { return NoneTrueMatcher{}; }
|
||||
|
||||
std::string AnyTrueMatcher::describe() const { return "contains at least one true"; }
|
||||
|
||||
AnyTrueMatcher AnyTrue() { return AnyTrueMatcher{}; }
|
||||
} // namespace Matchers
|
||||
} // namespace Catch
|
||||
|
||||
@@ -9057,7 +9139,11 @@ namespace Matchers {
|
||||
|
||||
namespace Catch {
|
||||
namespace Matchers {
|
||||
MatcherGenericBase::~MatcherGenericBase() = default;
|
||||
|
||||
std::string MatcherGenericBase::describe() const {
|
||||
using namespace std::string_literals;
|
||||
return "Undescribed generic matcher"s;
|
||||
}
|
||||
|
||||
namespace Detail {
|
||||
|
||||
@@ -9152,12 +9238,12 @@ namespace Catch {
|
||||
|
||||
void ReporterBase::listReporters(
|
||||
std::vector<ReporterDescription> const& descriptions ) {
|
||||
defaultListReporters(m_stream, descriptions, m_config->verbosity());
|
||||
defaultListReporters( m_stream, descriptions, m_config->verbosity() );
|
||||
}
|
||||
|
||||
void ReporterBase::listListeners(
|
||||
std::vector<ListenerDescription> const& descriptions ) {
|
||||
defaultListListeners( m_stream, descriptions );
|
||||
defaultListListeners( m_stream, descriptions, m_config->verbosity() );
|
||||
}
|
||||
|
||||
void ReporterBase::listTests(std::vector<TestCaseHandle> const& tests) {
|
||||
@@ -9169,7 +9255,7 @@ namespace Catch {
|
||||
}
|
||||
|
||||
void ReporterBase::listTags(std::vector<TagInfo> const& tags) {
|
||||
defaultListTags( m_stream, tags, m_config->hasTestFilters() );
|
||||
defaultListTags( m_stream, tags, m_config->hasTestFilters(), m_config->verbosity() );
|
||||
}
|
||||
|
||||
} // namespace Catch
|
||||
@@ -10080,9 +10166,8 @@ namespace Catch {
|
||||
bool operator()(
|
||||
Detail::unique_ptr<CumulativeReporterBase::SectionNode> const&
|
||||
node ) const {
|
||||
return (
|
||||
( node->stats.sectionInfo.name == m_other.name ) &&
|
||||
( node->stats.sectionInfo.lineInfo == m_other.lineInfo ) );
|
||||
return node->stats.sectionInfo.name == m_other.name
|
||||
&& node->stats.sectionInfo.lineInfo == m_other.lineInfo;
|
||||
}
|
||||
void operator=( BySectionInfo const& ) = delete;
|
||||
|
||||
@@ -10380,7 +10465,15 @@ namespace Catch {
|
||||
}
|
||||
|
||||
void defaultListListeners( std::ostream& out,
|
||||
std::vector<ListenerDescription> const& descriptions ) {
|
||||
std::vector<ListenerDescription> const& descriptions,
|
||||
Verbosity verbosity ) {
|
||||
if ( verbosity == Verbosity::Quiet ) {
|
||||
for ( auto const& desc : descriptions ) {
|
||||
out << desc.name << '\n';
|
||||
}
|
||||
return;
|
||||
}
|
||||
|
||||
out << "Registered listeners:\n";
|
||||
|
||||
if(descriptions.empty()) {
|
||||
@@ -10413,7 +10506,14 @@ namespace Catch {
|
||||
|
||||
void defaultListTags( std::ostream& out,
|
||||
std::vector<TagInfo> const& tags,
|
||||
bool isFiltered ) {
|
||||
bool isFiltered,
|
||||
Verbosity verbosity ) {
|
||||
if (verbosity == Verbosity::Quiet) {
|
||||
for (auto const& tagCount : tags) {
|
||||
out << tagCount.all() << '\n';
|
||||
}
|
||||
return;
|
||||
}
|
||||
if ( isFiltered ) {
|
||||
out << "Tags for matching test cases:\n";
|
||||
} else {
|
||||
@@ -10421,7 +10521,7 @@ namespace Catch {
|
||||
}
|
||||
|
||||
// minimum whitespace to pad tag counts, possibly overwritten below
|
||||
size_t maxTagCountLen = 2;
|
||||
int maxTagCountLen = 2;
|
||||
|
||||
// determine necessary padding for tag count column
|
||||
if ( ! tags.empty() ) {
|
||||
@@ -10432,11 +10532,11 @@ namespace Catch {
|
||||
return lhs.count < rhs.count;
|
||||
} )
|
||||
->count;
|
||||
|
||||
|
||||
// more padding necessary for 3+ digits
|
||||
if (maxTagCount >= 100) {
|
||||
auto numDigits = 1 + std::floor( std::log10( maxTagCount ) );
|
||||
maxTagCountLen = static_cast<size_t>( numDigits );
|
||||
maxTagCountLen = static_cast<int>( numDigits );
|
||||
}
|
||||
}
|
||||
|
||||
@@ -11189,70 +11289,77 @@ namespace Catch {
|
||||
void JunitReporter::writeAssertions( SectionNode const& sectionNode ) {
|
||||
for (auto const& assertionOrBenchmark : sectionNode.assertionsAndBenchmarks) {
|
||||
if (assertionOrBenchmark.isAssertion()) {
|
||||
writeAssertion(assertionOrBenchmark.asAssertion());
|
||||
// JUnit XML format supports only 1 error/failure/skip
|
||||
// assertion elements per test case
|
||||
if (writeAssertion(assertionOrBenchmark.asAssertion())) {
|
||||
break;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
void JunitReporter::writeAssertion( AssertionStats const& stats ) {
|
||||
bool JunitReporter::writeAssertion( AssertionStats const& stats ) {
|
||||
AssertionResult const& result = stats.assertionResult;
|
||||
if ( !result.isOk() ||
|
||||
result.getResultType() == ResultWas::ExplicitSkip ) {
|
||||
std::string elementName;
|
||||
switch( result.getResultType() ) {
|
||||
case ResultWas::ThrewException:
|
||||
case ResultWas::FatalErrorCondition:
|
||||
elementName = "error";
|
||||
break;
|
||||
case ResultWas::ExplicitFailure:
|
||||
case ResultWas::ExpressionFailed:
|
||||
case ResultWas::DidntThrowException:
|
||||
elementName = "failure";
|
||||
break;
|
||||
case ResultWas::ExplicitSkip:
|
||||
elementName = "skipped";
|
||||
break;
|
||||
// We should never see these here:
|
||||
case ResultWas::Info:
|
||||
case ResultWas::Warning:
|
||||
case ResultWas::Ok:
|
||||
case ResultWas::Unknown:
|
||||
case ResultWas::FailureBit:
|
||||
case ResultWas::Exception:
|
||||
elementName = "internalError";
|
||||
break;
|
||||
}
|
||||
|
||||
XmlWriter::ScopedElement e = xml.scopedElement( elementName );
|
||||
|
||||
xml.writeAttribute( "message"_sr, result.getExpression() );
|
||||
xml.writeAttribute( "type"_sr, result.getTestMacroName() );
|
||||
|
||||
ReusableStringStream rss;
|
||||
if ( result.getResultType() == ResultWas::ExplicitSkip ) {
|
||||
rss << "SKIPPED\n";
|
||||
} else {
|
||||
rss << "FAILED" << ":\n";
|
||||
if (result.hasExpression()) {
|
||||
rss << " ";
|
||||
rss << result.getExpressionInMacro();
|
||||
rss << '\n';
|
||||
}
|
||||
if (result.hasExpandedExpression()) {
|
||||
rss << "with expansion:\n";
|
||||
rss << TextFlow::Column(result.getExpandedExpression()).indent(2) << '\n';
|
||||
}
|
||||
}
|
||||
|
||||
if( result.hasMessage() )
|
||||
rss << result.getMessage() << '\n';
|
||||
for( auto const& msg : stats.infoMessages )
|
||||
if( msg.type == ResultWas::Info )
|
||||
rss << msg.message << '\n';
|
||||
|
||||
rss << "at " << result.getSourceInfo();
|
||||
xml.writeText( rss.str(), XmlFormatting::Newline );
|
||||
if ( result.isOk() &&
|
||||
result.getResultType() != ResultWas::ExplicitSkip ) {
|
||||
return false;
|
||||
}
|
||||
std::string elementName;
|
||||
switch ( result.getResultType() ) {
|
||||
case ResultWas::ThrewException:
|
||||
case ResultWas::FatalErrorCondition:
|
||||
elementName = "error";
|
||||
break;
|
||||
case ResultWas::ExplicitFailure:
|
||||
case ResultWas::ExpressionFailed:
|
||||
case ResultWas::DidntThrowException:
|
||||
elementName = "failure";
|
||||
break;
|
||||
case ResultWas::ExplicitSkip:
|
||||
elementName = "skipped";
|
||||
break;
|
||||
// We should never see these here:
|
||||
case ResultWas::Info:
|
||||
case ResultWas::Warning:
|
||||
case ResultWas::Ok:
|
||||
case ResultWas::Unknown:
|
||||
case ResultWas::FailureBit:
|
||||
case ResultWas::Exception:
|
||||
elementName = "internalError";
|
||||
break;
|
||||
}
|
||||
|
||||
XmlWriter::ScopedElement e = xml.scopedElement( elementName );
|
||||
|
||||
xml.writeAttribute( "message"_sr, result.getExpression() );
|
||||
xml.writeAttribute( "type"_sr, result.getTestMacroName() );
|
||||
|
||||
ReusableStringStream rss;
|
||||
if ( result.getResultType() == ResultWas::ExplicitSkip ) {
|
||||
rss << "SKIPPED\n";
|
||||
} else {
|
||||
rss << "FAILED:\n";
|
||||
if ( result.hasExpression() ) {
|
||||
rss << " ";
|
||||
rss << result.getExpressionInMacro();
|
||||
rss << '\n';
|
||||
}
|
||||
if ( result.hasExpandedExpression() ) {
|
||||
rss << "with expansion:\n";
|
||||
rss << TextFlow::Column( result.getExpandedExpression() )
|
||||
.indent( 2 )
|
||||
<< '\n';
|
||||
}
|
||||
}
|
||||
|
||||
if ( result.hasMessage() ) { rss << result.getMessage() << '\n'; }
|
||||
for ( auto const& msg : stats.infoMessages ) {
|
||||
if ( msg.type == ResultWas::Info ) { rss << msg.message << '\n'; }
|
||||
}
|
||||
|
||||
rss << "at " << result.getSourceInfo();
|
||||
xml.writeText( rss.str(), XmlFormatting::Newline );
|
||||
return true;
|
||||
}
|
||||
|
||||
} // end namespace Catch
|
||||
|
||||
+402
-280
File diff suppressed because it is too large
Load Diff
@@ -9,6 +9,7 @@
|
||||
|
||||
#include <catch2/internal/catch_test_spec_parser.hpp>
|
||||
#include <catch2/internal/catch_tag_alias_registry.hpp>
|
||||
#include <cstdint>
|
||||
|
||||
extern "C" int LLVMFuzzerTestOneInput(const uint8_t *Data, size_t Size) {
|
||||
|
||||
|
||||
@@ -13,6 +13,7 @@
|
||||
|
||||
#include <string>
|
||||
#include <string_view>
|
||||
#include <cstdint>
|
||||
|
||||
|
||||
template<class Callback>
|
||||
|
||||
+1
-1
@@ -8,7 +8,7 @@
|
||||
project(
|
||||
'catch2',
|
||||
'cpp',
|
||||
version: '3.13.0', # CML version placeholder, don't delete
|
||||
version: '3.15.3', # CML version placeholder, don't delete
|
||||
license: 'BSL-1.0',
|
||||
meson_version: '>=0.54.1',
|
||||
)
|
||||
|
||||
+2
-3
@@ -87,7 +87,7 @@ set(IMPL_HEADERS
|
||||
${SOURCES_DIR}/internal/catch_decomposer.hpp
|
||||
${SOURCES_DIR}/internal/catch_deprecation_macro.hpp
|
||||
${SOURCES_DIR}/internal/catch_enforce.hpp
|
||||
${SOURCES_DIR}/internal/catch_enum_values_registry.hpp
|
||||
${SOURCES_DIR}/internal/catch_enum_info.hpp
|
||||
${SOURCES_DIR}/internal/catch_errno_guard.hpp
|
||||
${SOURCES_DIR}/internal/catch_exception_translator_registry.hpp
|
||||
${SOURCES_DIR}/internal/catch_fatal_condition_handler.hpp
|
||||
@@ -181,7 +181,7 @@ set(IMPL_SOURCES
|
||||
${SOURCES_DIR}/internal/catch_debugger.cpp
|
||||
${SOURCES_DIR}/internal/catch_decomposer.cpp
|
||||
${SOURCES_DIR}/internal/catch_enforce.cpp
|
||||
${SOURCES_DIR}/internal/catch_enum_values_registry.cpp
|
||||
${SOURCES_DIR}/internal/catch_enum_info.cpp
|
||||
${SOURCES_DIR}/internal/catch_errno_guard.cpp
|
||||
${SOURCES_DIR}/internal/catch_exception_translator_registry.cpp
|
||||
${SOURCES_DIR}/internal/catch_fatal_condition_handler.cpp
|
||||
@@ -227,7 +227,6 @@ set(INTERFACE_HEADERS
|
||||
${SOURCES_DIR}/interfaces/catch_interfaces_all.hpp
|
||||
${SOURCES_DIR}/interfaces/catch_interfaces_capture.hpp
|
||||
${SOURCES_DIR}/interfaces/catch_interfaces_config.hpp
|
||||
${SOURCES_DIR}/interfaces/catch_interfaces_enum_values_registry.hpp
|
||||
${SOURCES_DIR}/interfaces/catch_interfaces_exception.hpp
|
||||
${SOURCES_DIR}/interfaces/catch_interfaces_generatortracker.hpp
|
||||
${SOURCES_DIR}/interfaces/catch_interfaces_registry_hub.hpp
|
||||
|
||||
@@ -36,11 +36,11 @@ namespace Catch {
|
||||
samples.data(), samples.data() + samples.size() );
|
||||
|
||||
auto wrap_estimate = [](Estimate<double> e) {
|
||||
return Estimate<FDuration> {
|
||||
FDuration(e.point),
|
||||
FDuration(e.lower_bound),
|
||||
FDuration(e.upper_bound),
|
||||
e.confidence_interval,
|
||||
return Estimate<FDuration>{
|
||||
FDuration( e.point ),
|
||||
FDuration( e.lower_bound ),
|
||||
FDuration( e.upper_bound ),
|
||||
e.confidence_interval,
|
||||
};
|
||||
};
|
||||
std::vector<FDuration> samples2;
|
||||
|
||||
@@ -41,7 +41,7 @@ namespace Catch {
|
||||
callable& operator=(callable&&) = default;
|
||||
};
|
||||
template <typename Fun>
|
||||
struct model : public callable {
|
||||
struct model final : public callable {
|
||||
model(Fun&& fun_) : fun(CATCH_MOVE(fun_)) {}
|
||||
model(Fun const& fun_) : fun(fun_) {}
|
||||
|
||||
|
||||
@@ -8,6 +8,7 @@
|
||||
#ifndef CATCH_BENCHMARK_STATS_HPP_INCLUDED
|
||||
#define CATCH_BENCHMARK_STATS_HPP_INCLUDED
|
||||
|
||||
#include <catch2/benchmark/catch_clock.hpp>
|
||||
#include <catch2/benchmark/catch_estimate.hpp>
|
||||
#include <catch2/benchmark/catch_outlier_classification.hpp>
|
||||
// The fwd decl & default specialization needs to be seen by VS2017 before
|
||||
|
||||
@@ -8,14 +8,16 @@
|
||||
#ifndef CATCH_BENCHMARK_STATS_FWD_HPP_INCLUDED
|
||||
#define CATCH_BENCHMARK_STATS_FWD_HPP_INCLUDED
|
||||
|
||||
#include <catch2/benchmark/catch_clock.hpp>
|
||||
|
||||
namespace Catch {
|
||||
|
||||
namespace Detail {
|
||||
struct DummyTemplateArgPlaceholder;
|
||||
}
|
||||
|
||||
// We cannot forward declare the type with default template argument
|
||||
// multiple times, so it is split out into a separate header so that
|
||||
// we can prevent multiple declarations in dependencies
|
||||
template <typename Duration = Benchmark::FDuration>
|
||||
template <typename Duration = Detail::DummyTemplateArgPlaceholder>
|
||||
struct BenchmarkStats;
|
||||
|
||||
} // end namespace Catch
|
||||
|
||||
@@ -14,7 +14,7 @@
|
||||
namespace Catch {
|
||||
namespace Benchmark {
|
||||
namespace Detail {
|
||||
struct optimized_away_error : std::exception {
|
||||
struct optimized_away_error final : std::exception {
|
||||
const char* what() const noexcept override;
|
||||
};
|
||||
|
||||
|
||||
@@ -68,7 +68,7 @@
|
||||
#include <catch2/internal/catch_decomposer.hpp>
|
||||
#include <catch2/internal/catch_deprecation_macro.hpp>
|
||||
#include <catch2/internal/catch_enforce.hpp>
|
||||
#include <catch2/internal/catch_enum_values_registry.hpp>
|
||||
#include <catch2/internal/catch_enum_info.hpp>
|
||||
#include <catch2/internal/catch_errno_guard.hpp>
|
||||
#include <catch2/internal/catch_exception_translator_registry.hpp>
|
||||
#include <catch2/internal/catch_fatal_condition_handler.hpp>
|
||||
|
||||
@@ -5,8 +5,8 @@
|
||||
// https://www.boost.org/LICENSE_1_0.txt)
|
||||
|
||||
// SPDX-License-Identifier: BSL-1.0
|
||||
|
||||
#include <catch2/catch_message.hpp>
|
||||
#include <catch2/interfaces/catch_interfaces_capture.hpp>
|
||||
#include <catch2/internal/catch_enforce.hpp>
|
||||
#include <catch2/internal/catch_move_and_forward.hpp>
|
||||
|
||||
@@ -22,7 +22,7 @@ namespace Catch {
|
||||
m_messageId( builder.m_info.sequence ) {
|
||||
MessageInfo info( CATCH_MOVE( builder.m_info ) );
|
||||
info.message = builder.m_stream.str();
|
||||
IResultCapture::pushScopedMessage( CATCH_MOVE( info ) );
|
||||
Detail::pushScopedMessage( CATCH_MOVE( info ) );
|
||||
}
|
||||
|
||||
ScopedMessage::ScopedMessage( ScopedMessage&& old ) noexcept:
|
||||
@@ -31,7 +31,7 @@ namespace Catch {
|
||||
}
|
||||
|
||||
ScopedMessage::~ScopedMessage() {
|
||||
if ( !m_moved ) { IResultCapture::popScopedMessage( m_messageId ); }
|
||||
if ( !m_moved ) { Detail::popScopedMessage( m_messageId ); }
|
||||
}
|
||||
|
||||
|
||||
@@ -103,7 +103,7 @@ namespace Catch {
|
||||
assert( m_captured == m_messages.size() );
|
||||
if ( m_isScoped ) {
|
||||
for ( auto const& message : m_messages ) {
|
||||
IResultCapture::popScopedMessage( message.sequence );
|
||||
Detail::popScopedMessage( message.sequence );
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -112,9 +112,9 @@ namespace Catch {
|
||||
assert( index < m_messages.size() );
|
||||
m_messages[index].message += value;
|
||||
if ( m_isScoped ) {
|
||||
IResultCapture::pushScopedMessage( CATCH_MOVE( m_messages[index] ) );
|
||||
Detail::pushScopedMessage( CATCH_MOVE( m_messages[index] ) );
|
||||
} else {
|
||||
IResultCapture::addUnscopedMessage( CATCH_MOVE( m_messages[index] ) );
|
||||
Detail::addUnscopedMessage( CATCH_MOVE( m_messages[index] ) );
|
||||
}
|
||||
m_captured++;
|
||||
}
|
||||
|
||||
@@ -13,16 +13,28 @@
|
||||
#include <catch2/internal/catch_reusable_string_stream.hpp>
|
||||
#include <catch2/internal/catch_stream_end_stop.hpp>
|
||||
#include <catch2/internal/catch_message_info.hpp>
|
||||
#include <catch2/internal/catch_move_and_forward.hpp>
|
||||
#include <catch2/catch_tostring.hpp>
|
||||
#include <catch2/interfaces/catch_interfaces_capture.hpp>
|
||||
|
||||
#include <string>
|
||||
#include <vector>
|
||||
|
||||
namespace Catch {
|
||||
struct MessageInfo;
|
||||
struct MessageBuilder;
|
||||
|
||||
namespace Detail {
|
||||
// The message state affecting functions have to be defined in
|
||||
// the TU where the thread-local message holders are defined.
|
||||
// Currently this is catch_run_context.cpp
|
||||
|
||||
void pushScopedMessage( MessageInfo&& message );
|
||||
void popScopedMessage( unsigned int messageId );
|
||||
void addUnscopedMessage( MessageInfo&& message );
|
||||
void emplaceUnscopedMessage( MessageBuilder&& builder );
|
||||
} // namespace Detail
|
||||
|
||||
struct SourceLineInfo;
|
||||
class IResultCapture;
|
||||
|
||||
struct MessageStream {
|
||||
|
||||
@@ -112,7 +124,7 @@ namespace Catch {
|
||||
|
||||
///////////////////////////////////////////////////////////////////////////////
|
||||
#define INTERNAL_CATCH_UNSCOPED_INFO( macroName, log ) \
|
||||
Catch::IResultCapture::emplaceUnscopedMessage( Catch::MessageBuilder( macroName##_catch_sr, CATCH_INTERNAL_LINEINFO, Catch::ResultWas::Info ) << log )
|
||||
Catch::Detail::emplaceUnscopedMessage( Catch::MessageBuilder( macroName##_catch_sr, CATCH_INTERNAL_LINEINFO, Catch::ResultWas::Info ) << log )
|
||||
|
||||
|
||||
#if defined(CATCH_CONFIG_PREFIX_MESSAGES) && !defined(CATCH_CONFIG_DISABLE)
|
||||
|
||||
@@ -15,7 +15,6 @@
|
||||
#include <catch2/internal/catch_tag_alias_registry.hpp>
|
||||
#include <catch2/internal/catch_startup_exception_registry.hpp>
|
||||
#include <catch2/internal/catch_singletons.hpp>
|
||||
#include <catch2/internal/catch_enum_values_registry.hpp>
|
||||
#include <catch2/catch_test_case_info.hpp>
|
||||
#include <catch2/internal/catch_noncopyable.hpp>
|
||||
#include <catch2/interfaces/catch_interfaces_reporter_factory.hpp>
|
||||
@@ -72,9 +71,6 @@ namespace Catch {
|
||||
CATCH_INTERNAL_ERROR("Attempted to register active exception under CATCH_CONFIG_DISABLE_EXCEPTIONS!");
|
||||
#endif
|
||||
}
|
||||
IMutableEnumValuesRegistry& getMutableEnumValuesRegistry() override {
|
||||
return m_enumValuesRegistry;
|
||||
}
|
||||
|
||||
private:
|
||||
TestRegistry m_testCaseRegistry;
|
||||
@@ -82,7 +78,6 @@ namespace Catch {
|
||||
ExceptionTranslatorRegistry m_exceptionTranslatorRegistry;
|
||||
TagAliasRegistry m_tagAliasRegistry;
|
||||
StartupExceptionRegistry m_exceptionRegistry;
|
||||
Detail::EnumValuesRegistry m_enumValuesRegistry;
|
||||
};
|
||||
}
|
||||
|
||||
|
||||
@@ -21,6 +21,7 @@
|
||||
#include <catch2/reporters/catch_reporter_multi.hpp>
|
||||
#include <catch2/internal/catch_reporter_registry.hpp>
|
||||
#include <catch2/interfaces/catch_interfaces_reporter_factory.hpp>
|
||||
#include <catch2/interfaces/catch_interfaces_registry_hub.hpp>
|
||||
#include <catch2/internal/catch_move_and_forward.hpp>
|
||||
#include <catch2/internal/catch_stdstreams.hpp>
|
||||
#include <catch2/internal/catch_istream.hpp>
|
||||
|
||||
@@ -18,7 +18,8 @@
|
||||
#include <catch2/internal/catch_config_wchar.hpp>
|
||||
#include <catch2/internal/catch_reusable_string_stream.hpp>
|
||||
#include <catch2/internal/catch_void_type.hpp>
|
||||
#include <catch2/interfaces/catch_interfaces_enum_values_registry.hpp>
|
||||
#include <catch2/internal/catch_enum_info.hpp>
|
||||
#include <catch2/internal/catch_stringref.hpp>
|
||||
|
||||
#ifdef CATCH_CONFIG_CPP17_STRING_VIEW
|
||||
#include <string_view>
|
||||
@@ -383,7 +384,10 @@ namespace Catch {
|
||||
}
|
||||
#endif // CATCH_CONFIG_ENABLE_PAIR_STRINGMAKER
|
||||
|
||||
#if defined(CATCH_CONFIG_ENABLE_OPTIONAL_STRINGMAKER) && defined(CATCH_CONFIG_CPP17_OPTIONAL)
|
||||
#if defined( CATCH_CONFIG_ENABLE_OPTIONAL_STRINGMAKER ) && \
|
||||
defined( CATCH_CONFIG_CPP17_OPTIONAL ) && \
|
||||
/* P3168 turned optional into a range, making this ambigous with the range support */ \
|
||||
!defined( __cpp_lib_optional_range_support )
|
||||
#include <optional>
|
||||
namespace Catch {
|
||||
template<typename T>
|
||||
@@ -630,17 +634,21 @@ struct ratio_string<std::milli> {
|
||||
};
|
||||
}
|
||||
|
||||
#include <catch2/interfaces/catch_interfaces_registry_hub.hpp>
|
||||
|
||||
#define INTERNAL_CATCH_REGISTER_ENUM( enumName, ... ) \
|
||||
namespace Catch { \
|
||||
template<> struct StringMaker<enumName> { \
|
||||
static std::string convert( enumName value ) { \
|
||||
static const auto& enumInfo = ::Catch::getMutableRegistryHub().getMutableEnumValuesRegistry().registerEnum( #enumName, #__VA_ARGS__, { __VA_ARGS__ } ); \
|
||||
return static_cast<std::string>(enumInfo.lookup( static_cast<int>( value ) )); \
|
||||
} \
|
||||
}; \
|
||||
}
|
||||
#define INTERNAL_CATCH_REGISTER_ENUM( enumName, ... ) \
|
||||
namespace Catch { \
|
||||
template <> \
|
||||
struct StringMaker<enumName> { \
|
||||
static std::string convert( enumName value ) { \
|
||||
CATCH_INTERNAL_START_WARNINGS_SUPPRESSION \
|
||||
CATCH_INTERNAL_SUPPRESS_GLOBALS_WARNINGS \
|
||||
static const auto enumInfo = ::Catch::Detail::makeEnumInfo( \
|
||||
#enumName, #__VA_ARGS__, { __VA_ARGS__ } ); \
|
||||
CATCH_INTERNAL_STOP_WARNINGS_SUPPRESSION \
|
||||
return static_cast<std::string>( \
|
||||
enumInfo.lookup( static_cast<int64_t>( value ) ) ); \
|
||||
} \
|
||||
}; \
|
||||
}
|
||||
|
||||
#define CATCH_REGISTER_ENUM( enumName, ... ) INTERNAL_CATCH_REGISTER_ENUM( enumName, __VA_ARGS__ )
|
||||
|
||||
|
||||
@@ -22,7 +22,7 @@ namespace Catch {
|
||||
|
||||
class ExceptionTranslatorRegistrar {
|
||||
template<typename T>
|
||||
class ExceptionTranslator : public IExceptionTranslator {
|
||||
class ExceptionTranslator final : public IExceptionTranslator {
|
||||
public:
|
||||
|
||||
constexpr ExceptionTranslator( std::string(*translateFunction)( T const& ) )
|
||||
@@ -41,6 +41,8 @@ namespace Catch {
|
||||
return m_translateFunction( ex );
|
||||
}
|
||||
#else
|
||||
(void)it;
|
||||
(void)itEnd;
|
||||
return "You should never get here!";
|
||||
#endif
|
||||
}
|
||||
|
||||
@@ -36,7 +36,7 @@ namespace Catch {
|
||||
}
|
||||
|
||||
Version const& libraryVersion() {
|
||||
static Version version( 3, 13, 0, "", 0 );
|
||||
static Version version( 3, 15, 3, "", 0 );
|
||||
return version;
|
||||
}
|
||||
|
||||
|
||||
@@ -9,7 +9,7 @@
|
||||
#define CATCH_VERSION_MACROS_HPP_INCLUDED
|
||||
|
||||
#define CATCH_VERSION_MAJOR 3
|
||||
#define CATCH_VERSION_MINOR 13
|
||||
#define CATCH_VERSION_PATCH 0
|
||||
#define CATCH_VERSION_MINOR 15
|
||||
#define CATCH_VERSION_PATCH 3
|
||||
|
||||
#endif // CATCH_VERSION_MACROS_HPP_INCLUDED
|
||||
|
||||
@@ -95,7 +95,7 @@ namespace Generators {
|
||||
void skipToNthElementImpl( std::size_t n ) override {
|
||||
if ( n >= m_values.size() ) {
|
||||
Detail::throw_generator_exception(
|
||||
"Coud not jump to Nth element: not enough elements" );
|
||||
"Could not jump to Nth element: not enough elements" );
|
||||
}
|
||||
m_idx = n;
|
||||
}
|
||||
@@ -179,8 +179,7 @@ namespace Generators {
|
||||
|
||||
bool isFinite() const override {
|
||||
for (auto const& gen : m_generators) {
|
||||
if (!gen.isFinite()) { return false;
|
||||
}
|
||||
if (!gen.isFinite()) { return false; }
|
||||
}
|
||||
return true;
|
||||
}
|
||||
|
||||
@@ -27,7 +27,7 @@ namespace Generators {
|
||||
void skipToNthElementImpl( std::size_t n ) override {
|
||||
if ( n >= m_target ) {
|
||||
Detail::throw_generator_exception(
|
||||
"Coud not jump to Nth element: not enough elements" );
|
||||
"Could not jump to Nth element: not enough elements" );
|
||||
}
|
||||
|
||||
m_generator.skipToNthElement( n );
|
||||
|
||||
@@ -24,7 +24,6 @@
|
||||
|
||||
#include <catch2/interfaces/catch_interfaces_capture.hpp>
|
||||
#include <catch2/interfaces/catch_interfaces_config.hpp>
|
||||
#include <catch2/interfaces/catch_interfaces_enum_values_registry.hpp>
|
||||
#include <catch2/interfaces/catch_interfaces_exception.hpp>
|
||||
#include <catch2/interfaces/catch_interfaces_generatortracker.hpp>
|
||||
#include <catch2/interfaces/catch_interfaces_registry_hub.hpp>
|
||||
|
||||
@@ -22,8 +22,6 @@ namespace Catch {
|
||||
struct AssertionInfo;
|
||||
struct SectionInfo;
|
||||
struct SectionEndInfo;
|
||||
struct MessageInfo;
|
||||
struct MessageBuilder;
|
||||
struct Counts;
|
||||
struct AssertionReaction;
|
||||
struct SourceLineInfo;
|
||||
@@ -63,11 +61,6 @@ namespace Catch {
|
||||
virtual void benchmarkEnded( BenchmarkStats<> const& stats ) = 0;
|
||||
virtual void benchmarkFailed( StringRef error ) = 0;
|
||||
|
||||
static void pushScopedMessage( MessageInfo&& message );
|
||||
static void popScopedMessage( unsigned int messageId );
|
||||
static void addUnscopedMessage( MessageInfo&& message );
|
||||
static void emplaceUnscopedMessage( MessageBuilder&& builder );
|
||||
|
||||
virtual void handleFatalErrorCondition( StringRef message ) = 0;
|
||||
|
||||
virtual void handleExpr
|
||||
@@ -93,9 +86,6 @@ namespace Catch {
|
||||
ResultWas::OfType resultType,
|
||||
AssertionReaction &reaction ) = 0;
|
||||
|
||||
|
||||
virtual bool lastAssertionPassed() = 0;
|
||||
|
||||
// Deprecated, do not use:
|
||||
virtual std::string getCurrentTestName() const = 0;
|
||||
virtual const AssertionResult* getLastResult() const = 0;
|
||||
|
||||
@@ -64,7 +64,7 @@ namespace Catch {
|
||||
class IStream;
|
||||
struct PathFilter;
|
||||
|
||||
class IConfig : public Detail::NonCopyable {
|
||||
class IConfig : Detail::NonCopyable {
|
||||
public:
|
||||
virtual ~IConfig();
|
||||
|
||||
|
||||
@@ -1,47 +0,0 @@
|
||||
|
||||
// Copyright Catch2 Authors
|
||||
// Distributed under the Boost Software License, Version 1.0.
|
||||
// (See accompanying file LICENSE.txt or copy at
|
||||
// https://www.boost.org/LICENSE_1_0.txt)
|
||||
|
||||
// SPDX-License-Identifier: BSL-1.0
|
||||
#ifndef CATCH_INTERFACES_ENUM_VALUES_REGISTRY_HPP_INCLUDED
|
||||
#define CATCH_INTERFACES_ENUM_VALUES_REGISTRY_HPP_INCLUDED
|
||||
|
||||
#include <catch2/internal/catch_stringref.hpp>
|
||||
|
||||
#include <vector>
|
||||
|
||||
namespace Catch {
|
||||
|
||||
namespace Detail {
|
||||
struct EnumInfo {
|
||||
StringRef m_name;
|
||||
std::vector<std::pair<int, StringRef>> m_values;
|
||||
|
||||
~EnumInfo();
|
||||
|
||||
StringRef lookup( int value ) const;
|
||||
};
|
||||
} // namespace Detail
|
||||
|
||||
class IMutableEnumValuesRegistry {
|
||||
public:
|
||||
virtual ~IMutableEnumValuesRegistry(); // = default;
|
||||
|
||||
virtual Detail::EnumInfo const& registerEnum( StringRef enumName, StringRef allEnums, std::vector<int> const& values ) = 0;
|
||||
|
||||
template<typename E>
|
||||
Detail::EnumInfo const& registerEnum( StringRef enumName, StringRef allEnums, std::initializer_list<E> values ) {
|
||||
static_assert(sizeof(int) >= sizeof(E), "Cannot serialize enum to int");
|
||||
std::vector<int> intValues;
|
||||
intValues.reserve( values.size() );
|
||||
for( auto enumValue : values )
|
||||
intValues.push_back( static_cast<int>( enumValue ) );
|
||||
return registerEnum( enumName, allEnums, intValues );
|
||||
}
|
||||
};
|
||||
|
||||
} // Catch
|
||||
|
||||
#endif // CATCH_INTERFACES_ENUM_VALUES_REGISTRY_HPP_INCLUDED
|
||||
@@ -28,7 +28,7 @@ namespace Catch {
|
||||
bool isValid = next();
|
||||
if ( !isValid ) {
|
||||
Detail::throw_generator_exception(
|
||||
"Coud not jump to Nth element: not enough elements" );
|
||||
"Could not jump to Nth element: not enough elements" );
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -53,7 +53,6 @@ namespace Catch {
|
||||
virtual void registerTranslator( Detail::unique_ptr<IExceptionTranslator>&& translator ) = 0;
|
||||
virtual void registerTagAlias( std::string const& alias, std::string const& tag, SourceLineInfo const& lineInfo ) = 0;
|
||||
virtual void registerStartupException() noexcept = 0;
|
||||
virtual IMutableEnumValuesRegistry& getMutableEnumValuesRegistry() = 0;
|
||||
};
|
||||
|
||||
IRegistryHub const& getRegistryHub();
|
||||
|
||||
@@ -6,21 +6,28 @@
|
||||
|
||||
// SPDX-License-Identifier: BSL-1.0
|
||||
#include <catch2/internal/catch_assertion_handler.hpp>
|
||||
#include <catch2/interfaces/catch_interfaces_capture.hpp>
|
||||
#include <catch2/interfaces/catch_interfaces_config.hpp>
|
||||
#include <catch2/interfaces/catch_interfaces_registry_hub.hpp>
|
||||
#include <catch2/internal/catch_context.hpp>
|
||||
#include <catch2/internal/catch_debugger.hpp>
|
||||
#include <catch2/internal/catch_run_context.hpp>
|
||||
#include <catch2/internal/catch_test_failure_exception.hpp>
|
||||
#include <catch2/matchers/catch_matchers_string.hpp>
|
||||
|
||||
namespace Catch {
|
||||
|
||||
void AssertionHandler::finishIncomplete() {
|
||||
m_resultCapture.handleIncomplete( m_assertionInfo );
|
||||
}
|
||||
|
||||
AssertionHandler::AssertionHandler
|
||||
( StringRef macroName,
|
||||
SourceLineInfo const& lineInfo,
|
||||
StringRef capturedExpression,
|
||||
ResultDisposition::Flags resultDisposition )
|
||||
: m_assertionInfo{ macroName, lineInfo, capturedExpression, resultDisposition },
|
||||
m_resultCapture( getResultCapture() )
|
||||
m_resultCapture( static_cast<RunContext&>(getResultCapture()) )
|
||||
{
|
||||
m_resultCapture.notifyAssertionStarted( m_assertionInfo );
|
||||
}
|
||||
|
||||
@@ -10,12 +10,13 @@
|
||||
|
||||
#include <catch2/catch_assertion_info.hpp>
|
||||
#include <catch2/internal/catch_decomposer.hpp>
|
||||
#include <catch2/interfaces/catch_interfaces_capture.hpp>
|
||||
|
||||
#include <string>
|
||||
|
||||
namespace Catch {
|
||||
|
||||
class RunContext;
|
||||
|
||||
struct AssertionReaction {
|
||||
bool shouldDebugBreak = false;
|
||||
bool shouldThrow = false;
|
||||
@@ -26,7 +27,12 @@ namespace Catch {
|
||||
AssertionInfo m_assertionInfo;
|
||||
AssertionReaction m_reaction;
|
||||
bool m_completed = false;
|
||||
IResultCapture& m_resultCapture;
|
||||
// Since all uses are hidden in the .cpp file, we can directly use
|
||||
// the final type and avoid going through virtual dispatch, without
|
||||
// massive compilation time overhead.
|
||||
RunContext& m_resultCapture;
|
||||
|
||||
void finishIncomplete();
|
||||
|
||||
public:
|
||||
AssertionHandler
|
||||
@@ -35,9 +41,9 @@ namespace Catch {
|
||||
StringRef capturedExpression,
|
||||
ResultDisposition::Flags resultDisposition );
|
||||
~AssertionHandler() {
|
||||
if ( !m_completed ) {
|
||||
m_resultCapture.handleIncomplete( m_assertionInfo );
|
||||
}
|
||||
// We want the common fast path inlinable, and the virtual
|
||||
// dispatch in a function in single TU.
|
||||
if ( !m_completed ) { finishIncomplete(); }
|
||||
}
|
||||
|
||||
|
||||
|
||||
@@ -27,16 +27,24 @@
|
||||
#include <catch2/internal/catch_platform.hpp>
|
||||
#include <catch2/catch_user_config.hpp>
|
||||
|
||||
#ifdef __cplusplus
|
||||
#if (__cplusplus >= 201703L) || (defined(_MSVC_LANG) && _MSVC_LANG >= 201703L)
|
||||
# define CATCH_CPP17_OR_GREATER
|
||||
#endif
|
||||
|
||||
# if (__cplusplus >= 201703L) || (defined(_MSVC_LANG) && _MSVC_LANG >= 201703L)
|
||||
# define CATCH_CPP17_OR_GREATER
|
||||
# endif
|
||||
|
||||
# if (__cplusplus >= 202002L) || (defined(_MSVC_LANG) && _MSVC_LANG >= 202002L)
|
||||
# define CATCH_CPP20_OR_GREATER
|
||||
# endif
|
||||
#if (__cplusplus >= 202002L) || (defined(_MSVC_LANG) && _MSVC_LANG >= 202002L)
|
||||
# define CATCH_CPP20_OR_GREATER
|
||||
#endif
|
||||
|
||||
// Matchers are only constexpr-able in C++20
|
||||
#if defined( CATCH_CPP20_OR_GREATER ) && \
|
||||
defined( __cpp_constexpr_dynamic_alloc ) && \
|
||||
__cpp_constexpr_dynamic_alloc >= 201907L && \
|
||||
/* GCC < 13 define the feature macro, but compiler bugs stop us from using it */ \
|
||||
( !defined( __GNUC__ ) || __GNUC__ >= 13 || defined(__clang__) )
|
||||
# define CATCH_INTERNAL_CONSTEXPR_MATCHERS_ENABLED
|
||||
# define CATCH_DESTRUCTOR_CONSTEXPR constexpr
|
||||
#else
|
||||
# define CATCH_DESTRUCTOR_CONSTEXPR
|
||||
#endif
|
||||
|
||||
// Only GCC compiler should be used in this block, so other compilers trying to
|
||||
@@ -110,10 +118,15 @@
|
||||
# define CATCH_INTERNAL_SUPPRESS_ZERO_VARIADIC_WARNINGS \
|
||||
_Pragma( "clang diagnostic ignored \"-Wc++20-extensions\"" )
|
||||
# else
|
||||
# define CATCH_INTERNAL_SUPPRESS_ZERO_VARIADIC_WARNINGS
|
||||
# define CATCH_INTERNAL_SUPPRESS_ZERO_VARIADIC_WARNINGS \
|
||||
_Pragma( "clang diagnostic ignored \"-Wgnu-zero-variadic-macro-arguments\"" )
|
||||
# endif
|
||||
|
||||
# if ( __clang_major__ >= 22 )
|
||||
# define CATCH_INTERNAL_SUPPRESS_COUNTER_WARNINGS \
|
||||
_Pragma( "clang diagnostic ignored \"-Wc2y-extensions\"" )
|
||||
# endif
|
||||
|
||||
# define CATCH_INTERNAL_SUPPRESS_UNUSED_TEMPLATE_WARNINGS \
|
||||
_Pragma( "clang diagnostic ignored \"-Wunused-template\"" )
|
||||
|
||||
@@ -429,6 +442,9 @@
|
||||
#if !defined( CATCH_INTERNAL_SUPPRESS_SHADOW_WARNINGS )
|
||||
# define CATCH_INTERNAL_SUPPRESS_SHADOW_WARNINGS
|
||||
#endif
|
||||
#if !defined( CATCH_INTERNAL_SUPPRESS_COUNTER_WARNINGS )
|
||||
# define CATCH_INTERNAL_SUPPRESS_COUNTER_WARNINGS
|
||||
#endif
|
||||
|
||||
#if defined(__APPLE__) && defined(__apple_build_version__) && (__clang_major__ < 10)
|
||||
# undef CATCH_INTERNAL_SUPPRESS_UNUSED_TEMPLATE_WARNINGS
|
||||
|
||||
@@ -193,7 +193,7 @@ namespace Catch {
|
||||
void formatReconstructedExpression( std::ostream &os, std::string const& lhs, StringRef op, std::string const& rhs );
|
||||
|
||||
template<typename LhsT, typename RhsT>
|
||||
class BinaryExpr : public ITransientExpression {
|
||||
class BinaryExpr final : public ITransientExpression {
|
||||
LhsT m_lhs;
|
||||
StringRef m_op;
|
||||
RhsT m_rhs;
|
||||
@@ -269,7 +269,7 @@ namespace Catch {
|
||||
};
|
||||
|
||||
template<typename LhsT>
|
||||
class UnaryExpr : public ITransientExpression {
|
||||
class UnaryExpr final : public ITransientExpression {
|
||||
LhsT m_lhs;
|
||||
|
||||
void streamReconstructedExpression( std::ostream &os ) const override {
|
||||
|
||||
+10
-18
@@ -5,15 +5,14 @@
|
||||
// https://www.boost.org/LICENSE_1_0.txt)
|
||||
|
||||
// SPDX-License-Identifier: BSL-1.0
|
||||
#include <catch2/internal/catch_enum_values_registry.hpp>
|
||||
|
||||
#include <catch2/internal/catch_enum_info.hpp>
|
||||
#include <catch2/internal/catch_string_manip.hpp>
|
||||
|
||||
#include <cassert>
|
||||
|
||||
namespace Catch {
|
||||
|
||||
IMutableEnumValuesRegistry::~IMutableEnumValuesRegistry() = default;
|
||||
|
||||
namespace Detail {
|
||||
|
||||
namespace {
|
||||
@@ -39,9 +38,7 @@ namespace Catch {
|
||||
return parsed;
|
||||
}
|
||||
|
||||
EnumInfo::~EnumInfo() = default;
|
||||
|
||||
StringRef EnumInfo::lookup( int value ) const {
|
||||
StringRef EnumInfo::lookup( int64_t value ) const {
|
||||
for( auto const& valueToName : m_values ) {
|
||||
if( valueToName.first == value )
|
||||
return valueToName.second;
|
||||
@@ -49,25 +46,20 @@ namespace Catch {
|
||||
return "{** unexpected enum value **}"_sr;
|
||||
}
|
||||
|
||||
Catch::Detail::unique_ptr<EnumInfo> makeEnumInfo( StringRef enumName, StringRef allValueNames, std::vector<int> const& values ) {
|
||||
auto enumInfo = Catch::Detail::make_unique<EnumInfo>();
|
||||
enumInfo->m_name = enumName;
|
||||
enumInfo->m_values.reserve( values.size() );
|
||||
EnumInfo makeEnumInfo( StringRef enumName, StringRef allValueNames, std::vector<int64_t> const& values ) {
|
||||
EnumInfo enumInfo;
|
||||
enumInfo.m_name = enumName;
|
||||
enumInfo.m_values.reserve( values.size() );
|
||||
|
||||
const auto valueNames = Catch::Detail::parseEnums( allValueNames );
|
||||
assert( valueNames.size() == values.size() );
|
||||
std::size_t i = 0;
|
||||
for( auto value : values )
|
||||
enumInfo->m_values.emplace_back(value, valueNames[i++]);
|
||||
for (size_t i = 0; i < values.size(); ++i) {
|
||||
enumInfo.m_values.emplace_back( values[i], valueNames[i] );
|
||||
}
|
||||
|
||||
return enumInfo;
|
||||
}
|
||||
|
||||
EnumInfo const& EnumValuesRegistry::registerEnum( StringRef enumName, StringRef allValueNames, std::vector<int> const& values ) {
|
||||
m_enumInfos.push_back(makeEnumInfo(enumName, allValueNames, values));
|
||||
return *m_enumInfos.back();
|
||||
}
|
||||
|
||||
} // Detail
|
||||
} // Catch
|
||||
|
||||
@@ -0,0 +1,48 @@
|
||||
|
||||
// Copyright Catch2 Authors
|
||||
// Distributed under the Boost Software License, Version 1.0.
|
||||
// (See accompanying file LICENSE.txt or copy at
|
||||
// https://www.boost.org/LICENSE_1_0.txt)
|
||||
|
||||
// SPDX-License-Identifier: BSL-1.0
|
||||
#ifndef CATCH_ENUM_INFO_HPP_INCLUDED
|
||||
#define CATCH_ENUM_INFO_HPP_INCLUDED
|
||||
|
||||
#include <catch2/internal/catch_stringref.hpp>
|
||||
|
||||
#include <cstdint>
|
||||
#include <utility>
|
||||
#include <vector>
|
||||
|
||||
namespace Catch {
|
||||
namespace Detail {
|
||||
|
||||
struct EnumInfo {
|
||||
StringRef m_name;
|
||||
std::vector<std::pair<int64_t, StringRef>> m_values;
|
||||
|
||||
StringRef lookup( int64_t value ) const;
|
||||
};
|
||||
|
||||
EnumInfo makeEnumInfo( StringRef enumName,
|
||||
StringRef allValueNames,
|
||||
std::vector<int64_t> const& values );
|
||||
template <typename E>
|
||||
Detail::EnumInfo makeEnumInfo( StringRef enumName,
|
||||
StringRef allEnums,
|
||||
std::initializer_list<E> values ) {
|
||||
static_assert( sizeof( int64_t ) >= sizeof( E ),
|
||||
"Cannot serialize enum to int64_t" );
|
||||
std::vector<int64_t> intValues;
|
||||
intValues.reserve( values.size() );
|
||||
for ( auto enumValue : values )
|
||||
intValues.push_back( static_cast<int64_t>( enumValue ) );
|
||||
return makeEnumInfo( enumName, allEnums, intValues );
|
||||
}
|
||||
|
||||
std::vector<StringRef> parseEnums( StringRef enums );
|
||||
|
||||
} // namespace Detail
|
||||
} // namespace Catch
|
||||
|
||||
#endif // CATCH_ENUM_INFO_HPP_INCLUDED
|
||||
@@ -1,36 +0,0 @@
|
||||
|
||||
// Copyright Catch2 Authors
|
||||
// Distributed under the Boost Software License, Version 1.0.
|
||||
// (See accompanying file LICENSE.txt or copy at
|
||||
// https://www.boost.org/LICENSE_1_0.txt)
|
||||
|
||||
// SPDX-License-Identifier: BSL-1.0
|
||||
#ifndef CATCH_ENUM_VALUES_REGISTRY_HPP_INCLUDED
|
||||
#define CATCH_ENUM_VALUES_REGISTRY_HPP_INCLUDED
|
||||
|
||||
#include <catch2/interfaces/catch_interfaces_enum_values_registry.hpp>
|
||||
#include <catch2/internal/catch_unique_ptr.hpp>
|
||||
#include <catch2/internal/catch_stringref.hpp>
|
||||
|
||||
#include <vector>
|
||||
|
||||
namespace Catch {
|
||||
|
||||
namespace Detail {
|
||||
|
||||
Catch::Detail::unique_ptr<EnumInfo> makeEnumInfo( StringRef enumName, StringRef allValueNames, std::vector<int> const& values );
|
||||
|
||||
class EnumValuesRegistry : public IMutableEnumValuesRegistry {
|
||||
|
||||
std::vector<Catch::Detail::unique_ptr<EnumInfo>> m_enumInfos;
|
||||
|
||||
EnumInfo const& registerEnum( StringRef enumName, StringRef allValueNames, std::vector<int> const& values) override;
|
||||
};
|
||||
|
||||
std::vector<StringRef> parseEnums( StringRef enums );
|
||||
|
||||
} // Detail
|
||||
|
||||
} // Catch
|
||||
|
||||
#endif // CATCH_ENUM_VALUES_REGISTRY_HPP_INCLUDED
|
||||
@@ -15,7 +15,7 @@
|
||||
|
||||
namespace Catch {
|
||||
|
||||
class ExceptionTranslatorRegistry : public IExceptionTranslatorRegistry {
|
||||
class ExceptionTranslatorRegistry final : public IExceptionTranslatorRegistry {
|
||||
public:
|
||||
~ExceptionTranslatorRegistry() override;
|
||||
void registerTranslator( Detail::unique_ptr<IExceptionTranslator>&& translator );
|
||||
|
||||
@@ -7,14 +7,31 @@
|
||||
// SPDX-License-Identifier: BSL-1.0
|
||||
#include <catch2/internal/catch_enforce.hpp>
|
||||
#include <catch2/internal/catch_jsonwriter.hpp>
|
||||
#include <catch2/internal/catch_polyfills.hpp>
|
||||
#include <catch2/internal/catch_unreachable.hpp>
|
||||
|
||||
#include <cmath>
|
||||
#include <locale>
|
||||
|
||||
namespace Catch {
|
||||
|
||||
namespace {
|
||||
static bool needsEscape( char c ) {
|
||||
return c == '"' || c == '\\' || c == '\b' || c == '\f' ||
|
||||
c == '\n' || c == '\r' || c == '\t';
|
||||
struct EscapeLUT {
|
||||
bool escape[256] = {};
|
||||
constexpr EscapeLUT() {
|
||||
escape[static_cast<unsigned char>( '"' )] = true;
|
||||
escape[static_cast<unsigned char>( '\\' )] = true;
|
||||
escape[static_cast<unsigned char>( '\b' )] = true;
|
||||
escape[static_cast<unsigned char>( '\f' )] = true;
|
||||
escape[static_cast<unsigned char>( '\n' )] = true;
|
||||
escape[static_cast<unsigned char>( '\r' )] = true;
|
||||
escape[static_cast<unsigned char>( '\t' )] = true;
|
||||
}
|
||||
};
|
||||
static constexpr EscapeLUT escapeLUT{};
|
||||
|
||||
static constexpr bool needsEscape( char c ) {
|
||||
return escapeLUT.escape[static_cast<unsigned char>( c )];
|
||||
}
|
||||
|
||||
static Catch::StringRef makeEscapeStringRef( char c ) {
|
||||
@@ -126,7 +143,10 @@ namespace Catch {
|
||||
|
||||
JsonValueWriter::JsonValueWriter( std::ostream& os,
|
||||
std::uint64_t indent_level ):
|
||||
m_os{ os }, m_indent_level{ indent_level } {}
|
||||
m_os{ os }, m_indent_level{ indent_level } {
|
||||
// We use C locale so that writing of numerical values is locale-independent.
|
||||
m_sstream.imbue( std::locale::classic() );
|
||||
}
|
||||
|
||||
JsonObjectWriter JsonValueWriter::writeObject() && {
|
||||
return JsonObjectWriter{ m_os, m_indent_level };
|
||||
@@ -140,26 +160,55 @@ namespace Catch {
|
||||
writeImpl( value, true );
|
||||
}
|
||||
|
||||
void JsonValueWriter::write( float value ) && {
|
||||
writeFloatingPoint( value );
|
||||
}
|
||||
|
||||
void JsonValueWriter::write( double value ) && {
|
||||
writeFloatingPoint( value );
|
||||
}
|
||||
|
||||
void JsonValueWriter::write( bool value ) && {
|
||||
writeImpl( value ? "true"_sr : "false"_sr, false );
|
||||
}
|
||||
|
||||
template <typename T>
|
||||
void JsonValueWriter::writeFloatingPoint( T value ) {
|
||||
if ( Catch::isnan( value ) ) {
|
||||
writeImpl( "NaN"_sr, false );
|
||||
} else if ( std::isinf( value ) ) {
|
||||
writeImpl( value < 0 ? "-Infinity"_sr : "Infinity"_sr, false );
|
||||
} else {
|
||||
m_sstream << value;
|
||||
writeImpl( m_sstream.str(), false );
|
||||
}
|
||||
}
|
||||
|
||||
void JsonValueWriter::writeImpl( Catch::StringRef value, bool quote ) {
|
||||
if ( quote ) { m_os << '"'; }
|
||||
size_t current_start = 0;
|
||||
for ( size_t i = 0; i < value.size(); ++i ) {
|
||||
if ( needsEscape( value[i] ) ) {
|
||||
if ( current_start < i ) {
|
||||
m_os << value.substr( current_start, i - current_start );
|
||||
// Escaping only makes sense for actual strings, which are passed
|
||||
// with `quote == true`. Non-quoted values are things like bools
|
||||
// and numbers, which cannot create inputs that need escaping.
|
||||
if ( !quote ) {
|
||||
m_os << value;
|
||||
} else {
|
||||
m_os << '"';
|
||||
size_t current_start = 0;
|
||||
for ( size_t i = 0; i < value.size(); ++i ) {
|
||||
if ( needsEscape( value[i] ) ) {
|
||||
if ( current_start < i ) {
|
||||
m_os
|
||||
<< value.substr( current_start, i - current_start );
|
||||
}
|
||||
m_os << makeEscapeStringRef( value[i] );
|
||||
current_start = i + 1;
|
||||
}
|
||||
m_os << makeEscapeStringRef( value[i] );
|
||||
current_start = i + 1;
|
||||
}
|
||||
if ( current_start < value.size() ) {
|
||||
m_os << value.substr( current_start,
|
||||
value.size() - current_start );
|
||||
}
|
||||
m_os << '"';
|
||||
}
|
||||
if ( current_start < value.size() ) {
|
||||
m_os << value.substr( current_start, value.size() - current_start );
|
||||
}
|
||||
if ( quote ) { m_os << '"'; }
|
||||
}
|
||||
|
||||
} // namespace Catch
|
||||
|
||||
@@ -39,11 +39,19 @@ namespace Catch {
|
||||
writeImpl( value, !std::is_arithmetic<T>::value );
|
||||
}
|
||||
void write( StringRef value ) &&;
|
||||
void write( float value ) &&;
|
||||
void write( double value ) &&;
|
||||
void write( bool value ) &&;
|
||||
|
||||
private:
|
||||
void writeImpl( StringRef value, bool quote );
|
||||
|
||||
// Helper to deal with non-finite floating point values, which
|
||||
// are not standard JSON, but we use JS/Python/etc. approach of
|
||||
// emitting `NaN`, `Infinity`, `-Infinity` as number(like).
|
||||
template <typename T>
|
||||
void writeFloatingPoint( T value );
|
||||
|
||||
// Without this SFINAE, this overload is a better match
|
||||
// for `std::string`, `char const*`, `char const[N]` args.
|
||||
// While it would still work, it would cause code bloat
|
||||
|
||||
@@ -33,7 +33,7 @@ namespace Catch {
|
||||
namespace {
|
||||
//! A no-op implementation, used if no reporter wants output
|
||||
//! redirection.
|
||||
class NoopRedirect : public OutputRedirect {
|
||||
class NoopRedirect final : public OutputRedirect {
|
||||
void activateImpl() override {}
|
||||
void deactivateImpl() override {}
|
||||
std::string getStdout() override { return {}; }
|
||||
@@ -70,7 +70,7 @@ namespace Catch {
|
||||
* Redirects the `std::cout`, `std::cerr`, `std::clog` streams,
|
||||
* but does not touch the actual `stdout`/`stderr` file descriptors.
|
||||
*/
|
||||
class StreamRedirect : public OutputRedirect {
|
||||
class StreamRedirect final : public OutputRedirect {
|
||||
ReusableStringStream m_redirectedOut, m_redirectedErr;
|
||||
RedirectedStreamNew m_cout, m_cerr, m_clog;
|
||||
|
||||
@@ -181,7 +181,7 @@ namespace Catch {
|
||||
* Works by replacing the file descriptors numbered 1 and 2
|
||||
* with an open temporary file.
|
||||
*/
|
||||
class FileRedirect : public OutputRedirect {
|
||||
class FileRedirect final : public OutputRedirect {
|
||||
TempFile m_outFile, m_errFile;
|
||||
int m_originalOut = -1;
|
||||
int m_originalErr = -1;
|
||||
|
||||
@@ -11,6 +11,7 @@
|
||||
#include <catch2/generators/catch_generators_throw.hpp>
|
||||
#include <catch2/interfaces/catch_interfaces_config.hpp>
|
||||
#include <catch2/interfaces/catch_interfaces_generatortracker.hpp>
|
||||
#include <catch2/interfaces/catch_interfaces_registry_hub.hpp>
|
||||
#include <catch2/interfaces/catch_interfaces_reporter.hpp>
|
||||
#include <catch2/internal/catch_compiler_capabilities.hpp>
|
||||
#include <catch2/internal/catch_context.hpp>
|
||||
@@ -60,9 +61,14 @@ namespace Catch {
|
||||
// can be, so the tracker has to throw for a wrong
|
||||
// filter to stop the execution flow.
|
||||
if (filter.type == PathFilter::For::Section) {
|
||||
// TBD: Explicit SKIP, or new exception that says
|
||||
// "don't continue", but doesn't show in totals?
|
||||
SKIP();
|
||||
// We want the semantics of `SKIP()`, but we inline it
|
||||
// to avoid issues with conditionally prefixed macros
|
||||
INTERNAL_CATCH_MSG(
|
||||
"SKIP",
|
||||
Catch::ResultWas::ExplicitSkip,
|
||||
Catch::ResultDisposition::Normal,
|
||||
"" );
|
||||
Catch::Detail::Unreachable();
|
||||
}
|
||||
// '*' is the wildcard for "all elements in generator"
|
||||
// used for filtering sections below the generator, but
|
||||
@@ -190,6 +196,8 @@ namespace Catch {
|
||||
auto getGenerator() const -> GeneratorBasePtr const& override {
|
||||
return m_generator;
|
||||
}
|
||||
|
||||
bool isFilteredImpl() const override { return m_isFiltered; }
|
||||
};
|
||||
} // namespace
|
||||
}
|
||||
@@ -303,6 +311,25 @@ namespace Catch {
|
||||
}
|
||||
CATCH_INTERNAL_STOP_WARNINGS_SUPPRESSION
|
||||
|
||||
|
||||
void pushScopedMessage( MessageInfo&& message ) {
|
||||
Detail::g_messageHolder().addScopedMessage( CATCH_MOVE( message ) );
|
||||
}
|
||||
|
||||
void popScopedMessage( unsigned int messageId ) {
|
||||
Detail::g_messageHolder().removeMessage( messageId );
|
||||
}
|
||||
|
||||
void emplaceUnscopedMessage( MessageBuilder&& builder ) {
|
||||
Detail::g_messageHolder().addUnscopedMessage( CATCH_MOVE( builder ) );
|
||||
}
|
||||
|
||||
void addUnscopedMessage( MessageInfo&& message ) {
|
||||
Detail::g_messageHolder().addUnscopedMessage( CATCH_MOVE( message ) );
|
||||
}
|
||||
|
||||
bool lastAssertionPassed() { return Detail::g_lastAssertionPassed; }
|
||||
|
||||
} // namespace Detail
|
||||
|
||||
RunContext::RunContext(IConfig const* _config, IEventListenerPtr&& reporter)
|
||||
@@ -436,14 +463,13 @@ namespace Catch {
|
||||
Detail::g_lastAssertionPassed = true;
|
||||
} else if (!result.succeeded()) {
|
||||
Detail::g_lastAssertionPassed = false;
|
||||
if (result.isOk()) {
|
||||
}
|
||||
else if( m_activeTestCase->getTestCaseInfo().okToFail() ) // Read from a shared state established before the threads could start, this is fine
|
||||
if (result.isOk()) {}
|
||||
else if( m_activeTestCase->getTestCaseInfo().okToFail() ) { // Read from a shared state established before the threads could start, this is fine
|
||||
m_atomicAssertionCount.failedButOk++;
|
||||
else
|
||||
} else {
|
||||
m_atomicAssertionCount.failed++;
|
||||
}
|
||||
else {
|
||||
}
|
||||
} else {
|
||||
Detail::g_lastAssertionPassed = true;
|
||||
}
|
||||
|
||||
@@ -516,13 +542,6 @@ namespace Catch {
|
||||
SourceLineInfo lineInfo,
|
||||
Generators::GeneratorBasePtr&& generator ) {
|
||||
|
||||
// TBD: Do we want to avoid the warning if the generator is filtered?
|
||||
if ( m_config->warnAboutInfiniteGenerators() &&
|
||||
!generator->isFinite() ) {
|
||||
// TBD: Would it be better to expand this macro inline?
|
||||
FAIL( "GENERATE() would run infinitely" );
|
||||
}
|
||||
|
||||
auto nameAndLoc = TestCaseTracking::NameAndLocation( static_cast<std::string>( generatorName ), lineInfo );
|
||||
auto& currentTracker = m_trackerContext.currentTracker();
|
||||
assert(
|
||||
@@ -535,11 +554,24 @@ namespace Catch {
|
||||
m_trackerContext,
|
||||
¤tTracker,
|
||||
CATCH_MOVE( generator ) );
|
||||
auto ret = newTracker.get();
|
||||
|
||||
// The warning shouldn't fire if the generator is infinite, **but** filtered down.
|
||||
if ( m_config->warnAboutInfiniteGenerators() &&
|
||||
!newTracker->m_generator->isFinite() &&
|
||||
!newTracker->isFiltered() ) {
|
||||
// We want the semantics of `FAIL()`, but we inline it
|
||||
// to avoid issues with conditionally prefixed macros
|
||||
INTERNAL_CATCH_MSG( "FAIL",
|
||||
Catch::ResultWas::ExplicitFailure,
|
||||
Catch::ResultDisposition::Normal,
|
||||
"GENERATE() would run infinitely" );
|
||||
}
|
||||
|
||||
auto returnPtr = newTracker.get();
|
||||
currentTracker.addChild( CATCH_MOVE( newTracker ) );
|
||||
|
||||
ret->open();
|
||||
return ret;
|
||||
returnPtr->open();
|
||||
return returnPtr;
|
||||
}
|
||||
|
||||
bool RunContext::testForMissingAssertions(Counts& assertions) {
|
||||
@@ -621,7 +653,7 @@ namespace Catch {
|
||||
// and since IResultCapture::getLastResult is deprecated,
|
||||
// we will leave it as is, until it is finally removed.
|
||||
Detail::LockGuard _( m_assertionMutex );
|
||||
return &(*m_lastResult);
|
||||
return &*m_lastResult;
|
||||
}
|
||||
|
||||
void RunContext::exceptionEarlyReported() {
|
||||
@@ -695,10 +727,6 @@ namespace Catch {
|
||||
m_reporter->testRunEnded(TestRunStats(m_runInfo, m_totals, false));
|
||||
}
|
||||
|
||||
bool RunContext::lastAssertionPassed() {
|
||||
return Detail::g_lastAssertionPassed;
|
||||
}
|
||||
|
||||
void RunContext::assertionPassedFastPath(SourceLineInfo lineInfo) {
|
||||
// We want to save the line info for better experience with unexpected assertions
|
||||
Detail::g_lastKnownLineInfo = lineInfo;
|
||||
@@ -877,7 +905,7 @@ namespace Catch {
|
||||
}
|
||||
|
||||
void RunContext::populateReaction( AssertionReaction& reaction,
|
||||
bool has_normal_disposition ) {
|
||||
bool has_normal_disposition ) const {
|
||||
reaction.shouldDebugBreak = m_shouldDebugBreak;
|
||||
reaction.shouldThrow = aborting() || has_normal_disposition;
|
||||
}
|
||||
@@ -928,22 +956,6 @@ namespace Catch {
|
||||
}
|
||||
}
|
||||
|
||||
void IResultCapture::pushScopedMessage( MessageInfo&& message ) {
|
||||
Detail::g_messageHolder().addScopedMessage( CATCH_MOVE( message ) );
|
||||
}
|
||||
|
||||
void IResultCapture::popScopedMessage( unsigned int messageId ) {
|
||||
Detail::g_messageHolder().removeMessage( messageId );
|
||||
}
|
||||
|
||||
void IResultCapture::emplaceUnscopedMessage( MessageBuilder&& builder ) {
|
||||
Detail::g_messageHolder().addUnscopedMessage( CATCH_MOVE( builder ) );
|
||||
}
|
||||
|
||||
void IResultCapture::addUnscopedMessage( MessageInfo&& message ) {
|
||||
Detail::g_messageHolder().addUnscopedMessage( CATCH_MOVE( message ) );
|
||||
}
|
||||
|
||||
void seedRng(IConfig const& config) {
|
||||
sharedRng().seed(config.rngSeed());
|
||||
}
|
||||
|
||||
@@ -102,8 +102,6 @@ namespace Catch {
|
||||
|
||||
void handleFatalErrorCondition( StringRef message ) override;
|
||||
|
||||
bool lastAssertionPassed() override;
|
||||
|
||||
public:
|
||||
// !TBD We need to do this another way!
|
||||
bool aborting() const;
|
||||
@@ -125,7 +123,7 @@ namespace Catch {
|
||||
ITransientExpression const *expr,
|
||||
bool negated );
|
||||
|
||||
void populateReaction( AssertionReaction& reaction, bool has_normal_disposition );
|
||||
void populateReaction( AssertionReaction& reaction, bool has_normal_disposition ) const;
|
||||
|
||||
// Creates dummy info for unexpected exceptions/fatal errors,
|
||||
// where we do not have the access to one, but we still need
|
||||
|
||||
@@ -20,7 +20,7 @@ namespace Catch {
|
||||
|
||||
|
||||
template<typename SingletonImplT, typename InterfaceT = SingletonImplT, typename MutableInterfaceT = InterfaceT>
|
||||
class Singleton : SingletonImplT, public ISingleton {
|
||||
class Singleton final : SingletonImplT, public ISingleton {
|
||||
|
||||
static auto getInternal() -> Singleton* {
|
||||
static Singleton* s_instance = nullptr;
|
||||
|
||||
@@ -16,10 +16,11 @@ namespace Catch {
|
||||
|
||||
TagAlias const* TagAliasRegistry::find( std::string const& alias ) const {
|
||||
auto it = m_registry.find( alias );
|
||||
if( it != m_registry.end() )
|
||||
return &(it->second);
|
||||
else
|
||||
if ( it != m_registry.end() ) {
|
||||
return &it->second;
|
||||
} else {
|
||||
return nullptr;
|
||||
}
|
||||
}
|
||||
|
||||
std::string TagAliasRegistry::expandAliases( std::string const& unexpandedTestSpec ) const {
|
||||
|
||||
@@ -17,7 +17,7 @@
|
||||
namespace Catch {
|
||||
struct SourceLineInfo;
|
||||
|
||||
class TagAliasRegistry : public ITagAliasRegistry {
|
||||
class TagAliasRegistry final : public ITagAliasRegistry {
|
||||
public:
|
||||
~TagAliasRegistry() override;
|
||||
TagAlias const* find( std::string const& alias ) const override;
|
||||
|
||||
@@ -245,42 +245,42 @@
|
||||
INTERNAL_CATCH_EXPAND_VARGS( INTERNAL_CATCH_TEMPLATE_TEST_CASE_METHOD_2( INTERNAL_CATCH_UNIQUE_NAME( CATCH2_INTERNAL_TEMPLATE_TEST_CLASS_ ), INTERNAL_CATCH_UNIQUE_NAME( CATCH2_INTERNAL_TEMPLATE_TEST_ ) , ClassName, Name, Tags, Signature, __VA_ARGS__ ) )
|
||||
#endif
|
||||
|
||||
#define INTERNAL_CATCH_TEMPLATE_PRODUCT_TEST_CASE_METHOD_2(TestNameClass, TestName, ClassName, Name, Tags, Signature, TmplTypes, TypesList)\
|
||||
CATCH_INTERNAL_START_WARNINGS_SUPPRESSION \
|
||||
CATCH_INTERNAL_SUPPRESS_GLOBALS_WARNINGS \
|
||||
CATCH_INTERNAL_SUPPRESS_ZERO_VARIADIC_WARNINGS \
|
||||
CATCH_INTERNAL_SUPPRESS_UNUSED_TEMPLATE_WARNINGS \
|
||||
CATCH_INTERNAL_SUPPRESS_UNUSED_VARIABLE_WARNINGS \
|
||||
#define INTERNAL_CATCH_TEMPLATE_PRODUCT_TEST_CASE_METHOD_2(TestNameClass, TestName, ClassName, Name, Tags, Signature, TmplTypes, TypesList)\
|
||||
CATCH_INTERNAL_START_WARNINGS_SUPPRESSION \
|
||||
CATCH_INTERNAL_SUPPRESS_GLOBALS_WARNINGS \
|
||||
CATCH_INTERNAL_SUPPRESS_ZERO_VARIADIC_WARNINGS \
|
||||
CATCH_INTERNAL_SUPPRESS_UNUSED_TEMPLATE_WARNINGS \
|
||||
CATCH_INTERNAL_SUPPRESS_UNUSED_VARIABLE_WARNINGS \
|
||||
namespace {\
|
||||
template<typename TestType> \
|
||||
struct TestName : INTERNAL_CATCH_REMOVE_PARENS(ClassName <TestType>) { \
|
||||
void test();\
|
||||
};\
|
||||
namespace {\
|
||||
namespace INTERNAL_CATCH_MAKE_NAMESPACE(TestNameClass) {\
|
||||
INTERNAL_CATCH_TYPE_GEN \
|
||||
INTERNAL_CATCH_NTTP_GEN(INTERNAL_CATCH_REMOVE_PARENS(Signature))\
|
||||
template<typename...Types>\
|
||||
struct TestNameClass{\
|
||||
void reg_tests(){\
|
||||
std::size_t index = 0;\
|
||||
using expander = std::size_t[];\
|
||||
constexpr char const* tmpl_types[] = {CATCH_REC_LIST(INTERNAL_CATCH_STRINGIZE_WITHOUT_PARENS, INTERNAL_CATCH_REMOVE_PARENS(TmplTypes))};\
|
||||
constexpr char const* types_list[] = {CATCH_REC_LIST(INTERNAL_CATCH_STRINGIZE_WITHOUT_PARENS, INTERNAL_CATCH_REMOVE_PARENS(TypesList))};\
|
||||
constexpr auto num_types = sizeof(types_list) / sizeof(types_list[0]);\
|
||||
(void)expander{(Catch::AutoReg( Catch::makeTestInvoker( &TestName<Types>::test ), CATCH_INTERNAL_LINEINFO, #ClassName, Catch::NameAndTags{ Name " - " + std::string(tmpl_types[index / num_types]) + '<' + types_list[index % num_types] + '>', Tags } ), index++)... };/* NOLINT */ \
|
||||
}\
|
||||
};\
|
||||
static const int INTERNAL_CATCH_UNIQUE_NAME( globalRegistrar ) = [](){\
|
||||
using TestInit = typename create<TestNameClass, decltype(get_wrapper<INTERNAL_CATCH_REMOVE_PARENS(TmplTypes)>(Catch::Detail::priority_tag<1>{})), TypeList<INTERNAL_CATCH_MAKE_TYPE_LISTS_FROM_TYPES(INTERNAL_CATCH_REMOVE_PARENS(TypesList))>>::type;\
|
||||
TestInit t;\
|
||||
t.reg_tests();\
|
||||
return 0;\
|
||||
}(); \
|
||||
}\
|
||||
}\
|
||||
CATCH_INTERNAL_STOP_WARNINGS_SUPPRESSION \
|
||||
template<typename TestType> \
|
||||
void TestName<TestType>::test()
|
||||
struct TestName : INTERNAL_CATCH_REMOVE_PARENS(ClassName <TestType>) { \
|
||||
void test();\
|
||||
};\
|
||||
namespace INTERNAL_CATCH_MAKE_NAMESPACE(TestNameClass) {\
|
||||
INTERNAL_CATCH_TYPE_GEN \
|
||||
INTERNAL_CATCH_NTTP_GEN(INTERNAL_CATCH_REMOVE_PARENS(Signature))\
|
||||
template<typename...Types>\
|
||||
struct TestNameClass{\
|
||||
void reg_tests(){\
|
||||
std::size_t index = 0;\
|
||||
using expander = std::size_t[];\
|
||||
constexpr char const* tmpl_types[] = {CATCH_REC_LIST(INTERNAL_CATCH_STRINGIZE_WITHOUT_PARENS, INTERNAL_CATCH_REMOVE_PARENS(TmplTypes))};\
|
||||
constexpr char const* types_list[] = {CATCH_REC_LIST(INTERNAL_CATCH_STRINGIZE_WITHOUT_PARENS, INTERNAL_CATCH_REMOVE_PARENS(TypesList))};\
|
||||
constexpr auto num_types = sizeof(types_list) / sizeof(types_list[0]);\
|
||||
(void)expander{(Catch::AutoReg( Catch::makeTestInvoker( &TestName<Types>::test ), CATCH_INTERNAL_LINEINFO, #ClassName, Catch::NameAndTags{ Name " - " + std::string(tmpl_types[index / num_types]) + '<' + types_list[index % num_types] + '>', Tags } ), index++)... };/* NOLINT */ \
|
||||
}\
|
||||
};\
|
||||
static const int INTERNAL_CATCH_UNIQUE_NAME( globalRegistrar ) = [](){\
|
||||
using TestInit = typename create<TestNameClass, decltype(get_wrapper<INTERNAL_CATCH_REMOVE_PARENS(TmplTypes)>(Catch::Detail::priority_tag<1>{})), TypeList<INTERNAL_CATCH_MAKE_TYPE_LISTS_FROM_TYPES(INTERNAL_CATCH_REMOVE_PARENS(TypesList))>>::type;\
|
||||
TestInit t;\
|
||||
t.reg_tests();\
|
||||
return 0;\
|
||||
}(); \
|
||||
}\
|
||||
}\
|
||||
CATCH_INTERNAL_STOP_WARNINGS_SUPPRESSION \
|
||||
template<typename TestType> \
|
||||
void TestName<TestType>::test()
|
||||
|
||||
#ifndef CATCH_CONFIG_TRADITIONAL_MSVC_PREPROCESSOR
|
||||
#define INTERNAL_CATCH_TEMPLATE_PRODUCT_TEST_CASE_METHOD( ClassName, Name, Tags, ... )\
|
||||
@@ -298,37 +298,37 @@
|
||||
INTERNAL_CATCH_EXPAND_VARGS( INTERNAL_CATCH_TEMPLATE_PRODUCT_TEST_CASE_METHOD_2( INTERNAL_CATCH_UNIQUE_NAME( CATCH2_INTERNAL_TEMPLATE_TEST_ ), INTERNAL_CATCH_UNIQUE_NAME( CATCH2_INTERNAL_TEMPLATE_TEST_ ), ClassName, Name, Tags, Signature,__VA_ARGS__ ) )
|
||||
#endif
|
||||
|
||||
#define INTERNAL_CATCH_TEMPLATE_LIST_TEST_CASE_METHOD_2( TestNameClass, TestName, ClassName, Name, Tags, TmplList) \
|
||||
CATCH_INTERNAL_START_WARNINGS_SUPPRESSION \
|
||||
CATCH_INTERNAL_SUPPRESS_GLOBALS_WARNINGS \
|
||||
CATCH_INTERNAL_SUPPRESS_UNUSED_TEMPLATE_WARNINGS \
|
||||
CATCH_INTERNAL_SUPPRESS_UNUSED_VARIABLE_WARNINGS \
|
||||
CATCH_INTERNAL_SUPPRESS_COMMA_WARNINGS \
|
||||
#define INTERNAL_CATCH_TEMPLATE_LIST_TEST_CASE_METHOD_2( TestNameClass, TestName, ClassName, Name, Tags, TmplList) \
|
||||
CATCH_INTERNAL_START_WARNINGS_SUPPRESSION \
|
||||
CATCH_INTERNAL_SUPPRESS_GLOBALS_WARNINGS \
|
||||
CATCH_INTERNAL_SUPPRESS_UNUSED_TEMPLATE_WARNINGS \
|
||||
CATCH_INTERNAL_SUPPRESS_UNUSED_VARIABLE_WARNINGS \
|
||||
CATCH_INTERNAL_SUPPRESS_COMMA_WARNINGS \
|
||||
namespace {\
|
||||
template<typename TestType> \
|
||||
struct TestName : INTERNAL_CATCH_REMOVE_PARENS(ClassName <TestType>) { \
|
||||
void test();\
|
||||
};\
|
||||
namespace {\
|
||||
namespace INTERNAL_CATCH_MAKE_NAMESPACE(TestName){ \
|
||||
INTERNAL_CATCH_TYPE_GEN\
|
||||
template<typename...Types>\
|
||||
struct TestNameClass{\
|
||||
void reg_tests(){\
|
||||
size_t index = 0;\
|
||||
using expander = size_t[];\
|
||||
(void)expander{(Catch::AutoReg( Catch::makeTestInvoker( &TestName<Types>::test ), CATCH_INTERNAL_LINEINFO, #ClassName##_catch_sr, Catch::NameAndTags{ Name " - " INTERNAL_CATCH_STRINGIZE(TmplList) " - " + std::to_string(index), Tags } ), index++)... };/* NOLINT */ \
|
||||
}\
|
||||
};\
|
||||
static const int INTERNAL_CATCH_UNIQUE_NAME( globalRegistrar ) = [](){\
|
||||
using TestInit = typename convert<TestNameClass, TmplList>::type;\
|
||||
TestInit t;\
|
||||
t.reg_tests();\
|
||||
return 0;\
|
||||
}(); \
|
||||
}}\
|
||||
CATCH_INTERNAL_STOP_WARNINGS_SUPPRESSION \
|
||||
template<typename TestType> \
|
||||
void TestName<TestType>::test()
|
||||
namespace INTERNAL_CATCH_MAKE_NAMESPACE(TestName){ \
|
||||
INTERNAL_CATCH_TYPE_GEN\
|
||||
template<typename...Types>\
|
||||
struct TestNameClass{\
|
||||
void reg_tests(){\
|
||||
size_t index = 0;\
|
||||
using expander = size_t[];\
|
||||
(void)expander{(Catch::AutoReg( Catch::makeTestInvoker( &TestName<Types>::test ), CATCH_INTERNAL_LINEINFO, #ClassName##_catch_sr, Catch::NameAndTags{ Name " - " INTERNAL_CATCH_STRINGIZE(TmplList) " - " + std::to_string(index), Tags } ), index++)... };/* NOLINT */ \
|
||||
}\
|
||||
};\
|
||||
static const int INTERNAL_CATCH_UNIQUE_NAME( globalRegistrar ) = [](){\
|
||||
using TestInit = typename convert<TestNameClass, TmplList>::type;\
|
||||
TestInit t;\
|
||||
t.reg_tests();\
|
||||
return 0;\
|
||||
}(); \
|
||||
}}\
|
||||
CATCH_INTERNAL_STOP_WARNINGS_SUPPRESSION \
|
||||
template<typename TestType> \
|
||||
void TestName<TestType>::test()
|
||||
|
||||
#define INTERNAL_CATCH_TEMPLATE_LIST_TEST_CASE_METHOD(ClassName, Name, Tags, TmplList) \
|
||||
INTERNAL_CATCH_TEMPLATE_LIST_TEST_CASE_METHOD_2( INTERNAL_CATCH_UNIQUE_NAME( CATCH2_INTERNAL_TEMPLATE_TEST_ ), INTERNAL_CATCH_UNIQUE_NAME( CATCH2_INTERNAL_TEMPLATE_TEST_ ), ClassName, Name, Tags, TmplList )
|
||||
|
||||
@@ -28,7 +28,7 @@ namespace Catch {
|
||||
std::vector<TestCaseHandle> filterTests( std::vector<TestCaseHandle> const& testCases, TestSpec const& testSpec, IConfig const& config );
|
||||
std::vector<TestCaseHandle> const& getAllTestCasesSorted( IConfig const& config );
|
||||
|
||||
class TestRegistry : public ITestCaseRegistry {
|
||||
class TestRegistry final : public ITestCaseRegistry {
|
||||
public:
|
||||
void registerTest( Detail::unique_ptr<TestCaseInfo> testInfo, Detail::unique_ptr<ITestInvoker> testInvoker );
|
||||
|
||||
|
||||
@@ -167,6 +167,28 @@ namespace TestCaseTracking {
|
||||
m_ctx.setCurrentTracker( this );
|
||||
}
|
||||
|
||||
bool SectionTracker::isFilteredImpl() const {
|
||||
// TBD: This is currently _very_ similar to the block in `isComplete`.
|
||||
// Is this neccessarily that way, or just accident of current semantics?
|
||||
const size_t filterIndex =
|
||||
m_newStyleFilters ? m_allTrackerDepth : m_sectionOnlyDepth;
|
||||
|
||||
if ( filterIndex < m_filterRef->size() ) {
|
||||
// 1) New style filter must explicitly target section
|
||||
if ( m_newStyleFilters && ( *m_filterRef )[filterIndex].type !=
|
||||
PathFilter::For::Section ) {
|
||||
return true;
|
||||
}
|
||||
// 2) Both style filters must match the trimmed name exactly
|
||||
if ( m_trimmed_name !=
|
||||
StringRef( ( *m_filterRef )[filterIndex].filter ) ) {
|
||||
return true;
|
||||
}
|
||||
}
|
||||
|
||||
return false;
|
||||
}
|
||||
|
||||
SectionTracker::SectionTracker( NameAndLocation&& nameAndLocation, TrackerContext& ctx, ITracker* parent )
|
||||
: TrackerBase( CATCH_MOVE(nameAndLocation), ctx, parent ),
|
||||
m_trimmed_name(trim(StringRef(ITracker::nameAndLocation().name)))
|
||||
|
||||
@@ -12,6 +12,7 @@
|
||||
#include <catch2/internal/catch_source_line_info.hpp>
|
||||
#include <catch2/internal/catch_unique_ptr.hpp>
|
||||
#include <catch2/internal/catch_stringref.hpp>
|
||||
#include <catch2/internal/catch_path_filter.hpp>
|
||||
|
||||
#include <string>
|
||||
#include <vector>
|
||||
@@ -81,6 +82,8 @@ namespace TestCaseTracking {
|
||||
|
||||
using Children = std::vector<ITrackerPtr>;
|
||||
|
||||
virtual bool isFilteredImpl() const = 0;
|
||||
|
||||
protected:
|
||||
enum CycleState {
|
||||
NotStarted,
|
||||
@@ -178,6 +181,20 @@ namespace TestCaseTracking {
|
||||
* for internal debug checks.
|
||||
*/
|
||||
virtual bool isGeneratorTracker() const;
|
||||
|
||||
/**
|
||||
* Returns true if the concrete tracker instance has a filter that applies to it.
|
||||
*/
|
||||
bool isFiltered() const {
|
||||
// Fast path: are there even filters for tracker in this position?
|
||||
const size_t filter_depth =
|
||||
m_newStyleFilters ? m_allTrackerDepth : m_sectionOnlyDepth;
|
||||
if ( m_filterRef->size() <= filter_depth ) { return false; }
|
||||
|
||||
// Slow path: If there are filters, ask the concrete tracker.
|
||||
// This handles things like match-all filters for that tracker.
|
||||
return isFilteredImpl();
|
||||
}
|
||||
};
|
||||
|
||||
class TrackerContext {
|
||||
@@ -233,6 +250,8 @@ namespace TestCaseTracking {
|
||||
// to not own the name, the name still has to outlive the `ITracker` parent, so
|
||||
// this should still be safe.
|
||||
StringRef m_trimmed_name;
|
||||
|
||||
bool isFilteredImpl() const override;
|
||||
public:
|
||||
SectionTracker( NameAndLocation&& nameAndLocation, TrackerContext& ctx, ITracker* parent );
|
||||
|
||||
|
||||
@@ -14,7 +14,7 @@ namespace Catch {
|
||||
|
||||
void throw_test_failure_exception() {
|
||||
#if !defined( CATCH_CONFIG_DISABLE_EXCEPTIONS )
|
||||
throw TestFailureException{};
|
||||
throw TestFailureException{}; //NOLINT(bugprone-std-exception-baseclass)
|
||||
#else
|
||||
CATCH_ERROR( "Test failure requires aborting test!" );
|
||||
#endif
|
||||
@@ -22,7 +22,7 @@ namespace Catch {
|
||||
|
||||
void throw_test_skip_exception() {
|
||||
#if !defined( CATCH_CONFIG_DISABLE_EXCEPTIONS )
|
||||
throw Catch::TestSkipException();
|
||||
throw Catch::TestSkipException(); //NOLINT(bugprone-std-exception-baseclass)
|
||||
#else
|
||||
CATCH_ERROR( "Explicitly skipping tests during runtime requires exceptions" );
|
||||
#endif
|
||||
|
||||
@@ -11,10 +11,16 @@
|
||||
#include <catch2/catch_user_config.hpp>
|
||||
#include <catch2/internal/catch_assertion_handler.hpp>
|
||||
#include <catch2/internal/catch_preprocessor_internal_stringify.hpp>
|
||||
#include <catch2/interfaces/catch_interfaces_capture.hpp>
|
||||
#include <catch2/internal/catch_stringref.hpp>
|
||||
#include <catch2/internal/catch_source_line_info.hpp>
|
||||
|
||||
namespace Catch {
|
||||
namespace Detail {
|
||||
// Defined in catch_run_context.cpp, where the thread-local data lives.
|
||||
bool lastAssertionPassed();
|
||||
}
|
||||
}
|
||||
|
||||
// We need this suppression to leak, because it took until GCC 10
|
||||
// for the front end to handle local suppression via _Pragma properly
|
||||
#if defined(__GNUC__) && !defined(__clang__) && !defined(__ICC) && __GNUC__ <= 9
|
||||
@@ -57,12 +63,12 @@
|
||||
///////////////////////////////////////////////////////////////////////////////
|
||||
#define INTERNAL_CATCH_IF( macroName, resultDisposition, ... ) \
|
||||
INTERNAL_CATCH_TEST( macroName, resultDisposition, __VA_ARGS__ ); \
|
||||
if( Catch::getResultCapture().lastAssertionPassed() )
|
||||
if( Catch::Detail::lastAssertionPassed() )
|
||||
|
||||
///////////////////////////////////////////////////////////////////////////////
|
||||
#define INTERNAL_CATCH_ELSE( macroName, resultDisposition, ... ) \
|
||||
INTERNAL_CATCH_TEST( macroName, resultDisposition, __VA_ARGS__ ); \
|
||||
if( !Catch::getResultCapture().lastAssertionPassed() )
|
||||
if( !Catch::Detail::lastAssertionPassed() )
|
||||
|
||||
///////////////////////////////////////////////////////////////////////////////
|
||||
#define INTERNAL_CATCH_NO_THROW( macroName, resultDisposition, ... ) \
|
||||
|
||||
@@ -29,7 +29,7 @@
|
||||
namespace Catch {
|
||||
|
||||
template<typename C>
|
||||
class TestInvokerAsMethod : public ITestInvoker {
|
||||
class TestInvokerAsMethod final : public ITestInvoker {
|
||||
void (C::*m_testAsMethod)();
|
||||
public:
|
||||
constexpr TestInvokerAsMethod( void ( C::*testAsMethod )() ) noexcept:
|
||||
@@ -49,7 +49,7 @@ Detail::unique_ptr<ITestInvoker> makeTestInvoker( void (C::*testAsMethod)() ) {
|
||||
}
|
||||
|
||||
template <typename C>
|
||||
class TestInvokerFixture : public ITestInvoker {
|
||||
class TestInvokerFixture final : public ITestInvoker {
|
||||
void ( C::*m_testAsMethod )() const;
|
||||
Detail::unique_ptr<C> m_fixture = nullptr;
|
||||
|
||||
@@ -124,7 +124,7 @@ struct AutoReg : Detail::NonCopyable {
|
||||
namespace Catch {
|
||||
namespace Detail {
|
||||
struct DummyUse {
|
||||
DummyUse( void ( * )( int ), Catch::NameAndTags const& );
|
||||
DummyUse( void ( * )( int ), Catch::NameAndTags const& ) noexcept;
|
||||
};
|
||||
} // namespace Detail
|
||||
} // namespace Catch
|
||||
|
||||
@@ -26,6 +26,10 @@ namespace {
|
||||
return std::memchr( chars, c, sizeof( chars ) - 1 ) != nullptr;
|
||||
}
|
||||
|
||||
bool isUtf8ContinuationByte( char c ) {
|
||||
return ( static_cast<unsigned char>( c ) & 0xC0 ) == 0x80;
|
||||
}
|
||||
|
||||
} // namespace
|
||||
|
||||
namespace Catch {
|
||||
@@ -52,6 +56,11 @@ namespace Catch {
|
||||
if ( it != m_string.end() ) {
|
||||
++m_size;
|
||||
++it;
|
||||
// Skip UTF-8 continuation bytes
|
||||
while ( it != m_string.end() &&
|
||||
isUtf8ContinuationByte( *it ) ) {
|
||||
++it;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -114,6 +123,11 @@ namespace Catch {
|
||||
void AnsiSkippingString::const_iterator::advance() {
|
||||
assert( m_it != m_string->end() );
|
||||
m_it++;
|
||||
// Skip UTF-8 continuation bytes
|
||||
while ( m_it != m_string->end() &&
|
||||
isUtf8ContinuationByte( *m_it ) ) {
|
||||
m_it++;
|
||||
}
|
||||
tryParseAnsiEscapes();
|
||||
}
|
||||
|
||||
@@ -133,6 +147,11 @@ namespace Catch {
|
||||
assert( *m_it == '\033' );
|
||||
m_it--;
|
||||
}
|
||||
// Skip back over UTF-8 continuation bytes to the leading byte
|
||||
while ( m_it != m_string->begin() &&
|
||||
isUtf8ContinuationByte( *m_it ) ) {
|
||||
m_it--;
|
||||
}
|
||||
}
|
||||
|
||||
static bool isBoundary( AnsiSkippingString const& line,
|
||||
|
||||
@@ -8,9 +8,22 @@
|
||||
#ifndef CATCH_UNIQUE_NAME_HPP_INCLUDED
|
||||
#define CATCH_UNIQUE_NAME_HPP_INCLUDED
|
||||
|
||||
#include <catch2/internal/catch_compiler_capabilities.hpp>
|
||||
#include <catch2/internal/catch_config_counter.hpp>
|
||||
|
||||
// Fixme: Clang 22 has an annoying bug where the localized suppression
|
||||
// below does not actually suppress the extension warning from
|
||||
// using __COUNTER__, so we have to leak the suppression for the
|
||||
// whole TU. Hopefully Clang 23 fixes this before full release.
|
||||
// As AppleClang does its own thing version-wise, we ignore it
|
||||
// completely.
|
||||
#if defined( __clang__ ) && ( __clang_major__ >= 22 ) && !defined( __APPLE__ )
|
||||
CATCH_INTERNAL_SUPPRESS_COUNTER_WARNINGS
|
||||
#endif
|
||||
|
||||
#define INTERNAL_CATCH_UNIQUE_NAME_LINE2( name, line ) name##line
|
||||
#define INTERNAL_CATCH_UNIQUE_NAME_LINE( name, line ) INTERNAL_CATCH_UNIQUE_NAME_LINE2( name, line )
|
||||
|
||||
#ifdef CATCH_CONFIG_COUNTER
|
||||
# define INTERNAL_CATCH_UNIQUE_NAME( name ) INTERNAL_CATCH_UNIQUE_NAME_LINE( name, __COUNTER__ )
|
||||
#else
|
||||
|
||||
@@ -13,13 +13,13 @@ namespace Catch {
|
||||
namespace Matchers {
|
||||
|
||||
std::string MatcherUntypedBase::toString() const {
|
||||
if (m_cachedToString.empty()) {
|
||||
m_cachedToString = describe();
|
||||
}
|
||||
return m_cachedToString;
|
||||
return describe();
|
||||
}
|
||||
|
||||
MatcherUntypedBase::~MatcherUntypedBase() = default;
|
||||
std::string MatcherUntypedBase::describe() const {
|
||||
using namespace std::string_literals;
|
||||
return "Undescribed matcher"s;
|
||||
}
|
||||
|
||||
} // namespace Matchers
|
||||
} // namespace Catch
|
||||
|
||||
@@ -20,10 +20,10 @@ namespace Matchers {
|
||||
|
||||
class MatcherUntypedBase {
|
||||
public:
|
||||
MatcherUntypedBase() = default;
|
||||
constexpr MatcherUntypedBase() = default;
|
||||
|
||||
MatcherUntypedBase(MatcherUntypedBase const&) = default;
|
||||
MatcherUntypedBase(MatcherUntypedBase&&) = default;
|
||||
constexpr MatcherUntypedBase(MatcherUntypedBase const&) = default;
|
||||
constexpr MatcherUntypedBase(MatcherUntypedBase&&) = default;
|
||||
|
||||
MatcherUntypedBase& operator = (MatcherUntypedBase const&) = delete;
|
||||
MatcherUntypedBase& operator = (MatcherUntypedBase&&) = delete;
|
||||
@@ -31,9 +31,9 @@ namespace Matchers {
|
||||
std::string toString() const;
|
||||
|
||||
protected:
|
||||
virtual ~MatcherUntypedBase(); // = default;
|
||||
virtual std::string describe() const = 0;
|
||||
mutable std::string m_cachedToString;
|
||||
CATCH_DESTRUCTOR_CONSTEXPR virtual ~MatcherUntypedBase() = default;
|
||||
//! Should be overridden, but we provide default "undescribed" impl
|
||||
virtual std::string describe() const;
|
||||
};
|
||||
|
||||
|
||||
@@ -215,6 +215,19 @@ namespace Matchers {
|
||||
#define CATCH_CHECK_THAT( arg, matcher ) INTERNAL_CHECK_THAT( "CATCH_CHECK_THAT", matcher, Catch::ResultDisposition::ContinueOnFailure, arg )
|
||||
#define CATCH_REQUIRE_THAT( arg, matcher ) INTERNAL_CHECK_THAT( "CATCH_REQUIRE_THAT", matcher, Catch::ResultDisposition::Normal, arg )
|
||||
|
||||
#if !defined(CATCH_CONFIG_RUNTIME_STATIC_REQUIRE)
|
||||
#define CATCH_STATIC_REQUIRE_THAT( arg, matcher ) \
|
||||
static_assert( ( matcher ).match( arg ), #matcher ".match( " #arg " )"); \
|
||||
CATCH_SUCCEED( #matcher ".match( " #arg " )" )
|
||||
#define CATCH_STATIC_CHECK_THAT( arg, matcher ) \
|
||||
static_assert( ( matcher ).match( arg ), #matcher ".match( " #arg " )"); \
|
||||
CATCH_SUCCEED( #matcher ".match( " #arg " )" )
|
||||
#else
|
||||
#define CATCH_STATIC_REQUIRE_THAT( arg, matcher ) CATCH_REQUIRE_THAT( arg, matcher )
|
||||
#define CATCH_STATIC_CHECK_THAT( arg, matcher ) CATCH_CHECK_THAT( arg, matcher )
|
||||
#endif
|
||||
|
||||
|
||||
#elif defined(CATCH_CONFIG_PREFIX_ALL) && defined(CATCH_CONFIG_DISABLE)
|
||||
|
||||
#define CATCH_REQUIRE_THROWS_WITH( expr, matcher ) (void)(0)
|
||||
@@ -226,6 +239,9 @@ namespace Matchers {
|
||||
#define CATCH_CHECK_THAT( arg, matcher ) (void)(0)
|
||||
#define CATCH_REQUIRE_THAT( arg, matcher ) (void)(0)
|
||||
|
||||
#define CATCH_STATIC_REQUIRE_THAT( arg, matcher ) (void)(0)
|
||||
#define CATCH_STATIC_CHECK_THAT( arg, matcher ) (void)(0)
|
||||
|
||||
#elif !defined(CATCH_CONFIG_PREFIX_ALL) && !defined(CATCH_CONFIG_DISABLE)
|
||||
|
||||
#define REQUIRE_THROWS_WITH( expr, matcher ) INTERNAL_CATCH_THROWS_STR_MATCHES( "REQUIRE_THROWS_WITH", Catch::ResultDisposition::Normal, matcher, expr )
|
||||
@@ -237,6 +253,19 @@ namespace Matchers {
|
||||
#define CHECK_THAT( arg, matcher ) INTERNAL_CHECK_THAT( "CHECK_THAT", matcher, Catch::ResultDisposition::ContinueOnFailure, arg )
|
||||
#define REQUIRE_THAT( arg, matcher ) INTERNAL_CHECK_THAT( "REQUIRE_THAT", matcher, Catch::ResultDisposition::Normal, arg )
|
||||
|
||||
#if !defined(CATCH_CONFIG_RUNTIME_STATIC_REQUIRE)
|
||||
#define STATIC_REQUIRE_THAT( arg, matcher ) \
|
||||
static_assert( ( matcher ).match( arg ), #matcher ".match( " #arg " )"); \
|
||||
SUCCEED( #matcher ".match( " #arg " )" )
|
||||
#define STATIC_CHECK_THAT( arg, matcher ) \
|
||||
static_assert( ( matcher ).match( arg ), #matcher ".match( " #arg " )"); \
|
||||
SUCCEED( #matcher ".match( " #arg " )" )
|
||||
#else
|
||||
#define STATIC_REQUIRE_THAT( arg, matcher ) REQUIRE_THAT( arg, matcher )
|
||||
#define STATIC_CHECK_THAT( arg, matcher ) CHECK_THAT( arg, matcher )
|
||||
#endif
|
||||
|
||||
|
||||
#elif !defined(CATCH_CONFIG_PREFIX_ALL) && defined(CATCH_CONFIG_DISABLE)
|
||||
|
||||
#define REQUIRE_THROWS_WITH( expr, matcher ) (void)(0)
|
||||
@@ -248,6 +277,9 @@ namespace Matchers {
|
||||
#define CHECK_THAT( arg, matcher ) (void)(0)
|
||||
#define REQUIRE_THAT( arg, matcher ) (void)(0)
|
||||
|
||||
#define STATIC_REQUIRE_THAT( arg, matcher ) (void)(0)
|
||||
#define STATIC_CHECK_THAT( arg, matcher ) (void)(0)
|
||||
|
||||
#endif // end of user facing macro declarations
|
||||
|
||||
#endif // CATCH_MATCHERS_HPP_INCLUDED
|
||||
|
||||
@@ -22,13 +22,5 @@ namespace Matchers {
|
||||
return sstr.str();
|
||||
}
|
||||
|
||||
IsEmptyMatcher IsEmpty() {
|
||||
return {};
|
||||
}
|
||||
|
||||
HasSizeMatcher SizeIs(std::size_t sz) {
|
||||
return HasSizeMatcher{ sz };
|
||||
}
|
||||
|
||||
} // end namespace Matchers
|
||||
} // end namespace Catch
|
||||
|
||||
@@ -18,7 +18,7 @@ namespace Catch {
|
||||
class IsEmptyMatcher final : public MatcherGenericBase {
|
||||
public:
|
||||
template <typename RangeLike>
|
||||
bool match(RangeLike&& rng) const {
|
||||
constexpr bool match(RangeLike&& rng) const {
|
||||
#if defined(CATCH_CONFIG_POLYFILL_NONMEMBER_CONTAINER_ACCESS)
|
||||
using Catch::Detail::empty;
|
||||
#else
|
||||
@@ -33,12 +33,12 @@ namespace Catch {
|
||||
class HasSizeMatcher final : public MatcherGenericBase {
|
||||
std::size_t m_target_size;
|
||||
public:
|
||||
explicit HasSizeMatcher(std::size_t target_size):
|
||||
constexpr explicit HasSizeMatcher(std::size_t target_size):
|
||||
m_target_size(target_size)
|
||||
{}
|
||||
|
||||
template <typename RangeLike>
|
||||
bool match(RangeLike&& rng) const {
|
||||
constexpr bool match(RangeLike&& rng) const {
|
||||
#if defined(CATCH_CONFIG_POLYFILL_NONMEMBER_CONTAINER_ACCESS)
|
||||
using Catch::Detail::size;
|
||||
#else
|
||||
@@ -54,12 +54,12 @@ namespace Catch {
|
||||
class SizeMatchesMatcher final : public MatcherGenericBase {
|
||||
Matcher m_matcher;
|
||||
public:
|
||||
explicit SizeMatchesMatcher(Matcher m):
|
||||
constexpr explicit SizeMatchesMatcher(Matcher m):
|
||||
m_matcher(CATCH_MOVE(m))
|
||||
{}
|
||||
|
||||
template <typename RangeLike>
|
||||
bool match(RangeLike&& rng) const {
|
||||
constexpr bool match(RangeLike&& rng) const {
|
||||
#if defined(CATCH_CONFIG_POLYFILL_NONMEMBER_CONTAINER_ACCESS)
|
||||
using Catch::Detail::size;
|
||||
#else
|
||||
@@ -75,11 +75,13 @@ namespace Catch {
|
||||
|
||||
|
||||
//! Creates a matcher that accepts empty ranges/containers
|
||||
IsEmptyMatcher IsEmpty();
|
||||
inline CATCH_DESTRUCTOR_CONSTEXPR IsEmptyMatcher IsEmpty() { return {}; }
|
||||
//! Creates a matcher that accepts ranges/containers with specific size
|
||||
HasSizeMatcher SizeIs(std::size_t sz);
|
||||
inline CATCH_DESTRUCTOR_CONSTEXPR HasSizeMatcher SizeIs( std::size_t sz ) {
|
||||
return HasSizeMatcher{ sz };
|
||||
}
|
||||
template <typename Matcher>
|
||||
std::enable_if_t<Detail::is_matcher_v<Matcher>,
|
||||
constexpr std::enable_if_t<Detail::is_matcher_v<Matcher>,
|
||||
SizeMatchesMatcher<Matcher>> SizeIs(Matcher&& m) {
|
||||
return SizeMatchesMatcher<Matcher>{CATCH_FORWARD(m)};
|
||||
}
|
||||
|
||||
@@ -23,7 +23,7 @@ namespace Catch {
|
||||
Equality m_eq;
|
||||
public:
|
||||
template <typename T2, typename Equality2>
|
||||
ContainsElementMatcher(T2&& target, Equality2&& predicate):
|
||||
constexpr ContainsElementMatcher(T2&& target, Equality2&& predicate):
|
||||
m_desired(CATCH_FORWARD(target)),
|
||||
m_eq(CATCH_FORWARD(predicate))
|
||||
{}
|
||||
@@ -33,7 +33,7 @@ namespace Catch {
|
||||
}
|
||||
|
||||
template <typename RangeLike>
|
||||
bool match( RangeLike&& rng ) const {
|
||||
constexpr bool match( RangeLike&& rng ) const {
|
||||
for ( auto&& elem : rng ) {
|
||||
if ( m_eq( elem, m_desired ) ) { return true; }
|
||||
}
|
||||
@@ -49,12 +49,12 @@ namespace Catch {
|
||||
// Note that we do a copy+move to avoid having to SFINAE this
|
||||
// constructor (and also avoid some perfect forwarding failure
|
||||
// cases)
|
||||
ContainsMatcherMatcher(Matcher matcher):
|
||||
constexpr ContainsMatcherMatcher(Matcher matcher):
|
||||
m_matcher(CATCH_MOVE(matcher))
|
||||
{}
|
||||
|
||||
template <typename RangeLike>
|
||||
bool match(RangeLike&& rng) const {
|
||||
constexpr bool match(RangeLike&& rng) const {
|
||||
for (auto&& elem : rng) {
|
||||
if (m_matcher.match(elem)) {
|
||||
return true;
|
||||
@@ -74,14 +74,14 @@ namespace Catch {
|
||||
* Uses `std::equal_to` to do the comparison
|
||||
*/
|
||||
template <typename T>
|
||||
std::enable_if_t<!Detail::is_matcher_v<T>,
|
||||
constexpr std::enable_if_t<!Detail::is_matcher_v<T>,
|
||||
ContainsElementMatcher<T, std::equal_to<>>> Contains(T&& elem) {
|
||||
return { CATCH_FORWARD(elem), std::equal_to<>{} };
|
||||
}
|
||||
|
||||
//! Creates a matcher that checks whether a range contains element matching a matcher
|
||||
template <typename Matcher>
|
||||
std::enable_if_t<Detail::is_matcher_v<Matcher>,
|
||||
constexpr std::enable_if_t<Detail::is_matcher_v<Matcher>,
|
||||
ContainsMatcherMatcher<Matcher>> Contains(Matcher&& matcher) {
|
||||
return { CATCH_FORWARD(matcher) };
|
||||
}
|
||||
@@ -92,7 +92,7 @@ namespace Catch {
|
||||
* Uses `eq` to do the comparisons, the element is provided on the rhs
|
||||
*/
|
||||
template <typename T, typename Equality>
|
||||
ContainsElementMatcher<T, Equality> Contains(T&& elem, Equality&& eq) {
|
||||
constexpr ContainsElementMatcher<T, Equality> Contains(T&& elem, Equality&& eq) {
|
||||
return { CATCH_FORWARD(elem), CATCH_FORWARD(eq) };
|
||||
}
|
||||
|
||||
|
||||
@@ -9,16 +9,12 @@
|
||||
|
||||
namespace Catch {
|
||||
namespace Matchers {
|
||||
std::string AllTrueMatcher::describe() const { return "contains only true"; }
|
||||
|
||||
AllTrueMatcher AllTrue() { return AllTrueMatcher{}; }
|
||||
std::string AllTrueMatcher::describe() const { return "contains only true"; }
|
||||
|
||||
std::string NoneTrueMatcher::describe() const { return "contains no true"; }
|
||||
|
||||
NoneTrueMatcher NoneTrue() { return NoneTrueMatcher{}; }
|
||||
|
||||
std::string AnyTrueMatcher::describe() const { return "contains at least one true"; }
|
||||
|
||||
AnyTrueMatcher AnyTrue() { return AnyTrueMatcher{}; }
|
||||
} // namespace Matchers
|
||||
} // namespace Catch
|
||||
|
||||
@@ -18,7 +18,7 @@ namespace Catch {
|
||||
class AllMatchMatcher final : public MatcherGenericBase {
|
||||
Matcher m_matcher;
|
||||
public:
|
||||
AllMatchMatcher(Matcher matcher):
|
||||
constexpr AllMatchMatcher(Matcher matcher):
|
||||
m_matcher(CATCH_MOVE(matcher))
|
||||
{}
|
||||
|
||||
@@ -27,7 +27,7 @@ namespace Catch {
|
||||
}
|
||||
|
||||
template <typename RangeLike>
|
||||
bool match(RangeLike&& rng) const {
|
||||
constexpr bool match(RangeLike&& rng) const {
|
||||
for (auto&& elem : rng) {
|
||||
if (!m_matcher.match(elem)) {
|
||||
return false;
|
||||
@@ -42,7 +42,7 @@ namespace Catch {
|
||||
class NoneMatchMatcher final : public MatcherGenericBase {
|
||||
Matcher m_matcher;
|
||||
public:
|
||||
NoneMatchMatcher(Matcher matcher):
|
||||
constexpr NoneMatchMatcher(Matcher matcher):
|
||||
m_matcher(CATCH_MOVE(matcher))
|
||||
{}
|
||||
|
||||
@@ -51,7 +51,7 @@ namespace Catch {
|
||||
}
|
||||
|
||||
template <typename RangeLike>
|
||||
bool match(RangeLike&& rng) const {
|
||||
constexpr bool match(RangeLike&& rng) const {
|
||||
for (auto&& elem : rng) {
|
||||
if (m_matcher.match(elem)) {
|
||||
return false;
|
||||
@@ -66,7 +66,7 @@ namespace Catch {
|
||||
class AnyMatchMatcher final : public MatcherGenericBase {
|
||||
Matcher m_matcher;
|
||||
public:
|
||||
AnyMatchMatcher(Matcher matcher):
|
||||
constexpr AnyMatchMatcher(Matcher matcher):
|
||||
m_matcher(CATCH_MOVE(matcher))
|
||||
{}
|
||||
|
||||
@@ -75,7 +75,7 @@ namespace Catch {
|
||||
}
|
||||
|
||||
template <typename RangeLike>
|
||||
bool match(RangeLike&& rng) const {
|
||||
constexpr bool match(RangeLike&& rng) const {
|
||||
for (auto&& elem : rng) {
|
||||
if (m_matcher.match(elem)) {
|
||||
return true;
|
||||
@@ -91,7 +91,7 @@ namespace Catch {
|
||||
std::string describe() const override;
|
||||
|
||||
template <typename RangeLike>
|
||||
bool match(RangeLike&& rng) const {
|
||||
constexpr bool match(RangeLike&& rng) const {
|
||||
for (auto&& elem : rng) {
|
||||
if (!elem) {
|
||||
return false;
|
||||
@@ -107,7 +107,7 @@ namespace Catch {
|
||||
std::string describe() const override;
|
||||
|
||||
template <typename RangeLike>
|
||||
bool match(RangeLike&& rng) const {
|
||||
constexpr bool match(RangeLike&& rng) const {
|
||||
for (auto&& elem : rng) {
|
||||
if (elem) {
|
||||
return false;
|
||||
@@ -123,7 +123,7 @@ namespace Catch {
|
||||
std::string describe() const override;
|
||||
|
||||
template <typename RangeLike>
|
||||
bool match(RangeLike&& rng) const {
|
||||
constexpr bool match(RangeLike&& rng) const {
|
||||
for (auto&& elem : rng) {
|
||||
if (elem) {
|
||||
return true;
|
||||
@@ -133,32 +133,35 @@ namespace Catch {
|
||||
}
|
||||
};
|
||||
|
||||
// Creates a matcher that checks whether all elements in a range match a matcher
|
||||
//! Creates a matcher that checks whether all elements in a range match a matcher
|
||||
template <typename Matcher>
|
||||
AllMatchMatcher<Matcher> AllMatch(Matcher&& matcher) {
|
||||
constexpr AllMatchMatcher<Matcher> AllMatch(Matcher&& matcher) {
|
||||
return { CATCH_FORWARD(matcher) };
|
||||
}
|
||||
|
||||
// Creates a matcher that checks whether no element in a range matches a matcher.
|
||||
//! Creates a matcher that checks whether no element in a range matches a matcher.
|
||||
template <typename Matcher>
|
||||
NoneMatchMatcher<Matcher> NoneMatch(Matcher&& matcher) {
|
||||
constexpr NoneMatchMatcher<Matcher> NoneMatch(Matcher&& matcher) {
|
||||
return { CATCH_FORWARD(matcher) };
|
||||
}
|
||||
|
||||
// Creates a matcher that checks whether any element in a range matches a matcher.
|
||||
//! Creates a matcher that checks whether any element in a range matches a matcher.
|
||||
template <typename Matcher>
|
||||
AnyMatchMatcher<Matcher> AnyMatch(Matcher&& matcher) {
|
||||
constexpr AnyMatchMatcher<Matcher> AnyMatch(Matcher&& matcher) {
|
||||
return { CATCH_FORWARD(matcher) };
|
||||
}
|
||||
|
||||
// Creates a matcher that checks whether all elements in a range are true
|
||||
AllTrueMatcher AllTrue();
|
||||
//! Creates a matcher that checks whether all elements in a range are true
|
||||
inline CATCH_DESTRUCTOR_CONSTEXPR
|
||||
AllTrueMatcher AllTrue() { return AllTrueMatcher{}; }
|
||||
|
||||
// Creates a matcher that checks whether no element in a range is true
|
||||
NoneTrueMatcher NoneTrue();
|
||||
//! Creates a matcher that checks whether no element in a range is true
|
||||
inline CATCH_DESTRUCTOR_CONSTEXPR
|
||||
NoneTrueMatcher NoneTrue() { return NoneTrueMatcher{}; }
|
||||
|
||||
// Creates a matcher that checks whether any element in a range is true
|
||||
AnyTrueMatcher AnyTrue();
|
||||
//! Creates a matcher that checks whether any element in a range is true
|
||||
inline CATCH_DESTRUCTOR_CONSTEXPR
|
||||
AnyTrueMatcher AnyTrue() { return AnyTrueMatcher{}; }
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
@@ -9,7 +9,11 @@
|
||||
|
||||
namespace Catch {
|
||||
namespace Matchers {
|
||||
MatcherGenericBase::~MatcherGenericBase() = default;
|
||||
|
||||
std::string MatcherGenericBase::describe() const {
|
||||
using namespace std::string_literals;
|
||||
return "Undescribed generic matcher"s;
|
||||
}
|
||||
|
||||
namespace Detail {
|
||||
|
||||
|
||||
@@ -22,12 +22,12 @@
|
||||
namespace Catch {
|
||||
namespace Matchers {
|
||||
class MatcherGenericBase : public MatcherUntypedBase {
|
||||
std::string describe() const override;
|
||||
public:
|
||||
MatcherGenericBase() = default;
|
||||
~MatcherGenericBase() override; // = default;
|
||||
constexpr MatcherGenericBase() = default;
|
||||
|
||||
MatcherGenericBase(MatcherGenericBase const&) = default;
|
||||
MatcherGenericBase(MatcherGenericBase&&) = default;
|
||||
constexpr MatcherGenericBase(MatcherGenericBase const&) = default;
|
||||
constexpr MatcherGenericBase(MatcherGenericBase&&) = default;
|
||||
|
||||
MatcherGenericBase& operator=(MatcherGenericBase const&) = delete;
|
||||
MatcherGenericBase& operator=(MatcherGenericBase&&) = delete;
|
||||
@@ -36,7 +36,9 @@ namespace Matchers {
|
||||
|
||||
namespace Detail {
|
||||
template<std::size_t N, std::size_t M>
|
||||
std::array<void const*, N + M> array_cat(std::array<void const*, N> && lhs, std::array<void const*, M> && rhs) {
|
||||
constexpr std::array<void const*, N + M>
|
||||
array_cat( std::array<void const*, N>&& lhs,
|
||||
std::array<void const*, M>&& rhs ) {
|
||||
std::array<void const*, N + M> arr{};
|
||||
std::copy_n(lhs.begin(), N, arr.begin());
|
||||
std::copy_n(rhs.begin(), M, arr.begin() + N);
|
||||
@@ -44,7 +46,8 @@ namespace Matchers {
|
||||
}
|
||||
|
||||
template<std::size_t N>
|
||||
std::array<void const*, N+1> array_cat(std::array<void const*, N> && lhs, void const* rhs) {
|
||||
constexpr std::array<void const*, N + 1>
|
||||
array_cat( std::array<void const*, N>&& lhs, void const* rhs ) {
|
||||
std::array<void const*, N+1> arr{};
|
||||
std::copy_n(lhs.begin(), N, arr.begin());
|
||||
arr[N] = rhs;
|
||||
@@ -52,7 +55,8 @@ namespace Matchers {
|
||||
}
|
||||
|
||||
template<std::size_t N>
|
||||
std::array<void const*, N+1> array_cat(void const* lhs, std::array<void const*, N> && rhs) {
|
||||
constexpr std::array<void const*, N + 1>
|
||||
array_cat( void const* lhs, std::array<void const*, N>&& rhs ) {
|
||||
std::array<void const*, N + 1> arr{ {lhs} };
|
||||
std::copy_n(rhs.begin(), N, arr.begin() + 1);
|
||||
return arr;
|
||||
@@ -75,23 +79,31 @@ namespace Matchers {
|
||||
|
||||
|
||||
template<std::size_t N, typename Arg>
|
||||
bool match_all_of(Arg&&, std::array<void const*, N> const&, std::index_sequence<>) {
|
||||
constexpr bool match_all_of( Arg&&,
|
||||
std::array<void const*, N> const&,
|
||||
std::index_sequence<> ) {
|
||||
return true;
|
||||
}
|
||||
|
||||
template<typename T, typename... MatcherTs, std::size_t N, typename Arg, std::size_t Idx, std::size_t... Indices>
|
||||
bool match_all_of(Arg&& arg, std::array<void const*, N> const& matchers, std::index_sequence<Idx, Indices...>) {
|
||||
constexpr bool match_all_of( Arg&& arg,
|
||||
std::array<void const*, N> const& matchers,
|
||||
std::index_sequence<Idx, Indices...> ) {
|
||||
return static_cast<T const*>(matchers[Idx])->match(arg) && match_all_of<MatcherTs...>(arg, matchers, std::index_sequence<Indices...>{});
|
||||
}
|
||||
|
||||
|
||||
template<std::size_t N, typename Arg>
|
||||
bool match_any_of(Arg&&, std::array<void const*, N> const&, std::index_sequence<>) {
|
||||
constexpr bool match_any_of( Arg&&,
|
||||
std::array<void const*, N> const&,
|
||||
std::index_sequence<> ) {
|
||||
return false;
|
||||
}
|
||||
|
||||
template<typename T, typename... MatcherTs, std::size_t N, typename Arg, std::size_t Idx, std::size_t... Indices>
|
||||
bool match_any_of(Arg&& arg, std::array<void const*, N> const& matchers, std::index_sequence<Idx, Indices...>) {
|
||||
constexpr bool match_any_of( Arg&& arg,
|
||||
std::array<void const*, N> const& matchers,
|
||||
std::index_sequence<Idx, Indices...> ) {
|
||||
return static_cast<T const*>(matchers[Idx])->match(arg) || match_any_of<MatcherTs...>(arg, matchers, std::index_sequence<Indices...>{});
|
||||
}
|
||||
|
||||
@@ -112,15 +124,18 @@ namespace Matchers {
|
||||
public:
|
||||
MatchAllOfGeneric(MatchAllOfGeneric const&) = delete;
|
||||
MatchAllOfGeneric& operator=(MatchAllOfGeneric const&) = delete;
|
||||
MatchAllOfGeneric(MatchAllOfGeneric&&) = default;
|
||||
MatchAllOfGeneric& operator=(MatchAllOfGeneric&&) = default;
|
||||
constexpr MatchAllOfGeneric( MatchAllOfGeneric&& ) = default;
|
||||
constexpr MatchAllOfGeneric& operator=(MatchAllOfGeneric&&) = default;
|
||||
|
||||
MatchAllOfGeneric(MatcherTs const&... matchers CATCH_ATTR_LIFETIMEBOUND)
|
||||
constexpr MatchAllOfGeneric(
|
||||
MatcherTs const&... matchers CATCH_ATTR_LIFETIMEBOUND )
|
||||
: m_matchers{ {std::addressof(matchers)...} } {}
|
||||
explicit MatchAllOfGeneric(std::array<void const*, sizeof...(MatcherTs)> matchers) : m_matchers{matchers} {}
|
||||
constexpr explicit MatchAllOfGeneric(
|
||||
std::array<void const*, sizeof...( MatcherTs )> matchers ):
|
||||
m_matchers{ matchers } {}
|
||||
|
||||
template<typename Arg>
|
||||
bool match(Arg&& arg) const {
|
||||
constexpr bool match( Arg&& arg ) const {
|
||||
return match_all_of<MatcherTs...>(arg, m_matchers, std::index_sequence_for<MatcherTs...>{});
|
||||
}
|
||||
|
||||
@@ -136,7 +151,7 @@ namespace Matchers {
|
||||
|
||||
//! Avoids type nesting for `GenericAllOf && GenericAllOf` case
|
||||
template<typename... MatchersRHS>
|
||||
friend
|
||||
constexpr friend
|
||||
MatchAllOfGeneric<MatcherTs..., MatchersRHS...> operator && (
|
||||
MatchAllOfGeneric<MatcherTs...>&& lhs CATCH_ATTR_LIFETIMEBOUND,
|
||||
MatchAllOfGeneric<MatchersRHS...>&& rhs CATCH_ATTR_LIFETIMEBOUND ) {
|
||||
@@ -145,7 +160,8 @@ namespace Matchers {
|
||||
|
||||
//! Avoids type nesting for `GenericAllOf && some matcher` case
|
||||
template<typename MatcherRHS>
|
||||
friend std::enable_if_t<is_matcher_v<MatcherRHS>,
|
||||
constexpr friend std::enable_if_t<
|
||||
is_matcher_v<MatcherRHS>,
|
||||
MatchAllOfGeneric<MatcherTs..., MatcherRHS>> operator && (
|
||||
MatchAllOfGeneric<MatcherTs...>&& lhs CATCH_ATTR_LIFETIMEBOUND,
|
||||
MatcherRHS const& rhs CATCH_ATTR_LIFETIMEBOUND ) {
|
||||
@@ -154,7 +170,8 @@ namespace Matchers {
|
||||
|
||||
//! Avoids type nesting for `some matcher && GenericAllOf` case
|
||||
template<typename MatcherLHS>
|
||||
friend std::enable_if_t<is_matcher_v<MatcherLHS>,
|
||||
constexpr friend std::enable_if_t<
|
||||
is_matcher_v<MatcherLHS>,
|
||||
MatchAllOfGeneric<MatcherLHS, MatcherTs...>> operator && (
|
||||
MatcherLHS const& lhs CATCH_ATTR_LIFETIMEBOUND,
|
||||
MatchAllOfGeneric<MatcherTs...>&& rhs CATCH_ATTR_LIFETIMEBOUND ) {
|
||||
@@ -168,15 +185,18 @@ namespace Matchers {
|
||||
public:
|
||||
MatchAnyOfGeneric(MatchAnyOfGeneric const&) = delete;
|
||||
MatchAnyOfGeneric& operator=(MatchAnyOfGeneric const&) = delete;
|
||||
MatchAnyOfGeneric(MatchAnyOfGeneric&&) = default;
|
||||
MatchAnyOfGeneric& operator=(MatchAnyOfGeneric&&) = default;
|
||||
constexpr MatchAnyOfGeneric( MatchAnyOfGeneric&& ) = default;
|
||||
constexpr MatchAnyOfGeneric& operator=(MatchAnyOfGeneric&&) = default;
|
||||
|
||||
MatchAnyOfGeneric(MatcherTs const&... matchers CATCH_ATTR_LIFETIMEBOUND)
|
||||
constexpr MatchAnyOfGeneric(
|
||||
MatcherTs const&... matchers CATCH_ATTR_LIFETIMEBOUND )
|
||||
: m_matchers{ {std::addressof(matchers)...} } {}
|
||||
explicit MatchAnyOfGeneric(std::array<void const*, sizeof...(MatcherTs)> matchers) : m_matchers{matchers} {}
|
||||
constexpr explicit MatchAnyOfGeneric(
|
||||
std::array<void const*, sizeof...( MatcherTs )> matchers ):
|
||||
m_matchers{ matchers } {}
|
||||
|
||||
template<typename Arg>
|
||||
bool match(Arg&& arg) const {
|
||||
constexpr bool match( Arg&& arg ) const {
|
||||
return match_any_of<MatcherTs...>(arg, m_matchers, std::index_sequence_for<MatcherTs...>{});
|
||||
}
|
||||
|
||||
@@ -192,7 +212,8 @@ namespace Matchers {
|
||||
|
||||
//! Avoids type nesting for `GenericAnyOf || GenericAnyOf` case
|
||||
template<typename... MatchersRHS>
|
||||
friend MatchAnyOfGeneric<MatcherTs..., MatchersRHS...> operator || (
|
||||
constexpr friend MatchAnyOfGeneric<MatcherTs..., MatchersRHS...>
|
||||
operator||(
|
||||
MatchAnyOfGeneric<MatcherTs...>&& lhs CATCH_ATTR_LIFETIMEBOUND,
|
||||
MatchAnyOfGeneric<MatchersRHS...>&& rhs CATCH_ATTR_LIFETIMEBOUND ) {
|
||||
return MatchAnyOfGeneric<MatcherTs..., MatchersRHS...>{array_cat(CATCH_MOVE(lhs.m_matchers), CATCH_MOVE(rhs.m_matchers))};
|
||||
@@ -200,7 +221,8 @@ namespace Matchers {
|
||||
|
||||
//! Avoids type nesting for `GenericAnyOf || some matcher` case
|
||||
template<typename MatcherRHS>
|
||||
friend std::enable_if_t<is_matcher_v<MatcherRHS>,
|
||||
constexpr friend std::enable_if_t<
|
||||
is_matcher_v<MatcherRHS>,
|
||||
MatchAnyOfGeneric<MatcherTs..., MatcherRHS>> operator || (
|
||||
MatchAnyOfGeneric<MatcherTs...>&& lhs CATCH_ATTR_LIFETIMEBOUND,
|
||||
MatcherRHS const& rhs CATCH_ATTR_LIFETIMEBOUND ) {
|
||||
@@ -209,7 +231,8 @@ namespace Matchers {
|
||||
|
||||
//! Avoids type nesting for `some matcher || GenericAnyOf` case
|
||||
template<typename MatcherLHS>
|
||||
friend std::enable_if_t<is_matcher_v<MatcherLHS>,
|
||||
constexpr friend std::enable_if_t<
|
||||
is_matcher_v<MatcherLHS>,
|
||||
MatchAnyOfGeneric<MatcherLHS, MatcherTs...>> operator || (
|
||||
MatcherLHS const& lhs CATCH_ATTR_LIFETIMEBOUND,
|
||||
MatchAnyOfGeneric<MatcherTs...>&& rhs CATCH_ATTR_LIFETIMEBOUND) {
|
||||
@@ -225,14 +248,15 @@ namespace Matchers {
|
||||
public:
|
||||
MatchNotOfGeneric(MatchNotOfGeneric const&) = delete;
|
||||
MatchNotOfGeneric& operator=(MatchNotOfGeneric const&) = delete;
|
||||
MatchNotOfGeneric(MatchNotOfGeneric&&) = default;
|
||||
MatchNotOfGeneric& operator=(MatchNotOfGeneric&&) = default;
|
||||
constexpr MatchNotOfGeneric( MatchNotOfGeneric&& ) = default;
|
||||
constexpr MatchNotOfGeneric& operator=(MatchNotOfGeneric&&) = default;
|
||||
|
||||
explicit MatchNotOfGeneric(MatcherT const& matcher CATCH_ATTR_LIFETIMEBOUND)
|
||||
constexpr explicit MatchNotOfGeneric(
|
||||
MatcherT const& matcher CATCH_ATTR_LIFETIMEBOUND )
|
||||
: m_matcher{matcher} {}
|
||||
|
||||
template<typename Arg>
|
||||
bool match(Arg&& arg) const {
|
||||
constexpr bool match( Arg&& arg ) const {
|
||||
return !m_matcher.match(arg);
|
||||
}
|
||||
|
||||
@@ -241,7 +265,7 @@ namespace Matchers {
|
||||
}
|
||||
|
||||
//! Negating negation can just unwrap and return underlying matcher
|
||||
friend MatcherT const&
|
||||
constexpr friend MatcherT const&
|
||||
operator!( MatchNotOfGeneric<MatcherT> const& matcher
|
||||
CATCH_ATTR_LIFETIMEBOUND ) {
|
||||
return matcher.m_matcher;
|
||||
@@ -252,14 +276,18 @@ namespace Matchers {
|
||||
|
||||
// compose only generic matchers
|
||||
template<typename MatcherLHS, typename MatcherRHS>
|
||||
std::enable_if_t<Detail::are_generic_matchers_v<MatcherLHS, MatcherRHS>, Detail::MatchAllOfGeneric<MatcherLHS, MatcherRHS>>
|
||||
constexpr std::enable_if_t<
|
||||
Detail::are_generic_matchers_v<MatcherLHS, MatcherRHS>,
|
||||
Detail::MatchAllOfGeneric<MatcherLHS, MatcherRHS>>
|
||||
operator&&( MatcherLHS const& lhs CATCH_ATTR_LIFETIMEBOUND,
|
||||
MatcherRHS const& rhs CATCH_ATTR_LIFETIMEBOUND ) {
|
||||
return { lhs, rhs };
|
||||
}
|
||||
|
||||
template<typename MatcherLHS, typename MatcherRHS>
|
||||
std::enable_if_t<Detail::are_generic_matchers_v<MatcherLHS, MatcherRHS>, Detail::MatchAnyOfGeneric<MatcherLHS, MatcherRHS>>
|
||||
constexpr std::enable_if_t<
|
||||
Detail::are_generic_matchers_v<MatcherLHS, MatcherRHS>,
|
||||
Detail::MatchAnyOfGeneric<MatcherLHS, MatcherRHS>>
|
||||
operator||( MatcherLHS const& lhs CATCH_ATTR_LIFETIMEBOUND,
|
||||
MatcherRHS const& rhs CATCH_ATTR_LIFETIMEBOUND ) {
|
||||
return { lhs, rhs };
|
||||
@@ -267,7 +295,8 @@ namespace Matchers {
|
||||
|
||||
//! Wrap provided generic matcher in generic negator
|
||||
template<typename MatcherT>
|
||||
std::enable_if_t<Detail::is_generic_matcher_v<MatcherT>, Detail::MatchNotOfGeneric<MatcherT>>
|
||||
constexpr std::enable_if_t<Detail::is_generic_matcher_v<MatcherT>,
|
||||
Detail::MatchNotOfGeneric<MatcherT>>
|
||||
operator!( MatcherT const& matcher CATCH_ATTR_LIFETIMEBOUND ) {
|
||||
return Detail::MatchNotOfGeneric<MatcherT>{matcher};
|
||||
}
|
||||
|
||||
@@ -29,7 +29,7 @@ namespace Catch {
|
||||
#endif
|
||||
|
||||
template<typename ArgT, typename MatcherT>
|
||||
class MatchExpr : public ITransientExpression {
|
||||
class MatchExpr final : public ITransientExpression {
|
||||
ArgT && m_arg;
|
||||
MatcherT const& m_matcher;
|
||||
public:
|
||||
|
||||
@@ -65,7 +65,6 @@ internal_headers = [
|
||||
'interfaces/catch_interfaces_all.hpp',
|
||||
'interfaces/catch_interfaces_capture.hpp',
|
||||
'interfaces/catch_interfaces_config.hpp',
|
||||
'interfaces/catch_interfaces_enum_values_registry.hpp',
|
||||
'interfaces/catch_interfaces_exception.hpp',
|
||||
'interfaces/catch_interfaces_generatortracker.hpp',
|
||||
'interfaces/catch_interfaces_registry_hub.hpp',
|
||||
@@ -95,7 +94,7 @@ internal_headers = [
|
||||
'internal/catch_decomposer.hpp',
|
||||
'internal/catch_deprecation_macro.hpp',
|
||||
'internal/catch_enforce.hpp',
|
||||
'internal/catch_enum_values_registry.hpp',
|
||||
'internal/catch_enum_info.hpp',
|
||||
'internal/catch_errno_guard.hpp',
|
||||
'internal/catch_exception_translator_registry.hpp',
|
||||
'internal/catch_fatal_condition_handler.hpp',
|
||||
@@ -224,7 +223,7 @@ internal_sources = files(
|
||||
'internal/catch_debugger.cpp',
|
||||
'internal/catch_decomposer.cpp',
|
||||
'internal/catch_enforce.cpp',
|
||||
'internal/catch_enum_values_registry.cpp',
|
||||
'internal/catch_enum_info.cpp',
|
||||
'internal/catch_errno_guard.cpp',
|
||||
'internal/catch_exception_translator_registry.cpp',
|
||||
'internal/catch_fatal_condition_handler.cpp',
|
||||
|
||||
@@ -26,12 +26,12 @@ namespace Catch {
|
||||
|
||||
void ReporterBase::listReporters(
|
||||
std::vector<ReporterDescription> const& descriptions ) {
|
||||
defaultListReporters(m_stream, descriptions, m_config->verbosity());
|
||||
defaultListReporters( m_stream, descriptions, m_config->verbosity() );
|
||||
}
|
||||
|
||||
void ReporterBase::listListeners(
|
||||
std::vector<ListenerDescription> const& descriptions ) {
|
||||
defaultListListeners( m_stream, descriptions );
|
||||
defaultListListeners( m_stream, descriptions, m_config->verbosity() );
|
||||
}
|
||||
|
||||
void ReporterBase::listTests(std::vector<TestCaseHandle> const& tests) {
|
||||
@@ -43,7 +43,7 @@ namespace Catch {
|
||||
}
|
||||
|
||||
void ReporterBase::listTags(std::vector<TagInfo> const& tags) {
|
||||
defaultListTags( m_stream, tags, m_config->hasTestFilters() );
|
||||
defaultListTags( m_stream, tags, m_config->hasTestFilters(), m_config->verbosity() );
|
||||
}
|
||||
|
||||
} // namespace Catch
|
||||
|
||||
@@ -20,9 +20,8 @@ namespace Catch {
|
||||
bool operator()(
|
||||
Detail::unique_ptr<CumulativeReporterBase::SectionNode> const&
|
||||
node ) const {
|
||||
return (
|
||||
( node->stats.sectionInfo.name == m_other.name ) &&
|
||||
( node->stats.sectionInfo.lineInfo == m_other.lineInfo ) );
|
||||
return node->stats.sectionInfo.name == m_other.name
|
||||
&& node->stats.sectionInfo.lineInfo == m_other.lineInfo;
|
||||
}
|
||||
void operator=( BySectionInfo const& ) = delete;
|
||||
|
||||
|
||||
@@ -143,7 +143,15 @@ namespace Catch {
|
||||
}
|
||||
|
||||
void defaultListListeners( std::ostream& out,
|
||||
std::vector<ListenerDescription> const& descriptions ) {
|
||||
std::vector<ListenerDescription> const& descriptions,
|
||||
Verbosity verbosity ) {
|
||||
if ( verbosity == Verbosity::Quiet ) {
|
||||
for ( auto const& desc : descriptions ) {
|
||||
out << desc.name << '\n';
|
||||
}
|
||||
return;
|
||||
}
|
||||
|
||||
out << "Registered listeners:\n";
|
||||
|
||||
if(descriptions.empty()) {
|
||||
@@ -176,7 +184,14 @@ namespace Catch {
|
||||
|
||||
void defaultListTags( std::ostream& out,
|
||||
std::vector<TagInfo> const& tags,
|
||||
bool isFiltered ) {
|
||||
bool isFiltered,
|
||||
Verbosity verbosity ) {
|
||||
if (verbosity == Verbosity::Quiet) {
|
||||
for (auto const& tagCount : tags) {
|
||||
out << tagCount.all() << '\n';
|
||||
}
|
||||
return;
|
||||
}
|
||||
if ( isFiltered ) {
|
||||
out << "Tags for matching test cases:\n";
|
||||
} else {
|
||||
@@ -184,7 +199,7 @@ namespace Catch {
|
||||
}
|
||||
|
||||
// minimum whitespace to pad tag counts, possibly overwritten below
|
||||
size_t maxTagCountLen = 2;
|
||||
int maxTagCountLen = 2;
|
||||
|
||||
// determine necessary padding for tag count column
|
||||
if ( ! tags.empty() ) {
|
||||
@@ -195,11 +210,11 @@ namespace Catch {
|
||||
return lhs.count < rhs.count;
|
||||
} )
|
||||
->count;
|
||||
|
||||
|
||||
// more padding necessary for 3+ digits
|
||||
if (maxTagCount >= 100) {
|
||||
auto numDigits = 1 + std::floor( std::log10( maxTagCount ) );
|
||||
maxTagCountLen = static_cast<size_t>( numDigits );
|
||||
maxTagCountLen = static_cast<int>( numDigits );
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
@@ -55,7 +55,8 @@ namespace Catch {
|
||||
* format
|
||||
*/
|
||||
void defaultListListeners( std::ostream& out,
|
||||
std::vector<ListenerDescription> const& descriptions );
|
||||
std::vector<ListenerDescription> const& descriptions,
|
||||
Verbosity verbosity );
|
||||
|
||||
/**
|
||||
* Lists tag information to the provided stream in user-friendly format
|
||||
@@ -64,7 +65,10 @@ namespace Catch {
|
||||
* bases. The output should be backwards compatible with the output of
|
||||
* Catch2 v2 binaries.
|
||||
*/
|
||||
void defaultListTags( std::ostream& out, std::vector<TagInfo> const& tags, bool isFiltered );
|
||||
void defaultListTags( std::ostream& out,
|
||||
std::vector<TagInfo> const& tags,
|
||||
bool isFiltered,
|
||||
Verbosity verbosity );
|
||||
|
||||
/**
|
||||
* Lists test case information to the provided stream in user-friendly
|
||||
|
||||
@@ -241,70 +241,77 @@ namespace Catch {
|
||||
void JunitReporter::writeAssertions( SectionNode const& sectionNode ) {
|
||||
for (auto const& assertionOrBenchmark : sectionNode.assertionsAndBenchmarks) {
|
||||
if (assertionOrBenchmark.isAssertion()) {
|
||||
writeAssertion(assertionOrBenchmark.asAssertion());
|
||||
// JUnit XML format supports only 1 error/failure/skip
|
||||
// assertion elements per test case
|
||||
if (writeAssertion(assertionOrBenchmark.asAssertion())) {
|
||||
break;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
void JunitReporter::writeAssertion( AssertionStats const& stats ) {
|
||||
bool JunitReporter::writeAssertion( AssertionStats const& stats ) {
|
||||
AssertionResult const& result = stats.assertionResult;
|
||||
if ( !result.isOk() ||
|
||||
result.getResultType() == ResultWas::ExplicitSkip ) {
|
||||
std::string elementName;
|
||||
switch( result.getResultType() ) {
|
||||
case ResultWas::ThrewException:
|
||||
case ResultWas::FatalErrorCondition:
|
||||
elementName = "error";
|
||||
break;
|
||||
case ResultWas::ExplicitFailure:
|
||||
case ResultWas::ExpressionFailed:
|
||||
case ResultWas::DidntThrowException:
|
||||
elementName = "failure";
|
||||
break;
|
||||
case ResultWas::ExplicitSkip:
|
||||
elementName = "skipped";
|
||||
break;
|
||||
// We should never see these here:
|
||||
case ResultWas::Info:
|
||||
case ResultWas::Warning:
|
||||
case ResultWas::Ok:
|
||||
case ResultWas::Unknown:
|
||||
case ResultWas::FailureBit:
|
||||
case ResultWas::Exception:
|
||||
elementName = "internalError";
|
||||
break;
|
||||
}
|
||||
|
||||
XmlWriter::ScopedElement e = xml.scopedElement( elementName );
|
||||
|
||||
xml.writeAttribute( "message"_sr, result.getExpression() );
|
||||
xml.writeAttribute( "type"_sr, result.getTestMacroName() );
|
||||
|
||||
ReusableStringStream rss;
|
||||
if ( result.getResultType() == ResultWas::ExplicitSkip ) {
|
||||
rss << "SKIPPED\n";
|
||||
} else {
|
||||
rss << "FAILED" << ":\n";
|
||||
if (result.hasExpression()) {
|
||||
rss << " ";
|
||||
rss << result.getExpressionInMacro();
|
||||
rss << '\n';
|
||||
}
|
||||
if (result.hasExpandedExpression()) {
|
||||
rss << "with expansion:\n";
|
||||
rss << TextFlow::Column(result.getExpandedExpression()).indent(2) << '\n';
|
||||
}
|
||||
}
|
||||
|
||||
if( result.hasMessage() )
|
||||
rss << result.getMessage() << '\n';
|
||||
for( auto const& msg : stats.infoMessages )
|
||||
if( msg.type == ResultWas::Info )
|
||||
rss << msg.message << '\n';
|
||||
|
||||
rss << "at " << result.getSourceInfo();
|
||||
xml.writeText( rss.str(), XmlFormatting::Newline );
|
||||
if ( result.isOk() &&
|
||||
result.getResultType() != ResultWas::ExplicitSkip ) {
|
||||
return false;
|
||||
}
|
||||
std::string elementName;
|
||||
switch ( result.getResultType() ) {
|
||||
case ResultWas::ThrewException:
|
||||
case ResultWas::FatalErrorCondition:
|
||||
elementName = "error";
|
||||
break;
|
||||
case ResultWas::ExplicitFailure:
|
||||
case ResultWas::ExpressionFailed:
|
||||
case ResultWas::DidntThrowException:
|
||||
elementName = "failure";
|
||||
break;
|
||||
case ResultWas::ExplicitSkip:
|
||||
elementName = "skipped";
|
||||
break;
|
||||
// We should never see these here:
|
||||
case ResultWas::Info:
|
||||
case ResultWas::Warning:
|
||||
case ResultWas::Ok:
|
||||
case ResultWas::Unknown:
|
||||
case ResultWas::FailureBit:
|
||||
case ResultWas::Exception:
|
||||
elementName = "internalError";
|
||||
break;
|
||||
}
|
||||
|
||||
XmlWriter::ScopedElement e = xml.scopedElement( elementName );
|
||||
|
||||
xml.writeAttribute( "message"_sr, result.getExpression() );
|
||||
xml.writeAttribute( "type"_sr, result.getTestMacroName() );
|
||||
|
||||
ReusableStringStream rss;
|
||||
if ( result.getResultType() == ResultWas::ExplicitSkip ) {
|
||||
rss << "SKIPPED\n";
|
||||
} else {
|
||||
rss << "FAILED:\n";
|
||||
if ( result.hasExpression() ) {
|
||||
rss << " ";
|
||||
rss << result.getExpressionInMacro();
|
||||
rss << '\n';
|
||||
}
|
||||
if ( result.hasExpandedExpression() ) {
|
||||
rss << "with expansion:\n";
|
||||
rss << TextFlow::Column( result.getExpandedExpression() )
|
||||
.indent( 2 )
|
||||
<< '\n';
|
||||
}
|
||||
}
|
||||
|
||||
if ( result.hasMessage() ) { rss << result.getMessage() << '\n'; }
|
||||
for ( auto const& msg : stats.infoMessages ) {
|
||||
if ( msg.type == ResultWas::Info ) { rss << msg.message << '\n'; }
|
||||
}
|
||||
|
||||
rss << "at " << result.getSourceInfo();
|
||||
xml.writeText( rss.str(), XmlFormatting::Newline );
|
||||
return true;
|
||||
}
|
||||
|
||||
} // end namespace Catch
|
||||
|
||||
@@ -41,7 +41,7 @@ namespace Catch {
|
||||
bool testOkToFail );
|
||||
|
||||
void writeAssertions(SectionNode const& sectionNode);
|
||||
void writeAssertion(AssertionStats const& stats);
|
||||
bool writeAssertion(AssertionStats const& stats);
|
||||
|
||||
XmlWriter xml;
|
||||
Timer suiteTimer;
|
||||
|
||||
Some files were not shown because too many files have changed in this diff Show More
Reference in New Issue
Block a user