mirror of
https://github.com/catchorg/Catch2.git
synced 2026-08-06 13:44:22 +02:00
Compare commits
95 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 | |||
| 29c9844f68 | |||
| 56024c04e4 | |||
| edfed6c04e | |||
| 75bfcc3f30 | |||
| 056e4fe88d | |||
| 3a0cf7e75f | |||
| b6c7b217d4 | |||
| 045ac7acce | |||
| 72671fdbdf | |||
| 120827d4d6 | |||
| daadf42a0e | |||
| d079ee13ab | |||
| 0056cd4efb | |||
| de7e863013 | |||
| 024aec9729 | |||
| 9eca713a1f | |||
| 44c597f074 | |||
| 6aedc79870 | |||
| fcbf006c78 | |||
| 2580eadc42 | |||
| b59f4f3522 | |||
| cd4fc88e2a | |||
| a50ac2f681 | |||
| b81ef2aa2e | |||
| ec4dcbf9cb | |||
| b66b89374e | |||
| b7e31c9ab3 |
@@ -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,
|
||||
|
||||
@@ -2,6 +2,10 @@ name: Linux Builds (Bazel)
|
||||
|
||||
on: [push, pull_request]
|
||||
|
||||
env:
|
||||
CTEST_OUTPUT_ON_FAILURE: 1
|
||||
CTEST_NO_TESTS_ACTION: error
|
||||
|
||||
jobs:
|
||||
build_and_test_ubuntu:
|
||||
name: Linux Ubuntu 22.04 Bazel build <GCC 11.2.0>
|
||||
@@ -12,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
|
||||
|
||||
@@ -2,6 +2,10 @@ name: Linux Builds (Meson)
|
||||
|
||||
on: [push, pull_request]
|
||||
|
||||
env:
|
||||
CTEST_OUTPUT_ON_FAILURE: 1
|
||||
CTEST_NO_TESTS_ACTION: error
|
||||
|
||||
jobs:
|
||||
build:
|
||||
name: meson ${{matrix.cxx}}, C++${{matrix.std}}, ${{matrix.build_type}}
|
||||
@@ -19,7 +23,7 @@ jobs:
|
||||
other_pkgs: clang-11
|
||||
|
||||
steps:
|
||||
- uses: actions/checkout@v4
|
||||
- uses: actions/checkout@v6
|
||||
|
||||
- name: Prepare environment
|
||||
run: |
|
||||
|
||||
@@ -5,6 +5,10 @@ name: Linux Builds (Complex)
|
||||
|
||||
on: [push, pull_request]
|
||||
|
||||
env:
|
||||
CTEST_OUTPUT_ON_FAILURE: 1
|
||||
CTEST_NO_TESTS_ACTION: error
|
||||
|
||||
jobs:
|
||||
build:
|
||||
name: ${{matrix.build_description}}, ${{matrix.cxx}}, C++${{matrix.std}} ${{matrix.build_type}}
|
||||
@@ -71,7 +75,7 @@ jobs:
|
||||
|
||||
|
||||
steps:
|
||||
- uses: actions/checkout@v4
|
||||
- uses: actions/checkout@v6
|
||||
|
||||
- name: Prepare environment
|
||||
run: |
|
||||
@@ -90,13 +94,13 @@ jobs:
|
||||
run: cmake --build build
|
||||
|
||||
- name: Test
|
||||
run: ctest --test-dir build -j --output-on-failure
|
||||
run: ctest --test-dir build -j
|
||||
|
||||
clang-tidy:
|
||||
name: clang-tidy
|
||||
runs-on: ubuntu-22.04
|
||||
steps:
|
||||
- uses: actions/checkout@v4
|
||||
- uses: actions/checkout@v6
|
||||
|
||||
- name: Prepare environment
|
||||
run: |
|
||||
|
||||
@@ -2,6 +2,10 @@ name: Linux Builds (Basic)
|
||||
|
||||
on: [push, pull_request]
|
||||
|
||||
env:
|
||||
CTEST_OUTPUT_ON_FAILURE: 1
|
||||
CTEST_NO_TESTS_ACTION: error
|
||||
|
||||
jobs:
|
||||
build:
|
||||
name: ${{matrix.cxx}}, C++${{matrix.std}}, ${{matrix.build_type}}
|
||||
@@ -79,7 +83,7 @@ jobs:
|
||||
other_pkgs: g++-11
|
||||
|
||||
steps:
|
||||
- uses: actions/checkout@v4
|
||||
- uses: actions/checkout@v6
|
||||
|
||||
- name: Add repositories for older compilers
|
||||
run: |
|
||||
@@ -102,4 +106,4 @@ jobs:
|
||||
run: cmake --build build
|
||||
|
||||
- name: Test
|
||||
run: ctest --test-dir build -j --output-on-failure
|
||||
run: ctest --test-dir build -j
|
||||
|
||||
@@ -2,6 +2,10 @@ name: Mac Builds
|
||||
|
||||
on: [push, pull_request]
|
||||
|
||||
env:
|
||||
CTEST_OUTPUT_ON_FAILURE: 1
|
||||
CTEST_NO_TESTS_ACTION: error
|
||||
|
||||
jobs:
|
||||
build:
|
||||
runs-on: ${{matrix.image}}
|
||||
@@ -13,7 +17,7 @@ jobs:
|
||||
std: [14, 17]
|
||||
|
||||
steps:
|
||||
- uses: actions/checkout@v4
|
||||
- uses: actions/checkout@v6
|
||||
|
||||
- name: Configure
|
||||
run: |
|
||||
@@ -27,4 +31,4 @@ jobs:
|
||||
run: cmake --build build
|
||||
|
||||
- name: Test
|
||||
run: ctest --test-dir build -j --output-on-failure
|
||||
run: ctest --test-dir build -j
|
||||
|
||||
@@ -0,0 +1,43 @@
|
||||
name: Mac Sanitizer Builds
|
||||
|
||||
on: [push, pull_request]
|
||||
|
||||
env:
|
||||
CTEST_OUTPUT_ON_FAILURE: 1
|
||||
CTEST_NO_TESTS_ACTION: error
|
||||
|
||||
jobs:
|
||||
build:
|
||||
runs-on: ${{matrix.image}}
|
||||
strategy:
|
||||
fail-fast: false
|
||||
matrix:
|
||||
image: [macos-15, macos-15-intel]
|
||||
build_type: [Debug]
|
||||
std: [17]
|
||||
sanitizer: [thread, address]
|
||||
include:
|
||||
- sanitizer: thread
|
||||
preset: basic-tests
|
||||
filter: -R ThreadSafetyTests
|
||||
- sanitizer: address
|
||||
preset: most-tests
|
||||
filter:
|
||||
|
||||
steps:
|
||||
- uses: actions/checkout@v6
|
||||
|
||||
- name: Configure
|
||||
|
||||
run: |
|
||||
CFXXFLAGS=-fsanitize=${{matrix.sanitizer}},undefined
|
||||
cmake --preset ${{matrix.preset}} -GNinja \
|
||||
-DCMAKE_BUILD_TYPE=${{matrix.build_type}} \
|
||||
-DCMAKE_CXX_STANDARD=${{matrix.std}} \
|
||||
-DCATCH_BUILD_EXTRA_TESTS=ON
|
||||
|
||||
- name: Build
|
||||
run: cmake --build build
|
||||
|
||||
- name: Test
|
||||
run: ctest --test-dir build ${{matrix.filter}}
|
||||
@@ -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
|
||||
|
||||
@@ -2,6 +2,10 @@ name: Windows Builds (Basic)
|
||||
|
||||
on: [push, pull_request]
|
||||
|
||||
env:
|
||||
CTEST_OUTPUT_ON_FAILURE: 1
|
||||
CTEST_NO_TESTS_ACTION: error
|
||||
|
||||
jobs:
|
||||
build:
|
||||
name: ${{matrix.os}}, ${{matrix.std}}, ${{matrix.build_type}}, ${{matrix.platform}}
|
||||
@@ -14,7 +18,7 @@ jobs:
|
||||
build_type: [Debug, Release]
|
||||
std: [14, 17]
|
||||
steps:
|
||||
- uses: actions/checkout@v4
|
||||
- uses: actions/checkout@v6
|
||||
|
||||
- name: Configure build
|
||||
run: |
|
||||
@@ -27,5 +31,5 @@ jobs:
|
||||
shell: cmd
|
||||
|
||||
- name: Run tests
|
||||
run: ctest --test-dir build -C ${{matrix.build_type}} -j %NUMBER_OF_PROCESSORS% --output-on-failure
|
||||
run: ctest --test-dir build -C ${{matrix.build_type}} -j %NUMBER_OF_PROCESSORS%
|
||||
shell: cmd
|
||||
|
||||
+1
-1
@@ -35,7 +35,7 @@ if(CMAKE_BINARY_DIR STREQUAL CMAKE_CURRENT_SOURCE_DIR)
|
||||
endif()
|
||||
|
||||
project(Catch2
|
||||
VERSION 3.12.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")
|
||||
|
||||
@@ -1,16 +1,5 @@
|
||||
<a id="top"></a>
|
||||
|
||||
<table width="100%">
|
||||
<tr>
|
||||
<td align="center" width="50%"><img src="/data/artwork/catch2-logo-full-with-background.svg" width="100%"></td>
|
||||
<td align="center" width="50%">
|
||||
<figure>
|
||||
<figcaption>Special thanks to:</figcaption>
|
||||
<a href="https://tuple.app/catch2"><img src="/data/sponsors/github_repo_sponsorship.png" width="100%"></a>
|
||||
</figure>
|
||||
</td>
|
||||
</tr>
|
||||
</table>
|
||||

|
||||
|
||||
[](https://github.com/catchorg/catch2/releases)
|
||||
[](https://github.com/catchorg/Catch2/actions/workflows/linux-simple-builds.yml)
|
||||
|
||||
@@ -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)
|
||||
|
||||
@@ -12,6 +12,7 @@
|
||||
/**
|
||||
* Event listener that listens to all assertions, forcing assertion slow path
|
||||
*/
|
||||
namespace {
|
||||
class AssertionSlowPathListener : public Catch::EventListenerBase {
|
||||
public:
|
||||
static std::string getDescription() {
|
||||
@@ -26,3 +27,4 @@ public:
|
||||
};
|
||||
|
||||
CATCH_REGISTER_LISTENER( AssertionSlowPathListener )
|
||||
}
|
||||
|
||||
@@ -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
|
||||
|
||||
Binary file not shown.
|
Before Width: | Height: | Size: 264 KiB |
+2
-1
@@ -24,7 +24,8 @@ Once you're up and running consider the following reference material.
|
||||
* [String Conversions](tostring.md#top)
|
||||
|
||||
**Running:**
|
||||
* [Command line](command-line.md#top)
|
||||
* [Command line reference](command-line.md#top)
|
||||
* [Running specific section/generator](filtering-execution-path.md#top)
|
||||
|
||||
**Odds and ends:**
|
||||
* [Frequently Asked Questions (FAQ)](faq.md#top)
|
||||
|
||||
+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.
|
||||
|
||||
+18
-38
@@ -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>
|
||||
@@ -52,6 +52,8 @@ Click one of the following links to take you straight to that option - or scroll
|
||||
<a href="#reporting-timings"> ` -d, --durations`</a><br />
|
||||
<a href="#input-file"> ` -f, --input-file`</a><br />
|
||||
<a href="#run-section"> ` -c, --section`</a><br />
|
||||
<a href="#path-filtering"> ` -g, --generator-index`</a><br />
|
||||
<a href="#path-filtering"> ` -p, --path-filter`</a><br />
|
||||
<a href="#filenames-as-tags"> ` -#, --filenames-as-tags`</a><br />
|
||||
|
||||
|
||||
@@ -287,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
|
||||
@@ -358,10 +361,13 @@ There are currently two warnings implemented:
|
||||
// (e.g. `REQUIRE`) is encountered.
|
||||
UnmatchedTestSpec // Fail test run if any of the CLI test specs did
|
||||
// not match any tests.
|
||||
InfiniteGenerators // Fail if GENERATE would run infinitely
|
||||
```
|
||||
|
||||
> `UnmatchedTestSpec` was introduced in Catch2 3.0.1.
|
||||
|
||||
> `InfiniteGenerators` was introduced in Catch2 3.13.0
|
||||
|
||||
|
||||
<a id="reporting-timings"></a>
|
||||
## Reporting timings
|
||||
@@ -529,45 +535,19 @@ Prints the command line arguments to stdout
|
||||
|
||||
|
||||
<a id="run-section"></a>
|
||||
## Specify the section to run
|
||||
<a id="path-filtering"></a>
|
||||
## Specify the section/generator element to run
|
||||
<pre>-c, --section <section name></pre>
|
||||
<pre>-g, --generator-index <index in generator></pre>
|
||||
<pre>-p, --path-filter <path filter spec></pre>
|
||||
|
||||
To limit execution to a specific section within a test case, use this option one or more times.
|
||||
To narrow to sub-sections use multiple instances, where each subsequent instance specifies a deeper nesting level.
|
||||
> The generator and generic path filtering was added in Catch2 3.13.0
|
||||
|
||||
E.g. if you have:
|
||||
These arguments allow you to run specific section(s) in a test case, or
|
||||
only get specific element from a generator. All the variants form a shared
|
||||
stack of filters.
|
||||
|
||||
<pre>
|
||||
TEST_CASE( "Test" ) {
|
||||
SECTION( "sa" ) {
|
||||
SECTION( "sb" ) {
|
||||
/*...*/
|
||||
}
|
||||
SECTION( "sc" ) {
|
||||
/*...*/
|
||||
}
|
||||
}
|
||||
SECTION( "sd" ) {
|
||||
/*...*/
|
||||
}
|
||||
}
|
||||
</pre>
|
||||
|
||||
Then you can run `sb` with:
|
||||
<pre>./MyExe Test -c sa -c sb</pre>
|
||||
|
||||
Or run just `sd` with:
|
||||
<pre>./MyExe Test -c sd</pre>
|
||||
|
||||
To run all of `sa`, including `sb` and `sc` use:
|
||||
<pre>./MyExe Test -c sa</pre>
|
||||
|
||||
There are some limitations of this feature to be aware of:
|
||||
- Code outside of sections being skipped will still be executed - e.g. any set-up code in the TEST_CASE before the
|
||||
start of the first section.</br>
|
||||
- At time of writing, wildcards are not supported in section names.
|
||||
- If you specify a section without narrowing to a test case first then all test cases will be executed
|
||||
(but only matching sections within them).
|
||||
[See the full documentation of path filtering for more details](filtering-execution-path.md#top)
|
||||
|
||||
|
||||
<a id="filenames-as-tags"></a>
|
||||
|
||||
@@ -29,7 +29,7 @@ with the same name.
|
||||
## Prefixing Catch macros
|
||||
|
||||
CATCH_CONFIG_PREFIX_ALL // Prefix all macros with CATCH_
|
||||
CATCH_CONFIG_PREFIX_MESSAGES // Prefix only INFO, UNSCOPED_INFO, WARN and CAPTURE
|
||||
CATCH_CONFIG_PREFIX_MESSAGES // Prefix only message macros ((UNSCOPED_)INFO, WARN, (UNSCOPED_)CAPTURE)
|
||||
|
||||
To keep test code clean and uncluttered Catch uses short macro names (e.g. ```TEST_CASE``` and ```REQUIRE```). Occasionally these may conflict with identifiers from platform headers or the system under test. In this case the above identifier can be defined. This will cause all the Catch user macros to be prefixed with ```CATCH_``` (e.g. ```CATCH_TEST_CASE``` and ```CATCH_REQUIRE```).
|
||||
|
||||
|
||||
@@ -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
|
||||
|
||||
@@ -48,6 +48,32 @@ If you are mutating the fixture instance from within the test case, and
|
||||
want to keep doing so in the future, mark the mutated members as `mutable`.
|
||||
|
||||
|
||||
### Section-only filtering with `-c/--section`
|
||||
|
||||
> Deprecated in Catch2 3.13.0
|
||||
|
||||
Currently, if you use only `-c/--section` parameters to decide which
|
||||
sections to enter, the filtering ignores generators completely. In the
|
||||
future, using only `-c/--section` will behave the same way as if you
|
||||
specified the filters through the new `-p/--path-filter` parameter, which
|
||||
means that generators are taken into account.
|
||||
|
||||
|
||||
### Generator interfaces
|
||||
|
||||
#### Defaulted `UntypedGeneratorBase::isFinite()`
|
||||
|
||||
> Deprecated in Catch2 3.13.0
|
||||
|
||||
The `UntypedGeneratorBase` currently provides a default implementation
|
||||
for `isFinite` that always returns `true`. This was done to keep backwards
|
||||
compatibility with pre-existing generators, as infinite generators can
|
||||
be diagnosed as errors in some cases.
|
||||
|
||||
In the future, all generators will be expected to override `isFinite`.
|
||||
|
||||
|
||||
|
||||
---
|
||||
|
||||
[Home](Readme.md#top)
|
||||
|
||||
@@ -0,0 +1,222 @@
|
||||
<a id="top"></a>
|
||||
# How to run specific section/generator
|
||||
|
||||
> The generator and generic path filtering was added in Catch2 3.13.0
|
||||
|
||||
Catch2 supports picking specific path through a test case by filtering
|
||||
sections and generator indices to run through. This is done by using one
|
||||
of the three commandline parameters, one or more times.
|
||||
|
||||
```
|
||||
-c, --section <section name>
|
||||
-g, --generator-index <index in generator>
|
||||
-p, --path-filter <path filter spec>
|
||||
```
|
||||
|
||||
All the variants form a shared stack of filters, but if you use only
|
||||
`-c`/`--section` form to specify section filters, you will get the old
|
||||
behaviour, which does not affect generators at all. If you also use either
|
||||
`-g`/`--generator-index`, or `-p`/`--path-filter`, you will get the new
|
||||
behaviour, which can also filter generator elements.
|
||||
|
||||
Both the new and old filter behaviours include some potentially surprising
|
||||
things:
|
||||
* Code outside of sections being skipped will still be executed. E.g.
|
||||
any setup code in the TEST_CASE that lives outside of sections.
|
||||
* Path filters filter the prefix of the path. So if you specify single
|
||||
filter, it affects only the top level sections/generator, with their
|
||||
child sections/generators being unfiltered.
|
||||
* Path filters are independent of test case selection, Catch2 will try
|
||||
to follow the path filters in all selected test cases. This means
|
||||
that if you specify path filters without a test case filter, Catch2
|
||||
will try to apply the path filters inside every registered test case.
|
||||
|
||||
## Old behaviour
|
||||
|
||||
> The old behaviour was deprecated in Catch2 3.13.0
|
||||
|
||||
```
|
||||
-c, --section <section name>
|
||||
```
|
||||
The argument to `-c`/`--section` can be any arbitrary string. When Catch2
|
||||
is deciding whether to enter a section, it will check its trimmed name
|
||||
against the appropriate trimmed section filter. If they are the same,
|
||||
the section can be opened. If not, Catch2 will skip over that section.
|
||||
|
||||
|
||||
### Examples
|
||||
|
||||
#### Simple section nesting
|
||||
Given
|
||||
```cpp
|
||||
TEST_CASE( "foo" ) {
|
||||
REQUIRE( true );
|
||||
SECTION( "A" ) {
|
||||
SECTION( "A1" ) { REQUIRE( true ); }
|
||||
SECTION( "A2" ) { REQUIRE( true ); }
|
||||
}
|
||||
SECTION( "B" ) {
|
||||
SECTION( "B1" ) { REQUIRE( true ); }
|
||||
SECTION( "B2" ) { REQUIRE( true ); }
|
||||
}
|
||||
}
|
||||
```
|
||||
|
||||
* `./tests foo -c A` runs section "A" and both of its subsections,
|
||||
resulting in 4 assertions.
|
||||
* `./tests foo -c A -c B` runs section "A", but none of its subsections,
|
||||
resulting in 1 assertion (the one before "A").
|
||||
* `./tests foo -c A -c A1` runs section "A" and only the "A1" subsection,
|
||||
resulting in 2 assertions.
|
||||
|
||||
|
||||
#### Sections with nested generators
|
||||
Note that old behaviour completely _ignores_ generators. This means both
|
||||
that they can't be filtered, but also that they aren't taken into account
|
||||
for the filter depth. In other words, given
|
||||
```cpp
|
||||
TEST_CASE( "bar" ) {
|
||||
REQUIRE( true );
|
||||
SECTION( "A" ) { REQUIRE( true ); }
|
||||
SECTION( "B" ) {
|
||||
auto i = GENERATE( 1, 2, 3 );
|
||||
DYNAMIC_SECTION( "i=" << i ) {
|
||||
REQUIRE( true );
|
||||
}
|
||||
}
|
||||
}
|
||||
```
|
||||
* `./tests bar -c A` results in 2 assertions.
|
||||
* `./tests bar -c B -c i=2` results in 4 assertions, because the whole
|
||||
generator in section "B" has to be used up, but the dynamic section is
|
||||
only entered when the generator returns 2 as the value for `i`.
|
||||
* `./tests bar -c B -c i=4` results in 3 assertions, because the assertion
|
||||
outside of section is executed every time the test case is entered, and
|
||||
the generator forces the test case to rerun 3 times before it is used up,
|
||||
even though the dynamic section will never be entered.
|
||||
|
||||
|
||||
#### Section with sibling generators
|
||||
For cases where sections have sibling generators, the filtering can get
|
||||
even more surprising.
|
||||
```cpp
|
||||
TEST_CASE( "qux" ) {
|
||||
REQUIRE( true );
|
||||
SECTION( "A" ) { REQUIRE( true ); }
|
||||
auto i = GENERATE( 1, 2, 3 );
|
||||
DYNAMIC_SECTION( "i=" << i ) {
|
||||
REQUIRE( true );
|
||||
}
|
||||
}
|
||||
```
|
||||
* `./tests qux -c A` results in **4** assertions, because section "A" is
|
||||
entered once, but the sibling generator has to be exhausted, and
|
||||
the first assertion is executed once per generator element.
|
||||
* `./tests qux -c i=2` also results in 4 assertions. Once again,
|
||||
the generator has to be exhausted and the dynamic section is entered
|
||||
once.
|
||||
|
||||
|
||||
## New behaviour
|
||||
|
||||
> The new behaviour was introduced in Catch2 3.13.0
|
||||
|
||||
```
|
||||
-g, --generator-index <index in generator>
|
||||
-p, --path-filter <path filter spec>
|
||||
```
|
||||
|
||||
The argument to `-g`/`--generator-index` must be either a non-negative
|
||||
number, which is interpreted as the index of the desired element from
|
||||
the generator, or "\*", which allows all elements from the generator.
|
||||
|
||||
Providing index outside of the generator is an error.
|
||||
|
||||
The argument to `-p`/`--path-filter` must start with either "c:" for
|
||||
a section filter, or with "g:" for a generator filter. Everything past
|
||||
the colon is then parsed as either a section filter, or a generator filter.
|
||||
|
||||
Note that using `p`/`--path-filter` enables new filtering behaviour, even
|
||||
if it is only used to add section filters.
|
||||
|
||||
There is another important difference between filtering out sections and
|
||||
generators. A section can be left un-entered, but a generator always has
|
||||
to be active. For this reason, if generator fails a filter
|
||||
(e.g. there is a section filter at given depth instead), it has to stop
|
||||
the execution of the test case. Currently, this is done via `SKIP()`
|
||||
equivalent, causing the section to be considered skipped.
|
||||
|
||||
|
||||
### Examples
|
||||
|
||||
#### Nested generators
|
||||
```cpp
|
||||
TEST_CASE( "waldo" ) {
|
||||
auto i = GENERATE( 1, 10, 100 );
|
||||
auto j = GENERATE( 2, 20, 200 );
|
||||
CAPTURE( i, j );
|
||||
REQUIRE( true );
|
||||
}
|
||||
```
|
||||
* `./tests waldo -g 1` results in 3 assertions, with `i := 10`, because
|
||||
the second nested generator is unfiltered.
|
||||
* `./tests waldo -g 1 -g 2` results in 1 assertion, with `i := 10, j := 200`.
|
||||
* `./tests waldo -g * -g 2` results in 3 assertions, all with `j := 200`.
|
||||
* `./tests waldo -g 1 -g *` results in 3 assertions, all with `i := 10`.
|
||||
* `./tests waldo -g 3` results in 1 **failed** assertion, because the first
|
||||
generator does not have 3rd element.
|
||||
* `./tests waldo -g * -g 3` results in 3 **failed** assertions, as the
|
||||
second generator does not have 3rd element, but we have to exhaust the
|
||||
first generator.
|
||||
|
||||
#### Generator with a nested dynamic section
|
||||
```cpp
|
||||
TEST_CASE( "grault" ) {
|
||||
REQUIRE( true );
|
||||
auto i = GENERATE( 1, 2, 3 );
|
||||
DYNAMIC_SECTION( "i=" << i ) {
|
||||
REQUIRE( true );
|
||||
}
|
||||
}
|
||||
```
|
||||
* `./tests grault -p g:1` results in 2 assertions, as there is no filter
|
||||
on the dynamic section.
|
||||
* `./tests grault -p g:1 -p c:i=2` results in 2 assertions, as the filter
|
||||
on the dynamic section matches the element given from the generator.
|
||||
* `./tests grault -p g:1 -p c:i=3` results in 1 assertion, as the generator
|
||||
is limited to only try `i := 2` and the dynamic section is filtered out.
|
||||
|
||||
#### Section with a sibling generator
|
||||
Because generators have to stop test execution when they don't pass filter,
|
||||
it is impossible to run only a section with sibling generator without
|
||||
triggering a test case skip. Consider this test case from an earlier example:
|
||||
```cpp
|
||||
TEST_CASE( "qux" ) {
|
||||
REQUIRE( true );
|
||||
SECTION( "A" ) { REQUIRE( true ); }
|
||||
auto i = GENERATE( 1, 2, 3 );
|
||||
DYNAMIC_SECTION( "i=" << i ) {
|
||||
REQUIRE( true );
|
||||
}
|
||||
}
|
||||
```
|
||||
* `./tests qux -p g:1` results in 2 assertions, as the dynamic section is
|
||||
entered only once.
|
||||
* `./tests qux -p g:1 -p c:i=1` results in 1 assertion, as the dynamic
|
||||
section filter is incompatible with the generator filter.
|
||||
* `./tests qux -p c:A` results in 2 assertions **and a skipped test case**.
|
||||
This is because the generator is sibling to section "A", and thus reads
|
||||
the same section filter. However, it is not a section and as thus cannot
|
||||
proceed.
|
||||
* `./tests qux -p c:i=2` results in 1 assertion **and a skipped test case**.
|
||||
Once again, the first filter in the filter stack is a section filter,
|
||||
and thus the generator cannot proceed.
|
||||
|
||||
Compare this with the old filter behaviour, where `./tests qux -c i=2`
|
||||
would instead result in 4 assertions, because the generator would go
|
||||
through all elements.
|
||||
|
||||
|
||||
---
|
||||
|
||||
[Home](Readme.md#top)
|
||||
+64
-1
@@ -1,6 +1,12 @@
|
||||
<a id="top"></a>
|
||||
# Data Generators
|
||||
|
||||
**Contents**<br>
|
||||
[Combining `GENERATE` and `SECTION`.](#combining-generate-and-section)<br>
|
||||
[Provided generators](#provided-generators)<br>
|
||||
[Generator interface](#generator-interface)<br>
|
||||
[Other usage examples](#other-usage-examples)<br>
|
||||
|
||||
> Introduced in Catch2 2.6.0.
|
||||
|
||||
Data generators (also known as _data driven/parametrized test cases_)
|
||||
@@ -106,7 +112,7 @@ a test case,
|
||||
* 2 fundamental generators
|
||||
* `SingleValueGenerator<T>` -- contains only single element
|
||||
* `FixedValuesGenerator<T>` -- contains multiple elements
|
||||
* 5 generic generators that modify other generators (defined in `catch2/generators/catch_generators_adapters.hpp`)
|
||||
* 6 generic generators that modify other generators (defined in `catch2/generators/catch_generators_adapters.hpp`)
|
||||
* `FilterGenerator<T, Predicate>` -- filters out elements from a generator
|
||||
for which the predicate returns "false"
|
||||
* `TakeGenerator<T>` -- takes first `n` elements from a generator
|
||||
@@ -114,6 +120,7 @@ a test case,
|
||||
* `MapGenerator<T, U, Func>` -- returns the result of applying `Func`
|
||||
on elements from a different generator
|
||||
* `ChunkGenerator<T>` -- returns chunks (inside `std::vector`) of n elements from a generator
|
||||
* `ConcatGenerator<T>` -- returns elements from multiple generators as if they were one
|
||||
* 2 random generators (defined in `catch2/generators/catch_generators_random.hpp`)
|
||||
* `RandomIntegerGenerator<Integral>` -- generates random Integrals from range
|
||||
* `RandomFloatGenerator<Float>` -- generates random Floats from range
|
||||
@@ -125,6 +132,8 @@ a test case,
|
||||
|
||||
> `IteratorGenerator<T>` was introduced in Catch2 2.10.0.
|
||||
|
||||
> `ConcatGenerator<T>` was introduced in Catch2 3.13.0
|
||||
|
||||
The generators also have associated helper functions that infer their
|
||||
type, making their usage much nicer. These are
|
||||
|
||||
@@ -142,6 +151,7 @@ type, making their usage much nicer. These are
|
||||
* `range(Arithmetic start, Arithmetic end, Arithmetic step)` for `RangeGenerator<Arithmetic>` with a custom step size
|
||||
* `from_range(InputIterator from, InputIterator to)` for `IteratorGenerator<T>`
|
||||
* `from_range(Container const&)` for `IteratorGenerator<T>`
|
||||
* `cat(GeneratorWrapper<T>&&...)` for `ConcatGenerator<T>`
|
||||
|
||||
> `chunk()`, `random()` and both `range()` functions were introduced in Catch2 2.7.0.
|
||||
|
||||
@@ -149,6 +159,8 @@ type, making their usage much nicer. These are
|
||||
|
||||
> `range()` for floating point numbers has been introduced in Catch2 2.11.0
|
||||
|
||||
> `cat` has been introduced in Catch2 3.13.0
|
||||
|
||||
And can be used as shown in the example below to create a generator
|
||||
that returns 100 odd random number:
|
||||
|
||||
@@ -252,9 +264,34 @@ struct IGenerator : GeneratorUntypedBase {
|
||||
// Returns user-friendly string showing the current generator element
|
||||
// Does not have to be overridden, IGenerator provides default implementation
|
||||
virtual std::string stringifyImpl() const;
|
||||
|
||||
/**
|
||||
* Customization point for `skipToNthElement`
|
||||
*
|
||||
* Does not have to be overridden, there is a default implementation.
|
||||
* Can be overridden for better performance.
|
||||
*
|
||||
* If there are not enough elements, shall throw an error.
|
||||
*
|
||||
* Going backwards is not supported.
|
||||
*/
|
||||
virtual void skipToNthElementImpl( std::size_t n );
|
||||
|
||||
/**
|
||||
* Returns true if calls to `next` will eventually return false
|
||||
*
|
||||
* Note that for backwards compatibility this is currently defaulted
|
||||
* to return `true`, but in the future all generators will have to
|
||||
* provide their own implementation.
|
||||
*/
|
||||
virtual bool isFinite() const = 0;
|
||||
};
|
||||
```
|
||||
|
||||
> `skipToNthElementImpl` was added in Catch2 3.13.0
|
||||
|
||||
> `isFinite` was added in Catch2 3.13.0
|
||||
|
||||
However, to be able to use your custom generator inside `GENERATE`, it
|
||||
will need to be wrapped inside a `GeneratorWrapper<T>`.
|
||||
`GeneratorWrapper<T>` is a value wrapper around a
|
||||
@@ -278,6 +315,32 @@ to be an error or not.
|
||||
* If empty generator **is not** an error, use the [`SKIP` macro](skipping-passing-failing.md#skipping-test-cases-at-runtime) in constructor.
|
||||
|
||||
|
||||
## Other usage examples
|
||||
|
||||
### Adding a reproducer to random tests
|
||||
|
||||
If you use generators to generate random inputs for testing, you might
|
||||
want to combine them with specific inputs, e.g. reproducers for previously
|
||||
found issues.
|
||||
|
||||
Because `GENERATE` accepts multiple values/generators, the basic case is simple:
|
||||
```cpp
|
||||
const int input = GENERATE(1, 2, take(10, random(10, 10'000'000)));
|
||||
```
|
||||
This will set `input` first to "1", then to "2", and then to 10 random
|
||||
integers.
|
||||
|
||||
But if you process the random inputs further (e.g. via `map`), you can't
|
||||
rely on `GENERATE`'s support for multiple generators. In that case, you
|
||||
have to use the `cat` generator combinator.
|
||||
```cpp
|
||||
const auto input = GENERATE(
|
||||
map( foo,
|
||||
cat( value( 4 ), take( 10, random( 10, 10'000'000 ) ) ) ) );
|
||||
```
|
||||
This will set `input` first to `foo(4)`, before transforming the 10 random
|
||||
integers through `foo`.
|
||||
|
||||
|
||||
---
|
||||
|
||||
|
||||
+22
-11
@@ -26,19 +26,22 @@ started" and "Section B", while the third one will only report "Test case
|
||||
started" as the extra info.
|
||||
|
||||
|
||||
## Logging without local scope
|
||||
## Logging outside of current scope
|
||||
|
||||
> [Introduced](https://github.com/catchorg/Catch2/issues/1522) in Catch2 2.7.0.
|
||||
> `UNSCOPED_INFO` was [introduced](https://github.com/catchorg/Catch2/issues/1522) in Catch2 2.7.0.
|
||||
|
||||
`UNSCOPED_INFO` is similar to `INFO` with two key differences:
|
||||
> `UNSCOPED_CAPTURE` was introduced in Catch2 3.13.0.
|
||||
|
||||
- Lifetime of an unscoped message is not tied to its own scope.
|
||||
The `UNSCOPED_X` macros are similar to their plain `X` macro counterparts,
|
||||
with two key differences:
|
||||
|
||||
- The lifetime of an unscoped message is not tied to its own scope.
|
||||
- An unscoped message can be reported by the first following assertion only, regardless of the result of that assertion.
|
||||
|
||||
In other words, lifetime of `UNSCOPED_INFO` is limited by the following assertion (or by the end of test case/section, whichever comes first) whereas lifetime of `INFO` is limited by its own scope.
|
||||
|
||||
These differences make this macro useful for reporting information from helper functions or inner scopes. An example:
|
||||
In other words, the `UNSCOPED_X` macros are useful to add extra information
|
||||
to the next assertion, e.g. from helper functions or inner scopes.
|
||||
|
||||
An example:
|
||||
```cpp
|
||||
void print_some_info() {
|
||||
UNSCOPED_INFO("Info from helper");
|
||||
@@ -83,9 +86,16 @@ Second info
|
||||
Second unscoped info
|
||||
```
|
||||
|
||||
Note that unscoped messages are not passed between test cases, even if
|
||||
there were no assertions between them.
|
||||
|
||||
|
||||
## Streaming macros
|
||||
|
||||
All these macros allow heterogeneous sequences of values to be streaming using the insertion operator (```<<```) in the same way that std::ostream, std::cout, etc support it.
|
||||
Apart from `CAPTURE` (and its close sibling, `UNSCOPED_CAPTURE`), message
|
||||
macros support gradual streaming of messages and values in the same way
|
||||
that the standard streams do.
|
||||
|
||||
|
||||
E.g.:
|
||||
```c++
|
||||
@@ -99,9 +109,6 @@ These macros come in three forms:
|
||||
|
||||
The message is logged to a buffer, but only reported with next assertions that are logged. This allows you to log contextual information in case of failures which is not shown during a successful test run (for the console reporter, without -s). Messages are removed from the buffer at the end of their scope, so may be used, for example, in loops.
|
||||
|
||||
_Note that in Catch2 2.x.x `INFO` can be used without a trailing semicolon as there is a trailing semicolon inside macro.
|
||||
This semicolon will be removed with next major version. It is highly advised to use a trailing semicolon after `INFO` macro._
|
||||
|
||||
**UNSCOPED_INFO(** _message expression_ **)**
|
||||
|
||||
> [Introduced](https://github.com/catchorg/Catch2/issues/1522) in Catch2 2.7.0.
|
||||
@@ -128,6 +135,10 @@ AS `FAIL`, but does not abort the test
|
||||
|
||||
**CAPTURE(** _expression1_, _expression2_, ... **)**
|
||||
|
||||
**UNSCOPED_CAPTURE(** _expression1_, _expression2_, ... **)**
|
||||
|
||||
> `UNSCOPED_CAPTURE` was introduced in Catch2 3.13.0.
|
||||
|
||||
Sometimes you just want to log a value of variable, or expression. For
|
||||
convenience, we provide the `CAPTURE` macro, that can take a variable,
|
||||
or an expression, and prints out that variable/expression and its value
|
||||
|
||||
@@ -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`
|
||||
|
||||
+1
-1
@@ -65,7 +65,7 @@ int main( int argc, char* argv[] ) {
|
||||
|
||||
returnCode = session.run();
|
||||
|
||||
// returnCode encodes the type of error that occured. See the
|
||||
// returnCode encodes the type of error that occurred. See the
|
||||
// integer constants in catch_session.hpp for more information
|
||||
// on what each return code means.
|
||||
|
||||
|
||||
@@ -2,6 +2,12 @@
|
||||
|
||||
# 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>
|
||||
[3.10.0](#3100)<br>
|
||||
@@ -72,6 +78,102 @@
|
||||
[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
|
||||
* `--benchmark-samples 0` no longer hard crashes (#3056)
|
||||
* 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)
|
||||
* Added `UNSCOPED_CAPTURE` macro (#2954)
|
||||
* Added `ConcatGenerator` to combine multiple separate generator into one
|
||||
* The short form is `cat`
|
||||
* Generators can now jump forward to nth element efficiently
|
||||
* Custom generators that can jump forward efficiently should override `skipToNthElementImpl`
|
||||
* Generators can declare themselves infinite
|
||||
* The generator base defaults to declaring itself finite for backwards compatibility
|
||||
* Custom generators should override `isFinite()` to return the proper value
|
||||
* Added `--warn InfiniteGenerators` to error out on `GENERATE` being given an infinite generator
|
||||
* Extended options for section filtering from CLI to include generators
|
||||
* The user can specify which element from the generator to use in the test case
|
||||
* See documentation for how the new filters work and how they can be specified
|
||||
* `MapGenerator` only calls the mapping function if the output will be used
|
||||
|
||||
|
||||
## 3.12.0
|
||||
|
||||
### Fixes
|
||||
|
||||
@@ -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.
|
||||
|
||||
|
||||
@@ -39,6 +39,24 @@ public:
|
||||
current_number = m_dist(m_rand);
|
||||
return true;
|
||||
}
|
||||
|
||||
bool isFinite() const override { return false; }
|
||||
|
||||
// Note: this improves the performance only a bit, but it is here
|
||||
// to show how you can override the skip functionality.
|
||||
void skipToNthElementImpl( std::size_t n ) override {
|
||||
auto current_index = currentElementIndex();
|
||||
assert(current_index <= n);
|
||||
// We cannot jump forward the underlying generator directly,
|
||||
// because we do not know how many bits each distributed number
|
||||
// would consume to be generated.
|
||||
for (; current_index < n; ++current_index) {
|
||||
(void)m_dist(m_rand);
|
||||
}
|
||||
|
||||
// We do not have to touch the current element index; it is handled
|
||||
// by the base class.
|
||||
}
|
||||
};
|
||||
|
||||
// Avoids -Wweak-vtables
|
||||
|
||||
@@ -40,6 +40,8 @@ public:
|
||||
bool next() override {
|
||||
return !!std::getline(m_stream, m_line);
|
||||
}
|
||||
|
||||
bool isFinite() const override { return true; }
|
||||
};
|
||||
|
||||
std::string const& LineGenerator::get() const {
|
||||
|
||||
+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}
|
||||
|
||||
+521
-250
File diff suppressed because it is too large
Load Diff
+655
-331
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.12.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',
|
||||
)
|
||||
|
||||
+5
-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
|
||||
@@ -108,6 +108,7 @@ set(IMPL_HEADERS
|
||||
${SOURCES_DIR}/internal/catch_optional.hpp
|
||||
${SOURCES_DIR}/internal/catch_output_redirect.hpp
|
||||
${SOURCES_DIR}/internal/catch_parse_numbers.hpp
|
||||
${SOURCES_DIR}/internal/catch_path_filter.hpp
|
||||
${SOURCES_DIR}/internal/catch_platform.hpp
|
||||
${SOURCES_DIR}/internal/catch_polyfills.hpp
|
||||
${SOURCES_DIR}/internal/catch_preprocessor.hpp
|
||||
@@ -180,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
|
||||
@@ -226,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
|
||||
@@ -255,11 +255,13 @@ set(GENERATOR_HEADERS
|
||||
${SOURCES_DIR}/generators/catch_generators_all.hpp
|
||||
${SOURCES_DIR}/generators/catch_generators_random.hpp
|
||||
${SOURCES_DIR}/generators/catch_generators_range.hpp
|
||||
${SOURCES_DIR}/generators/catch_generators_throw.hpp
|
||||
)
|
||||
set(GENERATOR_SOURCES
|
||||
${SOURCES_DIR}/generators/catch_generator_exception.cpp
|
||||
${SOURCES_DIR}/generators/catch_generators.cpp
|
||||
${SOURCES_DIR}/generators/catch_generators_random.cpp
|
||||
${SOURCES_DIR}/generators/catch_generators_throw.cpp
|
||||
)
|
||||
set(GENERATOR_FILES ${GENERATOR_HEADERS} ${GENERATOR_SOURCES})
|
||||
|
||||
|
||||
@@ -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 dependees
|
||||
template <typename Duration = Benchmark::FDuration>
|
||||
// we can prevent multiple declarations in dependencies
|
||||
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>
|
||||
@@ -89,6 +89,7 @@
|
||||
#include <catch2/internal/catch_optional.hpp>
|
||||
#include <catch2/internal/catch_output_redirect.hpp>
|
||||
#include <catch2/internal/catch_parse_numbers.hpp>
|
||||
#include <catch2/internal/catch_path_filter.hpp>
|
||||
#include <catch2/internal/catch_platform.hpp>
|
||||
#include <catch2/internal/catch_polyfills.hpp>
|
||||
#include <catch2/internal/catch_preprocessor.hpp>
|
||||
|
||||
@@ -92,6 +92,10 @@ namespace Catch {
|
||||
lhs.customOptions == rhs.customOptions;
|
||||
}
|
||||
|
||||
bool operator==( PathFilter const& lhs, PathFilter const& rhs ) {
|
||||
return lhs.type == rhs.type && lhs.filter == rhs.filter;
|
||||
}
|
||||
|
||||
Config::Config( ConfigData const& data ):
|
||||
m_data( data ) {
|
||||
// We need to trim filter specs to avoid trouble with superfluous
|
||||
@@ -101,9 +105,6 @@ namespace Catch {
|
||||
for (auto& elem : m_data.testsOrTags) {
|
||||
elem = trim(elem);
|
||||
}
|
||||
for (auto& elem : m_data.sectionsToRun) {
|
||||
elem = trim(elem);
|
||||
}
|
||||
|
||||
// Insert the default reporter if user hasn't asked for a specific one
|
||||
if ( m_data.reporterSpecifications.empty() ) {
|
||||
@@ -169,7 +170,8 @@ namespace Catch {
|
||||
bool Config::listListeners() const { return m_data.listListeners; }
|
||||
|
||||
std::vector<std::string> const& Config::getTestsOrTags() const { return m_data.testsOrTags; }
|
||||
std::vector<std::string> const& Config::getSectionsToRun() const { return m_data.sectionsToRun; }
|
||||
std::vector<PathFilter> const& Config::getPathFilters() const { return m_data.pathFilters; }
|
||||
bool Config::useNewFilterBehaviour() const { return m_data.useNewPathFilteringBehaviour; }
|
||||
|
||||
std::vector<ReporterSpec> const& Config::getReporterSpecs() const {
|
||||
return m_data.reporterSpecifications;
|
||||
@@ -197,6 +199,9 @@ namespace Catch {
|
||||
bool Config::warnAboutUnmatchedTestSpecs() const {
|
||||
return !!( m_data.warnings & WarnAbout::UnmatchedTestSpec );
|
||||
}
|
||||
bool Config::warnAboutInfiniteGenerators() const {
|
||||
return !!( m_data.warnings & WarnAbout::InfiniteGenerator );
|
||||
}
|
||||
bool Config::zeroTestsCountAsSuccess() const { return m_data.allowZeroTests; }
|
||||
ShowDurations Config::showDurations() const { return m_data.showDurations; }
|
||||
double Config::minDuration() const { return m_data.minDuration; }
|
||||
|
||||
@@ -12,6 +12,7 @@
|
||||
#include <catch2/interfaces/catch_interfaces_config.hpp>
|
||||
#include <catch2/internal/catch_unique_ptr.hpp>
|
||||
#include <catch2/internal/catch_optional.hpp>
|
||||
#include <catch2/internal/catch_path_filter.hpp>
|
||||
#include <catch2/internal/catch_stringref.hpp>
|
||||
#include <catch2/internal/catch_random_seed_generation.hpp>
|
||||
#include <catch2/internal/catch_reporter_spec_parser.hpp>
|
||||
@@ -86,7 +87,8 @@ namespace Catch {
|
||||
std::vector<ReporterSpec> reporterSpecifications;
|
||||
|
||||
std::vector<std::string> testsOrTags;
|
||||
std::vector<std::string> sectionsToRun;
|
||||
std::vector<PathFilter> pathFilters;
|
||||
bool useNewPathFilteringBehaviour = false;
|
||||
|
||||
std::string prematureExitGuardFilePath;
|
||||
};
|
||||
@@ -109,7 +111,8 @@ namespace Catch {
|
||||
getProcessedReporterSpecs() const;
|
||||
|
||||
std::vector<std::string> const& getTestsOrTags() const override;
|
||||
std::vector<std::string> const& getSectionsToRun() const override;
|
||||
std::vector<PathFilter> const& getPathFilters() const override;
|
||||
bool useNewFilterBehaviour() const override;
|
||||
|
||||
TestSpec const& testSpec() const override;
|
||||
bool hasTestFilters() const override;
|
||||
@@ -124,6 +127,7 @@ namespace Catch {
|
||||
bool includeSuccessfulResults() const override;
|
||||
bool warnAboutMissingAssertions() const override;
|
||||
bool warnAboutUnmatchedTestSpecs() const override;
|
||||
bool warnAboutInfiniteGenerators() const override;
|
||||
bool zeroTestsCountAsSuccess() const override;
|
||||
ShowDurations showDurations() const override;
|
||||
double minDuration() const override;
|
||||
|
||||
@@ -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,14 +31,16 @@ namespace Catch {
|
||||
}
|
||||
|
||||
ScopedMessage::~ScopedMessage() {
|
||||
if ( !m_moved ) { IResultCapture::popScopedMessage( m_messageId ); }
|
||||
if ( !m_moved ) { Detail::popScopedMessage( m_messageId ); }
|
||||
}
|
||||
|
||||
|
||||
Capturer::Capturer( StringRef macroName,
|
||||
SourceLineInfo const& lineInfo,
|
||||
ResultWas::OfType resultType,
|
||||
StringRef names ) {
|
||||
StringRef names,
|
||||
bool isScoped):
|
||||
m_isScoped(isScoped) {
|
||||
auto trimmed = [&] (size_t start, size_t end) {
|
||||
while (names[start] == ',' || isspace(static_cast<unsigned char>(names[start]))) {
|
||||
++start;
|
||||
@@ -99,15 +101,21 @@ namespace Catch {
|
||||
}
|
||||
Capturer::~Capturer() {
|
||||
assert( m_captured == m_messages.size() );
|
||||
for (auto const& message : m_messages) {
|
||||
IResultCapture::popScopedMessage( message.sequence );
|
||||
if ( m_isScoped ) {
|
||||
for ( auto const& message : m_messages ) {
|
||||
Detail::popScopedMessage( message.sequence );
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
void Capturer::captureValue( size_t index, std::string const& value ) {
|
||||
assert( index < m_messages.size() );
|
||||
m_messages[index].message += value;
|
||||
IResultCapture::pushScopedMessage( CATCH_MOVE( m_messages[index] ) );
|
||||
if ( m_isScoped ) {
|
||||
Detail::pushScopedMessage( CATCH_MOVE( m_messages[index] ) );
|
||||
} else {
|
||||
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 {
|
||||
|
||||
@@ -64,8 +76,9 @@ namespace Catch {
|
||||
class Capturer {
|
||||
std::vector<MessageInfo> m_messages;
|
||||
size_t m_captured = 0;
|
||||
bool m_isScoped = false;
|
||||
public:
|
||||
Capturer( StringRef macroName, SourceLineInfo const& lineInfo, ResultWas::OfType resultType, StringRef names );
|
||||
Capturer( StringRef macroName, SourceLineInfo const& lineInfo, ResultWas::OfType resultType, StringRef names, bool isScoped );
|
||||
|
||||
Capturer(Capturer const&) = delete;
|
||||
Capturer& operator=(Capturer const&) = delete;
|
||||
@@ -97,11 +110,12 @@ namespace Catch {
|
||||
} while( false )
|
||||
|
||||
///////////////////////////////////////////////////////////////////////////////
|
||||
#define INTERNAL_CATCH_CAPTURE( varName, macroName, ... ) \
|
||||
Catch::Capturer varName( macroName##_catch_sr, \
|
||||
CATCH_INTERNAL_LINEINFO, \
|
||||
Catch::ResultWas::Info, \
|
||||
#__VA_ARGS__##_catch_sr ); \
|
||||
#define INTERNAL_CATCH_CAPTURE( varName, macroName, scopedCapture, ... ) \
|
||||
Catch::Capturer varName( macroName##_catch_sr, \
|
||||
CATCH_INTERNAL_LINEINFO, \
|
||||
Catch::ResultWas::Info, \
|
||||
#__VA_ARGS__##_catch_sr, \
|
||||
scopedCapture ); \
|
||||
varName.captureValues( 0, __VA_ARGS__ )
|
||||
|
||||
///////////////////////////////////////////////////////////////////////////////
|
||||
@@ -110,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)
|
||||
@@ -118,28 +132,32 @@ namespace Catch {
|
||||
#define CATCH_INFO( msg ) INTERNAL_CATCH_INFO( "CATCH_INFO", msg )
|
||||
#define CATCH_UNSCOPED_INFO( msg ) INTERNAL_CATCH_UNSCOPED_INFO( "CATCH_UNSCOPED_INFO", msg )
|
||||
#define CATCH_WARN( msg ) INTERNAL_CATCH_MSG( "CATCH_WARN", Catch::ResultWas::Warning, Catch::ResultDisposition::ContinueOnFailure, msg )
|
||||
#define CATCH_CAPTURE( ... ) INTERNAL_CATCH_CAPTURE( INTERNAL_CATCH_UNIQUE_NAME(capturer), "CATCH_CAPTURE", __VA_ARGS__ )
|
||||
#define CATCH_CAPTURE( ... ) INTERNAL_CATCH_CAPTURE( INTERNAL_CATCH_UNIQUE_NAME(capturer), "CATCH_CAPTURE", true, __VA_ARGS__ )
|
||||
#define CATCH_UNSCOPED_CAPTURE( ... ) INTERNAL_CATCH_CAPTURE( INTERNAL_CATCH_UNIQUE_NAME(capturer), "CATCH_UNSCOPED_CAPTURE", false, __VA_ARGS__ )
|
||||
|
||||
#elif defined(CATCH_CONFIG_PREFIX_MESSAGES) && defined(CATCH_CONFIG_DISABLE)
|
||||
|
||||
#define CATCH_INFO( msg ) (void)(0)
|
||||
#define CATCH_UNSCOPED_INFO( msg ) (void)(0)
|
||||
#define CATCH_WARN( msg ) (void)(0)
|
||||
#define CATCH_CAPTURE( ... ) (void)(0)
|
||||
#define CATCH_INFO( msg ) (void)(0)
|
||||
#define CATCH_UNSCOPED_INFO( msg ) (void)(0)
|
||||
#define CATCH_WARN( msg ) (void)(0)
|
||||
#define CATCH_CAPTURE( ... ) (void)(0)
|
||||
#define CATCH_UNSCOPED_CAPTURE( ... ) (void)(0)
|
||||
|
||||
#elif !defined(CATCH_CONFIG_PREFIX_MESSAGES) && !defined(CATCH_CONFIG_DISABLE)
|
||||
|
||||
#define INFO( msg ) INTERNAL_CATCH_INFO( "INFO", msg )
|
||||
#define UNSCOPED_INFO( msg ) INTERNAL_CATCH_UNSCOPED_INFO( "UNSCOPED_INFO", msg )
|
||||
#define WARN( msg ) INTERNAL_CATCH_MSG( "WARN", Catch::ResultWas::Warning, Catch::ResultDisposition::ContinueOnFailure, msg )
|
||||
#define CAPTURE( ... ) INTERNAL_CATCH_CAPTURE( INTERNAL_CATCH_UNIQUE_NAME(capturer), "CAPTURE", __VA_ARGS__ )
|
||||
#define CAPTURE( ... ) INTERNAL_CATCH_CAPTURE( INTERNAL_CATCH_UNIQUE_NAME(capturer), "CAPTURE", true, __VA_ARGS__ )
|
||||
#define UNSCOPED_CAPTURE( ... ) INTERNAL_CATCH_CAPTURE( INTERNAL_CATCH_UNIQUE_NAME(capturer), "UNSCOPED_CAPTURE", false, __VA_ARGS__ )
|
||||
|
||||
#elif !defined(CATCH_CONFIG_PREFIX_MESSAGES) && defined(CATCH_CONFIG_DISABLE)
|
||||
|
||||
#define INFO( msg ) (void)(0)
|
||||
#define UNSCOPED_INFO( msg ) (void)(0)
|
||||
#define WARN( msg ) (void)(0)
|
||||
#define CAPTURE( ... ) (void)(0)
|
||||
#define INFO( msg ) (void)(0)
|
||||
#define UNSCOPED_INFO( msg ) (void)(0)
|
||||
#define WARN( msg ) (void)(0)
|
||||
#define CAPTURE( ... ) (void)(0)
|
||||
#define UNSCOPED_CAPTURE( ... ) (void)(0)
|
||||
|
||||
#endif // end of user facing macro declarations
|
||||
|
||||
|
||||
@@ -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, 12, 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 12
|
||||
#define CATCH_VERSION_PATCH 0
|
||||
#define CATCH_VERSION_MINOR 15
|
||||
#define CATCH_VERSION_PATCH 3
|
||||
|
||||
#endif // CATCH_VERSION_MACROS_HPP_INCLUDED
|
||||
|
||||
@@ -7,8 +7,6 @@
|
||||
// SPDX-License-Identifier: BSL-1.0
|
||||
|
||||
#include <catch2/generators/catch_generators.hpp>
|
||||
#include <catch2/internal/catch_enforce.hpp>
|
||||
#include <catch2/generators/catch_generator_exception.hpp>
|
||||
#include <catch2/interfaces/catch_interfaces_capture.hpp>
|
||||
|
||||
namespace Catch {
|
||||
@@ -17,14 +15,6 @@ namespace Catch {
|
||||
|
||||
namespace Generators {
|
||||
|
||||
namespace Detail {
|
||||
|
||||
[[noreturn]]
|
||||
void throw_generator_exception(char const* msg) {
|
||||
Catch::throw_exception(GeneratorException{ msg });
|
||||
}
|
||||
} // end namespace Detail
|
||||
|
||||
GeneratorUntypedBase::~GeneratorUntypedBase() = default;
|
||||
|
||||
IGeneratorTracker* acquireGeneratorTracker(StringRef generatorName, SourceLineInfo const& lineInfo ) {
|
||||
|
||||
@@ -9,6 +9,7 @@
|
||||
#define CATCH_GENERATORS_HPP_INCLUDED
|
||||
|
||||
#include <catch2/catch_tostring.hpp>
|
||||
#include <catch2/generators/catch_generators_throw.hpp>
|
||||
#include <catch2/interfaces/catch_interfaces_generatortracker.hpp>
|
||||
#include <catch2/internal/catch_source_line_info.hpp>
|
||||
#include <catch2/internal/catch_stringref.hpp>
|
||||
@@ -22,14 +23,6 @@ namespace Catch {
|
||||
|
||||
namespace Generators {
|
||||
|
||||
namespace Detail {
|
||||
|
||||
//! Throws GeneratorException with the provided message
|
||||
[[noreturn]]
|
||||
void throw_generator_exception(char const * msg);
|
||||
|
||||
} // end namespace detail
|
||||
|
||||
template<typename T>
|
||||
class IGenerator : public GeneratorUntypedBase {
|
||||
std::string stringifyImpl() const override {
|
||||
@@ -64,6 +57,9 @@ namespace Detail {
|
||||
bool next() {
|
||||
return m_generator->countedNext();
|
||||
}
|
||||
|
||||
bool isFinite() const { return m_generator->isFinite(); }
|
||||
void skipToNthElement( size_t n ) { m_generator->skipToNthElement(n); }
|
||||
};
|
||||
|
||||
|
||||
@@ -84,6 +80,8 @@ namespace Detail {
|
||||
bool next() override {
|
||||
return false;
|
||||
}
|
||||
|
||||
bool isFinite() const override { return true; }
|
||||
};
|
||||
|
||||
template<typename T>
|
||||
@@ -93,6 +91,15 @@ namespace Detail {
|
||||
"specialization, use SingleValue Generator instead.");
|
||||
std::vector<T> m_values;
|
||||
size_t m_idx = 0;
|
||||
|
||||
void skipToNthElementImpl( std::size_t n ) override {
|
||||
if ( n >= m_values.size() ) {
|
||||
Detail::throw_generator_exception(
|
||||
"Could not jump to Nth element: not enough elements" );
|
||||
}
|
||||
m_idx = n;
|
||||
}
|
||||
|
||||
public:
|
||||
FixedValuesGenerator( std::initializer_list<T> values ) : m_values( values ) {}
|
||||
|
||||
@@ -103,6 +110,8 @@ namespace Detail {
|
||||
++m_idx;
|
||||
return m_idx < m_values.size();
|
||||
}
|
||||
|
||||
bool isFinite() const override { return true; }
|
||||
};
|
||||
|
||||
template <typename T, typename DecayedT = std::decay_t<T>>
|
||||
@@ -167,6 +176,13 @@ namespace Detail {
|
||||
}
|
||||
return m_current < m_generators.size();
|
||||
}
|
||||
|
||||
bool isFinite() const override {
|
||||
for (auto const& gen : m_generators) {
|
||||
if (!gen.isFinite()) { return false; }
|
||||
}
|
||||
return true;
|
||||
}
|
||||
};
|
||||
|
||||
|
||||
|
||||
@@ -11,6 +11,7 @@
|
||||
#include <catch2/generators/catch_generators.hpp>
|
||||
#include <catch2/internal/catch_meta.hpp>
|
||||
#include <catch2/internal/catch_move_and_forward.hpp>
|
||||
#include <catch2/internal/catch_optional.hpp>
|
||||
|
||||
#include <cassert>
|
||||
|
||||
@@ -22,6 +23,17 @@ namespace Generators {
|
||||
GeneratorWrapper<T> m_generator;
|
||||
size_t m_returned = 0;
|
||||
size_t m_target;
|
||||
|
||||
void skipToNthElementImpl( std::size_t n ) override {
|
||||
if ( n >= m_target ) {
|
||||
Detail::throw_generator_exception(
|
||||
"Could not jump to Nth element: not enough elements" );
|
||||
}
|
||||
|
||||
m_generator.skipToNthElement( n );
|
||||
m_returned = n;
|
||||
}
|
||||
|
||||
public:
|
||||
TakeGenerator(size_t target, GeneratorWrapper<T>&& generator):
|
||||
m_generator(CATCH_MOVE(generator)),
|
||||
@@ -46,6 +58,8 @@ namespace Generators {
|
||||
}
|
||||
return success;
|
||||
}
|
||||
|
||||
bool isFinite() const override { return true; }
|
||||
};
|
||||
|
||||
template <typename T>
|
||||
@@ -87,6 +101,8 @@ namespace Generators {
|
||||
while (!m_predicate(m_generator.get()) && (success = m_generator.next()) == true);
|
||||
return success;
|
||||
}
|
||||
|
||||
bool isFinite() const override { return m_generator.isFinite(); }
|
||||
};
|
||||
|
||||
|
||||
@@ -111,6 +127,9 @@ namespace Generators {
|
||||
m_target_repeats(repeats)
|
||||
{
|
||||
assert(m_target_repeats > 0 && "Repeat generator must repeat at least once");
|
||||
if (!m_generator.isFinite()) {
|
||||
Detail::throw_generator_exception( "Cannot repeat infinite generator" );
|
||||
}
|
||||
}
|
||||
|
||||
T const& get() const override {
|
||||
@@ -144,6 +163,8 @@ namespace Generators {
|
||||
}
|
||||
return m_current_repeat < m_target_repeats;
|
||||
}
|
||||
|
||||
bool isFinite() const override { return m_generator.isFinite(); }
|
||||
};
|
||||
|
||||
template <typename T>
|
||||
@@ -157,25 +178,30 @@ namespace Generators {
|
||||
GeneratorWrapper<U> m_generator;
|
||||
Func m_function;
|
||||
// To avoid returning dangling reference, we have to save the values
|
||||
T m_cache;
|
||||
mutable Optional<T> m_cache;
|
||||
|
||||
void skipToNthElementImpl( std::size_t n ) override {
|
||||
m_generator.skipToNthElement( n );
|
||||
m_cache.reset();
|
||||
}
|
||||
|
||||
public:
|
||||
template <typename F2 = Func>
|
||||
MapGenerator(F2&& function, GeneratorWrapper<U>&& generator) :
|
||||
m_generator(CATCH_MOVE(generator)),
|
||||
m_function(CATCH_FORWARD(function)),
|
||||
m_cache(m_function(m_generator.get()))
|
||||
m_function(CATCH_FORWARD(function))
|
||||
{}
|
||||
|
||||
T const& get() const override {
|
||||
return m_cache;
|
||||
if ( !m_cache ) { m_cache = m_function( m_generator.get() ); }
|
||||
return *m_cache;
|
||||
}
|
||||
bool next() override {
|
||||
const auto success = m_generator.next();
|
||||
if (success) {
|
||||
m_cache = m_function(m_generator.get());
|
||||
}
|
||||
return success;
|
||||
m_cache.reset();
|
||||
return m_generator.next();
|
||||
}
|
||||
|
||||
bool isFinite() const override { return m_generator.isFinite(); }
|
||||
};
|
||||
|
||||
template <typename Func, typename U, typename T = FunctionReturnType<Func, U>>
|
||||
@@ -197,7 +223,6 @@ namespace Generators {
|
||||
std::vector<T> m_chunk;
|
||||
size_t m_chunk_size;
|
||||
GeneratorWrapper<T> m_generator;
|
||||
bool m_used_up = false;
|
||||
public:
|
||||
ChunkGenerator(size_t size, GeneratorWrapper<T> generator) :
|
||||
m_chunk_size(size), m_generator(CATCH_MOVE(generator))
|
||||
@@ -226,6 +251,8 @@ namespace Generators {
|
||||
}
|
||||
return true;
|
||||
}
|
||||
|
||||
bool isFinite() const override { return m_generator.isFinite(); }
|
||||
};
|
||||
|
||||
template <typename T>
|
||||
@@ -235,6 +262,56 @@ namespace Generators {
|
||||
);
|
||||
}
|
||||
|
||||
template <typename T>
|
||||
class ConcatGenerator final : public IGenerator<T> {
|
||||
std::vector<GeneratorWrapper<T>> m_generators;
|
||||
size_t m_current_generator = 0;
|
||||
|
||||
void InsertGenerators( GeneratorWrapper<T>&& gen ) {
|
||||
m_generators.push_back( CATCH_MOVE( gen ) );
|
||||
}
|
||||
|
||||
template <typename... Generators>
|
||||
void InsertGenerators( GeneratorWrapper<T>&& gen, Generators&&... gens ) {
|
||||
m_generators.push_back( CATCH_MOVE( gen ) );
|
||||
InsertGenerators( CATCH_MOVE( gens )... );
|
||||
}
|
||||
|
||||
public:
|
||||
template <typename... Generators>
|
||||
ConcatGenerator( Generators&&... generators ) {
|
||||
InsertGenerators( CATCH_MOVE( generators )... );
|
||||
}
|
||||
|
||||
T const& get() const override {
|
||||
return m_generators[m_current_generator].get();
|
||||
}
|
||||
bool next() override {
|
||||
const bool success = m_generators[m_current_generator].next();
|
||||
if ( success ) { return true; }
|
||||
|
||||
// If current generator is used up, we have to move to the next one
|
||||
++m_current_generator;
|
||||
return m_current_generator < m_generators.size();
|
||||
}
|
||||
|
||||
bool isFinite() const override {
|
||||
for ( auto const& gen : m_generators ) {
|
||||
if ( !gen.isFinite() ) { return false; }
|
||||
}
|
||||
return true;
|
||||
}
|
||||
};
|
||||
|
||||
template <typename T, typename... Generators>
|
||||
GeneratorWrapper<T> cat( GeneratorWrapper<T>&& generator,
|
||||
Generators&&... generators ) {
|
||||
return GeneratorWrapper<T>(
|
||||
Catch::Detail::make_unique<ConcatGenerator<T>>(
|
||||
CATCH_MOVE( generator ), CATCH_MOVE( generators )... ) );
|
||||
}
|
||||
|
||||
|
||||
} // namespace Generators
|
||||
} // namespace Catch
|
||||
|
||||
|
||||
@@ -26,5 +26,6 @@
|
||||
#include <catch2/generators/catch_generators_adapters.hpp>
|
||||
#include <catch2/generators/catch_generators_random.hpp>
|
||||
#include <catch2/generators/catch_generators_range.hpp>
|
||||
#include <catch2/generators/catch_generators_throw.hpp>
|
||||
|
||||
#endif // CATCH_GENERATORS_ALL_HPP_INCLUDED
|
||||
|
||||
@@ -37,5 +37,10 @@ namespace Catch {
|
||||
m_current_number = m_pimpl->dist( m_pimpl->rng );
|
||||
return true;
|
||||
}
|
||||
|
||||
bool RandomFloatingGenerator<long double>::isFinite() const {
|
||||
return false;
|
||||
}
|
||||
|
||||
} // namespace Generators
|
||||
} // namespace Catch
|
||||
|
||||
@@ -42,6 +42,7 @@ public:
|
||||
m_current_number = m_dist(m_rng);
|
||||
return true;
|
||||
}
|
||||
bool isFinite() const override { return false; }
|
||||
};
|
||||
|
||||
template <>
|
||||
@@ -59,6 +60,7 @@ public:
|
||||
bool next() override;
|
||||
|
||||
~RandomFloatingGenerator() override; // = default
|
||||
bool isFinite() const override;
|
||||
};
|
||||
|
||||
template <typename Integer>
|
||||
@@ -80,6 +82,7 @@ public:
|
||||
m_current_number = m_dist(m_rng);
|
||||
return true;
|
||||
}
|
||||
bool isFinite() const override { return false; }
|
||||
};
|
||||
|
||||
template <typename T>
|
||||
|
||||
@@ -48,6 +48,8 @@ public:
|
||||
m_current += m_step;
|
||||
return (m_positive) ? (m_current < m_end) : (m_current > m_end);
|
||||
}
|
||||
|
||||
bool isFinite() const override { return true; }
|
||||
};
|
||||
|
||||
template <typename T>
|
||||
@@ -87,6 +89,8 @@ public:
|
||||
++m_current;
|
||||
return m_current != m_elems.size();
|
||||
}
|
||||
|
||||
bool isFinite() const override { return true; }
|
||||
};
|
||||
|
||||
template <typename InputIterator,
|
||||
|
||||
@@ -0,0 +1,24 @@
|
||||
|
||||
// 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
|
||||
|
||||
#include <catch2/generators/catch_generator_exception.hpp>
|
||||
#include <catch2/generators/catch_generators_throw.hpp>
|
||||
#include <catch2/internal/catch_enforce.hpp>
|
||||
|
||||
namespace Catch {
|
||||
namespace Generators {
|
||||
namespace Detail {
|
||||
|
||||
[[noreturn]]
|
||||
void throw_generator_exception( char const* msg ) {
|
||||
Catch::throw_exception( GeneratorException{ msg } );
|
||||
}
|
||||
|
||||
} // namespace Detail
|
||||
} // namespace Generators
|
||||
} // namespace Catch
|
||||
@@ -0,0 +1,23 @@
|
||||
|
||||
// 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_GENERATORS_THROW_HPP_INCLUDED
|
||||
#define CATCH_GENERATORS_THROW_HPP_INCLUDED
|
||||
|
||||
namespace Catch {
|
||||
namespace Generators {
|
||||
namespace Detail {
|
||||
|
||||
//! Throws GeneratorException with the provided message
|
||||
[[noreturn]]
|
||||
void throw_generator_exception( char const* msg );
|
||||
|
||||
} // namespace Detail
|
||||
} // namespace Generators
|
||||
} // namespace Catch
|
||||
|
||||
#endif // CATCH_GENERATORS_THROW_HPP_INCLUDED
|
||||
@@ -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,10 +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 emplaceUnscopedMessage( MessageBuilder&& builder );
|
||||
|
||||
virtual void handleFatalErrorCondition( StringRef message ) = 0;
|
||||
|
||||
virtual void handleExpr
|
||||
@@ -92,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;
|
||||
|
||||
@@ -29,6 +29,8 @@ namespace Catch {
|
||||
NoAssertions = 0x01,
|
||||
//! A command line test spec matched no test cases
|
||||
UnmatchedTestSpec = 0x02,
|
||||
//! The resulting generator in GENERATE is infinite
|
||||
InfiniteGenerator = 0x04,
|
||||
}; };
|
||||
|
||||
enum class ShowDurations {
|
||||
@@ -60,8 +62,9 @@ namespace Catch {
|
||||
|
||||
class TestSpec;
|
||||
class IStream;
|
||||
struct PathFilter;
|
||||
|
||||
class IConfig : public Detail::NonCopyable {
|
||||
class IConfig : Detail::NonCopyable {
|
||||
public:
|
||||
virtual ~IConfig();
|
||||
|
||||
@@ -71,6 +74,7 @@ namespace Catch {
|
||||
virtual bool shouldDebugBreak() const = 0;
|
||||
virtual bool warnAboutMissingAssertions() const = 0;
|
||||
virtual bool warnAboutUnmatchedTestSpecs() const = 0;
|
||||
virtual bool warnAboutInfiniteGenerators() const = 0;
|
||||
virtual bool zeroTestsCountAsSuccess() const = 0;
|
||||
virtual int abortAfter() const = 0;
|
||||
virtual bool showInvisibles() const = 0;
|
||||
@@ -84,7 +88,9 @@ namespace Catch {
|
||||
virtual unsigned int shardCount() const = 0;
|
||||
virtual unsigned int shardIndex() const = 0;
|
||||
virtual ColourMode defaultColourMode() const = 0;
|
||||
virtual std::vector<std::string> const& getSectionsToRun() const = 0;
|
||||
virtual std::vector<PathFilter> const& getPathFilters() const = 0;
|
||||
virtual bool useNewFilterBehaviour() const = 0;
|
||||
|
||||
virtual Verbosity verbosity() const = 0;
|
||||
|
||||
virtual bool skipBenchmarks() const = 0;
|
||||
|
||||
@@ -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
|
||||
@@ -7,6 +7,8 @@
|
||||
// SPDX-License-Identifier: BSL-1.0
|
||||
|
||||
#include <catch2/interfaces/catch_interfaces_generatortracker.hpp>
|
||||
#include <catch2/generators/catch_generators.hpp>
|
||||
|
||||
#include <string>
|
||||
|
||||
namespace Catch {
|
||||
@@ -21,6 +23,31 @@ namespace Catch {
|
||||
return ret;
|
||||
}
|
||||
|
||||
void GeneratorUntypedBase::skipToNthElementImpl( std::size_t n ) {
|
||||
for ( size_t i = m_currentElementIndex; i < n; ++i ) {
|
||||
bool isValid = next();
|
||||
if ( !isValid ) {
|
||||
Detail::throw_generator_exception(
|
||||
"Could not jump to Nth element: not enough elements" );
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
void GeneratorUntypedBase::skipToNthElement( std::size_t n ) {
|
||||
if ( n < m_currentElementIndex ) {
|
||||
Detail::throw_generator_exception(
|
||||
"Tried to jump generator backwards" );
|
||||
}
|
||||
if ( n == m_currentElementIndex ) { return; }
|
||||
|
||||
skipToNthElementImpl(n);
|
||||
// Fixup tracking after moving the generator forward
|
||||
// * Ensure that the correct element index is set after skipping
|
||||
// * Invalidate cache
|
||||
m_currentElementIndex = n;
|
||||
m_stringReprCache.clear();
|
||||
}
|
||||
|
||||
StringRef GeneratorUntypedBase::currentElementAsString() const {
|
||||
if ( m_stringReprCache.empty() ) {
|
||||
m_stringReprCache = stringifyImpl();
|
||||
@@ -28,5 +55,7 @@ namespace Catch {
|
||||
return m_stringReprCache;
|
||||
}
|
||||
|
||||
bool GeneratorUntypedBase::isFinite() const { return true; }
|
||||
|
||||
} // namespace Generators
|
||||
} // namespace Catch
|
||||
|
||||
@@ -35,6 +35,15 @@ namespace Catch {
|
||||
//! Customization point for `currentElementAsString`
|
||||
virtual std::string stringifyImpl() const = 0;
|
||||
|
||||
/**
|
||||
* Customization point for skipping to the n-th element
|
||||
*
|
||||
* Defaults to successively calling `countedNext`. If there
|
||||
* are not enough elements to reach the nth one, will throw
|
||||
* an error.
|
||||
*/
|
||||
virtual void skipToNthElementImpl( std::size_t n );
|
||||
|
||||
public:
|
||||
GeneratorUntypedBase() = default;
|
||||
// Generation of copy ops is deprecated (and Clang will complain)
|
||||
@@ -58,6 +67,13 @@ namespace Catch {
|
||||
|
||||
std::size_t currentElementIndex() const { return m_currentElementIndex; }
|
||||
|
||||
/**
|
||||
* Moves the generator forward **to** the n-th element
|
||||
*
|
||||
* Cannot move backwards. Can stay in place.
|
||||
*/
|
||||
void skipToNthElement( std::size_t n );
|
||||
|
||||
/**
|
||||
* Returns generator's current element as user-friendly string.
|
||||
*
|
||||
@@ -72,6 +88,15 @@ namespace Catch {
|
||||
* comes first.
|
||||
*/
|
||||
StringRef currentElementAsString() const;
|
||||
|
||||
/**
|
||||
* Returns true if calls to `next` will eventually return false
|
||||
*
|
||||
* Note that for backwards compatibility this is currently defaulted
|
||||
* to return `true`, but in the future all generators will have to
|
||||
* provide their own implementation.
|
||||
*/
|
||||
virtual bool isFinite() const;
|
||||
};
|
||||
using GeneratorBasePtr = Catch::Detail::unique_ptr<GeneratorUntypedBase>;
|
||||
|
||||
@@ -80,9 +105,7 @@ namespace Catch {
|
||||
class IGeneratorTracker {
|
||||
public:
|
||||
virtual ~IGeneratorTracker(); // = default;
|
||||
virtual auto hasGenerator() const -> bool = 0;
|
||||
virtual auto getGenerator() const -> Generators::GeneratorBasePtr const& = 0;
|
||||
virtual void setGenerator( Generators::GeneratorBasePtr&& generator ) = 0;
|
||||
};
|
||||
|
||||
} // namespace Catch
|
||||
|
||||
@@ -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(); }
|
||||
}
|
||||
|
||||
|
||||
|
||||
@@ -32,6 +32,9 @@ namespace Catch {
|
||||
} else if ( warning == "UnmatchedTestSpec" ) {
|
||||
config.warnings = static_cast<WarnAbout::What>(config.warnings | WarnAbout::UnmatchedTestSpec);
|
||||
return ParserResult::ok( ParseResultType::Matched );
|
||||
} else if ( warning == "InfiniteGenerators" ) {
|
||||
config.warnings = static_cast<WarnAbout::What>(config.warnings | WarnAbout::InfiniteGenerator);
|
||||
return ParserResult::ok( ParseResultType::Matched );
|
||||
}
|
||||
|
||||
return ParserResult ::runtimeError(
|
||||
@@ -189,6 +192,19 @@ namespace Catch {
|
||||
config.shardCount = *parsedCount;
|
||||
return ParserResult::ok( ParseResultType::Matched );
|
||||
};
|
||||
auto const setBenchmarkSamples = [&]( std::string const& samples ) {
|
||||
auto parsedSamples = parseUInt( samples );
|
||||
if ( !parsedSamples ) {
|
||||
return ParserResult::runtimeError(
|
||||
"Could not parse '" + samples + "' as benchmark samples" );
|
||||
}
|
||||
if ( *parsedSamples == 0 ) {
|
||||
return ParserResult::runtimeError(
|
||||
"Benchmark samples must be greater than 0" );
|
||||
}
|
||||
config.benchmarkSamples = *parsedSamples;
|
||||
return ParserResult::ok( ParseResultType::Matched );
|
||||
};
|
||||
|
||||
auto const setShardIndex = [&](std::string const& shardIndex) {
|
||||
auto parsedIndex = parseUInt( shardIndex );
|
||||
@@ -200,6 +216,43 @@ namespace Catch {
|
||||
return ParserResult::ok( ParseResultType::Matched );
|
||||
};
|
||||
|
||||
auto const setSectionFilter = [&]( std::string const& sectionFilter ) {
|
||||
config.pathFilters.emplace_back( PathFilter::For::Section, trim(sectionFilter) );
|
||||
return ParserResult::ok( ParseResultType::Matched );
|
||||
};
|
||||
auto const setGeneratorFilter = [&]( std::string const& generatorFilter ) {
|
||||
if (generatorFilter != "*") {
|
||||
// TODO: avoid re-parsing the index?
|
||||
auto parsedIndex = parseUInt( generatorFilter );
|
||||
if ( !parsedIndex ) {
|
||||
return ParserResult::runtimeError( "Could not parse '" +
|
||||
generatorFilter +
|
||||
"' as generator index" );
|
||||
}
|
||||
}
|
||||
config.useNewPathFilteringBehaviour = true;
|
||||
config.pathFilters.emplace_back( PathFilter::For::Generator, trim(generatorFilter) );
|
||||
return ParserResult::ok( ParseResultType::Matched );
|
||||
};
|
||||
// Copy-capturing other `setFoo` functions enables calling them later,
|
||||
// as the config ref remains valid, but the local lambda vars won't.
|
||||
auto const setPathFilter = [=, &config]( std::string const& pathFilter ) {
|
||||
config.useNewPathFilteringBehaviour = true;
|
||||
if ( pathFilter.size() < 3 ) {
|
||||
return ParserResult::runtimeError(
|
||||
"Path filter '" + pathFilter + "' is too short" );
|
||||
}
|
||||
if ( startsWith( pathFilter, "g:" ) ) {
|
||||
return setGeneratorFilter( pathFilter.substr( 2 ) );
|
||||
}
|
||||
if ( startsWith( pathFilter, "c:" ) ) {
|
||||
return setSectionFilter( pathFilter.substr( 2 ) );
|
||||
}
|
||||
return ParserResult::runtimeError( "Path filter '" + pathFilter +
|
||||
"' has unknown type prefix" );
|
||||
};
|
||||
|
||||
|
||||
auto cli
|
||||
= ExeName( config.processName )
|
||||
| Help( config.showHelp )
|
||||
@@ -245,9 +298,15 @@ namespace Catch {
|
||||
| Opt( config.filenamesAsTags )
|
||||
["-#"]["--filenames-as-tags"]
|
||||
( "adds a tag for the filename" )
|
||||
| Opt( config.sectionsToRun, "section name" )
|
||||
| Opt( accept_many, setSectionFilter, "section name" )
|
||||
["-c"]["--section"]
|
||||
( "specify section to run" )
|
||||
| Opt( accept_many, setGeneratorFilter, "index spec" )
|
||||
["-g"]["--generator-index"]
|
||||
( "specify generator elements to try" )
|
||||
| Opt( accept_many, setPathFilter, "path filter spec" )
|
||||
["-p"]["--path-filter"]
|
||||
( "qualified path filter" )
|
||||
| Opt( setVerbosity, "quiet|normal|high" )
|
||||
["-v"]["--verbosity"]
|
||||
( "set output verbosity" )
|
||||
@@ -281,7 +340,7 @@ namespace Catch {
|
||||
| Opt( config.skipBenchmarks)
|
||||
["--skip-benchmarks"]
|
||||
( "disable running benchmarks")
|
||||
| Opt( config.benchmarkSamples, "samples" )
|
||||
| Opt( setBenchmarkSamples, "samples" )
|
||||
["--benchmark-samples"]
|
||||
( "number of samples to collect (default: 100)" )
|
||||
| Opt( config.benchmarkResamples, "resamples" )
|
||||
|
||||
@@ -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\"" )
|
||||
|
||||
@@ -208,7 +221,7 @@
|
||||
|
||||
////////////////////////////////////////////////////////////////////////////////
|
||||
// Visual C++
|
||||
#if defined(_MSC_VER)
|
||||
#if defined(_MSC_VER) && !defined(__clang__)
|
||||
|
||||
// We want to defer to nvcc-specific warning suppression if we are compiled
|
||||
// with nvcc masquerading for MSVC.
|
||||
@@ -219,6 +232,11 @@
|
||||
__pragma( warning( pop ) )
|
||||
# endif
|
||||
|
||||
// Suppress MSVC C++ Core Guidelines checker warning 26426:
|
||||
// "Global initializer calls a non-constexpr function (i.22)"
|
||||
# define CATCH_INTERNAL_SUPPRESS_GLOBALS_WARNINGS \
|
||||
__pragma( warning( disable : 26426 ) )
|
||||
|
||||
// Universal Windows platform does not support SEH
|
||||
# if !defined(CATCH_PLATFORM_WINDOWS_UWP)
|
||||
# define CATCH_INTERNAL_CONFIG_WINDOWS_SEH
|
||||
@@ -424,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 {
|
||||
|
||||
@@ -11,9 +11,9 @@
|
||||
#include <catch2/catch_user_config.hpp>
|
||||
|
||||
#if !defined( CATCH_CONFIG_NO_DEPRECATION_ANNOTATIONS )
|
||||
# define DEPRECATED( msg ) [[deprecated( msg )]]
|
||||
# define CATCH_DEPRECATED( msg ) [[deprecated( msg )]]
|
||||
#else
|
||||
# define DEPRECATED( msg )
|
||||
# define CATCH_DEPRECATED( msg )
|
||||
#endif
|
||||
|
||||
#endif // CATCH_DEPRECATION_MACRO_HPP_INCLUDED
|
||||
|
||||
+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
|
||||
|
||||
@@ -29,11 +29,11 @@ namespace Catch {
|
||||
// The "ID" of the message, used to know when to remove it from reporter context.
|
||||
unsigned int sequence;
|
||||
|
||||
DEPRECATED( "Explicitly use the 'sequence' member instead" )
|
||||
CATCH_DEPRECATED( "Explicitly use the 'sequence' member instead" )
|
||||
bool operator == (MessageInfo const& other) const {
|
||||
return sequence == other.sequence;
|
||||
}
|
||||
DEPRECATED( "Explicitly use the 'sequence' member instead" )
|
||||
CATCH_DEPRECATED( "Explicitly use the 'sequence' member instead" )
|
||||
bool operator < (MessageInfo const& other) const {
|
||||
return sequence < other.sequence;
|
||||
}
|
||||
|
||||
@@ -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;
|
||||
|
||||
@@ -0,0 +1,33 @@
|
||||
|
||||
// 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_PATH_FILTER_HPP_INCLUDED
|
||||
#define CATCH_PATH_FILTER_HPP_INCLUDED
|
||||
|
||||
#include <catch2/internal/catch_move_and_forward.hpp>
|
||||
|
||||
#include <string>
|
||||
|
||||
namespace Catch {
|
||||
|
||||
struct PathFilter {
|
||||
enum class For {
|
||||
Section,
|
||||
Generator,
|
||||
};
|
||||
PathFilter( For type_, std::string filter_ ):
|
||||
type( type_ ), filter( CATCH_MOVE( filter_ ) ) {}
|
||||
|
||||
For type;
|
||||
std::string filter;
|
||||
|
||||
friend bool operator==( PathFilter const& lhs, PathFilter const& rhs );
|
||||
};
|
||||
|
||||
} // end namespace Catch
|
||||
|
||||
#endif // CATCH_PATH_FILTER_HPP_INCLUDED
|
||||
@@ -8,8 +8,10 @@
|
||||
#include <catch2/internal/catch_run_context.hpp>
|
||||
|
||||
#include <catch2/catch_user_config.hpp>
|
||||
#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>
|
||||
@@ -19,9 +21,12 @@
|
||||
#include <catch2/catch_timer.hpp>
|
||||
#include <catch2/internal/catch_output_redirect.hpp>
|
||||
#include <catch2/internal/catch_assertion_handler.hpp>
|
||||
#include <catch2/internal/catch_path_filter.hpp>
|
||||
#include <catch2/internal/catch_test_failure_exception.hpp>
|
||||
#include <catch2/internal/catch_thread_local.hpp>
|
||||
#include <catch2/internal/catch_unreachable.hpp>
|
||||
#include <catch2/internal/catch_result_type.hpp>
|
||||
#include <catch2/catch_test_macros.hpp>
|
||||
|
||||
#include <cassert>
|
||||
#include <algorithm>
|
||||
@@ -33,12 +38,51 @@ namespace Catch {
|
||||
struct GeneratorTracker final : TestCaseTracking::TrackerBase,
|
||||
IGeneratorTracker {
|
||||
GeneratorBasePtr m_generator;
|
||||
// Filtered generator has moved to specific index due to
|
||||
// a filter, it needs special handling of `countedNext()`
|
||||
bool m_isFiltered = false;
|
||||
|
||||
GeneratorTracker(
|
||||
TestCaseTracking::NameAndLocation&& nameAndLocation,
|
||||
TrackerContext& ctx,
|
||||
ITracker* parent ):
|
||||
TrackerBase( CATCH_MOVE( nameAndLocation ), ctx, parent ) {}
|
||||
ITracker* parent,
|
||||
GeneratorBasePtr&& generator ):
|
||||
TrackerBase( CATCH_MOVE( nameAndLocation ), ctx, parent ),
|
||||
m_generator( CATCH_MOVE( generator ) ) {
|
||||
assert( m_generator &&
|
||||
"Cannot create tracker without generator" );
|
||||
|
||||
// Handle potential filter and move forward here...
|
||||
// Old style filters do not affect generators at all
|
||||
if (m_newStyleFilters && m_allTrackerDepth < m_filterRef->size()) {
|
||||
auto const& filter =
|
||||
( *m_filterRef )[m_allTrackerDepth];
|
||||
// Generator cannot be un-entered the way a section
|
||||
// can be, so the tracker has to throw for a wrong
|
||||
// filter to stop the execution flow.
|
||||
if (filter.type == PathFilter::For::Section) {
|
||||
// We want the semantics of `SKIP()`, but we inline it
|
||||
// to avoid issues with conditionally prefixed macros
|
||||
INTERNAL_CATCH_MSG(
|
||||
"SKIP",
|
||||
Catch::ResultWas::ExplicitSkip,
|
||||
Catch::ResultDisposition::Normal,
|
||||
"" );
|
||||
Catch::Detail::Unreachable();
|
||||
}
|
||||
// '*' is the wildcard for "all elements in generator"
|
||||
// used for filtering sections below the generator, but
|
||||
// not the generator itself.
|
||||
if ( filter.filter != "*" ) {
|
||||
m_isFiltered = true;
|
||||
// TBD: We assume that the filter was validated as
|
||||
// number during parsing. We should pass it
|
||||
// as number from the CLI parser.
|
||||
size_t targetIndex = std::stoul( filter.filter );
|
||||
m_generator->skipToNthElement( targetIndex );
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
static GeneratorTracker*
|
||||
acquire( TrackerContext& ctx,
|
||||
@@ -82,9 +126,6 @@ namespace Catch {
|
||||
|
||||
// TrackerBase interface
|
||||
bool isGeneratorTracker() const override { return true; }
|
||||
auto hasGenerator() const -> bool override {
|
||||
return !!m_generator;
|
||||
}
|
||||
void close() override {
|
||||
TrackerBase::close();
|
||||
// If a generator has a child (it is followed by a section)
|
||||
@@ -113,25 +154,24 @@ namespace Catch {
|
||||
// _can_ start, and thus we should wait for them, or
|
||||
// they cannot start (due to filters), and we shouldn't
|
||||
// wait for them
|
||||
ITracker* parent = m_parent;
|
||||
// This is safe: there is always at least one section
|
||||
// tracker in a test case tracking tree
|
||||
while ( !parent->isSectionTracker() ) {
|
||||
parent = parent->parent();
|
||||
|
||||
// No filters left -> no restrictions on running sections
|
||||
size_t childDepth = 1 + (m_newStyleFilters ? m_allTrackerDepth : m_sectionOnlyDepth);
|
||||
if ( childDepth >= m_filterRef->size() ) {
|
||||
return true;
|
||||
}
|
||||
assert( parent &&
|
||||
"Missing root (test case) level section" );
|
||||
|
||||
auto const& parentSection =
|
||||
static_cast<SectionTracker const&>( *parent );
|
||||
auto const& filters = parentSection.getFilters();
|
||||
// No filters -> no restrictions on running sections
|
||||
if ( filters.empty() ) { return true; }
|
||||
|
||||
// If we are using the new style filters, we need to check
|
||||
// whether the successive filter is for section or a generator.
|
||||
if ( m_newStyleFilters
|
||||
&& (*m_filterRef)[childDepth].type != PathFilter::For::Section ) {
|
||||
return false;
|
||||
}
|
||||
// Look for any child section that could match the remaining filters
|
||||
for ( auto const& child : m_children ) {
|
||||
if ( child->isSectionTracker() &&
|
||||
static_cast<SectionTracker const&>( *child )
|
||||
.trimmedName() == filters[0] ) {
|
||||
.trimmedName() == StringRef((*m_filterRef)[childDepth].filter) ) {
|
||||
return true;
|
||||
}
|
||||
}
|
||||
@@ -143,9 +183,10 @@ namespace Catch {
|
||||
// value, but we do not want to invoke the side-effect if
|
||||
// this generator is still waiting for any child to start.
|
||||
assert( m_generator && "Tracker without generator" );
|
||||
if ( should_wait_for_child ||
|
||||
( m_runState == CompletedSuccessfully &&
|
||||
m_generator->countedNext() ) ) {
|
||||
if ( should_wait_for_child
|
||||
|| ( m_runState == CompletedSuccessfully
|
||||
&& !m_isFiltered // filtered generators cannot meaningfully move forward, as they would get past the filter
|
||||
&& m_generator->countedNext() ) ) {
|
||||
m_children.clear();
|
||||
m_runState = Executing;
|
||||
}
|
||||
@@ -155,9 +196,8 @@ namespace Catch {
|
||||
auto getGenerator() const -> GeneratorBasePtr const& override {
|
||||
return m_generator;
|
||||
}
|
||||
void setGenerator( GeneratorBasePtr&& generator ) override {
|
||||
m_generator = CATCH_MOVE( generator );
|
||||
}
|
||||
|
||||
bool isFilteredImpl() const override { return m_isFiltered; }
|
||||
};
|
||||
} // namespace
|
||||
}
|
||||
@@ -211,15 +251,18 @@ namespace Catch {
|
||||
// we only keep around the raw msg ids.
|
||||
~MessageHolder() = default;
|
||||
|
||||
|
||||
void addUnscopedMessage(MessageBuilder&& builder) {
|
||||
void addUnscopedMessage( MessageInfo&& info ) {
|
||||
repairUnscopedMessageInvariant();
|
||||
MessageInfo info( CATCH_MOVE( builder.m_info ) );
|
||||
info.message = builder.m_stream.str();
|
||||
unscoped_ids.push_back( info.sequence );
|
||||
messages.push_back( CATCH_MOVE( info ) );
|
||||
}
|
||||
|
||||
void addUnscopedMessage(MessageBuilder&& builder) {
|
||||
MessageInfo info( CATCH_MOVE( builder.m_info ) );
|
||||
info.message = builder.m_stream.str();
|
||||
addUnscopedMessage( CATCH_MOVE( info ) );
|
||||
}
|
||||
|
||||
void addScopedMessage(MessageInfo&& info) {
|
||||
messages.push_back( CATCH_MOVE( info ) );
|
||||
}
|
||||
@@ -268,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)
|
||||
@@ -308,7 +370,8 @@ namespace Catch {
|
||||
|
||||
ITracker& rootTracker = m_trackerContext.startRun();
|
||||
assert(rootTracker.isSectionTracker());
|
||||
static_cast<SectionTracker&>(rootTracker).addInitialFilters(m_config->getSectionsToRun());
|
||||
rootTracker.setFilters( &m_config->getPathFilters(),
|
||||
m_config->useNewFilterBehaviour() );
|
||||
|
||||
// We intentionally only seed the internal RNG once per test case,
|
||||
// before it is first invoked. The reason for that is a complex
|
||||
@@ -400,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;
|
||||
}
|
||||
|
||||
@@ -486,14 +548,30 @@ namespace Catch {
|
||||
currentTracker.nameAndLocation() != nameAndLoc &&
|
||||
"Trying to create tracker for a generator that already has one" );
|
||||
|
||||
auto newTracker = Catch::Detail::make_unique<Generators::GeneratorTracker>(
|
||||
CATCH_MOVE(nameAndLoc), m_trackerContext, ¤tTracker );
|
||||
auto ret = newTracker.get();
|
||||
auto newTracker =
|
||||
Catch::Detail::make_unique<Generators::GeneratorTracker>(
|
||||
CATCH_MOVE( nameAndLoc ),
|
||||
m_trackerContext,
|
||||
¤tTracker,
|
||||
CATCH_MOVE( generator ) );
|
||||
|
||||
// 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->setGenerator( CATCH_MOVE( generator ) );
|
||||
ret->open();
|
||||
return ret;
|
||||
returnPtr->open();
|
||||
return returnPtr;
|
||||
}
|
||||
|
||||
bool RunContext::testForMissingAssertions(Counts& assertions) {
|
||||
@@ -575,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() {
|
||||
@@ -649,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;
|
||||
@@ -831,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;
|
||||
}
|
||||
@@ -882,18 +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 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;
|
||||
|
||||
Some files were not shown because too many files have changed in this diff Show More
Reference in New Issue
Block a user