From 164a66da172494458f39a240cbc1cf191c656c8d Mon Sep 17 00:00:00 2001 From: Elwardi Date: Sun, 12 Oct 2025 16:16:17 +0200 Subject: [PATCH 1/5] feat: switch to bashly for more maintainable scripting --- Alltest | 237 +-------- foamut | 916 ++++++++++++++++++++++++++++++++ src/bashly.yml | 77 +++ src/initialize.sh | 15 + src/lib/cleanup.sh | 18 + src/lib/compile_catch2.sh | 45 ++ src/lib/compile_test_drivers.sh | 35 ++ src/lib/create_dummy_libs.sh | 16 + src/lib/run_parallel_tests.sh | 138 +++++ src/lib/run_serial_tests.sh | 107 ++++ src/lib/setup_test_symlinks.sh | 16 + src/root_command.sh | 162 ++++++ 12 files changed, 1546 insertions(+), 236 deletions(-) mode change 100755 => 120000 Alltest create mode 100755 foamut create mode 100644 src/bashly.yml create mode 100644 src/initialize.sh create mode 100644 src/lib/cleanup.sh create mode 100644 src/lib/compile_catch2.sh create mode 100644 src/lib/compile_test_drivers.sh create mode 100644 src/lib/create_dummy_libs.sh create mode 100644 src/lib/run_parallel_tests.sh create mode 100644 src/lib/run_serial_tests.sh create mode 100644 src/lib/setup_test_symlinks.sh create mode 100644 src/root_command.sh diff --git a/Alltest b/Alltest deleted file mode 100755 index c7d1238..0000000 --- a/Alltest +++ /dev/null @@ -1,236 +0,0 @@ -#!/usr/bin/bash - -# ShellChecked on every push - Always ShellCheck your scripts! - -# First some configs -# Assumes it's running from the root dir. of the repository -if [ -z "${FOAM_LIBBIN}" ]; then - echo "Please source an OpenFOAM version. Aborting..." - echo "USAGE: ./Alltest [--no-serial|--no-parallel] [--report] [--html]" - exit 1 -fi - -# If tests folder is supplied through an env. var. -# do the symlinking -if [ -n "${FOAM_FOAMUT_TESTS}" ]; then - find "${FOAM_FOAMUT_TESTS}" -type d -name "Make" | while read -r make_dir; do - parent_dir=$(basename "$(dirname "$make_dir")") - target_dir="$PWD/tests/$parent_dir" - ln -f -s "$(dirname "$make_dir")" "$target_dir" - echo "picked up $parent_dir tests" - done -fi - -doSerial=true -doParallel=true -doReport=false -for i in "$@"; do - case $i in - --no-serial) - doSerial=false - ;; - --no-parallel) - doParallel=false - ;; - --report) - doReport=true - ;; - *) - ;; - esac -done - -set -e -root=$PWD -export FOAM_FOAMUT="$root" -foamV=$(echo "${WM_PROJECT}$(test ! -z "${WM_FORK}" && echo '-'"${WM_FORK}")-${WM_PROJECT_VERSION}") -args_array=("$@") - -# If there is no lib${libName}.so, create a dummy one -dummyLibChecks="Pstream dynamicFvMesh" -for dummy in $dummyLibChecks; do - if [ ! -f "${FOAM_LIBBIN}/lib${dummy}.so" ] && - [ ! -f "${FOAM_USER_LIBBIN}/lib${dummy}.so" ] && - [ ! -f "${FOAM_LIBBIN}/${FOAM_MPI}/lib${dummy}.so" ]; then - echo '!' > "${FOAM_USER_LIBBIN}/lib${dummy}.so" - fi -done - -# libraries to test -libs=$(find -L tests/ -maxdepth 1 -mindepth 1 -type d) -libs_array=($libs) -# where to run OpenFOAM cases -caseRun=/tmp/foamUtCases -mkdir -p "$caseRun" -# a timeout to prevent hanging processes (be CI friendly) -timeOut="${CATCH_TIMEOUT:-60}" - -serialTests() { - local fargs=("$@") - local params=() - for ((i = 4; i <= $#; i++)); do - arg="${fargs[i]}" - case "$arg" in - --no-parallel|--no-serial|--report) - ;; - *) - params+=("$arg") - ;; - esac - done - for ((i = 0; i < ${#libs_array[@]}; i++)); do - lib="${libs_array[i]}" - printf "\n\nRunning tests for lib %s, on %s case in serial...\n" "$lib" "${2}" >&2 - libName=$(basename "$lib") - report="" - case $doReport in - (true) report="-s -r json::out=${root}/reports/${libName}_${2}_serial.json -d yes" - echo "Report: ${root}/reports/${libName}_${2}_serial.json" >&2 - ;; - esac - timeout "$timeOut" "$root"/"$lib"/testDriver --allow-running-no-tests \ - -n "$(basename "$lib")" $report "[serial][${2}]" "${params[@]}" \ - --- -case "${3}" - locErr=$? - err=$((err+locErr)) - # separator in case json reporter is used - for element in "${params[@]}"; do - if [ "$element" == "json" ]; then - if ((i != ${#libs_array[@]})); then - printf "," - fi - fi - done - done -} - -parallelTests(){ - if ! test -f "${3}/system/decomposeParDict" ; then - rm -rf "${3}/processor*" - echo "decomposeParDict not found, not running parallel tests on this case..." >&2 - return 0 - fi - local fargs=("$@") - local params=() - for ((i = 4; i <= $#; i++)); do - arg="${fargs[i]}" - case "$arg" in - --no-parallel|--no-serial|--report) - ;; - *) - params+=("$arg") - ;; - esac - done - nProcs=$(grep -oP "numberOfSubdomains\s+\K\d+" "${3}"/system/decomposeParDict) - decomposePar -force -case "${3}" > "${3}/log.decomposePar" - err=$((err + $?)) - # run the case in parallel mode - report="" - case $doReport in - (true) report="-s -r json -d yes";; - esac - for ((i = 0; i < ${#libs_array[@]}; i++)); do - lib="${libs_array[i]}" - printf "\n\nRunning tests for lib %s, on %s case in parallel...\n" "$lib" "${2}" >&2 - libName=$(basename "$lib") - logdir="reports/${libName}_${2}_parallel" - logparams="" - direct="" - case $doReport in - (true) logparams="-output-filename $logdir" - direct="> /dev/null" - echo "Parallel Reports (${nProcs}): ${logdir}" >&2 - esac - timeout "$timeOut" mpirun $logparams -np "$nProcs" \ - "$root"/"$lib"/testDriver \ - --allow-running-no-tests \ - -n "$(basename "$lib")" $report \ - --rng-seed time "[parallel][${2}]" ${params[@]} --- -parallel -case "${3}" $direct - set +x - # separator in case json reporter is used - for element in "${params[@]}"; do - if [ "$element" == "json" ]; then - if ((i != ${#libs_array[@]} - 1)); then - printf "," - fi - fi - done - err=$((err + lerr)) - done -} - -# Compile catch -echo "Compiling Catch2 v3..." >&2 -if [ ! -f "$FOAM_USER_LIBBIN/catch2/lib/libCatch2.a" ]; then - cd catch2 - mkdir -p build - cd build - cmake -DCMAKE_INSTALL_PREFIX="$FOAM_USER_LIBBIN"/catch2 .. > log.cmake 2>&1 - make -j"$(nproc)" > log.makeBuild 2>&1 - make install > log.makeInstall 2>&1 - cd - > /dev/null - rm -rf build - cd .. -fi - -# 1st: compile tests -for lib in $libs; do - cd "$lib" - echo "Compiling $lib test driver..." >&2 - wmakeLnInclude . # Just in case the tested libs are header only - if ! wmake > log.wmake 2>&1; then - echo "Errors compiling $lib test driver, see $PWD/log.wmake for more information" - exit 1 - else - echo "Test driver for $lib has been compiled." >&2 - fi - cd - > /dev/null -done - -# go back to the root of the repository -cd "$root" - -# get setup to run tests -mapfile -t cases_array < <(find -L cases/ -maxdepth 1 -mindepth 1 -type d) -if [ ${#cases_array[@]} -eq 0 ]; then - echo "No cases found to run tests" - exit 1 -fi -mkdir -p "$root"/reports - -# run the tests, but do not exit until attempting everything -set +e -err=0 -for ((k = 0; k < ${#cases_array[@]}; k++)); do - ofCase="${cases_array[k]}" - caseName=$(basename "$ofCase") - casePath=$caseRun/$caseName - cp -rL "$ofCase" "$casePath" - case $doSerial in - (true) serialTests "$err" "$caseName" "$casePath" "$libs" "$@";; - esac - case $doParallel in - (true) parallelTests "$err" "$caseName" "$casePath" "$libs" "$@";; - esac - # separator in case json reporter is used - for element in "${args_array[@]}"; do - if [ "$element" == "json" ]; then - if ((k != ${#cases_array[@]} - 1)); then - printf "," - fi - fi - done - rm -rf "$casePath" -done - -# Cleanup -for dummy in $dummyLibChecks; do - if [ -f "${FOAM_USER_LIBBIN}/lib${dummy}.so" ] && - tr '\n' ' ' < "${FOAM_USER_LIBBIN}/lib${dummy}.so" | grep -q '^! $'; then - rm -rf "${FOAM_USER_LIBBIN}/lib${dummy}.so" - fi -done -rm -rf "$caseRun" - -exit $err diff --git a/Alltest b/Alltest new file mode 120000 index 0000000..9434592 --- /dev/null +++ b/Alltest @@ -0,0 +1 @@ +foamut \ No newline at end of file diff --git a/foamut b/foamut new file mode 100755 index 0000000..4da3312 --- /dev/null +++ b/foamut @@ -0,0 +1,916 @@ +#!/usr/bin/env bash +# This script was generated by bashly 1.2.10 (https://bashly.dev) +# Modifying it manually is not recommended + +# :wrapper.bash3_bouncer +if [[ "${BASH_VERSINFO:-0}" -lt 4 ]]; then + printf "bash version 4 or higher is required\n" >&2 + exit 1 +fi + +# :command.master_script +# :command.root_command +root_command() { + # src/root_command.sh + # Main command logic for foamUT + # Orchestrates the entire test execution pipeline + + # ShellChecked on every push - Always ShellCheck your scripts! + + # Validate custom test driver if provided + if [ -n "${args[--test-driver]}" ]; then + driver_path="${args[--test-driver]}" + if [ ! -f "$driver_path" ]; then + echo "Error: Test driver file not found: $driver_path" >&2 + exit 1 + fi + if [[ ! "$driver_path" =~ \.(C|cpp)$ ]]; then + echo "Error: Test driver must be a .C or .cpp source file" >&2 + exit 1 + fi + fi + + # Validate --force-timeout is only used with --test-prefix + if [ "${args[--force-timeout]}" = "1" ] && [ -z "${args[--test-prefix]}" ]; then + echo "Warning: --force-timeout has no effect without --test-prefix" >&2 + fi + + # Validate --standalone is not used with --parallel + if [ "${args[--standalone]}" = "1" ] && [ "${args[--parallel]}" = "1" ]; then + echo "Error: --standalone is not compatible with --parallel" >&2 + echo "Standalone mode only runs serial tests" >&2 + exit 1 + fi + + # Load helper functions + source "$root/src/lib/setup_test_symlinks.sh" + source "$root/src/lib/create_dummy_libs.sh" + source "$root/src/lib/compile_catch2.sh" + source "$root/src/lib/compile_test_drivers.sh" + source "$root/src/lib/run_serial_tests.sh" + source "$root/src/lib/run_parallel_tests.sh" + source "$root/src/lib/cleanup.sh" + + # Parse flags from bashly (bashly uses "1" for true flags) + doSerial=true # Default: serial only + doParallel=false + doReport=false + standalone_mode=false + + # If --parallel flag is set, run parallel only (disable serial) + if [ "${args[--parallel]}" = "1" ]; then + doSerial=false + doParallel=true + fi + + if [ "${args[--report]}" = "1" ]; then + doReport=true + fi + + if [ "${args[--standalone]}" = "1" ]; then + standalone_mode=true + fi + + set -e + + # Get OpenFOAM version string + foamV=$(echo "${WM_PROJECT}$(test ! -z "${WM_FORK}" && echo '-'"${WM_FORK}")-${WM_PROJECT_VERSION}") + + # Collect catch2 arguments from catch_all + args_array=() + if [ -n "${other_args[*]}" ]; then + args_array=("${other_args[@]}") + fi + + # Setup test symlinks if FOAM_FOAMUT_TESTS is set + setup_test_symlinks + + # Create dummy libraries if needed + create_dummy_libs + + # libraries to test + libs=$(find -L tests/ -maxdepth 1 -mindepth 1 -type d) + libs_array=($libs) + + # where to run OpenFOAM cases + caseRun=/tmp/foamUtCases + mkdir -p "$caseRun" + + # a timeout to prevent hanging processes (be CI friendly) + timeOut="${CATCH_TIMEOUT:-60}" + + # Compile Catch2 + compile_catch2 + + # Compile test drivers + compile_test_drivers + + # Go back to the root of the repository + cd "$root" + + # Get setup to run tests + mapfile -t cases_array < <(find -L cases/ -maxdepth 1 -mindepth 1 -type d) + + # Build list of case names for tag filtering + case_names=() + for case_dir in "${cases_array[@]}"; do + case_names+=("$(basename "$case_dir")") + done + + if [ "$standalone_mode" = "true" ]; then + echo "Running in standalone mode (no OpenFOAM cases)..." >&2 + echo "Will exclude tests tagged with: ${case_names[*]}" >&2 + # Create a dummy case array with one entry for the loop structure + cases_array=("standalone") + else + if [ ${#cases_array[@]} -eq 0 ]; then + echo "No cases found to run tests" + exit 1 + fi + fi + + mkdir -p "$root"/reports + + # Run the tests, but do not exit until attempting everything + set +e + err=0 + + for ((k = 0; k < ${#cases_array[@]}; k++)); do + if [ "$standalone_mode" = "true" ]; then + caseName="standalone" + casePath="" + echo "Running standalone tests (no case)..." >&2 + else + ofCase="${cases_array[k]}" + caseName=$(basename "$ofCase") + casePath=$caseRun/$caseName + cp -rL "$ofCase" "$casePath" + fi + + case $doSerial in + (true) run_serial_tests "$err" "$caseName" "$casePath" "$libs" "${args_array[@]}";; + esac + + case $doParallel in + (true) run_parallel_tests "$err" "$caseName" "$casePath" "$libs" "${args_array[@]}";; + esac + + # separator in case json reporter is used + for element in "${args_array[@]}"; do + if [ "$element" == "json" ]; then + if ((k != ${#cases_array[@]} - 1)); then + printf "," + fi + fi + done + + # Cleanup case directory if not in standalone mode + if [ "$standalone_mode" != "true" ]; then + rm -rf "$casePath" + fi + done + + # Cleanup + cleanup + + exit $err + +} + +# :command.version_command +version_command() { + echo "$version" +} + +# :command.usage +foamut_usage() { + printf "foamut - Unit/integration testing framework for OpenFOAM\n\n" + + printf "%s\n" "Usage:" + printf " foamut [OPTIONS] [--] [CATCH2_ARGS...]\n" + printf " foamut --help | -h\n" + printf " foamut --version | -v\n" + echo + + # :command.long_usage + if [[ -n "$long_usage" ]]; then + printf "%s\n" "Options:" + + # :command.usage_flags + # :flag.usage + printf " %s\n" "--parallel" + printf " Run parallel tests only (default is serial only, incompatible with\n --standalone)\n" + echo + + # :flag.usage + printf " %s\n" "--report" + printf " Generate JSON test reports in reports/ directory\n" + echo + + # :flag.usage + printf " %s\n" "--test-driver SOURCE_PATH" + printf " Path to custom testDriver.C source file (overrides default)\n" + echo + + # :flag.usage + printf " %s\n" "--standalone" + printf " Run tests without OpenFOAM cases (tests not tagged by case names, serial or\n mode-agnostic)\n" + echo + + # :flag.usage + printf " %s\n" "--test-prefix COMMAND" + printf " Prefix command for test execution (automatically disables timeout)\n Serial: testDriver ...\n Parallel: mpirun -np N testDriver ...\n Examples: \"gdb --args\", \"valgrind\", \"hpctoolkit\"\n" + echo + + # :flag.usage + printf " %s\n" "--force-timeout" + printf " Keep timeout wrapper even when using --test-prefix\n" + echo + + # :command.usage_fixed_flags + printf " %s\n" "--help, -h" + printf " Show this help\n" + echo + printf " %s\n" "--version, -v" + printf " Show version number\n" + echo + + # :command.usage_args + printf "%s\n" "Arguments:" + + echo " CATCH2_ARGS..." + printf " Arguments passed to Catch2 test framework.\n \n Common Catch2 Options:\n --list-tests List all tests\n --list-tags List all tags\n -h, --help Show Catch2 help\n -s, --success Show successful tests\n -b, --break Break into debugger on failure\n -d, --durations Show test durations\n -#, --filenames-as-tags Add filename as tag\n -r, --reporter Use specific reporter (console, xml,\n junit, etc.)\n --order Test execution order\n \n Tag Filters:\n \"[tag1]\" Run tests with tag1\n \"[tag1][tag2]\" Run tests with both tags\n \"[tag1],[tag2]\" Run tests with either tag\n \"~[tag1]\" Exclude tests with tag1\n \n Note: testDriver uses --- to separate Catch2 from OpenFOAM args internally\n" + echo + + # :command.usage_environment_variables + printf "%s\n" "Environment Variables:" + + # :environment_variable.usage + printf " %s\n" "FOAM_LIBBIN" + printf " OpenFOAM library directory (required, set by sourcing OpenFOAM)\n" + echo + + # :environment_variable.usage + printf " %s\n" "FOAM_FOAMUT_TESTS" + printf " Path to external tests directory (will be symlinked under tests/)\n" + echo + + # :environment_variable.usage + printf " %s\n" "CATCH_TIMEOUT" + printf " Test timeout in seconds (default 60)\n" + echo + + # :environment_variable.usage + printf " %s\n" "FOAM_FOAMUT" + printf " Path to foamUT repository root (set automatically)\n" + echo + + # :command.usage_examples + printf "%s\n" "Examples:" + printf " foamut --help\n" + printf " foamut\n" + printf " foamut --parallel\n" + printf " foamut --parallel --report\n" + printf " foamut --standalone\n" + printf " foamut --list-tests\n" + printf " foamut \"[serial]\"\n" + printf " foamut -s\n" + printf " foamut --test-prefix \"gdb --args\"\n" + printf " foamut --test-driver /path/to/custom/testDriver.C\n" + echo + + fi +} + +# :command.normalize_input +# :command.normalize_input_function +normalize_input() { + local arg passthru flags + passthru=false + + while [[ $# -gt 0 ]]; do + arg="$1" + if [[ $passthru == true ]]; then + input+=("$arg") + elif [[ $arg =~ ^(--[a-zA-Z0-9_\-]+)=(.+)$ ]]; then + input+=("${BASH_REMATCH[1]}") + input+=("${BASH_REMATCH[2]}") + elif [[ $arg =~ ^(-[a-zA-Z0-9])=(.+)$ ]]; then + input+=("${BASH_REMATCH[1]}") + input+=("${BASH_REMATCH[2]}") + elif [[ $arg =~ ^-([a-zA-Z0-9][a-zA-Z0-9]+)$ ]]; then + flags="${BASH_REMATCH[1]}" + for ((i = 0; i < ${#flags}; i++)); do + input+=("-${flags:i:1}") + done + elif [[ "$arg" == "--" ]]; then + passthru=true + input+=("$arg") + else + input+=("$arg") + fi + + shift + done +} + +# :command.inspect_args +inspect_args() { + if ((${#args[@]})); then + readarray -t sorted_keys < <(printf '%s\n' "${!args[@]}" | sort) + echo args: + for k in "${sorted_keys[@]}"; do + echo "- \${args[$k]} = ${args[$k]}" + done + else + echo args: none + fi + + if ((${#other_args[@]})); then + echo + echo other_args: + echo "- \${other_args[*]} = ${other_args[*]}" + for i in "${!other_args[@]}"; do + echo "- \${other_args[$i]} = ${other_args[$i]}" + done + fi + + if ((${#deps[@]})); then + readarray -t sorted_keys < <(printf '%s\n' "${!deps[@]}" | sort) + echo + echo deps: + for k in "${sorted_keys[@]}"; do + echo "- \${deps[$k]} = ${deps[$k]}" + done + fi + + if ((${#env_var_names[@]})); then + readarray -t sorted_names < <(printf '%s\n' "${env_var_names[@]}" | sort) + echo + echo "environment variables:" + for k in "${sorted_names[@]}"; do + echo "- \$$k = ${!k:-}" + done + fi +} + +# :command.user_lib +# src/lib/cleanup.sh +#!/usr/bin/bash + +# Cleanup dummy libraries and temporary directories + +cleanup() { + local dummyLibChecks="Pstream dynamicFvMesh" + + # Cleanup dummy libraries + for dummy in $dummyLibChecks; do + if [ -f "${FOAM_USER_LIBBIN}/lib${dummy}.so" ] && + tr '\n' ' ' < "${FOAM_USER_LIBBIN}/lib${dummy}.so" | grep -q '^! $'; then + rm -rf "${FOAM_USER_LIBBIN}/lib${dummy}.so" + fi + done + + # Cleanup temporary case directory + rm -rf "$caseRun" +} + +# src/lib/compile_catch2.sh +#!/usr/bin/bash + +# Compile Catch2 v3 if not already compiled +# Installs to FOAM_USER_LIBBIN/catch2 + +compile_catch2() { + echo "Compiling Catch2 v3..." >&2 + if [ ! -f "$FOAM_USER_LIBBIN/catch2/lib/libCatch2.a" ]; then + cd catch2 || exit 1 + mkdir -p build + cd build || exit 1 + + # Use pipefail to catch failures in piped commands + set -o pipefail + + if ! cmake -DCMAKE_INSTALL_PREFIX="$FOAM_USER_LIBBIN"/catch2 .. 2>&1 | tee log.cmake; then + set +o pipefail + echo "" >&2 + echo "Error: CMake configuration failed for Catch2" >&2 + echo "Full log saved to: $PWD/log.cmake" >&2 + exit 1 + fi + + if ! make -j"$(nproc)" 2>&1 | tee log.makeBuild; then + set +o pipefail + echo "" >&2 + echo "Error: Failed to build Catch2" >&2 + echo "Full log saved to: $PWD/log.makeBuild" >&2 + exit 1 + fi + + if ! make install 2>&1 | tee log.makeInstall; then + set +o pipefail + echo "" >&2 + echo "Error: Failed to install Catch2" >&2 + echo "Full log saved to: $PWD/log.makeInstall" >&2 + exit 1 + fi + + set +o pipefail + cd - > /dev/null || exit 1 + rm -rf build + cd .. || exit 1 + fi +} + +# src/lib/compile_test_drivers.sh +#!/usr/bin/bash + +# Compile test drivers for all test libraries +# Supports custom test driver via --test-driver flag + +compile_test_drivers() { + local custom_driver="${args[--test-driver]}" + + # Compile tests + for lib in $libs; do + cd "$lib" || exit 1 + + # If custom driver is specified, copy it to this test directory + if [ -n "$custom_driver" ]; then + cp "$custom_driver" "$root/tests/testDriver.C" + echo "Using custom test driver: $custom_driver" >&2 + fi + + echo "Compiling $lib test driver..." >&2 + wmakeLnInclude . # Just in case the tested libs are header only + + # Use set -o pipefail to catch wmake failures even when piped to tee + set -o pipefail + if ! wmake 2>&1 | tee log.wmake; then + set +o pipefail + echo "" >&2 + echo "Error: Failed to compile $lib test driver" >&2 + echo "Full log saved to: $PWD/log.wmake" >&2 + exit 1 + fi + set +o pipefail + echo "Test driver for $lib has been compiled." >&2 + cd - > /dev/null || exit 1 + done +} + +# src/lib/create_dummy_libs.sh +#!/usr/bin/bash + +# Create dummy libraries if they don't exist +# Some OpenFOAM forks/configurations may not have certain libraries + +create_dummy_libs() { + # If there is no lib${libName}.so, create a dummy one + local dummyLibChecks="Pstream dynamicFvMesh" + for dummy in $dummyLibChecks; do + if [ ! -f "${FOAM_LIBBIN}/lib${dummy}.so" ] && + [ ! -f "${FOAM_USER_LIBBIN}/lib${dummy}.so" ] && + [ ! -f "${FOAM_LIBBIN}/${FOAM_MPI}/lib${dummy}.so" ]; then + echo '!' > "${FOAM_USER_LIBBIN}/lib${dummy}.so" + fi + done +} + +# src/lib/run_parallel_tests.sh +#!/usr/bin/bash + +# Run parallel tests for all libraries on a specific case +# Arguments: $1=error_count, $2=case_name, $3=case_path, $4=libs, $@=additional args + +run_parallel_tests() { + if [ "$standalone_mode" = "true" ]; then + echo "Skipping decomposePar for standalone mode..." >&2 + else + if ! test -f "${3}/system/decomposeParDict" ; then + rm -rf "${3}/processor*" + echo "decomposeParDict not found, not running parallel tests on this case..." >&2 + return 0 + fi + fi + + local fargs=("$@") + local params=() + local skip_next=false + + # Filter out foamUT-specific flags from params + for ((i = 4; i <= $#; i++)); do + arg="${fargs[i]}" + + if [ "$skip_next" = true ]; then + skip_next=false + continue + fi + + case "$arg" in + --parallel|--report|--standalone|--force-timeout) + # These flags don't take arguments + ;; + --test-driver|--test-prefix) + # These flags take an argument, skip the next one too + skip_next=true + ;; + *) + params+=("$arg") + ;; + esac + done + + local test_prefix="${args[--test-prefix]}" + local force_timeout="${args[--force-timeout]}" + + # Decompose case if not in standalone mode + if [ "$standalone_mode" != "true" ]; then + local nProcs=$(grep -oP "numberOfSubdomains\s+\K\d+" "${3}"/system/decomposeParDict) + decomposePar -force -case "${3}" > "${3}/log.decomposePar" + err=$((err + $?)) + else + # In standalone mode, default to 2 processors or read from FOAM_FOAMUT_NPROCS + local nProcs="${FOAM_FOAMUT_NPROCS:-2}" + fi + + # run the case in parallel mode + report="" + case $doReport in + (true) report="-s -r json -d yes";; + esac + + for ((i = 0; i < ${#libs_array[@]}; i++)); do + lib="${libs_array[i]}" + printf "\n\nRunning tests for lib %s, on %s case in parallel...\n" "$lib" "${2}" >&2 + libName=$(basename "$lib") + logdir="reports/${libName}_${2}_parallel" + logparams="" + direct="" + case $doReport in + (true) logparams="-output-filename $logdir" + direct="> /dev/null" + echo "Parallel Reports (${nProcs}): ${logdir}" >&2 + esac + + # Execute the test + if [ "$standalone_mode" = "true" ]; then + # Standalone mode: no case argument, exclude all case-tagged tests + # Build exclusion filter: [parallel]~[case1]~[case2]... + local tag_filter="[parallel]" + for case_name in "${case_names[@]}"; do + tag_filter="${tag_filter}~[${case_name}]" + done + + # Build and execute command with or without timeout and prefix + # Prefix comes AFTER mpirun in parallel mode + if [ -z "$test_prefix" ]; then + timeout "$timeOut" mpirun $logparams -np "$nProcs" "$root"/"$lib"/testDriver \ + --allow-running-no-tests \ + -n "$(basename "$lib")" $report \ + --rng-seed time "$tag_filter" ${params[@]} --- -parallel $direct + else + if [ "$force_timeout" = "1" ]; then + timeout "$timeOut" mpirun $logparams -np "$nProcs" $test_prefix "$root"/"$lib"/testDriver \ + --allow-running-no-tests \ + -n "$(basename "$lib")" $report \ + --rng-seed time "$tag_filter" ${params[@]} --- -parallel $direct + else + mpirun $logparams -np "$nProcs" $test_prefix "$root"/"$lib"/testDriver \ + --allow-running-no-tests \ + -n "$(basename "$lib")" $report \ + --rng-seed time "$tag_filter" ${params[@]} --- -parallel $direct + fi + fi + else + # Normal mode: with case argument and -parallel flag + if [ -z "$test_prefix" ]; then + timeout "$timeOut" mpirun $logparams -np "$nProcs" "$root"/"$lib"/testDriver \ + --allow-running-no-tests \ + -n "$(basename "$lib")" $report \ + --rng-seed time "[parallel][${2}]" ${params[@]} --- -parallel -case "${3}" $direct + else + if [ "$force_timeout" = "1" ]; then + timeout "$timeOut" mpirun $logparams -np "$nProcs" $test_prefix "$root"/"$lib"/testDriver \ + --allow-running-no-tests \ + -n "$(basename "$lib")" $report \ + --rng-seed time "[parallel][${2}]" ${params[@]} --- -parallel -case "${3}" $direct + else + mpirun $logparams -np "$nProcs" $test_prefix "$root"/"$lib"/testDriver \ + --allow-running-no-tests \ + -n "$(basename "$lib")" $report \ + --rng-seed time "[parallel][${2}]" ${params[@]} --- -parallel -case "${3}" $direct + fi + fi + fi + + set +x + # separator in case json reporter is used + for element in "${params[@]}"; do + if [ "$element" == "json" ]; then + if ((i != ${#libs_array[@]} - 1)); then + printf "," + fi + fi + done + err=$((err + lerr)) + done +} + +# src/lib/run_serial_tests.sh +#!/usr/bin/bash + +# Run serial tests for all libraries on a specific case +# Arguments: $1=error_count, $2=case_name, $3=case_path, $4=libs, $@=additional args + +run_serial_tests() { + local fargs=("$@") + local params=() + local skip_next=false + + # Filter out foamUT-specific flags from params + for ((i = 4; i <= $#; i++)); do + arg="${fargs[i]}" + + if [ "$skip_next" = true ]; then + skip_next=false + continue + fi + + case "$arg" in + --parallel|--report|--standalone|--force-timeout) + # These flags don't take arguments + ;; + --test-driver|--test-prefix) + # These flags take an argument, skip the next one too + skip_next=true + ;; + *) + params+=("$arg") + ;; + esac + done + + local test_prefix="${args[--test-prefix]}" + local force_timeout="${args[--force-timeout]}" + + for ((i = 0; i < ${#libs_array[@]}; i++)); do + lib="${libs_array[i]}" + printf "\n\nRunning tests for lib %s, on %s case in serial...\n" "$lib" "${2}" >&2 + libName=$(basename "$lib") + report="" + case $doReport in + (true) report="-s -r json::out=${root}/reports/${libName}_${2}_serial.json -d yes" + echo "Report: ${root}/reports/${libName}_${2}_serial.json" >&2 + ;; + esac + + # Execute the test + if [ "$standalone_mode" = "true" ]; then + # Standalone mode: no case argument, exclude all case-tagged tests + # Run tests that are: + # - ([serial] AND not-case-tagged) OR (not-[parallel] AND not-case-tagged) + # This includes: [serial] tests, and tests with neither [serial] nor [parallel] + local tag_filter="[serial]" + for case_name in "${case_names[@]}"; do + tag_filter="${tag_filter}~[${case_name}]" + done + tag_filter="${tag_filter},~[parallel]" + for case_name in "${case_names[@]}"; do + tag_filter="${tag_filter}~[${case_name}]" + done + + # Build and execute command with or without timeout and prefix + if [ -z "$test_prefix" ]; then + timeout "$timeOut" "$root"/"$lib"/testDriver --allow-running-no-tests \ + -n "$(basename "$lib")" $report "$tag_filter" "${params[@]}" + else + if [ "$force_timeout" = "1" ]; then + timeout "$timeOut" $test_prefix "$root"/"$lib"/testDriver --allow-running-no-tests \ + -n "$(basename "$lib")" $report "$tag_filter" "${params[@]}" + else + $test_prefix "$root"/"$lib"/testDriver --allow-running-no-tests \ + -n "$(basename "$lib")" $report "$tag_filter" "${params[@]}" + fi + fi + else + # Normal mode: with case argument + if [ -z "$test_prefix" ]; then + timeout "$timeOut" "$root"/"$lib"/testDriver --allow-running-no-tests \ + -n "$(basename "$lib")" $report "[serial][${2}]" "${params[@]}" \ + --- -case "${3}" + else + if [ "$force_timeout" = "1" ]; then + timeout "$timeOut" $test_prefix "$root"/"$lib"/testDriver --allow-running-no-tests \ + -n "$(basename "$lib")" $report "[serial][${2}]" "${params[@]}" \ + --- -case "${3}" + else + $test_prefix "$root"/"$lib"/testDriver --allow-running-no-tests \ + -n "$(basename "$lib")" $report "[serial][${2}]" "${params[@]}" \ + --- -case "${3}" + fi + fi + fi + + locErr=$? + err=$((err+locErr)) + + # separator in case json reporter is used + for element in "${params[@]}"; do + if [ "$element" == "json" ]; then + if ((i != ${#libs_array[@]})); then + printf "," + fi + fi + done + done +} + +# src/lib/setup_test_symlinks.sh +#!/usr/bin/bash + +# Setup test symlinks if FOAM_FOAMUT_TESTS environment variable is set +# Discovers Make directories and symlinks parent directories under tests/ + +setup_test_symlinks() { + # If tests folder is supplied through an env. var. do the symlinking + if [ -n "${FOAM_FOAMUT_TESTS}" ]; then + find "${FOAM_FOAMUT_TESTS}" -type d -name "Make" | while read -r make_dir; do + parent_dir=$(basename "$(dirname "$make_dir")") + target_dir="$root/tests/$parent_dir" + ln -f -s "$(dirname "$make_dir")" "$target_dir" + echo "picked up $parent_dir tests" + done + fi +} + +# :command.command_functions + +# :command.parse_requirements +parse_requirements() { + # :command.fixed_flags_filter + while [[ $# -gt 0 ]]; do + key="$1" + case "$key" in + --version | -v) + version_command + exit + ;; + + --help | -h) + long_usage=yes + foamut_usage + exit + ;; + + *) + break + ;; + + esac + done + + # :command.environment_variables_filter + + env_var_names+=("FOAM_LIBBIN") + env_var_names+=("FOAM_FOAMUT_TESTS") + env_var_names+=("CATCH_TIMEOUT") + env_var_names+=("FOAM_FOAMUT") + + # :command.command_filter + action="root" + + # :command.parse_requirements_while + while [[ $# -gt 0 ]]; do + key="$1" + case "$key" in + # :flag.case + --parallel) + + # :flag.case_no_arg + args['--parallel']=1 + shift + ;; + + # :flag.case + --report) + + # :flag.case_no_arg + args['--report']=1 + shift + ;; + + # :flag.case + --test-driver) + + # :flag.case_arg + if [[ -n ${2+x} ]]; then + args['--test-driver']="$2" + shift + shift + else + printf "%s\n" "--test-driver requires an argument: --test-driver SOURCE_PATH" >&2 + exit 1 + fi + ;; + + # :flag.case + --standalone) + + # :flag.case_no_arg + args['--standalone']=1 + shift + ;; + + # :flag.case + --test-prefix) + + # :flag.case_arg + if [[ -n ${2+x} ]]; then + args['--test-prefix']="$2" + shift + shift + else + printf "%s\n" "--test-prefix requires an argument: --test-prefix COMMAND" >&2 + exit 1 + fi + ;; + + # :flag.case + --force-timeout) + + # :flag.case_no_arg + args['--force-timeout']=1 + shift + ;; + + --) + shift + other_args+=("$@") + break + ;; + + -?*) + other_args+=("$1") + shift + ;; + + *) + # :command.parse_requirements_case + # :command.parse_requirements_case_catch_all + other_args+=("$1") + shift + + ;; + + esac + done + +} + +# :command.initialize +initialize() { + declare -g version="2.0.0" + set -e + + # src/initialize.sh + #!/usr/bin/bash + + # Bashly initialization hook - runs before command execution + # Validates environment and performs sanity checks + + # Check if OpenFOAM is sourced + if [ -z "${FOAM_LIBBIN}" ]; then + echo "Error: Please source an OpenFOAM version. Aborting..." >&2 + echo "USAGE: ./foamut [OPTIONS] [CATCH2_ARGS...]" >&2 + exit 1 + fi + + # Set root directory as the repository root + root="$(cd "$(dirname "${BASH_SOURCE[0]}")" && pwd)" + export FOAM_FOAMUT="$root" + +} + +# :command.run +run() { + # :command.globals + declare -g long_usage='' + declare -g -A args=() + declare -g -a other_args=() + declare -g -A deps=() + declare -g -a env_var_names=() + declare -g -a input=() + + normalize_input "$@" + parse_requirements "${input[@]}" + + case "$action" in + "root") root_command ;; + esac +} + +if [[ "${BASH_SOURCE[0]}" == "${0}" ]]; then + initialize + run "$@" +fi diff --git a/src/bashly.yml b/src/bashly.yml new file mode 100644 index 0000000..b70d326 --- /dev/null +++ b/src/bashly.yml @@ -0,0 +1,77 @@ +name: foamut +help: Unit/integration testing framework for OpenFOAM +version: 2.0.0 + +flags: + - long: --parallel + help: Run parallel tests only (default is serial only, incompatible with --standalone) + + - long: --report + help: Generate JSON test reports in reports/ directory + + - long: --test-driver + arg: source_path + help: Path to custom testDriver.C source file (overrides default) + + - long: --standalone + help: Run tests without OpenFOAM cases (tests not tagged by case names, serial or mode-agnostic) + + - long: --test-prefix + arg: command + help: | + Prefix command for test execution (automatically disables timeout) + Serial: testDriver ... + Parallel: mpirun -np N testDriver ... + Examples: "gdb --args", "valgrind", "hpctoolkit" + + - long: --force-timeout + help: Keep timeout wrapper even when using --test-prefix + +environment_variables: + - name: FOAM_LIBBIN + help: OpenFOAM library directory (required, set by sourcing OpenFOAM) + + - name: FOAM_FOAMUT_TESTS + help: Path to external tests directory (will be symlinked under tests/) + + - name: CATCH_TIMEOUT + help: Test timeout in seconds (default 60) + + - name: FOAM_FOAMUT + help: Path to foamUT repository root (set automatically) + +catch_all: + label: catch2_args + help: |- + Arguments passed to Catch2 test framework. + + Common Catch2 Options: + --list-tests List all tests + --list-tags List all tags + -h, --help Show Catch2 help + -s, --success Show successful tests + -b, --break Break into debugger on failure + -d, --durations Show test durations + -#, --filenames-as-tags Add filename as tag + -r, --reporter Use specific reporter (console, xml, junit, etc.) + --order Test execution order + + Tag Filters: + "[tag1]" Run tests with tag1 + "[tag1][tag2]" Run tests with both tags + "[tag1],[tag2]" Run tests with either tag + "~[tag1]" Exclude tests with tag1 + + Note: testDriver uses --- to separate Catch2 from OpenFOAM args internally + +examples: + - foamut --help # Show this help (foamUT options + Catch2 reference) + - foamut # Run serial tests only (default) + - foamut --parallel # Run parallel tests only + - foamut --parallel --report # Run parallel tests with JSON reports + - foamut --standalone # Run tests NOT tagged with case names + - foamut --list-tests # List all available tests (Catch2) + - foamut "[serial]" # Run specific test tags (Catch2) + - foamut -s # Show successful tests (Catch2) + - foamut --test-prefix "gdb --args" # Debug serial tests + - foamut --test-driver /path/to/custom/testDriver.C # Use custom test driver diff --git a/src/initialize.sh b/src/initialize.sh new file mode 100644 index 0000000..534066a --- /dev/null +++ b/src/initialize.sh @@ -0,0 +1,15 @@ +#!/usr/bin/bash + +# Bashly initialization hook - runs before command execution +# Validates environment and performs sanity checks + +# Check if OpenFOAM is sourced +if [ -z "${FOAM_LIBBIN}" ]; then + echo "Error: Please source an OpenFOAM version. Aborting..." >&2 + echo "USAGE: ./foamut [OPTIONS] [CATCH2_ARGS...]" >&2 + exit 1 +fi + +# Set root directory as the repository root +root="$(cd "$(dirname "${BASH_SOURCE[0]}")" && pwd)" +export FOAM_FOAMUT="$root" diff --git a/src/lib/cleanup.sh b/src/lib/cleanup.sh new file mode 100644 index 0000000..9b9f7b3 --- /dev/null +++ b/src/lib/cleanup.sh @@ -0,0 +1,18 @@ +#!/usr/bin/bash + +# Cleanup dummy libraries and temporary directories + +cleanup() { + local dummyLibChecks="Pstream dynamicFvMesh" + + # Cleanup dummy libraries + for dummy in $dummyLibChecks; do + if [ -f "${FOAM_USER_LIBBIN}/lib${dummy}.so" ] && + tr '\n' ' ' < "${FOAM_USER_LIBBIN}/lib${dummy}.so" | grep -q '^! $'; then + rm -rf "${FOAM_USER_LIBBIN}/lib${dummy}.so" + fi + done + + # Cleanup temporary case directory + rm -rf "$caseRun" +} diff --git a/src/lib/compile_catch2.sh b/src/lib/compile_catch2.sh new file mode 100644 index 0000000..a89190a --- /dev/null +++ b/src/lib/compile_catch2.sh @@ -0,0 +1,45 @@ +#!/usr/bin/bash + +# Compile Catch2 v3 if not already compiled +# Installs to FOAM_USER_LIBBIN/catch2 + +compile_catch2() { + echo "Compiling Catch2 v3..." >&2 + if [ ! -f "$FOAM_USER_LIBBIN/catch2/lib/libCatch2.a" ]; then + cd catch2 || exit 1 + mkdir -p build + cd build || exit 1 + + # Use pipefail to catch failures in piped commands + set -o pipefail + + if ! cmake -DCMAKE_INSTALL_PREFIX="$FOAM_USER_LIBBIN"/catch2 .. 2>&1 | tee log.cmake; then + set +o pipefail + echo "" >&2 + echo "Error: CMake configuration failed for Catch2" >&2 + echo "Full log saved to: $PWD/log.cmake" >&2 + exit 1 + fi + + if ! make -j"$(nproc)" 2>&1 | tee log.makeBuild; then + set +o pipefail + echo "" >&2 + echo "Error: Failed to build Catch2" >&2 + echo "Full log saved to: $PWD/log.makeBuild" >&2 + exit 1 + fi + + if ! make install 2>&1 | tee log.makeInstall; then + set +o pipefail + echo "" >&2 + echo "Error: Failed to install Catch2" >&2 + echo "Full log saved to: $PWD/log.makeInstall" >&2 + exit 1 + fi + + set +o pipefail + cd - > /dev/null || exit 1 + rm -rf build + cd .. || exit 1 + fi +} diff --git a/src/lib/compile_test_drivers.sh b/src/lib/compile_test_drivers.sh new file mode 100644 index 0000000..f75cab3 --- /dev/null +++ b/src/lib/compile_test_drivers.sh @@ -0,0 +1,35 @@ +#!/usr/bin/bash + +# Compile test drivers for all test libraries +# Supports custom test driver via --test-driver flag + +compile_test_drivers() { + local custom_driver="${args[--test-driver]}" + + # Compile tests + for lib in $libs; do + cd "$lib" || exit 1 + + # If custom driver is specified, copy it to this test directory + if [ -n "$custom_driver" ]; then + cp "$custom_driver" "$root/tests/testDriver.C" + echo "Using custom test driver: $custom_driver" >&2 + fi + + echo "Compiling $lib test driver..." >&2 + wmakeLnInclude . # Just in case the tested libs are header only + + # Use set -o pipefail to catch wmake failures even when piped to tee + set -o pipefail + if ! wmake 2>&1 | tee log.wmake; then + set +o pipefail + echo "" >&2 + echo "Error: Failed to compile $lib test driver" >&2 + echo "Full log saved to: $PWD/log.wmake" >&2 + exit 1 + fi + set +o pipefail + echo "Test driver for $lib has been compiled." >&2 + cd - > /dev/null || exit 1 + done +} diff --git a/src/lib/create_dummy_libs.sh b/src/lib/create_dummy_libs.sh new file mode 100644 index 0000000..b6547b1 --- /dev/null +++ b/src/lib/create_dummy_libs.sh @@ -0,0 +1,16 @@ +#!/usr/bin/bash + +# Create dummy libraries if they don't exist +# Some OpenFOAM forks/configurations may not have certain libraries + +create_dummy_libs() { + # If there is no lib${libName}.so, create a dummy one + local dummyLibChecks="Pstream dynamicFvMesh" + for dummy in $dummyLibChecks; do + if [ ! -f "${FOAM_LIBBIN}/lib${dummy}.so" ] && + [ ! -f "${FOAM_USER_LIBBIN}/lib${dummy}.so" ] && + [ ! -f "${FOAM_LIBBIN}/${FOAM_MPI}/lib${dummy}.so" ]; then + echo '!' > "${FOAM_USER_LIBBIN}/lib${dummy}.so" + fi + done +} diff --git a/src/lib/run_parallel_tests.sh b/src/lib/run_parallel_tests.sh new file mode 100644 index 0000000..1644597 --- /dev/null +++ b/src/lib/run_parallel_tests.sh @@ -0,0 +1,138 @@ +#!/usr/bin/bash + +# Run parallel tests for all libraries on a specific case +# Arguments: $1=error_count, $2=case_name, $3=case_path, $4=libs, $@=additional args + +run_parallel_tests() { + if [ "$standalone_mode" = "true" ]; then + echo "Skipping decomposePar for standalone mode..." >&2 + else + if ! test -f "${3}/system/decomposeParDict" ; then + rm -rf "${3}/processor*" + echo "decomposeParDict not found, not running parallel tests on this case..." >&2 + return 0 + fi + fi + + local fargs=("$@") + local params=() + local skip_next=false + + # Filter out foamUT-specific flags from params + for ((i = 4; i <= $#; i++)); do + arg="${fargs[i]}" + + if [ "$skip_next" = true ]; then + skip_next=false + continue + fi + + case "$arg" in + --parallel|--report|--standalone|--force-timeout) + # These flags don't take arguments + ;; + --test-driver|--test-prefix) + # These flags take an argument, skip the next one too + skip_next=true + ;; + *) + params+=("$arg") + ;; + esac + done + + local test_prefix="${args[--test-prefix]}" + local force_timeout="${args[--force-timeout]}" + + # Decompose case if not in standalone mode + if [ "$standalone_mode" != "true" ]; then + local nProcs=$(grep -oP "numberOfSubdomains\s+\K\d+" "${3}"/system/decomposeParDict) + decomposePar -force -case "${3}" > "${3}/log.decomposePar" + err=$((err + $?)) + else + # In standalone mode, default to 2 processors or read from FOAM_FOAMUT_NPROCS + local nProcs="${FOAM_FOAMUT_NPROCS:-2}" + fi + + # run the case in parallel mode + report="" + case $doReport in + (true) report="-s -r json -d yes";; + esac + + for ((i = 0; i < ${#libs_array[@]}; i++)); do + lib="${libs_array[i]}" + printf "\n\nRunning tests for lib %s, on %s case in parallel...\n" "$lib" "${2}" >&2 + libName=$(basename "$lib") + logdir="reports/${libName}_${2}_parallel" + logparams="" + direct="" + case $doReport in + (true) logparams="-output-filename $logdir" + direct="> /dev/null" + echo "Parallel Reports (${nProcs}): ${logdir}" >&2 + esac + + # Execute the test + if [ "$standalone_mode" = "true" ]; then + # Standalone mode: no case argument, exclude all case-tagged tests + # Build exclusion filter: [parallel]~[case1]~[case2]... + local tag_filter="[parallel]" + for case_name in "${case_names[@]}"; do + tag_filter="${tag_filter}~[${case_name}]" + done + + # Build and execute command with or without timeout and prefix + # Prefix comes AFTER mpirun in parallel mode + if [ -z "$test_prefix" ]; then + timeout "$timeOut" mpirun $logparams -np "$nProcs" "$root"/"$lib"/testDriver \ + --allow-running-no-tests \ + -n "$(basename "$lib")" $report \ + --rng-seed time "$tag_filter" ${params[@]} --- -parallel $direct + else + if [ "$force_timeout" = "1" ]; then + timeout "$timeOut" mpirun $logparams -np "$nProcs" $test_prefix "$root"/"$lib"/testDriver \ + --allow-running-no-tests \ + -n "$(basename "$lib")" $report \ + --rng-seed time "$tag_filter" ${params[@]} --- -parallel $direct + else + mpirun $logparams -np "$nProcs" $test_prefix "$root"/"$lib"/testDriver \ + --allow-running-no-tests \ + -n "$(basename "$lib")" $report \ + --rng-seed time "$tag_filter" ${params[@]} --- -parallel $direct + fi + fi + else + # Normal mode: with case argument and -parallel flag + if [ -z "$test_prefix" ]; then + timeout "$timeOut" mpirun $logparams -np "$nProcs" "$root"/"$lib"/testDriver \ + --allow-running-no-tests \ + -n "$(basename "$lib")" $report \ + --rng-seed time "[parallel][${2}]" ${params[@]} --- -parallel -case "${3}" $direct + else + if [ "$force_timeout" = "1" ]; then + timeout "$timeOut" mpirun $logparams -np "$nProcs" $test_prefix "$root"/"$lib"/testDriver \ + --allow-running-no-tests \ + -n "$(basename "$lib")" $report \ + --rng-seed time "[parallel][${2}]" ${params[@]} --- -parallel -case "${3}" $direct + else + mpirun $logparams -np "$nProcs" $test_prefix "$root"/"$lib"/testDriver \ + --allow-running-no-tests \ + -n "$(basename "$lib")" $report \ + --rng-seed time "[parallel][${2}]" ${params[@]} --- -parallel -case "${3}" $direct + fi + fi + fi + + set +x + # separator in case json reporter is used + for element in "${params[@]}"; do + if [ "$element" == "json" ]; then + if ((i != ${#libs_array[@]} - 1)); then + printf "," + fi + fi + done + err=$((err + lerr)) + done +} diff --git a/src/lib/run_serial_tests.sh b/src/lib/run_serial_tests.sh new file mode 100644 index 0000000..6c72102 --- /dev/null +++ b/src/lib/run_serial_tests.sh @@ -0,0 +1,107 @@ +#!/usr/bin/bash + +# Run serial tests for all libraries on a specific case +# Arguments: $1=error_count, $2=case_name, $3=case_path, $4=libs, $@=additional args + +run_serial_tests() { + local fargs=("$@") + local params=() + local skip_next=false + + # Filter out foamUT-specific flags from params + for ((i = 4; i <= $#; i++)); do + arg="${fargs[i]}" + + if [ "$skip_next" = true ]; then + skip_next=false + continue + fi + + case "$arg" in + --parallel|--report|--standalone|--force-timeout) + # These flags don't take arguments + ;; + --test-driver|--test-prefix) + # These flags take an argument, skip the next one too + skip_next=true + ;; + *) + params+=("$arg") + ;; + esac + done + + local test_prefix="${args[--test-prefix]}" + local force_timeout="${args[--force-timeout]}" + + for ((i = 0; i < ${#libs_array[@]}; i++)); do + lib="${libs_array[i]}" + printf "\n\nRunning tests for lib %s, on %s case in serial...\n" "$lib" "${2}" >&2 + libName=$(basename "$lib") + report="" + case $doReport in + (true) report="-s -r json::out=${root}/reports/${libName}_${2}_serial.json -d yes" + echo "Report: ${root}/reports/${libName}_${2}_serial.json" >&2 + ;; + esac + + # Execute the test + if [ "$standalone_mode" = "true" ]; then + # Standalone mode: no case argument, exclude all case-tagged tests + # Run tests that are: + # - ([serial] AND not-case-tagged) OR (not-[parallel] AND not-case-tagged) + # This includes: [serial] tests, and tests with neither [serial] nor [parallel] + local tag_filter="[serial]" + for case_name in "${case_names[@]}"; do + tag_filter="${tag_filter}~[${case_name}]" + done + tag_filter="${tag_filter},~[parallel]" + for case_name in "${case_names[@]}"; do + tag_filter="${tag_filter}~[${case_name}]" + done + + # Build and execute command with or without timeout and prefix + if [ -z "$test_prefix" ]; then + timeout "$timeOut" "$root"/"$lib"/testDriver --allow-running-no-tests \ + -n "$(basename "$lib")" $report "$tag_filter" "${params[@]}" + else + if [ "$force_timeout" = "1" ]; then + timeout "$timeOut" $test_prefix "$root"/"$lib"/testDriver --allow-running-no-tests \ + -n "$(basename "$lib")" $report "$tag_filter" "${params[@]}" + else + $test_prefix "$root"/"$lib"/testDriver --allow-running-no-tests \ + -n "$(basename "$lib")" $report "$tag_filter" "${params[@]}" + fi + fi + else + # Normal mode: with case argument + if [ -z "$test_prefix" ]; then + timeout "$timeOut" "$root"/"$lib"/testDriver --allow-running-no-tests \ + -n "$(basename "$lib")" $report "[serial][${2}]" "${params[@]}" \ + --- -case "${3}" + else + if [ "$force_timeout" = "1" ]; then + timeout "$timeOut" $test_prefix "$root"/"$lib"/testDriver --allow-running-no-tests \ + -n "$(basename "$lib")" $report "[serial][${2}]" "${params[@]}" \ + --- -case "${3}" + else + $test_prefix "$root"/"$lib"/testDriver --allow-running-no-tests \ + -n "$(basename "$lib")" $report "[serial][${2}]" "${params[@]}" \ + --- -case "${3}" + fi + fi + fi + + locErr=$? + err=$((err+locErr)) + + # separator in case json reporter is used + for element in "${params[@]}"; do + if [ "$element" == "json" ]; then + if ((i != ${#libs_array[@]})); then + printf "," + fi + fi + done + done +} diff --git a/src/lib/setup_test_symlinks.sh b/src/lib/setup_test_symlinks.sh new file mode 100644 index 0000000..3a2335d --- /dev/null +++ b/src/lib/setup_test_symlinks.sh @@ -0,0 +1,16 @@ +#!/usr/bin/bash + +# Setup test symlinks if FOAM_FOAMUT_TESTS environment variable is set +# Discovers Make directories and symlinks parent directories under tests/ + +setup_test_symlinks() { + # If tests folder is supplied through an env. var. do the symlinking + if [ -n "${FOAM_FOAMUT_TESTS}" ]; then + find "${FOAM_FOAMUT_TESTS}" -type d -name "Make" | while read -r make_dir; do + parent_dir=$(basename "$(dirname "$make_dir")") + target_dir="$root/tests/$parent_dir" + ln -f -s "$(dirname "$make_dir")" "$target_dir" + echo "picked up $parent_dir tests" + done + fi +} diff --git a/src/root_command.sh b/src/root_command.sh new file mode 100644 index 0000000..450f7cb --- /dev/null +++ b/src/root_command.sh @@ -0,0 +1,162 @@ +# Main command logic for foamUT +# Orchestrates the entire test execution pipeline + +# ShellChecked on every push - Always ShellCheck your scripts! + +# Validate custom test driver if provided +if [ -n "${args[--test-driver]}" ]; then + driver_path="${args[--test-driver]}" + if [ ! -f "$driver_path" ]; then + echo "Error: Test driver file not found: $driver_path" >&2 + exit 1 + fi + if [[ ! "$driver_path" =~ \.(C|cpp)$ ]]; then + echo "Error: Test driver must be a .C or .cpp source file" >&2 + exit 1 + fi +fi + +# Validate --force-timeout is only used with --test-prefix +if [ "${args[--force-timeout]}" = "1" ] && [ -z "${args[--test-prefix]}" ]; then + echo "Warning: --force-timeout has no effect without --test-prefix" >&2 +fi + +# Validate --standalone is not used with --parallel +if [ "${args[--standalone]}" = "1" ] && [ "${args[--parallel]}" = "1" ]; then + echo "Error: --standalone is not compatible with --parallel" >&2 + echo "Standalone mode only runs serial tests" >&2 + exit 1 +fi + +# Load helper functions +source "$root/src/lib/setup_test_symlinks.sh" +source "$root/src/lib/create_dummy_libs.sh" +source "$root/src/lib/compile_catch2.sh" +source "$root/src/lib/compile_test_drivers.sh" +source "$root/src/lib/run_serial_tests.sh" +source "$root/src/lib/run_parallel_tests.sh" +source "$root/src/lib/cleanup.sh" + +# Parse flags from bashly (bashly uses "1" for true flags) +doSerial=true # Default: serial only +doParallel=false +doReport=false +standalone_mode=false + +# If --parallel flag is set, run parallel only (disable serial) +if [ "${args[--parallel]}" = "1" ]; then + doSerial=false + doParallel=true +fi + +if [ "${args[--report]}" = "1" ]; then + doReport=true +fi + +if [ "${args[--standalone]}" = "1" ]; then + standalone_mode=true +fi + +set -e + +# Get OpenFOAM version string +foamV=$(echo "${WM_PROJECT}$(test ! -z "${WM_FORK}" && echo '-'"${WM_FORK}")-${WM_PROJECT_VERSION}") + +# Collect catch2 arguments from catch_all +args_array=() +if [ -n "${other_args[*]}" ]; then + args_array=("${other_args[@]}") +fi + +# Setup test symlinks if FOAM_FOAMUT_TESTS is set +setup_test_symlinks + +# Create dummy libraries if needed +create_dummy_libs + +# libraries to test +libs=$(find -L tests/ -maxdepth 1 -mindepth 1 -type d) +libs_array=($libs) + +# where to run OpenFOAM cases +caseRun=/tmp/foamUtCases +mkdir -p "$caseRun" + +# a timeout to prevent hanging processes (be CI friendly) +timeOut="${CATCH_TIMEOUT:-60}" + +# Compile Catch2 +compile_catch2 + +# Compile test drivers +compile_test_drivers + +# Go back to the root of the repository +cd "$root" + +# Get setup to run tests +mapfile -t cases_array < <(find -L cases/ -maxdepth 1 -mindepth 1 -type d) + +# Build list of case names for tag filtering +case_names=() +for case_dir in "${cases_array[@]}"; do + case_names+=("$(basename "$case_dir")") +done + +if [ "$standalone_mode" = "true" ]; then + echo "Running in standalone mode (no OpenFOAM cases)..." >&2 + echo "Will exclude tests tagged with: ${case_names[*]}" >&2 + # Create a dummy case array with one entry for the loop structure + cases_array=("standalone") +else + if [ ${#cases_array[@]} -eq 0 ]; then + echo "No cases found to run tests" + exit 1 + fi +fi + +mkdir -p "$root"/reports + +# Run the tests, but do not exit until attempting everything +set +e +err=0 + +for ((k = 0; k < ${#cases_array[@]}; k++)); do + if [ "$standalone_mode" = "true" ]; then + caseName="standalone" + casePath="" + echo "Running standalone tests (no case)..." >&2 + else + ofCase="${cases_array[k]}" + caseName=$(basename "$ofCase") + casePath=$caseRun/$caseName + cp -rL "$ofCase" "$casePath" + fi + + case $doSerial in + (true) run_serial_tests "$err" "$caseName" "$casePath" "$libs" "${args_array[@]}";; + esac + + case $doParallel in + (true) run_parallel_tests "$err" "$caseName" "$casePath" "$libs" "${args_array[@]}";; + esac + + # separator in case json reporter is used + for element in "${args_array[@]}"; do + if [ "$element" == "json" ]; then + if ((k != ${#cases_array[@]} - 1)); then + printf "," + fi + fi + done + + # Cleanup case directory if not in standalone mode + if [ "$standalone_mode" != "true" ]; then + rm -rf "$casePath" + fi +done + +# Cleanup +cleanup + +exit $err From 3965481205511c71fdc7c3aec2c252f62d21eae2 Mon Sep 17 00:00:00 2001 From: Elwardi Date: Sun, 12 Oct 2025 16:17:13 +0200 Subject: [PATCH 2/5] feat: standalone mode support for tests --- tests/exampleTests/Make/files | 1 + tests/exampleTests/Make/options | 2 +- tests/exampleTests/fields.C | 4 ++-- tests/exampleTests/noCase.C | 11 +++++++++++ tests/testDriver.C | 28 ++++++++++++++++++++++------ 5 files changed, 37 insertions(+), 9 deletions(-) create mode 100644 tests/exampleTests/noCase.C diff --git a/tests/exampleTests/Make/files b/tests/exampleTests/Make/files index cfd31bb..8917c6a 100644 --- a/tests/exampleTests/Make/files +++ b/tests/exampleTests/Make/files @@ -2,5 +2,6 @@ $(FOAM_FOAMUT)/tests/testDriver.C time.C fields.C +noCase.C EXE = testDriver diff --git a/tests/exampleTests/Make/options b/tests/exampleTests/Make/options index 2c51c9c..f96056d 100644 --- a/tests/exampleTests/Make/options +++ b/tests/exampleTests/Make/options @@ -1,4 +1,4 @@ -EXE_INC = -std=c++14 \ +EXE_INC = -std=c++17 \ -I$(LIB_SRC)/finiteVolume/lnInclude \ -I$(LIB_SRC)/meshTools/lnInclude \ -I$(LIB_SRC)/dynamicMesh/dynamicFvMesh/lnInclude \ diff --git a/tests/exampleTests/fields.C b/tests/exampleTests/fields.C index 0aed381..0daeeb9 100644 --- a/tests/exampleTests/fields.C +++ b/tests/exampleTests/fields.C @@ -11,7 +11,6 @@ extern Time* timePtr; extern argList* argsPtr; SCENARIO("Check field initialization methods", "[cavity][serial][parallel]") { - Foam::FatalError.dontThrowExceptions(); Time& runTime = *timePtr; argList& args = *argsPtr; #include "createMesh.H" @@ -29,7 +28,8 @@ SCENARIO("Check field initialization methods", "[cavity][serial][parallel]") { runTime.timeName(), mesh, IOobject::NO_READ, - IOobject::NO_WRITE + IOobject::NO_WRITE, + false ), mesh, zero diff --git a/tests/exampleTests/noCase.C b/tests/exampleTests/noCase.C new file mode 100644 index 0000000..0a81bfb --- /dev/null +++ b/tests/exampleTests/noCase.C @@ -0,0 +1,11 @@ +#include "catch2/catch_all.hpp" +#include "catch2/catch_test_macros.hpp" + + +TEST_CASE("No case tagged so no time object needed", "[serial]") { + REQUIRE(true); +} + +TEST_CASE("another no case tagged test") { + REQUIRE(true); +} diff --git a/tests/testDriver.C b/tests/testDriver.C index dd9fcf9..993f482 100644 --- a/tests/testDriver.C +++ b/tests/testDriver.C @@ -75,13 +75,29 @@ int main(int argc, char *argv[]) Warning().stdStream().setstate(std::ios_base::failbit); } - //argList::noBanner(); - #include "setRootCase.H" - #include "createTime.H" - argsPtr = &args; - timePtr = &runTime; + // Only create OpenFOAM Time object if case arguments are provided + // (standalone mode doesn't provide --- separator or foam arguments) + int ret = 0; + if (foamArgc > 1) + { + #include "setRootCase.H" + #include "createTime.H" + argsPtr = &args; + timePtr = &runTime; + + // Run tests with valid args and runTime pointers + ret = session.run(); + } + else + { + // Standalone mode: no OpenFOAM case + argsPtr = nullptr; + timePtr = nullptr; + + // Run tests without OpenFOAM objects + ret = session.run(); + } - auto ret = session.run(); if (ret != 0) abort(); return 0; } From 0d471919e473b71353754b4a37700d7de8a0e16a Mon Sep 17 00:00:00 2001 From: Elwardi Date: Sun, 12 Oct 2025 16:17:50 +0200 Subject: [PATCH 3/5] build: add foamut generation script --- generate.sh | 13 +++++++++++++ 1 file changed, 13 insertions(+) create mode 100755 generate.sh diff --git a/generate.sh b/generate.sh new file mode 100755 index 0000000..0b8facd --- /dev/null +++ b/generate.sh @@ -0,0 +1,13 @@ +#!/usr/bin/bash + +# Script to generate foamut executable and create backward compatibility symlink + +set -e + +echo "Generating foamut executable with bashly..." +bashly generate + +echo "Creating Alltest symlink for backward compatibility..." +ln -sf foamut Alltest + +echo "Done! Run ./foamut --help or ./Alltest --help to test" From 1f6b6fbc114c3290418380f9e25ba98b9c96c2b5 Mon Sep 17 00:00:00 2001 From: Elwardi Date: Sun, 12 Oct 2025 16:18:13 +0200 Subject: [PATCH 4/5] docs: update README for v2.0.0 --- README.md | 152 ++++++++++++++++++++++++++++++++++++++++++++++-------- 1 file changed, 131 insertions(+), 21 deletions(-) diff --git a/README.md b/README.md index d3032f4..70027e7 100644 --- a/README.md +++ b/README.md @@ -3,12 +3,42 @@ > This offering is not approved or endorsed by OpenCFD Limited, > the producer of the OpenFOAM software and owner of the OPENFOAM® and OpenCFD® trade marks. +> [!IMPORTANT] +> There have been breaking changes on the CLI interface from **v2.0.0**: +> - `foamut` is now the main command (built with [bashly](https://bashly.dannyb.co/)) +> - `Alltest` remains available as a symlink for backward compatibility +> - **New default behavior**: Serial tests only (use `--parallel` for parallel-only execution) +> - **New features**: +> - `--standalone`: Run tests without OpenFOAM cases (mode-agnostic and serial tests) +> - `--test-driver `: Use custom testDriver.C source file +> - `--test-prefix `: Prefix test execution with debuggers/profilers (e.g., `gdb --args`, `valgrind`) +> - `--report`: Generate JSON test reports in `reports/` directory +> - `--force-timeout`: Keep timeout even when using `--test-prefix` +> - All Catch2 arguments can be passed directly (no special separator needed) +> +> If you need the old behavior, use tag **v1.0.0** + This is a unit/integration testing framework to help test-proof new OpenFOAM code (might be too late for the OpenFOAM library itself). This repository will always work with the latest versions of the main three OpenFOAM forks: [ESI OpenCFD's](https://openfoam.com), [Foundation version](https://openfoam.org) and [Foam-Extend](https://sourceforge.net/projects/foam-extend/files/). + + +* [A quick demo](#a-quick-demo) +* [How to use this repo](#how-to-use-this-repo) +* [Usage](#usage) +* [Migration from v1.0.0](#migration-from-v100) + * [Command Changes](#command-changes) + * [Behavior Changes](#behavior-changes) + * [New Capabilities](#new-capabilities) + * [CI/Automation](#ciautomation) +* [Documentation](#documentation) +* [Contributing](#contributing) + + + If you intend to write cross-forks code, this can help you maintain your sanity. You can just keep branches for each fork on your code's repository and invoke CI on them with the help of this testing framework. @@ -19,27 +49,107 @@ help of this testing framework. ## How to use this repo -The [Alltest](Alltest) script loops through the [OpenFOAM cases](cases) and runs corresponding -test cases (for directories it finds under [tests](tests) that contain a `Make` folder) -- both in serial and in parallel. - -You can use this to your advantage as you don't need to include this repo into your code base: - -1. In your library's repo, write tests the way you like; - ship them with their own `Make` directory to compile the test driver (see [example tests](tests/exampleTests)) -1. Set the `FOAM_FOAMUT` environment variable to your local clone of `foamUT` repository -1. In your CI workflows, checkout this repository, set `FOAM_FOAMUT` to its path, and either: - - symlink your tests somewhere under `tests` - ```bash - ln -s /path/to/your/tests/myLibTests tests/myLibTests - ``` - - or set `FOAM_FOAMUT_TESTS` to your `tests` folder - ```bash - export FOAM_FOAMUT_TESTS=/path/to/your/tests - ``` -1. Do the same for your OpenFOAM cases if you have any (only a simple cavity case is provided by default). - Put these under `cases`. -1. Run `./Alltest` to compile and run your tests on your cases. +The `foamut` command orchestrates the entire test pipeline: +1. Compiles [Catch2](https://github.com/catchorg/Catch2) v3 (if needed) +2. Compiles test drivers for each test library under [tests](tests) +3. Runs tests on [OpenFOAM cases](cases) in serial (default) or parallel (`--parallel`) +4. Supports standalone mode (`--standalone`) for tests that don't require OpenFOAM cases + +You can use this framework without including it in your codebase: + +1. **Write your tests** following the [Catch2 v3](https://github.com/catchorg/Catch2) framework + - Ship tests with their own `Make` directory (see [example tests](tests/exampleTests)) + - Tag tests appropriately: `[serial]`, `[parallel]`, and case-specific tags like `[cavity]` + +2. **Setup foamUT** in your environment: + ```bash + git clone https://github.com/FoamScience/foamUT.git + export FOAM_FOAMUT=/path/to/foamUT + ``` + +3. **Link your tests** using one of these methods: + - **Symlink** your tests under `tests/`: + ```bash + ln -s /path/to/your/tests/myLibTests $FOAM_FOAMUT/tests/myLibTests + ``` + - **Environment variable** (recommended for CI): + ```bash + export FOAM_FOAMUT_TESTS=/path/to/your/tests + ``` + +4. **Provide OpenFOAM cases** (optional, skip if using `--standalone`, or + if the default cavity case is enough): + - Symlink or copy your cases to `cases/` + - Cases must include `system/controlDict` (and `system/decomposeParDict` for parallel) + +5. **Run tests**: + ```bash + # Source OpenFOAM environment first + source /path/to/OpenFOAM/etc/bashrc + + # Run serial tests (default) + ./foamut + + # Run parallel tests only + ./foamut --parallel + + # Run standalone tests (no OpenFOAM cases needed) + ./foamut --standalone + + # Debug a specific test + ./foamut --test-prefix "gdb --args" "[mytest]" + + # Generate JSON reports + ./foamut --report + ``` + +## Usage + +Run `./foamut --help` to see all available options. `foamut -- -h` to see all +Catch2 options. + +## Migration from v1.0.0 + +If you're upgrading from v1.0.0, here are the key changes: + +### Command Changes +| v1.0.0 | v2.0.0 | Notes | +|--------|--------|-------| +| `./Alltest` | `./foamut` | `Alltest` still works (symlink) | +| `./Alltest` (both modes) | `./foamut` (serial only) | Use `--parallel` for parallel tests | +| N/A | `./foamut --standalone` | New: Run tests without cases | + +### Behavior Changes +- **Default**: v1.0.0 ran both serial and parallel tests; v2.0.0 runs **serial only** by default +- **Parallel mode**: Use `--parallel` flag explicitly to run parallel tests +- **Catch2 args**: No longer need special handling, pass directly (e.g., `./foamut "[mytag]"`) + +### New Capabilities +```bash +# Debug failing tests +./foamut --test-prefix "gdb --args" "[failing-test]" + +# Profile test performance +./foamut --test-prefix "valgrind --tool=callgrind" + +# Use custom test driver +./foamut --test-driver /path/to/myTestDriver.C + +# Run unit tests without OpenFOAM cases +./foamut --standalone +``` + +### CI/Automation +If you have CI pipelines using v1.0.0: +```bash +# Old way (v1.0.0) +./Alltest + +# New way (v2.0.0) - for same behavior, run both: +./foamut # Serial tests +./foamut --parallel # Parallel tests +./Alltest # This is just a symlink for ./foamut +``` ## Documentation From c3f033b3be7fe17954d1537fb93f88921a79f0cd Mon Sep 17 00:00:00 2001 From: Elwardi Date: Sun, 12 Oct 2025 16:18:57 +0200 Subject: [PATCH 5/5] Squashed 'catch2/' changes from 2b60af89e..b3fb4b9fe b3fb4b9fe v3.11.0 6500dc814 Add Tuple sponsorship banner to readme 4be9de6c5 Disable test for deprecated CATCH_CONFIG_BAZEL_SUPPORT option 91b3b3bf4 Support Bazel's TEST_RANDOM_SEED a00d65443 Speed up processing mostly visible strings in convertIntoString 756ae05d3 Inline the `getResultCapture` helper into header. a2e41916f Keep the main Context instance as static value, not pointer 0e772cc0d Allow composability of unhandled exception filters (#3033) 3bcd0a4e7 Changed GetOptions to use sequence f7e7fa098 Fix ReusableStringStream to access the container under the lock (#3031) b626e4c7a Add simple runtime benchmarks 434bf55d4 Make message push/pop static 9048c24fa Improve QNX support 8ed8c431c Support Bazel's TEST_PREMATURE_EXIT_FILE and add it to CLI as well dc3a4ea41 Fix non-desktop Windows platforms not defining CATCH_INTERNAL_CONFIG_NO_COLOUR_WIN32 correctly cd93d202e Small refactoring in CAPTURE parsing 227af796b Move MessageInfos into the run context 33adb4c77 Use only the ID of a message when removing it from context 25319fd30 v3.10.0 85c4bad86 Forbid deducing reference types for m_predicate in FilterGenerator (#3005) 582200a1f Make message macros (FAIL, WARN, INFO, etc) thread safe f4e05a67b Improve performance of writing XML fb2e4fbe4 Improve performance of writing JSON values 78a9518a2 Don't add / to start of pkg-config file path when DESTDIR is unset 3e82ef931 Fix color mode detection on FreeBSD by adding platform macro 7cad6d753 Handle DESTDIR env var when generating pkgconfig files 644821ce2 v3.9.1 03c62cdf2 Add tests for comparing & stringifying volatile pointers 9a3d68315 Refactor CATCH_TRAP selection logic to prefer compiler-specific impls bcd4116df Update generators.md 9b3f508a1 Cleanup WIP changes from last commit c5e0ef4e6 Catch exceptions from StringMakers inside Detail::stringify 17fe5eaa5 Fix StringMaker for time_point with non-default duration fbfd13501 Fix warning in `catch_unique_ptr::bool()` ccabd4de8 Add enum types to what is captured by value by default a1c7ee115 Don't follow __assume(false) with std::terminate in NDEBUG builds d547cae54 Fix bad error reporting for nested exceptions in default configuration fee81626d v3.9.0 b6c5a635b Link to the thread safety documentation from docs/Readme.md 4ed308819 Remove 'thread safe assertions' from limitations.md bdc634e9f Document that the default test run order is now random 2a8a8a721 Add configuration option to make assertions thread-safe 900a6d551 Release script also recognizes version placeholders in 'vX.Y.Z' form 1aa6fa215 Backfill release version placeholders that used wrong format 5aa8d1132 Clear unscoped messages lazily 9c5c21df8 Simplify removal of messages from RunContext 10aef62f2 Regularize scoped message lifetime to only consider the object's scope 98b4bbb35 Deprecate comparison operators on MessageInfo 8c3ffe05e Skeleton for applying deprecation warnings 715558fd9 Use the fast path for passing assertions in RunContext::handleNonExpr 55b14e1b3 Update last seen line info in the assertionEnded fast path ce128f584 Remove assertionPassed fast path from the capture interface db6171a70 Mention ReporterPreferences::shouldReportAllAssertionStarts in docs c22096846 Let reporters opt into fast path in RunContext::assertionStarting ae33e5b99 Remove empty implementations of assertionStarting from reporters 0e8112a76 Only track the last line info in RunContext 9be81c0e0 Move resetAssertionInfo into RunContext::reportExpr efcb76874 Store only lineinfo if m_lastAssertionInfo is going to be reset 895b8af6b Avoid needless copy in handling fatal errors 066f00acf Pass result disposition into RunContext::populateReaction directly 1b72e4535 Cache config::abortAfter and config::shouldDebugBreak in RunContext f7968e969 Constify various autoregistry globals to avoid clang-tidy complaints 3ebc346bc Fix Clang-Tidy's readability-static-accessed-through-instance in tests a483b6d7d Default to randomized test case order b9c018b38 Use template variables for our trait-likes in Clara 050e14dce Disable Wmissing-noreturn for test files 6097bd6ee Mark code past FAIL and SKIP as unreachable b62413aee Add stream flushes after reporters print out the test run metadata. 3839e27f0 Generate pkg-config files at install time 39c32b966 Avoid needless string temporary in template product test registration 2de12cb05 Allow using only types in `TEMPLATE_TEST_CASE_SIG`. (#2995) 8dc9f1a12 Move header from internal 038ee6ea1 Drop windows-2019 server from list of GitHub Action runners bd2d918f3 patched >2-digit tag-count padding in base reporter d134b0cae clang: do not issue bogus warnings about integer manipulation in hash functions with fsanitize=undefined/integer (#2965) ceed26842 fix(python): fix invalid escape sequences 04cbcfa1d Vectorize artwork 416b07521 fix: make deleted member function public 334827eb5 Replace deprecated FindPythonInterp with FindPython3 ac207fc90 Use latest release in docs 1de7d0ed7 Remove redundant `override` specifier from functions that are declared `final` 65cd20392 Stop testing with GCC 7 and 8 3013cb897 Bazel support: Add license annotations 74fcff6e5 Use Start-BitsTransfer for downloading OpenCppCoverage instead 5abfc0aa9 Add return code constants to API 1e7b879fa Small include cleanup 4ff57aba4 Use variable templates dde822061 Add VS 2025 CI jobs cecb02e68 Use presets in CI 66085dac5 Simplify Windows CI jobs 5a6d70eeb Remove unused headers 10d1a2750 Simplify Linux CI jobs c17d69f81 Simplify macOS CI jobs 4c93a595a Fix typos e8f4b60e6 Add more useful preset settings 371b11b5a Modernize tools/misc/CMakeLists.txt 8039e3ea1 Prevent unnecessarily finding C compiler 1d3bfb324 Inherit C++14 requirement from upstream target 5c97a8583 Use more modern `-S` for specifying CMake source dir 8cfca70ae Fix formatting of CMake files 6aac11e17 Use CTest `--output-on-failure` flag ec571515c Don't fail CI fast edb6f8086 Export compile commands 25b86ef3f Upgrade CI runners to Ubuntu 22 4c8671cfb Add MAINTAINERS.md 5b3b22860 Update generator docs with relevant headers git-subtree-dir: catch2 git-subtree-split: b3fb4b9feafcd8d91c5cb510a4775143fdbef02f --- .bazelrc | 4 +- .clang-tidy | 2 +- .conan/test_package/CMakeLists.txt | 3 +- .github/workflows/linux-bazel-builds.yml | 10 +- .github/workflows/linux-meson-builds.yml | 12 +- .github/workflows/linux-other-builds.yml | 129 ++- .github/workflows/linux-simple-builds.yml | 108 +-- .github/workflows/mac-builds-m1.yml | 44 - .github/workflows/mac-builds.yml | 42 +- .github/workflows/package-manager-builds.yaml | 2 +- .github/workflows/validate-header-guards.yml | 2 +- .github/workflows/windows-simple-builds.yml | 20 +- BUILD.bazel | 22 +- CMake/Catch2Config.cmake.in | 7 +- CMake/CatchConfigOptions.cmake | 7 +- CMake/CatchMiscFunctions.cmake | 181 ++-- CMake/catch2-with-main.pc.in | 10 +- CMake/catch2.pc.in | 6 +- CMakeLists.txt | 218 +++-- CMakePresets.json | 18 +- MAINTAINERS.md | 11 + MODULE.bazel | 3 +- README.md | 13 +- benchmarks/CMakeLists.txt | 16 + benchmarks/assertion_listener.cpp | 28 + benchmarks/runtime_assertion_benches.cpp | 27 + data/artwork/catch2-c-logo.png | Bin 10636 -> 0 bytes data/artwork/catch2-c-logo.svg | 105 +++ data/artwork/catch2-hand-logo.png | Bin 33761 -> 0 bytes data/artwork/catch2-hand-logo.svg | 83 ++ .../catch2-logo-full-with-background.svg | 104 +++ data/artwork/catch2-logo-full.svg | 88 ++ .../catch2-logo-small-with-background.png | Bin 25330 -> 0 bytes data/artwork/catch2-logo-small.png | Bin 20939 -> 0 bytes data/sponsors/github_repo_sponsorship.png | Bin 0 -> 270450 bytes docs/Readme.md | 1 + docs/ci-and-misc.md | 7 + docs/cmake-integration.md | 2 +- docs/command-line.md | 30 +- docs/configuration.md | 34 +- docs/deprecations.md | 2 +- docs/generators.md | 14 +- docs/limitations.md | 36 +- docs/logging.md | 36 +- docs/own-main.md | 11 +- docs/release-notes.md | 95 ++- docs/reporters.md | 7 +- docs/skipping-passing-failing.md | 14 + docs/test-fixtures.md | 4 +- docs/thread-safety.md | 229 +++++ examples/210-Evt-EventListeners.cpp | 2 +- examples/CMakeLists.txt | 58 +- extras/Catch.cmake | 6 +- extras/CatchAddTests.cmake | 6 +- extras/CatchShardTests.cmake | 6 +- extras/CatchShardTestsImpl.cmake | 4 +- extras/ParseAndAddCatchTests.cmake | 304 ++++--- extras/catch_amalgamated.cpp | 796 ++++++++++++------ extras/catch_amalgamated.hpp | 583 ++++++++----- fuzzing/CMakeLists.txt | 4 +- fuzzing/NullOStream.h | 2 +- meson.build | 2 +- src/CMakeLists.txt | 170 ++-- src/catch2/benchmark/catch_benchmark.hpp | 6 +- .../detail/catch_benchmark_function.hpp | 5 +- .../benchmark/detail/catch_estimate_clock.hpp | 1 - src/catch2/benchmark/detail/catch_timing.hpp | 2 - src/catch2/catch_all.hpp | 5 +- src/catch2/catch_config.cpp | 24 + src/catch2/catch_config.hpp | 6 +- src/catch2/catch_message.cpp | 34 +- src/catch2/catch_message.hpp | 5 +- src/catch2/catch_registry_hub.cpp | 1 - src/catch2/catch_session.cpp | 65 +- src/catch2/catch_session.hpp | 8 + src/catch2/catch_tag_alias_autoregistrar.hpp | 2 +- src/catch2/catch_test_macros.hpp | 21 +- .../{internal => }/catch_test_run_info.hpp | 0 src/catch2/catch_tostring.cpp | 47 +- src/catch2/catch_tostring.hpp | 42 +- src/catch2/catch_translate_exception.hpp | 2 +- src/catch2/catch_user_config.hpp.in | 18 + src/catch2/catch_version.cpp | 2 +- src/catch2/catch_version_macros.hpp | 4 +- .../generators/catch_generator_exception.hpp | 2 +- .../generators/catch_generators_adapters.hpp | 5 +- .../interfaces/catch_interfaces_capture.cpp | 9 +- .../interfaces/catch_interfaces_capture.hpp | 23 +- .../interfaces/catch_interfaces_config.hpp | 1 - .../interfaces/catch_interfaces_reporter.hpp | 8 +- src/catch2/internal/catch_clara.hpp | 18 +- src/catch2/internal/catch_commandline.cpp | 3 + .../internal/catch_compiler_capabilities.hpp | 12 +- src/catch2/internal/catch_console_colour.cpp | 6 +- src/catch2/internal/catch_context.cpp | 15 +- src/catch2/internal/catch_context.hpp | 13 +- src/catch2/internal/catch_debugger.cpp | 2 +- src/catch2/internal/catch_debugger.hpp | 21 +- src/catch2/internal/catch_decomposer.hpp | 32 +- .../internal/catch_deprecation_macro.hpp | 19 + src/catch2/internal/catch_enforce.hpp | 2 +- .../catch_exception_translator_registry.cpp | 4 +- .../catch_exception_translator_registry.hpp | 1 - .../catch_fatal_condition_handler.cpp | 22 +- src/catch2/internal/catch_is_permutation.hpp | 2 +- src/catch2/internal/catch_istream.cpp | 2 - src/catch2/internal/catch_istream.hpp | 2 - src/catch2/internal/catch_jsonwriter.cpp | 61 +- src/catch2/internal/catch_main.cpp | 2 +- src/catch2/internal/catch_message_info.cpp | 11 +- src/catch2/internal/catch_message_info.hpp | 6 +- src/catch2/internal/catch_noncopyable.hpp | 1 + src/catch2/internal/catch_platform.hpp | 3 + src/catch2/internal/catch_preprocessor.hpp | 36 +- .../catch_random_number_generator.cpp | 8 + .../internal/catch_reusable_string_stream.cpp | 30 +- src/catch2/internal/catch_run_context.cpp | 291 +++++-- src/catch2/internal/catch_run_context.hpp | 36 +- src/catch2/internal/catch_section.hpp | 2 +- src/catch2/internal/catch_sharding.hpp | 1 - src/catch2/internal/catch_string_manip.cpp | 2 +- src/catch2/internal/catch_stringref.cpp | 1 - .../internal/catch_template_test_registry.hpp | 18 +- src/catch2/internal/catch_thread_support.hpp | 49 ++ src/catch2/internal/catch_unique_ptr.hpp | 2 +- src/catch2/internal/catch_unreachable.hpp | 56 ++ src/catch2/internal/catch_xmlwriter.cpp | 110 +-- .../catch_matchers_container_properties.hpp | 2 +- .../matchers/catch_matchers_contains.hpp | 6 +- .../matchers/catch_matchers_range_equals.hpp | 3 +- .../matchers/catch_matchers_templated.hpp | 32 +- src/catch2/meson.build | 5 +- .../reporters/catch_reporter_automake.hpp | 8 +- .../reporters/catch_reporter_compact.cpp | 3 +- .../reporters/catch_reporter_compact.hpp | 5 +- .../reporters/catch_reporter_console.cpp | 10 +- .../reporters/catch_reporter_console.hpp | 2 - .../reporters/catch_reporter_helpers.cpp | 23 +- src/catch2/reporters/catch_reporter_json.cpp | 3 +- src/catch2/reporters/catch_reporter_json.hpp | 1 - src/catch2/reporters/catch_reporter_junit.cpp | 1 + src/catch2/reporters/catch_reporter_multi.cpp | 2 + src/catch2/reporters/catch_reporter_multi.hpp | 5 + .../reporters/catch_reporter_registrars.hpp | 30 +- .../reporters/catch_reporter_sonarqube.hpp | 1 + src/catch2/reporters/catch_reporter_tap.cpp | 3 +- src/catch2/reporters/catch_reporter_tap.hpp | 1 + .../reporters/catch_reporter_teamcity.hpp | 1 + src/catch2/reporters/catch_reporter_xml.cpp | 3 +- src/catch2/reporters/catch_reporter_xml.hpp | 2 - tests/BUILD.bazel | 3 + tests/CMakeLists.txt | 368 ++++---- tests/ExtraTests/CMakeLists.txt | 268 +++--- tests/ExtraTests/X40-QuickExit.cpp | 28 + .../Baselines/automake.sw.approved.txt | 18 +- .../Baselines/automake.sw.multi.approved.txt | 18 +- .../Baselines/compact.sw.approved.txt | 68 +- .../Baselines/compact.sw.multi.approved.txt | 68 +- .../Baselines/console.std.approved.txt | 117 ++- .../Baselines/console.sw.approved.txt | 333 +++++++- .../Baselines/console.sw.multi.approved.txt | 333 +++++++- .../Baselines/console.swa4.approved.txt | 25 +- .../SelfTest/Baselines/junit.sw.approved.txt | 135 ++- .../Baselines/junit.sw.multi.approved.txt | 135 ++- .../Baselines/sonarqube.sw.approved.txt | 130 ++- .../Baselines/sonarqube.sw.multi.approved.txt | 130 ++- tests/SelfTest/Baselines/tap.sw.approved.txt | 110 ++- .../Baselines/tap.sw.multi.approved.txt | 110 ++- .../Baselines/teamcity.sw.approved.txt | 54 +- .../Baselines/teamcity.sw.multi.approved.txt | 54 +- tests/SelfTest/Baselines/xml.sw.approved.txt | 383 ++++++++- .../Baselines/xml.sw.multi.approved.txt | 383 ++++++++- .../AssertionHandler.tests.cpp | 25 + .../IntrospectiveTests/Clara.tests.cpp | 26 +- .../GeneratorsImpl.tests.cpp | 13 + .../IntrospectiveTests/Integer.tests.cpp | 2 +- .../IntrospectiveTests/Json.tests.cpp | 30 +- .../RandomNumberGeneration.tests.cpp | 4 +- .../IntrospectiveTests/ToString.tests.cpp | 56 +- .../SelfTest/IntrospectiveTests/Xml.tests.cpp | 24 +- tests/SelfTest/TestRegistrations.cpp | 1 + .../SelfTest/UsageTests/Compilation.tests.cpp | 17 + tests/SelfTest/UsageTests/Message.tests.cpp | 50 ++ tests/SelfTest/UsageTests/Misc.tests.cpp | 18 +- .../UsageTests/ToStringChrono.tests.cpp | 6 + tests/TestScripts/ConfigureTestsCommon.py | 2 +- .../TestScripts/DiscoverTests/CMakeLists.txt | 20 +- .../DiscoverTests/VerifyRegistration.py | 2 +- tests/TestScripts/testBazelExitGuardFile.py | 88 ++ tests/TestScripts/testBazelSharding.py | 7 +- tools/misc/CMakeLists.txt | 11 +- .../misc/appveyorBuildConfigurationScript.bat | 8 +- tools/misc/installOpenCppCoverage.ps1 | 2 +- tools/scripts/approvalTests.py | 6 +- tools/scripts/checkConvenienceHeaders.py | 4 +- tools/scripts/releaseCommon.py | 2 +- 196 files changed, 6525 insertions(+), 2362 deletions(-) delete mode 100644 .github/workflows/mac-builds-m1.yml create mode 100644 MAINTAINERS.md create mode 100644 benchmarks/CMakeLists.txt create mode 100644 benchmarks/assertion_listener.cpp create mode 100644 benchmarks/runtime_assertion_benches.cpp delete mode 100644 data/artwork/catch2-c-logo.png create mode 100644 data/artwork/catch2-c-logo.svg delete mode 100644 data/artwork/catch2-hand-logo.png create mode 100644 data/artwork/catch2-hand-logo.svg create mode 100644 data/artwork/catch2-logo-full-with-background.svg create mode 100644 data/artwork/catch2-logo-full.svg delete mode 100644 data/artwork/catch2-logo-small-with-background.png delete mode 100644 data/artwork/catch2-logo-small.png create mode 100644 data/sponsors/github_repo_sponsorship.png create mode 100644 docs/thread-safety.md rename src/catch2/{internal => }/catch_test_run_info.hpp (100%) create mode 100644 src/catch2/internal/catch_deprecation_macro.hpp create mode 100644 src/catch2/internal/catch_thread_support.hpp create mode 100644 src/catch2/internal/catch_unreachable.hpp create mode 100644 tests/ExtraTests/X40-QuickExit.cpp create mode 100755 tests/TestScripts/testBazelExitGuardFile.py diff --git a/.bazelrc b/.bazelrc index 6084470..b10a64f 100644 --- a/.bazelrc +++ b/.bazelrc @@ -1,10 +1,8 @@ -# Enable Bzlmod for every Bazel command -common --enable_bzlmod - build --enable_platform_specific_config build:gcc9 --cxxopt=-std=c++2a build:gcc11 --cxxopt=-std=c++2a +build:gcc13 --cxxopt=-std=c++2a build:clang13 --cxxopt=-std=c++17 build:vs2019 --cxxopt=/std:c++17 build:vs2022 --cxxopt=/std:c++17 diff --git a/.clang-tidy b/.clang-tidy index 3488457..fd18173 100644 --- a/.clang-tidy +++ b/.clang-tidy @@ -78,5 +78,5 @@ WarningsAsErrors: >- readability-duplicate-include, HeaderFilterRegex: '.*\.(c|cxx|cpp)$' FormatStyle: none -CheckOptions: {} +CheckOptions: [] ... diff --git a/.conan/test_package/CMakeLists.txt b/.conan/test_package/CMakeLists.txt index f067457..04d638a 100644 --- a/.conan/test_package/CMakeLists.txt +++ b/.conan/test_package/CMakeLists.txt @@ -1,8 +1,7 @@ cmake_minimum_required(VERSION 3.16) -project(PackageTest CXX) +project(PackageTest LANGUAGES CXX) find_package(Catch2 CONFIG REQUIRED) add_executable(test_package test_package.cpp) target_link_libraries(test_package Catch2::Catch2WithMain) -target_compile_features(test_package PRIVATE cxx_std_14) diff --git a/.github/workflows/linux-bazel-builds.yml b/.github/workflows/linux-bazel-builds.yml index dc826ac..92a1c5b 100644 --- a/.github/workflows/linux-bazel-builds.yml +++ b/.github/workflows/linux-bazel-builds.yml @@ -1,4 +1,4 @@ -name: Bazel build +name: Linux Builds (Bazel) on: [push, pull_request] @@ -7,18 +7,18 @@ jobs: name: Linux Ubuntu 22.04 Bazel build runs-on: ubuntu-22.04 strategy: + fail-fast: false matrix: compilation_mode: [fastbuild, dbg, opt] steps: - uses: actions/checkout@v4 - - name: Mount bazel cache + - name: Mount Bazel cache uses: actions/cache@v3 with: path: "/home/runner/.cache/bazel" key: bazel-ubuntu22-gcc11 - - name: Build Catch2 - run: | - bazelisk build --compilation_mode=${{matrix.compilation_mode}} //... + - name: Build + run: bazelisk build --compilation_mode=${{matrix.compilation_mode}} //... diff --git a/.github/workflows/linux-meson-builds.yml b/.github/workflows/linux-meson-builds.yml index 4a6cfd5..e48dea9 100644 --- a/.github/workflows/linux-meson-builds.yml +++ b/.github/workflows/linux-meson-builds.yml @@ -1,4 +1,4 @@ -name: Linux builds (meson) +name: Linux Builds (Meson) on: [push, pull_request] @@ -7,6 +7,7 @@ jobs: name: meson ${{matrix.cxx}}, C++${{matrix.std}}, ${{matrix.build_type}} runs-on: ubuntu-22.04 strategy: + fail-fast: false matrix: cxx: - g++-11 @@ -25,7 +26,7 @@ jobs: sudo apt-get update sudo apt-get install -y meson ninja-build ${{matrix.other_pkgs}} - - name: Configure build + - name: Configure env: CXX: ${{matrix.cxx}} CXXFLAGS: -std=c++${{matrix.std}} ${{matrix.cxxflags}} @@ -34,11 +35,10 @@ jobs: run: | meson -Dbuildtype=${{matrix.build_type}} ${{runner.workspace}}/meson-build - - name: Build tests + lib + - name: Build working-directory: ${{runner.workspace}}/meson-build run: ninja - - name: Run tests + - name: Test working-directory: ${{runner.workspace}}/meson-build - run: | - meson test --verbose + run: meson test --verbose diff --git a/.github/workflows/linux-other-builds.yml b/.github/workflows/linux-other-builds.yml index 6993c81..6095f24 100644 --- a/.github/workflows/linux-other-builds.yml +++ b/.github/workflows/linux-other-builds.yml @@ -1,72 +1,73 @@ # The builds in this file are more complex (e.g. they need custom CMake # configuration) and thus are unsuitable to the simple build matrix # approach used in simple-builds -name: Linux builds (complex) +name: Linux Builds (Complex) on: [push, pull_request] jobs: build: name: ${{matrix.build_description}}, ${{matrix.cxx}}, C++${{matrix.std}} ${{matrix.build_type}} - runs-on: ubuntu-20.04 + runs-on: ubuntu-22.04 strategy: + fail-fast: false matrix: # We add builds one by one in this case, because there are no # dimensions that are shared across the builds include: # Single surrogate header build - - cxx: clang++-10 + - cxx: clang++-14 build_description: Surrogates build build_type: Debug std: 14 - other_pkgs: clang-10 + other_pkgs: clang-14 cmake_configurations: -DCATCH_BUILD_SURROGATES=ON - # Extras and examples with gcc-7 - - cxx: g++-7 + # Extras and examples with gcc-11 + - cxx: g++-11 build_description: Extras + Examples build_type: Debug std: 14 - other_pkgs: g++-7 + other_pkgs: g++-11 cmake_configurations: -DCATCH_BUILD_EXTRA_TESTS=ON -DCATCH_BUILD_EXAMPLES=ON -DCATCH_ENABLE_CMAKE_HELPER_TESTS=ON - - cxx: g++-7 + - cxx: g++-11 build_description: Extras + Examples build_type: Release std: 14 - other_pkgs: g++-7 + other_pkgs: g++-11 cmake_configurations: -DCATCH_BUILD_EXTRA_TESTS=ON -DCATCH_BUILD_EXAMPLES=ON -DCATCH_ENABLE_CMAKE_HELPER_TESTS=ON - # Extras and examples with Clang-10 - - cxx: clang++-10 + # Extras and examples with Clang-14 + - cxx: clang++-14 build_description: Extras + Examples build_type: Debug std: 17 - other_pkgs: clang-10 + other_pkgs: clang-14 cmake_configurations: -DCATCH_BUILD_EXTRA_TESTS=ON -DCATCH_BUILD_EXAMPLES=ON -DCATCH_ENABLE_CMAKE_HELPER_TESTS=ON - - cxx: clang++-10 + - cxx: clang++-14 build_description: Extras + Examples build_type: Release std: 17 - other_pkgs: clang-10 + other_pkgs: clang-14 cmake_configurations: -DCATCH_BUILD_EXTRA_TESTS=ON -DCATCH_BUILD_EXAMPLES=ON -DCATCH_ENABLE_CMAKE_HELPER_TESTS=ON - # Configure tests with Clang-10 - - cxx: clang++-10 + # Configure tests with Clang-14 + - cxx: clang++-14 build_description: CMake configuration tests build_type: Debug std: 14 - other_pkgs: clang-10 + other_pkgs: clang-14 cmake_configurations: -DCATCH_ENABLE_CONFIGURE_TESTS=ON - # Valgrind test Clang-10 - - cxx: clang++-10 - build_description: Valgrind tests - build_type: Debug - std: 14 - other_pkgs: clang-10 valgrind - cmake_configurations: -DMEMORYCHECK_COMMAND=`which valgrind` -DMEMORYCHECK_COMMAND_OPTIONS="-q --track-origins=yes --leak-check=full --num-callers=50 --show-leak-kinds=definite --error-exitcode=1" - other_ctest_args: -T memcheck -LE uses-python + # Valgrind test Clang-14 + # - cxx: clang++-14 + # build_description: Valgrind tests + # build_type: Debug + # std: 14 + # other_pkgs: clang-14 valgrind + # cmake_configurations: -DMEMORYCHECK_COMMAND=`which valgrind` -DMEMORYCHECK_COMMAND_OPTIONS="-q --track-origins=yes --leak-check=full --num-callers=50 --show-leak-kinds=definite --error-exitcode=1" + # other_ctest_args: -T memcheck -LE uses-python steps: @@ -78,77 +79,47 @@ jobs: sudo apt-get install -y ninja-build ${{matrix.other_pkgs}} - name: Configure build - working-directory: ${{runner.workspace}} - env: - CXX: ${{matrix.cxx}} - CXXFLAGS: ${{matrix.cxxflags}} - # Note: $GITHUB_WORKSPACE is distinct from ${{runner.workspace}}. - # This is important run: | - cmake -Bbuild -H$GITHUB_WORKSPACE \ + cmake --preset basic-tests -GNinja \ -DCMAKE_BUILD_TYPE=${{matrix.build_type}} \ + -DCMAKE_CXX_COMPILER=${{matrix.cxx}} \ -DCMAKE_CXX_STANDARD=${{matrix.std}} \ - -DCMAKE_CXX_STANDARD_REQUIRED=ON \ - -DCMAKE_CXX_EXTENSIONS=OFF \ - -DCATCH_DEVELOPMENT_BUILD=ON \ - ${{matrix.cmake_configurations}} \ - -G Ninja - - - name: Build tests + lib - working-directory: ${{runner.workspace}}/build - run: ninja - - - name: Run tests - env: - CTEST_OUTPUT_ON_FAILURE: 1 - working-directory: ${{runner.workspace}}/build - run: ctest -C ${{matrix.build_type}} -j `nproc` ${{matrix.other_ctest_args}} + ${{matrix.cmake_configurations}} + + - name: Build + run: cmake --build build + + - name: Test + run: ctest --test-dir build -j --output-on-failure + clang-tidy: - name: clang-tidy ${{matrix.version}}, ${{matrix.build_description}}, C++${{matrix.std}} ${{matrix.build_type}} + name: clang-tidy runs-on: ubuntu-22.04 - strategy: - matrix: - include: - - version: "15" - build_description: all - build_type: Debug - std: 17 - other_pkgs: '' - cmake_configurations: -DCATCH_BUILD_EXAMPLES=ON -DCATCH_ENABLE_CMAKE_HELPER_TESTS=ON steps: - uses: actions/checkout@v4 - name: Prepare environment run: | sudo apt-get update - sudo apt-get install -y ninja-build clang-${{matrix.version}} clang-tidy-${{matrix.version}} ${{matrix.other_pkgs}} + sudo apt-get install -y ninja-build clang-15 clang-tidy-15 - - name: Configure build - working-directory: ${{runner.workspace}} - env: - CXX: clang++-${{matrix.version}} - CXXFLAGS: ${{matrix.cxxflags}} - # Note: $GITHUB_WORKSPACE is distinct from ${{runner.workspace}}. - # This is important + - name: Configure run: | - clangtidy="clang-tidy-${{matrix.version}};-use-color" + clangtidy="clang-tidy-15;-use-color" # Use a dummy compiler/linker/ar/ranlib to effectively disable the # compilation and only run clang-tidy. - cmake -Bbuild -H$GITHUB_WORKSPACE \ - -DCMAKE_BUILD_TYPE=${{matrix.build_type}} \ - -DCMAKE_CXX_STANDARD=${{matrix.std}} \ - -DCMAKE_CXX_STANDARD_REQUIRED=ON \ - -DCMAKE_CXX_EXTENSIONS=OFF \ - -DCATCH_DEVELOPMENT_BUILD=ON \ - -DCMAKE_CXX_CLANG_TIDY="$clangtidy" \ - -DCMAKE_CXX_COMPILER_LAUNCHER=/usr/bin/true \ + cmake --preset basic-tests -GNinja \ -DCMAKE_AR=/usr/bin/true \ + -DCMAKE_BUILD_TYPE=Debug \ + -DCMAKE_CXX_CLANG_TIDY="$clangtidy" \ -DCMAKE_CXX_COMPILER_AR=/usr/bin/true \ - -DCMAKE_RANLIB=/usr/bin/true \ + -DCMAKE_CXX_COMPILER_LAUNCHER=/usr/bin/true \ + -DCMAKE_CXX_COMPILER=clang++-15 \ -DCMAKE_CXX_LINK_EXECUTABLE=/usr/bin/true \ - ${{matrix.cmake_configurations}} \ - -G Ninja + -DCMAKE_CXX_STANDARD=17 \ + -DCMAKE_RANLIB=/usr/bin/true \ + -DCATCH_BUILD_EXAMPLES=ON \ + -DCATCH_ENABLE_CMAKE_HELPER_TESTS=ON - name: Run clang-tidy - working-directory: ${{runner.workspace}}/build - run: ninja + run: cmake --build build diff --git a/.github/workflows/linux-simple-builds.yml b/.github/workflows/linux-simple-builds.yml index 4cca316..15db5cf 100644 --- a/.github/workflows/linux-simple-builds.yml +++ b/.github/workflows/linux-simple-builds.yml @@ -1,40 +1,39 @@ -name: Linux builds (basic) +name: Linux Builds (Basic) on: [push, pull_request] jobs: build: name: ${{matrix.cxx}}, C++${{matrix.std}}, ${{matrix.build_type}} - runs-on: ubuntu-20.04 + runs-on: ubuntu-22.04 strategy: + fail-fast: false matrix: cxx: - - g++-5 - - g++-6 - - g++-7 - - g++-8 - g++-9 - g++-10 + - g++-11 + - g++-12 - clang++-6.0 - clang++-7 - clang++-8 - clang++-9 - clang++-10 + - clang++-11 + - clang++-12 + - clang++-13 + - clang++-14 build_type: [Debug, Release] std: [14] include: - - cxx: g++-5 - other_pkgs: g++-5 - - cxx: g++-6 - other_pkgs: g++-6 - - cxx: g++-7 - other_pkgs: g++-7 - - cxx: g++-8 - other_pkgs: g++-8 - cxx: g++-9 other_pkgs: g++-9 - cxx: g++-10 other_pkgs: g++-10 + - cxx: g++-11 + other_pkgs: g++-11 + - cxx: g++-12 + other_pkgs: g++-12 - cxx: clang++-6.0 other_pkgs: clang-6.0 - cxx: clang++-7 @@ -45,79 +44,62 @@ jobs: other_pkgs: clang-9 - cxx: clang++-10 other_pkgs: clang-10 - # Clang 6 + C++17 - # does not work with the default libstdc++ version thanks - # to a disagreement on variant implementation. - # - cxx: clang++-6.0 - # build_type: Debug - # std: 17 - # other_pkgs: clang-6.0 - # - cxx: clang++-6.0 - # build_type: Release - # std: 17 - # other_pkgs: clang-6.0 - # Clang 10 + C++17 - - cxx: clang++-10 + - cxx: clang++-11 + other_pkgs: clang-11 + - cxx: clang++-12 + other_pkgs: clang-12 + - cxx: clang++-13 + other_pkgs: clang-13 + - cxx: clang++-14 + other_pkgs: clang-14 + # Clang 14 + C++17 + - cxx: clang++-14 build_type: Debug std: 17 - other_pkgs: clang-10 - - cxx: clang++-10 + other_pkgs: clang-14 + - cxx: clang++-14 build_type: Release std: 17 - other_pkgs: clang-10 - - cxx: clang++-10 + other_pkgs: clang-14 + - cxx: clang++-14 build_type: Debug std: 20 - other_pkgs: clang-10 - - cxx: clang++-10 + other_pkgs: clang-14 + - cxx: clang++-14 build_type: Release std: 20 - other_pkgs: clang-10 - - cxx: g++-10 + other_pkgs: clang-14 + - cxx: g++-11 build_type: Debug std: 20 - other_pkgs: g++-10 - - cxx: g++-10 + other_pkgs: g++-11 + - cxx: g++-11 build_type: Release std: 20 - other_pkgs: g++-10 + other_pkgs: g++-11 steps: - uses: actions/checkout@v4 - - name: Add repositories for older GCC + - name: Add repositories for older compilers run: | - sudo apt-add-repository 'deb http://azure.archive.ubuntu.com/ubuntu/ bionic main' - sudo apt-add-repository 'deb http://azure.archive.ubuntu.com/ubuntu/ bionic universe' - if: ${{ matrix.cxx == 'g++-5' || matrix.cxx == 'g++-6' }} + sudo apt-add-repository 'deb http://azure.archive.ubuntu.com/ubuntu/ focal main' + sudo apt-add-repository 'deb http://azure.archive.ubuntu.com/ubuntu/ focal universe' - name: Prepare environment run: | sudo apt-get update sudo apt-get install -y ninja-build ${{matrix.other_pkgs}} - - name: Configure build - working-directory: ${{runner.workspace}} - env: - CXX: ${{matrix.cxx}} - CXXFLAGS: ${{matrix.cxxflags}} - # Note: $GITHUB_WORKSPACE is distinct from ${{runner.workspace}}. - # This is important + - name: Configure run: | - cmake -Bbuild -H$GITHUB_WORKSPACE \ + cmake --preset basic-tests -GNinja \ -DCMAKE_BUILD_TYPE=${{matrix.build_type}} \ - -DCMAKE_CXX_STANDARD=${{matrix.std}} \ - -DCMAKE_CXX_STANDARD_REQUIRED=ON \ - -DCMAKE_CXX_EXTENSIONS=OFF \ - -DCATCH_DEVELOPMENT_BUILD=ON \ - -G Ninja + -DCMAKE_CXX_COMPILER=${{matrix.cxx}} \ + -DCMAKE_CXX_STANDARD=${{matrix.std}} - - name: Build tests + lib - working-directory: ${{runner.workspace}}/build - run: ninja + - name: Build + run: cmake --build build - - name: Run tests - env: - CTEST_OUTPUT_ON_FAILURE: 1 - working-directory: ${{runner.workspace}}/build - run: ctest -C ${{matrix.build_type}} -j `nproc` + - name: Test + run: ctest --test-dir build -j --output-on-failure diff --git a/.github/workflows/mac-builds-m1.yml b/.github/workflows/mac-builds-m1.yml deleted file mode 100644 index 45629cd..0000000 --- a/.github/workflows/mac-builds-m1.yml +++ /dev/null @@ -1,44 +0,0 @@ -name: Arm Mac builds - -on: [push, pull_request] - -jobs: - build: - runs-on: macos-14 - strategy: - matrix: - cxx: - - clang++ - build_type: [Debug, Release] - std: [14, 17] - include: - - build_type: Debug - examples: ON - extra_tests: ON - - steps: - - uses: actions/checkout@v4 - - - name: Configure build - working-directory: ${{runner.workspace}} - env: - CXX: ${{matrix.cxx}} - CXXFLAGS: ${{matrix.cxxflags}} - run: | - cmake -Bbuild -H$GITHUB_WORKSPACE \ - -DCMAKE_BUILD_TYPE=${{matrix.build_type}} \ - -DCMAKE_CXX_STANDARD=${{matrix.std}} \ - -DCMAKE_CXX_STANDARD_REQUIRED=ON \ - -DCATCH_DEVELOPMENT_BUILD=ON \ - -DCATCH_BUILD_EXAMPLES=${{matrix.examples}} \ - -DCATCH_BUILD_EXTRA_TESTS=${{matrix.examples}} - - - name: Build tests + lib - working-directory: ${{runner.workspace}}/build - run: make -j `sysctl -n hw.ncpu` - - - name: Run tests - env: - CTEST_OUTPUT_ON_FAILURE: 1 - working-directory: ${{runner.workspace}}/build - run: ctest -C ${{matrix.build_type}} -j `sysctl -n hw.ncpu` diff --git a/.github/workflows/mac-builds.yml b/.github/workflows/mac-builds.yml index fb41f5d..6ac4c2a 100644 --- a/.github/workflows/mac-builds.yml +++ b/.github/workflows/mac-builds.yml @@ -1,48 +1,32 @@ -name: Intel Mac builds +name: Mac Builds on: [push, pull_request] jobs: build: # From macos-14 forward, the baseline "macos-X" image is Arm based, - # and not Intel based. Thus this is the newest image we can use for - # Intel MacOS CI, and there don't seem to be any plans to keep providing - # the Intel based images for free to OSS projects. - runs-on: macos-13 + # and not Intel based. + runs-on: ${{matrix.image}} strategy: + fail-fast: false matrix: - cxx: - - clang++ + image: [macos-13, macos-14, macos-15] build_type: [Debug, Release] std: [14, 17] - include: - - build_type: Debug - examples: ON - extra_tests: ON steps: - uses: actions/checkout@v4 - - name: Configure build - working-directory: ${{runner.workspace}} - env: - CXX: ${{matrix.cxx}} - CXXFLAGS: ${{matrix.cxxflags}} + - name: Configure run: | - cmake -Bbuild -H$GITHUB_WORKSPACE \ + cmake --preset basic-tests -GNinja \ -DCMAKE_BUILD_TYPE=${{matrix.build_type}} \ -DCMAKE_CXX_STANDARD=${{matrix.std}} \ - -DCMAKE_CXX_STANDARD_REQUIRED=ON \ - -DCATCH_DEVELOPMENT_BUILD=ON \ - -DCATCH_BUILD_EXAMPLES=${{matrix.examples}} \ - -DCATCH_BUILD_EXTRA_TESTS=${{matrix.examples}} + -DCATCH_BUILD_EXAMPLES=ON \ + -DCATCH_BUILD_EXTRA_TESTS=ON - - name: Build tests + lib - working-directory: ${{runner.workspace}}/build - run: make -j `sysctl -n hw.ncpu` + - name: Build + run: cmake --build build - - name: Run tests - env: - CTEST_OUTPUT_ON_FAILURE: 1 - working-directory: ${{runner.workspace}}/build - run: ctest -C ${{matrix.build_type}} -j `sysctl -n hw.ncpu` + - name: Test + run: ctest --test-dir build -j --output-on-failure diff --git a/.github/workflows/package-manager-builds.yaml b/.github/workflows/package-manager-builds.yaml index 6d90d14..74704c6 100644 --- a/.github/workflows/package-manager-builds.yaml +++ b/.github/workflows/package-manager-builds.yaml @@ -5,7 +5,7 @@ on: [push, pull_request] jobs: conan_builds: name: Conan ${{matrix.conan_version}} - runs-on: ubuntu-20.04 + runs-on: ubuntu-22.04 strategy: matrix: conan_version: diff --git a/.github/workflows/validate-header-guards.yml b/.github/workflows/validate-header-guards.yml index fa9d157..13dac2a 100644 --- a/.github/workflows/validate-header-guards.yml +++ b/.github/workflows/validate-header-guards.yml @@ -5,7 +5,7 @@ on: [push, pull_request] jobs: build: # Set the type of machine to run on - runs-on: ubuntu-20.04 + runs-on: ubuntu-22.04 steps: - name: Checkout source code diff --git a/.github/workflows/windows-simple-builds.yml b/.github/workflows/windows-simple-builds.yml index 5fb7b8f..e6d852b 100644 --- a/.github/workflows/windows-simple-builds.yml +++ b/.github/workflows/windows-simple-builds.yml @@ -1,4 +1,4 @@ -name: Windows builds (basic) +name: Windows Builds (Basic) on: [push, pull_request] @@ -7,8 +7,9 @@ jobs: name: ${{matrix.os}}, ${{matrix.std}}, ${{matrix.build_type}}, ${{matrix.platform}} runs-on: ${{matrix.os}} strategy: + fail-fast: false matrix: - os: [windows-2019, windows-2022] + os: [windows-2022, windows-2025] platform: [Win32, x64] build_type: [Debug, Release] std: [14, 17] @@ -16,22 +17,15 @@ jobs: - uses: actions/checkout@v4 - name: Configure build - working-directory: ${{runner.workspace}} run: | - cmake -S $Env:GITHUB_WORKSPACE ` - -B ${{runner.workspace}}/build ` - -DCMAKE_CXX_STANDARD=${{matrix.std}} ` - -A ${{matrix.platform}} ` - --preset all-tests + cmake --preset all-tests ` + -A ${{matrix.platform}} ` + -DCMAKE_CXX_STANDARD=${{matrix.std}} ` - name: Build tests - working-directory: ${{runner.workspace}} run: cmake --build build --config ${{matrix.build_type}} --parallel %NUMBER_OF_PROCESSORS% shell: cmd - name: Run tests - working-directory: ${{runner.workspace}}/build - env: - CTEST_OUTPUT_ON_FAILURE: 1 - run: ctest -C ${{matrix.build_type}} -j %NUMBER_OF_PROCESSORS% + run: ctest --test-dir build -C ${{matrix.build_type}} -j %NUMBER_OF_PROCESSORS% --output-on-failure shell: cmd diff --git a/BUILD.bazel b/BUILD.bazel index d96c7fa..eb484e4 100644 --- a/BUILD.bazel +++ b/BUILD.bazel @@ -1,4 +1,20 @@ load("@bazel_skylib//rules:expand_template.bzl", "expand_template") +load("@rules_cc//cc:cc_library.bzl", "cc_library") +load("@rules_license//rules:license.bzl", "license") + +package( + default_applicable_licenses = [":license"], +) + +exports_files([ + "LICENSE.MIT", +]) + +license( + name = "license", + license_kinds = ["@rules_license//licenses/spdx:BSL-1.0"], + license_text = "LICENSE.MIT", +) expand_template( name = "catch_user_config", @@ -16,6 +32,7 @@ expand_template( "#cmakedefine CATCH_CONFIG_CPP17_STRING_VIEW": "", "#cmakedefine CATCH_CONFIG_CPP17_UNCAUGHT_EXCEPTIONS": "", "#cmakedefine CATCH_CONFIG_CPP17_VARIANT": "", + "#cmakedefine CATCH_CONFIG_DEPRECATION_ANNOTATIONS": "", "#cmakedefine CATCH_CONFIG_DISABLE_EXCEPTIONS_CUSTOM_HANDLER": "", "#cmakedefine CATCH_CONFIG_DISABLE_EXCEPTIONS": "", "#cmakedefine CATCH_CONFIG_DISABLE_STRINGIFICATION": "", @@ -39,6 +56,7 @@ expand_template( "#cmakedefine CATCH_CONFIG_NO_CPP17_STRING_VIEW": "", "#cmakedefine CATCH_CONFIG_NO_CPP17_UNCAUGHT_EXCEPTIONS": "", "#cmakedefine CATCH_CONFIG_NO_CPP17_VARIANT": "", + "#cmakedefine CATCH_CONFIG_NO_DEPRECATION_ANNOTATIONS": "", "#cmakedefine CATCH_CONFIG_NO_GETENV": "", "#cmakedefine CATCH_CONFIG_NO_GLOBAL_NEXTAFTER": "", "#cmakedefine CATCH_CONFIG_NO_POSIX_SIGNALS": "", @@ -58,6 +76,8 @@ expand_template( "#cmakedefine CATCH_CONFIG_WINDOWS_SEH": "", "#cmakedefine CATCH_CONFIG_USE_BUILTIN_CONSTANT_P": "", "#cmakedefine CATCH_CONFIG_NO_USE_BUILTIN_CONSTANT_P": "", + "#cmakedefine CATCH_CONFIG_EXPERIMENTAL_THREAD_SAFE_ASSERTIONS": "", + "#cmakedefine CATCH_CONFIG_NO_EXPERIMENTAL_THREAD_SAFE_ASSERTIONS": "", }, template = "src/catch2/catch_user_config.hpp.in", ) @@ -68,7 +88,7 @@ expand_template( cc_library( name = "catch2_generated", hdrs = ["catch2/catch_user_config.hpp"], - include_prefix = ".", # to manipulate -I of dependenices + include_prefix = ".", # to manipulate -I of dependencies visibility = ["//visibility:public"], ) diff --git a/CMake/Catch2Config.cmake.in b/CMake/Catch2Config.cmake.in index c485219..9268138 100644 --- a/CMake/Catch2Config.cmake.in +++ b/CMake/Catch2Config.cmake.in @@ -1,10 +1,9 @@ @PACKAGE_INIT@ - # Avoid repeatedly including the targets if(NOT TARGET Catch2::Catch2) - # Provide path for scripts - list(APPEND CMAKE_MODULE_PATH "${CMAKE_CURRENT_LIST_DIR}") + # Provide path for scripts + list(APPEND CMAKE_MODULE_PATH "${CMAKE_CURRENT_LIST_DIR}") - include(${CMAKE_CURRENT_LIST_DIR}/Catch2Targets.cmake) + include(${CMAKE_CURRENT_LIST_DIR}/Catch2Targets.cmake) endif() diff --git a/CMake/CatchConfigOptions.cmake b/CMake/CatchConfigOptions.cmake index a2f2870..3621fac 100644 --- a/CMake/CatchConfigOptions.cmake +++ b/CMake/CatchConfigOptions.cmake @@ -14,7 +14,6 @@ # # For detailed docs look into docs/configuration.md - macro(AddOverridableConfigOption OptionBaseName) option(CATCH_CONFIG_${OptionBaseName} "Read docs/configuration.md for details" OFF) option(CATCH_CONFIG_NO_${OptionBaseName} "Read docs/configuration.md for details" OFF) @@ -45,6 +44,8 @@ set(_OverridableOptions "GETENV" "EXPERIMENTAL_STATIC_ANALYSIS_SUPPORT" "USE_BUILTIN_CONSTANT_P" + "DEPRECATION_ANNOTATIONS" + "EXPERIMENTAL_THREAD_SAFE_ASSERTIONS" ) foreach(OptionName ${_OverridableOptions}) @@ -74,9 +75,9 @@ foreach(OptionName ${_OtherConfigOptions}) AddConfigOption(${OptionName}) endforeach() if(DEFINED BUILD_SHARED_LIBS) - set(CATCH_CONFIG_SHARED_LIBRARY ${BUILD_SHARED_LIBS}) + set(CATCH_CONFIG_SHARED_LIBRARY ${BUILD_SHARED_LIBS}) else() - set(CATCH_CONFIG_SHARED_LIBRARY "") + set(CATCH_CONFIG_SHARED_LIBRARY "") endif() set(CATCH_CONFIG_DEFAULT_REPORTER "console" CACHE STRING "Read docs/configuration.md for details. The name of the reporter should be without quotes.") diff --git a/CMake/CatchMiscFunctions.cmake b/CMake/CatchMiscFunctions.cmake index 05bc83c..d1cf5fc 100644 --- a/CMake/CatchMiscFunctions.cmake +++ b/CMake/CatchMiscFunctions.cmake @@ -8,108 +8,107 @@ include(CheckCXXCompilerFlag) function(add_cxx_flag_if_supported_to_targets flagname targets) - string(MAKE_C_IDENTIFIER ${flagname} flag_identifier ) - check_cxx_compiler_flag("${flagname}" HAVE_FLAG_${flag_identifier}) + string(MAKE_C_IDENTIFIER ${flagname} flag_identifier) + check_cxx_compiler_flag("${flagname}" HAVE_FLAG_${flag_identifier}) - if (HAVE_FLAG_${flag_identifier}) - foreach(target ${targets}) - target_compile_options(${target} PRIVATE ${flagname}) - endforeach() - endif() + if(HAVE_FLAG_${flag_identifier}) + foreach(target ${targets}) + target_compile_options(${target} PRIVATE ${flagname}) + endforeach() + endif() endfunction() # Assumes that it is only called for development builds, where warnings # and Werror is desired, so it also enables Werror. function(add_warnings_to_targets targets) - LIST(LENGTH targets TARGETS_LEN) - # For now we just assume 2 possibilities: msvc and msvc-like compilers, - # and other. - if (MSVC) - foreach(target ${targets}) - # Force MSVC to consider everything as encoded in utf-8 - target_compile_options( ${target} PRIVATE /utf-8 ) - # Enable Werror equivalent - if (CATCH_ENABLE_WERROR) - target_compile_options( ${target} PRIVATE /WX ) - endif() - - # MSVC is currently handled specially - if ( CMAKE_CXX_COMPILER_ID MATCHES "MSVC" ) - STRING(REGEX REPLACE "/W[0-9]" "/W4" CMAKE_CXX_FLAGS ${CMAKE_CXX_FLAGS}) # override default warning level - target_compile_options( ${target} PRIVATE /w44265 /w44061 /w44062 /w45038 ) - endif() - endforeach() + LIST(LENGTH targets TARGETS_LEN) + # For now we just assume 2 possibilities: msvc and msvc-like compilers, + # and other. + if(MSVC) + foreach(target ${targets}) + # Force MSVC to consider everything as encoded in utf-8 + target_compile_options(${target} PRIVATE /utf-8) + # Enable Werror equivalent + if(CATCH_ENABLE_WERROR) + target_compile_options(${target} PRIVATE /WX) + endif() - endif() + # MSVC is currently handled specially + if(CMAKE_CXX_COMPILER_ID MATCHES "MSVC") + STRING(REGEX REPLACE "/W[0-9]" "/W4" CMAKE_CXX_FLAGS ${CMAKE_CXX_FLAGS}) # override default warning level + target_compile_options(${target} PRIVATE /w44265 /w44061 /w44062 /w45038) + endif() + endforeach() + endif() - if (NOT MSVC) - set(CHECKED_WARNING_FLAGS - "-Wabsolute-value" - "-Wall" - "-Wcall-to-pure-virtual-from-ctor-dtor" - "-Wcast-align" - "-Wcatch-value" - "-Wdangling" - "-Wdeprecated" - "-Wdeprecated-register" - "-Wexceptions" - "-Wexit-time-destructors" - "-Wextra" - "-Wextra-semi" - "-Wfloat-equal" - "-Wglobal-constructors" - "-Winit-self" - "-Wmisleading-indentation" - "-Wmismatched-new-delete" - "-Wmismatched-return-types" - "-Wmismatched-tags" - "-Wmissing-braces" - "-Wmissing-declarations" - "-Wmissing-noreturn" - "-Wmissing-prototypes" - "-Wmissing-variable-declarations" - "-Wnon-virtual-dtor" - "-Wnull-dereference" - "-Wold-style-cast" - "-Woverloaded-virtual" - "-Wparentheses" - "-Wpedantic" - "-Wredundant-decls" - "-Wreorder" - "-Wreturn-std-move" - "-Wshadow" - "-Wstrict-aliasing" - "-Wsubobject-linkage" - "-Wsuggest-destructor-override" - "-Wsuggest-override" - "-Wundef" - "-Wuninitialized" - "-Wunneeded-internal-declaration" - "-Wunreachable-code-aggressive" - "-Wunused" - "-Wunused-function" - "-Wunused-parameter" - "-Wvla" - "-Wweak-vtables" + if(NOT MSVC) + set(CHECKED_WARNING_FLAGS + "-Wabsolute-value" + "-Wall" + "-Wcall-to-pure-virtual-from-ctor-dtor" + "-Wcast-align" + "-Wcatch-value" + "-Wdangling" + "-Wdeprecated" + "-Wdeprecated-register" + "-Wexceptions" + "-Wexit-time-destructors" + "-Wextra" + "-Wextra-semi" + "-Wfloat-equal" + "-Wglobal-constructors" + "-Winit-self" + "-Wmisleading-indentation" + "-Wmismatched-new-delete" + "-Wmismatched-return-types" + "-Wmismatched-tags" + "-Wmissing-braces" + "-Wmissing-declarations" + "-Wmissing-noreturn" + "-Wmissing-prototypes" + "-Wmissing-variable-declarations" + "-Wnon-virtual-dtor" + "-Wnull-dereference" + "-Wold-style-cast" + "-Woverloaded-virtual" + "-Wparentheses" + "-Wpedantic" + "-Wredundant-decls" + "-Wreorder" + "-Wreturn-std-move" + "-Wshadow" + "-Wstrict-aliasing" + "-Wsubobject-linkage" + "-Wsuggest-destructor-override" + "-Wsuggest-override" + "-Wundef" + "-Wuninitialized" + "-Wunneeded-internal-declaration" + "-Wunreachable-code-aggressive" + "-Wunused" + "-Wunused-function" + "-Wunused-parameter" + "-Wvla" + "-Wweak-vtables" - # This is a useful warning, but our tests sometimes rely on - # functions being present, but not picked (e.g. various checks - # for stringification implementation ordering). - # Ergo, we should use it every now and then, but we cannot - # enable it by default. - # "-Wunused-member-function" - ) - foreach(warning ${CHECKED_WARNING_FLAGS}) - add_cxx_flag_if_supported_to_targets(${warning} "${targets}") - endforeach() + # This is a useful warning, but our tests sometimes rely on + # functions being present, but not picked (e.g. various checks + # for stringification implementation ordering). + # Ergo, we should use it every now and then, but we cannot + # enable it by default. + # "-Wunused-member-function" + ) + foreach(warning ${CHECKED_WARNING_FLAGS}) + add_cxx_flag_if_supported_to_targets(${warning} "${targets}") + endforeach() - if (CATCH_ENABLE_WERROR) - foreach(target ${targets}) - # Enable Werror equivalent - target_compile_options( ${target} PRIVATE -Werror ) - endforeach() - endif() + if(CATCH_ENABLE_WERROR) + foreach(target ${targets}) + # Enable Werror equivalent + target_compile_options(${target} PRIVATE -Werror) + endforeach() endif() + endif() endfunction() # Adds flags required for reproducible build to the target diff --git a/CMake/catch2-with-main.pc.in b/CMake/catch2-with-main.pc.in index 69a790b..eeac3e3 100644 --- a/CMake/catch2-with-main.pc.in +++ b/CMake/catch2-with-main.pc.in @@ -1,10 +1,10 @@ -includedir=@CMAKE_INSTALL_FULL_INCLUDEDIR@ -libdir=@CMAKE_INSTALL_FULL_LIBDIR@ +prefix=@CMAKE_INSTALL_PREFIX@ +libdir=${prefix}/@lib_dir@ pkg_version=@Catch2_VERSION@ -Name: Catch2-With-Main +Name: Catch2 with main function Description: A modern, C++-native test framework for C++14 and above (links in default main) +URL: https://github.com/catchorg/Catch2 Version: ${pkg_version} Requires: catch2 = ${pkg_version} -Cflags: -I${includedir} -Libs: -L${libdir} -lCatch2Main +Libs: -L${libdir} -l@lib_name@ diff --git a/CMake/catch2.pc.in b/CMake/catch2.pc.in index bd1c95a..74e22c1 100644 --- a/CMake/catch2.pc.in +++ b/CMake/catch2.pc.in @@ -1,11 +1,11 @@ prefix=@CMAKE_INSTALL_PREFIX@ exec_prefix=${prefix} -includedir=@CMAKE_INSTALL_FULL_INCLUDEDIR@ -libdir=@CMAKE_INSTALL_FULL_LIBDIR@ +includedir=${prefix}/@include_dir@ +libdir=${prefix}/@lib_dir@ Name: Catch2 Description: A modern, C++-native, test framework for C++14 and above URL: https://github.com/catchorg/Catch2 Version: @Catch2_VERSION@ Cflags: -I${includedir} -Libs: -L${libdir} -lCatch2 +Libs: -L${libdir} -l@lib_name@ diff --git a/CMakeLists.txt b/CMakeLists.txt index 0d8226b..918927c 100644 --- a/CMakeLists.txt +++ b/CMakeLists.txt @@ -20,22 +20,22 @@ cmake_dependent_option(CATCH_BUILD_TESTING "Build the SelfTest project" ON "CATC cmake_dependent_option(CATCH_BUILD_EXAMPLES "Build code examples" OFF "CATCH_DEVELOPMENT_BUILD" OFF) cmake_dependent_option(CATCH_BUILD_EXTRA_TESTS "Build extra tests" OFF "CATCH_DEVELOPMENT_BUILD" OFF) cmake_dependent_option(CATCH_BUILD_FUZZERS "Build fuzzers" OFF "CATCH_DEVELOPMENT_BUILD" OFF) +cmake_dependent_option(CATCH_BUILD_BENCHMARKS "Build the benchmarks" OFF "CATCH_DEVELOPMENT_BUILD" OFF) cmake_dependent_option(CATCH_ENABLE_COVERAGE "Generate coverage for codecov.io" OFF "CATCH_DEVELOPMENT_BUILD" OFF) cmake_dependent_option(CATCH_ENABLE_WERROR "Enables Werror during build" ON "CATCH_DEVELOPMENT_BUILD" OFF) cmake_dependent_option(CATCH_BUILD_SURROGATES "Enable generating and building surrogate TUs for the main headers" OFF "CATCH_DEVELOPMENT_BUILD" OFF) cmake_dependent_option(CATCH_ENABLE_CONFIGURE_TESTS "Enable CMake configuration tests. WARNING: VERY EXPENSIVE" OFF "CATCH_DEVELOPMENT_BUILD" OFF) cmake_dependent_option(CATCH_ENABLE_CMAKE_HELPER_TESTS "Enable CMake helper tests. WARNING: VERY EXPENSIVE" OFF "CATCH_DEVELOPMENT_BUILD" OFF) - # Catch2's build breaks if done in-tree. You probably should not build # things in tree anyway, but we can allow projects that include Catch2 # as a subproject to build in-tree as long as it is not in our tree. -if (CMAKE_BINARY_DIR STREQUAL CMAKE_CURRENT_SOURCE_DIR) - message(FATAL_ERROR "Building in-source is not supported! Create a build dir and remove ${CMAKE_SOURCE_DIR}/CMakeCache.txt") +if(CMAKE_BINARY_DIR STREQUAL CMAKE_CURRENT_SOURCE_DIR) + message(FATAL_ERROR "Building in-source is not supported! Create a build dir and remove ${CMAKE_SOURCE_DIR}/CMakeCache.txt") endif() project(Catch2 - VERSION 3.8.1 # CML version placeholder, don't delete + VERSION 3.11.0 # CML version placeholder, don't delete LANGUAGES CXX HOMEPAGE_URL "https://github.com/catchorg/Catch2" DESCRIPTION "A modern, C++-native, unit test framework." @@ -46,7 +46,7 @@ project(Catch2 # Then we also add path that we use to configure the project, but is of # no use to top level projects. list(APPEND CMAKE_MODULE_PATH "${CMAKE_CURRENT_LIST_DIR}/extras") -if (NOT NOT_SUBPROJECT) +if(NOT NOT_SUBPROJECT) set(CMAKE_MODULE_PATH "${CMAKE_MODULE_PATH}" PARENT_SCOPE) endif() list(APPEND CMAKE_MODULE_PATH "${CMAKE_CURRENT_LIST_DIR}/CMake") @@ -67,10 +67,9 @@ set(CATCH_CMAKE_CONFIG_DESTINATION "${CMAKE_INSTALL_LIBDIR}/cmake/Catch2") # are built during these tests, so this is required here, before # the subdirectories are added. if(CATCH_TEST_USE_WMAIN) - set(CMAKE_EXE_LINKER_FLAGS "${CMAKE_EXE_LINKER_FLAGS} /ENTRY:wmainCRTStartup") + set(CMAKE_EXE_LINKER_FLAGS "${CMAKE_EXE_LINKER_FLAGS} /ENTRY:wmainCRTStartup") endif() - # Basic paths set(CATCH_DIR ${CMAKE_CURRENT_SOURCE_DIR}) set(SOURCES_DIR ${CATCH_DIR}/src/catch2) @@ -79,120 +78,153 @@ set(SELF_TEST_DIR ${CATCH_DIR}/tests/SelfTest) # We need to bring-in the variables defined there to this scope add_subdirectory(src) +if (CATCH_BUILD_BENCHMARKS) + set(CMAKE_FOLDER "benchmarks") + add_subdirectory(benchmarks) +endif() + # Build tests only if requested -if (BUILD_TESTING AND CATCH_BUILD_TESTING AND NOT_SUBPROJECT) - find_package(PythonInterp 3 REQUIRED) - if (NOT PYTHONINTERP_FOUND) - message(FATAL_ERROR "Python not found, but required for tests") - endif() - set(CMAKE_FOLDER "tests") - add_subdirectory(tests) +if(BUILD_TESTING AND CATCH_BUILD_TESTING AND NOT_SUBPROJECT) + find_package(Python3 REQUIRED COMPONENTS Interpreter) + if(NOT TARGET Python3::Interpreter) + message(FATAL_ERROR "Python not found, but required for tests") + endif() + set(CMAKE_FOLDER "tests") + add_subdirectory(tests) endif() if(CATCH_BUILD_EXAMPLES) - set(CMAKE_FOLDER "Examples") - add_subdirectory(examples) + set(CMAKE_FOLDER "Examples") + add_subdirectory(examples) endif() if(CATCH_BUILD_EXTRA_TESTS) - set(CMAKE_FOLDER "tests/ExtraTests") - add_subdirectory(tests/ExtraTests) + set(CMAKE_FOLDER "tests/ExtraTests") + add_subdirectory(tests/ExtraTests) endif() if(CATCH_BUILD_FUZZERS) - set(CMAKE_FOLDER "fuzzing") - add_subdirectory(fuzzing) + set(CMAKE_FOLDER "fuzzing") + add_subdirectory(fuzzing) endif() -if (CATCH_DEVELOPMENT_BUILD) - add_warnings_to_targets("${CATCH_WARNING_TARGETS}") +if(CATCH_DEVELOPMENT_BUILD) + set(CATCH_ALL_TARGETS ${CATCH_IMPL_TARGETS} ${CATCH_TEST_TARGETS}) + add_warnings_to_targets("${CATCH_ALL_TARGETS}") + # After we added the noreturn hint to FAIL and SKIP, Clang became + # extremely good at diagnosing tests that test these macros as being + # noreturn, but not marked as such. This made the warning useless for + # our test files. + add_cxx_flag_if_supported_to_targets("-Wno-missing-noreturn" "${CATCH_TEST_TARGETS}") endif() # Only perform the installation steps when Catch is not being used as # a subproject via `add_subdirectory`, or the destinations will break, # see https://github.com/catchorg/Catch2/issues/1373 -if (NOT_SUBPROJECT) - configure_package_config_file( - ${CMAKE_CURRENT_LIST_DIR}/CMake/Catch2Config.cmake.in - ${CMAKE_CURRENT_BINARY_DIR}/Catch2Config.cmake - INSTALL_DESTINATION - ${CATCH_CMAKE_CONFIG_DESTINATION} - ) - - write_basic_package_version_file( +if(NOT_SUBPROJECT) + configure_package_config_file( + ${CMAKE_CURRENT_LIST_DIR}/CMake/Catch2Config.cmake.in + ${CMAKE_CURRENT_BINARY_DIR}/Catch2Config.cmake + INSTALL_DESTINATION + ${CATCH_CMAKE_CONFIG_DESTINATION} + ) + + write_basic_package_version_file( + "${CMAKE_CURRENT_BINARY_DIR}/Catch2ConfigVersion.cmake" + COMPATIBILITY + SameMajorVersion + ) + + install( + FILES + "${CMAKE_CURRENT_BINARY_DIR}/Catch2Config.cmake" "${CMAKE_CURRENT_BINARY_DIR}/Catch2ConfigVersion.cmake" - COMPATIBILITY - SameMajorVersion + DESTINATION + ${CATCH_CMAKE_CONFIG_DESTINATION} + ) + + # Install documentation + if(CATCH_INSTALL_DOCS) + install( + DIRECTORY + docs/ + DESTINATION + "${CMAKE_INSTALL_DOCDIR}" + PATTERN "doxygen" EXCLUDE ) + endif() + if(CATCH_INSTALL_EXTRAS) + # Install CMake scripts install( FILES - "${CMAKE_CURRENT_BINARY_DIR}/Catch2Config.cmake" - "${CMAKE_CURRENT_BINARY_DIR}/Catch2ConfigVersion.cmake" + "extras/ParseAndAddCatchTests.cmake" + "extras/Catch.cmake" + "extras/CatchAddTests.cmake" + "extras/CatchShardTests.cmake" + "extras/CatchShardTestsImpl.cmake" DESTINATION ${CATCH_CMAKE_CONFIG_DESTINATION} ) - # Install documentation - if(CATCH_INSTALL_DOCS) - install( - DIRECTORY - docs/ - DESTINATION - "${CMAKE_INSTALL_DOCDIR}" - PATTERN "doxygen" EXCLUDE - ) - endif() - - if(CATCH_INSTALL_EXTRAS) - # Install CMake scripts - install( - FILES - "extras/ParseAndAddCatchTests.cmake" - "extras/Catch.cmake" - "extras/CatchAddTests.cmake" - "extras/CatchShardTests.cmake" - "extras/CatchShardTestsImpl.cmake" - DESTINATION - ${CATCH_CMAKE_CONFIG_DESTINATION} - ) - - # Install debugger helpers - install( - FILES - "extras/gdbinit" - "extras/lldbinit" - DESTINATION - ${CMAKE_INSTALL_DATAROOTDIR}/Catch2 - ) - endif() - - ## Provide some pkg-config integration - set(PKGCONFIG_INSTALL_DIR - "${CMAKE_INSTALL_DATAROOTDIR}/pkgconfig" - CACHE PATH "Path where catch2.pc is installed" - ) - configure_file( - ${CMAKE_CURRENT_SOURCE_DIR}/CMake/catch2.pc.in - ${CMAKE_CURRENT_BINARY_DIR}/catch2.pc - @ONLY - ) - configure_file( - ${CMAKE_CURRENT_SOURCE_DIR}/CMake/catch2-with-main.pc.in - ${CMAKE_CURRENT_BINARY_DIR}/catch2-with-main.pc - @ONLY - ) + # Install debugger helpers install( FILES - "${CMAKE_CURRENT_BINARY_DIR}/catch2.pc" - "${CMAKE_CURRENT_BINARY_DIR}/catch2-with-main.pc" + "extras/gdbinit" + "extras/lldbinit" DESTINATION - ${PKGCONFIG_INSTALL_DIR} + ${CMAKE_INSTALL_DATAROOTDIR}/Catch2 ) - - set(CPACK_PACKAGE_CONTACT "https://github.com/catchorg/Catch2/") - - - include( CPack ) - + endif() + + ## Provide some pkg-config integration + set(PKGCONFIG_INSTALL_DIR + "${CMAKE_INSTALL_DATAROOTDIR}/pkgconfig" + CACHE PATH "Path where catch2.pc is installed" + ) + + # Generate the pkg-config files + # To understand the script below, you have to understand that it works in two steps. + # 1) A CMake script is generated at configuration time + # 2) It is executed at install time. + # And both of these have access to different parts of the information we need. + # + # Further, the variables before "[[" are expanded at configuration time, + # while the ones inside the [[]] block are expanded at script execution (install) time. + string( + JOIN "\n" + install_script + "set(install_pkgconfdir \"${PKGCONFIG_INSTALL_DIR}\")" + "set(impl_pc_file \"${CMAKE_CURRENT_SOURCE_DIR}/CMake/catch2.pc.in\")" + "set(main_pc_file \"${CMAKE_CURRENT_SOURCE_DIR}/CMake/catch2-with-main.pc.in\")" + "set(Catch2_VERSION ${Catch2_VERSION})" + "set(include_dir \"${CMAKE_INSTALL_INCLUDEDIR}\")" + "set(lib_dir \"${CMAKE_INSTALL_LIBDIR}\")" + [[ + message(STATUS "DESTDIR: $ENV{DESTDIR}") + set(DESTDIR_PREFIX "") + if (DEFINED ENV{DESTDIR}) + set(DESTDIR_PREFIX "$ENV{DESTDIR}") + endif () + message(STATUS "PREFIX: ${DESTDIR_PREFIX}") + set(lib_name "$") + configure_file( + "${impl_pc_file}" + "${DESTDIR_PREFIX}${CMAKE_INSTALL_PREFIX}/${install_pkgconfdir}/catch2.pc" + @ONLY + ) + + set(lib_name "$") + configure_file( + "${main_pc_file}" + "${DESTDIR_PREFIX}${CMAKE_INSTALL_PREFIX}/${install_pkgconfdir}/catch2-with-main.pc" + @ONLY + ) + ]] + ) + install(CODE "${install_script}") + + set(CPACK_PACKAGE_CONTACT "https://github.com/catchorg/Catch2/") + + include(CPack) endif() diff --git a/CMakePresets.json b/CMakePresets.json index 8854128..ffc6e24 100644 --- a/CMakePresets.json +++ b/CMakePresets.json @@ -3,21 +3,35 @@ "configurePresets": [ { "name": "basic-tests", + "binaryDir": "build", + "installDir": "build/install", "displayName": "Basic development build", "description": "Enables development build with basic tests that are cheap to build and run", "cacheVariables": { + "CMAKE_CXX_EXTENSIONS": "OFF", + "CMAKE_CXX_STANDARD_REQUIRED": "ON", + "CMAKE_EXPORT_COMPILE_COMMANDS": "ON", "CATCH_DEVELOPMENT_BUILD": "ON" } }, { - "name": "all-tests", + "name": "most-tests", "inherits": "basic-tests", "displayName": "Full development build", - "description": "Enables development build with examples and ALL tests", + "description": "Enables development build with extended set of tests (still relatively cheap to build)", "cacheVariables": { "CATCH_BUILD_EXAMPLES": "ON", "CATCH_BUILD_EXTRA_TESTS": "ON", "CATCH_BUILD_SURROGATES": "ON", + "CATCH_BUILD_BENCHMARKS": "ON" + } + }, + { + "name": "all-tests", + "inherits": "most-tests", + "displayName": "Full development build", + "description": "Enables development build with examples and ALL tests", + "cacheVariables": { "CATCH_ENABLE_CONFIGURE_TESTS": "ON", "CATCH_ENABLE_CMAKE_HELPER_TESTS": "ON" } diff --git a/MAINTAINERS.md b/MAINTAINERS.md new file mode 100644 index 0000000..574418a --- /dev/null +++ b/MAINTAINERS.md @@ -0,0 +1,11 @@ + +# Catch2 Maintainers + +## Current + +* Chris Thrasher ([@christhrasher](https://github.com/ChrisThrasher)), gpg key: 56FB686C9DFC8E2C +* Martin Hořeňovský ([@horenmar](https://github.com/horenmar)), gpg key: E29C46F3B8A7502860793B7DECC9C20E314B2360 + +## Retired + +* Phil Nash ([@philsquared](https://github.com/philsquared)) diff --git a/MODULE.bazel b/MODULE.bazel index 628d77a..8ee125b 100644 --- a/MODULE.bazel +++ b/MODULE.bazel @@ -1,4 +1,5 @@ module(name = "catch2") bazel_dep(name = "bazel_skylib", version = "1.7.1") -bazel_dep(name = "rules_cc", version = "0.0.17") +bazel_dep(name = "rules_cc", version = "0.1.1") +bazel_dep(name = "rules_license", version = "1.0.0") diff --git a/README.md b/README.md index 3ea54a5..ebd6bfd 100644 --- a/README.md +++ b/README.md @@ -1,5 +1,16 @@ -![Catch2 logo](data/artwork/catch2-logo-small-with-background.png) + + + + + + +
+
+
Special thanks to:
+ +
+
[![Github Releases](https://img.shields.io/github/release/catchorg/catch2.svg)](https://github.com/catchorg/catch2/releases) [![Linux build status](https://github.com/catchorg/Catch2/actions/workflows/linux-simple-builds.yml/badge.svg)](https://github.com/catchorg/Catch2/actions/workflows/linux-simple-builds.yml) diff --git a/benchmarks/CMakeLists.txt b/benchmarks/CMakeLists.txt new file mode 100644 index 0000000..6883778 --- /dev/null +++ b/benchmarks/CMakeLists.txt @@ -0,0 +1,16 @@ +include(CatchMiscFunctions) + +add_executable(AssertionsFastPath + runtime_assertion_benches.cpp +) + +add_executable(AssertionsSlowPath + runtime_assertion_benches.cpp + assertion_listener.cpp +) + +target_link_libraries(AssertionsFastPath PRIVATE Catch2::Catch2WithMain) +target_link_libraries(AssertionsSlowPath PRIVATE Catch2::Catch2WithMain) + +list(APPEND CATCH_TEST_TARGETS AssertionsFastPath AssertionsSlowPath) +set(CATCH_TEST_TARGETS ${CATCH_TEST_TARGETS} PARENT_SCOPE) diff --git a/benchmarks/assertion_listener.cpp b/benchmarks/assertion_listener.cpp new file mode 100644 index 0000000..2aa65b4 --- /dev/null +++ b/benchmarks/assertion_listener.cpp @@ -0,0 +1,28 @@ + +// 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 +#include + +/** + * Event listener that listens to all assertions, forcing assertion slow path + */ +class AssertionSlowPathListener : public Catch::EventListenerBase { +public: + static std::string getDescription() { + return "Validates ordering of Catch2's listener events"; + } + + AssertionSlowPathListener(Catch::IConfig const* config) : + EventListenerBase(config) { + m_preferences.shouldReportAllAssertions = true; + m_preferences.shouldReportAllAssertionStarts = true; + } +}; + +CATCH_REGISTER_LISTENER( AssertionSlowPathListener ) diff --git a/benchmarks/runtime_assertion_benches.cpp b/benchmarks/runtime_assertion_benches.cpp new file mode 100644 index 0000000..6a28e3f --- /dev/null +++ b/benchmarks/runtime_assertion_benches.cpp @@ -0,0 +1,27 @@ + +// 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 + +TEST_CASE("Simple REQUIRE - 10M") { + 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("Simple THROWS - 10M") { + for (size_t i = 0; i < 10'000'000; ++i) { + REQUIRE_THROWS([]() { throw 1; }()); + } +} diff --git a/data/artwork/catch2-c-logo.png b/data/artwork/catch2-c-logo.png deleted file mode 100644 index b1066b8ee7edda10ec0b70d9e207e7c19062ace8..0000000000000000000000000000000000000000 GIT binary patch literal 0 HcmV?d00001 literal 10636 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 + + + diff --git a/data/artwork/catch2-hand-logo.png b/data/artwork/catch2-hand-logo.png deleted file mode 100644 index ab857eaab75d6159d251fa7e50f989edeab193f9..0000000000000000000000000000000000000000 GIT binary patch literal 0 HcmV?d00001 literal 33761 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 OhfvW`F1u&*`2PSLXejgm diff --git a/data/artwork/catch2-hand-logo.svg b/data/artwork/catch2-hand-logo.svg new file mode 100644 index 0000000..3b24df1 --- /dev/null +++ b/data/artwork/catch2-hand-logo.svg @@ -0,0 +1,83 @@ + + + + diff --git a/data/artwork/catch2-logo-full-with-background.svg b/data/artwork/catch2-logo-full-with-background.svg new file mode 100644 index 0000000..fce859d --- /dev/null +++ b/data/artwork/catch2-logo-full-with-background.svg @@ -0,0 +1,104 @@ + + + + diff --git a/data/artwork/catch2-logo-full.svg b/data/artwork/catch2-logo-full.svg new file mode 100644 index 0000000..03bf15b --- /dev/null +++ b/data/artwork/catch2-logo-full.svg @@ -0,0 +1,88 @@ + + + + diff --git a/data/artwork/catch2-logo-small-with-background.png b/data/artwork/catch2-logo-small-with-background.png deleted file mode 100644 index 963eaf492dcebd18a7fc1c9209f6e93c09d6ef18..0000000000000000000000000000000000000000 GIT binary patch literal 0 HcmV?d00001 literal 25330 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≈A;CKj7oFT`gJObuc`tFP$uetqJ%|HiqePQ}lKroBNK zLb2InPgIy^`6lr16;yBo#Q8%(m3_h7*L*9muO6I@9=?^tW@#oB3p;+6UL#nEj=8Uhc?bXJwtZ)Q z({6aqDqnlqfqBrS|GjKJ1!CDnzChJ2qQWY94w1sn-i6tLG z@NcW{2=7QJm-=~>)RwEY0MsgcONTa2BSp?**~CcqayDFY>4r95XDgOM_=!s;4&snd z^2W8lIW)WLV9qwZyb)ZbhO3@2blXY9+~`i03Qv#;V$mLWwV0OvM=%1fv0rcl?aCkF zbp$7aU1t4}Ykx2Ju5fGVXfen6jLVqO57(}nuYf6!-)$FkoWchXgV^J0{nCYIxZD8S z{PPYMbH7$#T7a?w>RP19J-!@Zn*3$r#pjft=KVXB=Z`%(J>Zy-0}YjPFgj_LalM26 z4TeJ5@L{;R+hmT&{0(fD7GC-$Y+^f7_P2^gD^F!97`#dP6(q{(9>JU_wO`!r zHsre;`f}Re3!OlIhcSZll319uFNo1df+)dp*!&4OmsA9{pC1kah;~`jmtYp_ft(Jwf-z~-EY8#{N+5Lsl*$# zj=$)Ph8*<&TJHmd6@UUQxbq4Lncn##K{MSDXzokZ#s2E)R~c{kT1fYAy5$r?hskOE zfj&d8&p)W&F^9S=U|HMSQ}6)f%4EBW_0&F<3kD0CYjg_cCpk*&Mwo@*ei{xSry?sE z7JbT*tjYC99yx-)FHl+pKkr~}xx|?>ZJj#uZ^XuYO-W_(S zsq~glw75!RifZcGb4tRIvwmjAB5fO;AmE@dy7KGT*d)u=8l9+D*{}VtT$ZI*VY9U^ zP-f=GofG@mo^j(jv{#46dNT!;;aLVSl%h*rCe`GmUBUvzHjnLa4RReD!gN=NFSAC@ zfq0}~_boqk()duO#VclK0pXxqOg>pQ_GMV+|4N2Y|J`I|RZ-!I<5i8FMLR6X(GgDW ze0hFw89^SBUBMiCT&~k26s*EuuL|nb6mjSge9L*ZGe=KfvfAV&r`(voO#tp7rMA=N zGwtjaCo5x7(6`O=o$t|8kr*XY^vG_Ew9LOX#`w+78>=cr&gevQY5eNW#t8N&O-M_i z=iEF9r2|r&O0^!iknv5ii5c|L#?LC!`HFSx?gOwBhE-;2i*da3u!uM<+Q>?vV-%Gh z(Tx|T5S{F{#5DC@Ng$J$Pr)}Ov913`r<6k_NC?L3 zAokl}utp-sfxVik4{CO-OQ0aH1XMNF)-w> zchdymak;2f<@J_f-+wPDM>w|oN+NTs;5eQ3F(aFBR?b|;|I&D01(xuT`Cui4Ud4r+naMAZ-Jl?;D63c`2*8h_PqSOdX(Lb_Z zN2^tB1?K7FN536^G^w$YX*Jgb2RR#L_XFFb=$T%AI}U+Yt-5ai*8gqw(B8ywuM+@4 z^Rfc!1XR#YlANjhn6K(mHkg2BMW+-5TBWPFf?B2*ADbtk7UD*QA4ny3ngYtz+%e?xmL+6D$I>OwMV!3+gfZf^K)-{Z@lR~1 zG`cp{qC>?T=SXS_=WMXcBQU4n$YJLS71)AUir6gjZhDz;(xkqQH+kd*uH2%vu@gC7 zv^=P&ZO`D~LB5+jxo~ntVn1 z7*tH&p6agFH-jUD;`CE6x#}0O&YzG`rZePH)*;_^J6+f@vlFTYlBp$cG+jVXhITsD zMaY!k-d{@!&!O`0g%`<*49_G;8haEK|AmFEVC4^GVRBPT=${A~ygLyRmS*dLD}l`B z_F*k085Ky8ONyDc;Sq5i#$mHL`$*PddQStj9TG`!p1&lOGF4oxx(Le3D|o?pXLM)v z!Dg?bcl1WO5y*=PG@>Fj_YX`4_0b`&)I3FwC*!PYQcf;@vswR|o0jV7_-1^|u-QQ} zIU$tCIKyB&JRk2~8;QvE&x;_xkxqJ#-%AFOZQy8KtMF1O%QufU8Zn>B)VQ8y-O}4v z=|ms5mCjUNX&U@=C2nh^RPSlnB9l-$t>oox0Tb1#w^p3>&E>VMx}N-R6>0$84UIC^ z5-`ERP@Z!hT#s}v7RC2BDgE8UQJUWhaeDiS<9-T9!bcVDk9w^N_v`#6b^KM#-YoxX zcCucH;3p$if3S|65-U9T2Aq z*8fHOQP_NmI%cM?Uun|Y8#paax6s+wMK&|}tQ@1B95uGB`hyC28LUUGL@M)9Fq1t; zR5zlhHG;d!thLU74JjF2u*7M8s7+n!`v28*-ce0#O#oMVq=g_IB!tdGs)Qmn^lGRI zBE3tKCP;_STj(966WT-C6I2ug3?&8-kRl*mdQ*xZej7dCUpagBX7}DZcV>3)%>0&g zKTSWCG8ozIIi}zXq$1sfYggPciJb7c<|B@2J#8^CoxN`d$?>#t$=kNnAx-%Vl(%R8 zcke`~*4%!!5`hKHD z_OD0qQ#P0}6DR9_jz`8n(Vz?57B zb!tG=p%rs@KC{ENDBk7oemBu2C3SQDYnbnHYv*Dy-}`DPcZkz!xRZLB)zZe+>R>O* zv(W4z;;H&*aNos%>G$HVvNeSRKr;MbR&ss08k_Iq8HWyrz*x&)`Tv+TXzey`S^aS? z&Bhj_p*3kjW{Sd=D_63PCL$Gm_QV^02}GKjbp9dmgp*!?XBT8WmhxXPc| z5m0D*h!^voy~VsG!y*&@J8&;>QK0@ngz;QfW@jA7V%OzAiEG3`YsUs&E(DG>W=XJN z1O&A$y2`GGS9MCsNdzSwj~tb*h0|Pt=l1P@6t;GJ!cwQhi=GkfLTDIzy`LI}6U5AI z7En8k7@$+<9ToFCPa z7;rtya*927i+Bh!aEZE=kErA^-z3w}2ygS+j4cSY3;29&BW9{3#QQoqBn*CUId{&( z^YQ}Lh(|ldE9Y0DaPcu;PFAu1_SeU-e{lo$gqub>5@i*#spsGyIjf}B^qAq64o9AY zg}YtT(5{ZtHzK$3AsPlh&s1j5sgg+M%ZfhSAmS-?240A=_oiArSiD{%qv!+N{P_rW zCN?w5YqL6WZ^P=^O2Z=_Ww;Gvc2^y@`$CabQXnMJ5Bpqfy2C6^cu~0k*gHF7WqrI3 z{x?90+sS24DYtSmuQD4bIHu>5mL1sZUShF-Au2J?gqRljSxDctizU)gGZk4>rr=_2 zt7_r*GdgCrAH!L?v57eQru!AkxQwuN6l>xtlYGp}7218t4S;B`HC#6>kVCqsZe=ak zI$*0i5orz4oGjo|eWGqU>|0T^DqDejD%` zT?+a=oFQW$7!i6x{VOT-D8}+2c^-I7jfKMN_rZ&7 zpgMI6^H+kr@((-KeF{&@X$D_5^a#9ZQraL?H`G0;^d(Qln14>;Bf5XDf0<<3VDH~L zImE^78qq@a1{cb%;Q`#nf#L55V(DjtbWaW+kA=P)?3#1E>09gJdgHg+NWNl|4yf_t z3q|@u5gz*NS{G#=y#1>K`EI7U0MDt}y7s2^EebdGeXaD;IBOtT{3tc<`js9{eFvsm z{c4ZDB(R*ZaWX`sgzNLK>^IpEdlbLP}2}qHLK$(Z1ajrh9zu>L!a$iCWdAd~TXnJ=NIF!lQb?j4i->GdQvXCK&g0*wSKys)}2OF^tZFjmsbAH&I%ZSLuwGWH;%p;;H z_tR4fdho-?^ZH|m^Y6tKQ#x-NjLGShi;o4Dyy-~6pYj$`vUrX&n+3(ZN1UgqMa_3k zdc(*T>1sTf&S`0h9g~D6AK6}LrN6Ef{sz~s#0|KGu&SF<-A^O_7}WK!t9YUF!zF8$ zrsXjll%oh_3DyKMxKqcti@flO3G@WvT0%WLy|{?Bk20W(^MV)H0*8thj(mVD9Ee14d3S$tx7xlqnq=md84C^XHecNMRO`eyL{oU8Kdm)th) zT?$^B`D)cwG;uznu0RRw+82^cPjB|=%*147(oKbsRd=;vS}@BWTyy8fsi;~-7Q^|| zOkWw))QHk;;?^$rP^v{{!r-{$8*U3O}u(`)VqTi8BPYhSKpfyouBC+erj6Lb1s z8<&3e-K7Tq&d%Jg^~LpW;#|0k$fc5dr8}Y+%m17DC@N9?Kb9GWroPPmD0g-?Adg2i z(w9rwZ|^`dcCP{JtY)|#q1Kvy&V0MGv;$9};hZZF9l*5BNz(i=+G;bv*HE(XZBo3s z;%#kh2_+D}0{lj{XC3XTKhr0hm44R}i%`8m8P4hcb)7;HRCa*VqJ`Vu?pG3BiTdsJ z!RobgzP>J~xqG*OdG{hE+rzP)6f>r)TNDmjc2N_1zHbE&%WkPakjiH)o%bv+C4Z-; z+`!X1O!CtbBccYUc-I&Bp~>=Gav=huh$c(vM{rEY174t9pG^CQ0>$%d%jqqqe1=^i zre^~w%;Q%4zK`z`>1MP%?tL6qHmzZlY>|=S5C$nV)>{GTRu!JvR5+g`;a%gB#zDka z53Z@w4Hm^8WGI&$ST*;&fX1-)_T!7a3KaQy4N~){RM9+(l; zd)c`d@nC+V67nqa#f@vcAW~GvlQ0dDWjrY7#$nN+yiHWK@CIV&rL)K}rr;P!pf03H zDgTJ{djUFjSC;YNr2jqAA3mJGa>YW!&ipp0`;adt@KoT(sL%egcC^d+Sv~7yAPu)8 zOSEdV6xG^nO|q)1gi(e^P$f$JLrr&yH8Z1Wmp1X%RY&^Nt^voNg6chCCp4(ga8(vG zc+U~rrq$;T$*WeamNpWywdG!tQoGAuS{%a`u4V4P^E)4!|K%0h=MEdauYZUl=MW<~ z^HxlF$-;IjO*P!-&BD1P+z0I?tRZT(8)4cz6T5 zThaR*TQ}53`P-6yyqhdq3{l!be{pL+_F62-uCP>H%L}+A7_t8f*xvk}35|T!rYR)W zD6b5aVTqMUeG#9+EDqcDi#TW&$Q-;jxEfhQl4*Szex>l6!!<7^11WCsFBUJJMb4

UNyc{+AdV*K&zO%{8@B}IK8toX2HV_{U54z`*k*R_3iGR-6*=P{hc z7cy)HCbeGcq)%U)S-MfB7+~=^NGYV8|LGwKUjvg!cUXVx_W_5-0N*2 zX#{HwUAo}gHT-v?q0rC9SGgnAf2f8p6%yTs)lQ*ju2F)^^4J1hEVgEngLj#7uo0aQ z$SFbRwf6uv*tzy3T^I6YF4wSBhH(*_r`x`>uMWo5ICSg(FC|{aM&gjJ67D>r<8j9+KNl-S}_q9 z86@YC#N&k>EJN9MI*Z2M%-&7ujsiMWj-HwbBGxD|RUUV82Do)#OdaDTGcj}fpmc9$ zpT*eg4qsZq&f_a|G5r(j^Q)|{CY2<|EJ-gitajPYV5mqdk+d^wwhv#VqV&tsuVp zSd#WTKUp42-qwX{bbg&c+kBf?j8KXlH=$6qS{e9&S3Rc;>PU`Z z=#BZlTVHFqOd;i3z$!Oy?O023SzK!e(9nXbo%-ZMk3;eKk{@I%g(vTRm~bmGOl~dP zC!d$AjTTvy!V5dDgqbcG?l-F+6lHwqdjk~B9{n2E2kKU7HD~%yV+7^$P2($DcW1MM zo^4qU*!I|cQf*(kV`XN)QSzGXUFnT}IN1(=F)-Anf*r25H?)#0lp*IoDh7wf% ztPTNj_`vI*jipZ`-`+K?frh8yEo<`D45z{*(!`)2o@tynxvsQJbsbrvi~H}IZ=l=c zJOvwdq_yR|Y)YHn&$GUQp{Fm@E$W5NXk17e3L=|bFl6niqk_q3M^WY1OxS*{DsYu2 zP~Fd8qwjWD3)9Q2T1fg`E_D6gnN$2)zzlOx^|w3_M>5uVXyaCYn5SU_v_Ors&rEYx z!MIKJm}TfB@1S8kHq{H|30Hi$^E7ov5v~Z^qrxnrbj%7^1cZB9O^A<|3~nfjJ^iBm!c#n{Jnu|J9#e)l1EeAvUnpNLXq zKn|WRl4M`mb|Mahs5jCe zVuL!evaiZNHJuaq3ChAE{gHt(od}|7_Mp<4o5;v%=5Mp0nV2!^KvDGhF<5aVS6(ti zQOk%sVY&4DX^5wyWR+x=un7e`WHvQ)9oA3Q7m@Lgt;cKnKs17#=OyPC$=RY#rZ2i$ ze(}mLkryPCvSgN7J0?O>I-yH-q$i2P$PD@v zV{ynIkKu}NWjQwimZ5MHMS)ZC3ex2sUqnJ84F{Z*hBfQiP~BR^P_sDQmWtqL; z)q$1h)A*zo@kik!@ha5jZ7zn@mSe6aH7Xup-v!Y6nT-r@9|X~JS2dryH=BOoa{0IF zgpb5PZ0gU5$XumpPfi*>aC%)Gb!}RM;r3MH@Xg?I>S^7|R1+kxwz^IrbBM8}`G|}^ zXp)5go`E*AXu7v?wx7=@{sA~#F2niI*`)^~q7o_aQ?DxJ*Ey2$K>7I>Z$k+u3iEmX zet11UTD%MYjixtGFCi1cx|)`zJQ`HqZlCLDU4GfSz&a(cOIeF&w+y9x@@4*=m$tqP z#*!;CxJ_Y)vtqpmEBc@p6+@%y+%SNHiDYXNodZOaM};}OC`!es#N9B0jC1b`l$|8k zUB^6c%(2!ZY3->W-Ri*2`bPSSoRfv{PIbPzCrk~Sj0@P6xOwtpLu7vWgf^B^KczN3( z_AV1qqFErlDKPC3bDrGr4tvy0-lj?o-3)`@EsQY$9PLR*SLTJjj}J&rYN|x-BmhjH z&20vq>pa8~i0N_rG=&`A`yInJw#%Zi?sP6H0E3O4LOW5ghnW$yP}cq_CTlcQ1)UD9 zBgrn}BLJ$YLQ`Uq zkI5*7riM}eqw5vjEWdHgJ20yZ7D*>9Ub^z@ixgyA?TWch!JP@l;Oe&r_Kxrcx_=j?QAN%0!_dwH|t$1JV+16SasizCubL-z`jy6Q4QhB?z%t&jsW1zr93(aF-y` z4m@AW#Q|6=31T-biHzVL?nfc!~j|fJW{&1Y%w?sAeSd^qR!cFg8m!Kgwbl-%aR$;V&%PZs4I zqvIncv~Jrre{`dx=Yk9LC@UhBpq%bkoZ**Gs&#<+ub#`jr*9>nE(AH}yw=G{!M!9Z z6}!`-m-qHNfIpa=ylccG&9%lLG=WLHKXW>Dauv%Xrq~DT52ZguvS@-W>N#bvtNeO? z8O4NxP_0h@Xkiex`ohW?nGVJ`rW?TctkmHygsp2@P27n0g;*wjGiJSx(EiL4Q_ zs`3iSIhE#7`Od`%qly}mL0FLT@Ix<7_wnP;q&>gnIR<|TyV!O5HdYhjV9i@z# zN_u{InJho)()BMk9)Jzs(_0m#0}X0XNG2&>9=snRKO!)VW}wccp#g%o(APgOT2p)i zHUdlhC!cQ}9490LOyY^SJgoJ&vy1SDQR-s?ONR&b?Zo7fm^hZ=cK0RwrJhv&);|(} zy?icV5MIZXC61&$xQ|TDmk(-@J^kYQSQA0Un(^SC?AlG=8OPjwLT%jT5^Z>`angQS z#eNZ?Us3Ve`}B0l#ht7O?e#HDPMm~For?~K!1NS=Ocg~)9H$;w_QQ+XIF^3CPZu(_QZ$1g5fP*3f%S=XBUB#|xwN^1fT z6nC;a*V-u~b>nWP956Frb16D;%!8nH0M&<;xeRh6TIV7LL32;+NGk%;DwD)R+kC{Dn3Vt$JllKvhAeb8ox?H)Cc z8=$!Bn>D3y05Le6RdNf)CG;5>C=ZP9C_hVC;~){dZBTguP(h4b-wO#*)Zy*{K;?#; zs$n1`$0G!|5eBO8AQ9kQ1O1(>{Eem9B06>w2+z`2K0-^Futp6X zNl-~TBE7{bA*Q0(5urcL;)d>2HWHu^Rp|8BY7JpfkbEdrnV7^w1g zJcovw#2R*L0KUCW`}eg9eim*48nvn#p--!dU@R>NbEd43%I>=N`q-cq;9@nns~K$( zSkiRhY#`YeTv_m4V)~n4(5U|L!FyWgK4{F1C|Z=?1VHX^+U-kIx(due|Gc``FN`Lq zJ$WXbJV98F7(N&uu8LJcmO#P9c_&Qj18WTZbJ+T7+!8=ruJ66t|Hwy_nc4*)L&BuW z|IF4m-$}K;Rp32FiL5()yXlpPv4|a-?nnixhLs39=pk=48i^z(-@2o4e~X|e;OxBb z{<3jef%2^4x>9XmQE1lyI_>eQ)d?p706TJ85GHki zK*4Hy-HYb$N99c@eH0P++A_Up2_hETSgI;6l`2+X+$w#m!E3Crsr-;&Akay|%gt;L zlB&>CZA!?Z`=h5SeU%=a>i`)KON2%6wJfXlu-|nZuWaxoXV3t%{Jn+6zIz-^{(bAv z$z)b3p}hR;B07kbX4)-}sisGT{iBpHf-)d1?RjqZBQMzuioX%rYUHo`1I8Xdb+$ z9PyftY*n0+d`;arC>lbCfdPGtk`%|Zy+!#9eJs9<1JYIB9BYd88J(zj*jhB}NWlnA z5bg2Wj=y-(WPCfZ-G$C({@S2srZH6cSPJu!)*M) zAI#N=@%sGU*11Puy>Jp<^Tvh~QlrUGJe8Z6fB9zsnGkT2@W3@BFXQI)Xkqu@sXZvMY7KDs9x zF5V?tgd0KI`vjR&QUT)nfK0xHp(cS9?Jib28={T?{!D`5{rG&u2Oz&WtlGURYh4F; z>mT{fPhQBuAOywMx(zbyuw75q^^@lzn06_^Gww_KtML%Jhqi#Q_I=3YlK2W{NDD`q zS*L^3!Gsm%mLUDD!xr)HZ9xKo+{tBk%)#vM8Lh?~_nDS{0-h6yAC`pxLDO&Tep3OF zjxa95>`!FebOSw-=GIIV*3)7hl76~pc|7A>D%h+CTozoJgL40Rzi;{OpSFH0HN#M?s+|{%~kf}DZWI(zsUAe-5u^fLn1VAp|oms z(q|N1WG@-`t#rPci38&IxP>>t+iF^OCJYGo+N#Y_)&mf148HHFrSl~5@{+^137}_O ztzpH;Fd%|A-i>2IZ@w+5WpcNx1m=s;K{e z0L;)I#|g|YMvTk+Ho^ZwDc0(Ff4M#LFwr!C{8`c@boAM71R<%M(TH!7hbQPr5#|U< zY&Sl91^Iib%*jn#H0Q}5_Xs%Rgg>SXc!bZ*!(s_07w|wrlZ4->(ZxQszY712-mYcm zTXneB-$U>a58sDd^WW<$3u;XO{{ejZKwrmH|F87`y!3u8pWvXjfm%oX-?Ipo)yf%3 zpTDjjN}-M&47e9HUK{^c$w+$gi`YM_A>IGgP*fNkl~y-y-Gl85J4#lQUn1d zDn$_#1nET(0pW@i3n(H@iuB$~AV5MA(laxevhN>j9T|ry`}=l1GwXex{amk@aLzt^ ze`~FGz3W|IC^l3A{ee-yHo%d{IUC#hBfngTh4F-e3 zV8{*?z#w2B;LpG^$AI4YS_GV|f1kl%Fc=Jm6j1{71-1fi@Tkx`x?Tb{v8vr*Fc=Jm z^iTH)A2*hQHDgTY`h7z`;?={q5``-&I?df{4zsO2l|b{h-^gTas- z$`Ntg$-sNGuX2y$st0Bu>Ux90U@#aA37{9SA@Dn3NkRd=a4iP*u!`MaFc=JmWY8D* z8t@-rRl-3%03HRV*ljl$3DK}qk&n6cLICd%3uI918?Y~;H`pE68kmeM zxfu!!KtzUStYa`342H~6qSf+=zy~Q*>B|t2+<|1@PcPSmW{3vyeSoup+kt0+Pk`lZ z5}hVxGv)!WAT#|JAU^ryHON~vZ!j1PhRmwe5x~!Z&q%(PJ{PzI>EV}U_?`PA{roMF z+5Ec@kzf?%eSW;^!*TVU3>27|$1NC}m|X24%aRwUPg>|?(j*f@hgR|@o2R$^aGVm=~y4MCvy zb_zzz8w`eSj$jt0!F0w1QWDw8`*E`5+TdUd)21EA<=zGvRn8;OK2J^O8E$;`j zOnVeJ`T|F4ut{o(OJO$sK4c`UDy8pMi75NG0e+3B`_rJ?!>|@d>Ag1?42I$j=({n{ zE1chtU!Pfx zQGon4iOd9w62DV{^M5N)Y3fpCV=|Nd>QZ#I5e@$_yllidGf+^%~HF9ABRTyI^B) z27{qkg8HUxoLE3FT+^tvJM9&K2^%{a;I%Di+y9f2vfnNa&NseM(1Y7}(BRP-sQbc&R5n|i= z5y`6gtO!z27{qkFpJWqK7o2&9u78T<6su0byJyXfj$19z9w)f$*o*n z1-yq?u?|V_yHq10ywiZ!HDKm7n71G;iG{#p$X@wD_Ba|0hGN4!mw>)L7Y2LKyY-*{ zbQgwp`R8`aZQK3G_+mdsHgpa0Kvg4VZZi=&KNCy$^sp^)s)D z&8-^@h9br6^_dl-Lcg~o0bHoSZX<7@gZkg4n-fDXc4F*HeqpLpq6lQex#~Y?Fqbs8tyUF2#`Y>c`S`tBhl~$x@ zlH4d;Z(tH4_WPLp3FDd&G2CasQ-~<;s|ek0nWw>E=w7jXPx{q$&`a<15AH$lF01v) z9BX&DYYYW0BpLPl3av_$qTVYsSvV4ShHUL$I}7H|C>yeeG9yN{8w`f-9o%=Js&QTJ z{U7?h3kvwZ0p}6_F7+l&6s|&ci^om9mmq5QX}~Syhjwq$WM;kw^UHvJ z5bZ*lJ#Ger!O%Y5pU#*ty?MJ+oufa}-J zHl^Bvy}@9}5+Bs^eyEvuTiMzns(^!mw_~ha7Xg3J;E{x=p%1c?{7z&{EN$bM24q^` zP2gr^NBlY(RMW1)D>aA@(a$gqI21S=S!VM9@FFsWun?J%U#ZtJL`(5D@Gqog@C{&N zL<7*rg1o_C$X%sgLlA7r#&);bMlW0Va@b3b~c2F2OH zJ-~6mM3VKhZ?7V+K-v#O5E0^@8pQtuyr!Qq>TmfOaKExGQxGoz3-AU*eo=uibydau zu6`1>Qnxj&od(IRfG1)IkcGYJHv^j_6zqBd>jPJloC{t@ee=(NM>T+MPP)c1Wynna zK&{AkL{{0}1-y@V0wj44-UfbxSn(R|WiaFmCCD(oT348PD5OUzD_rW6JeA4+SVf#>-~v1C&Iyx;i3|`;cS>a)lAKPmr~-#{;z~X1x{2?)lY~NjU|$7nxE> zv*pm4Q1HiKqa_AIPO!QL@VXA(*-!aGyqwG69&E>mI+vhsqir2xFyeT&i0D1%myr4F z-U)opN`!j99-;Y@uFhWpevMGw{Zg#V`yoQL>A(*W5!)j2xf}jfU<<3-4Tj9oS1a|d zinDs)EONLRGT4M2oYuH*!6f_H+eaUSGLLFN(}>J!?}qfgllYuFBi6G?Y+`Fcdgt?j z2Z0k)tj^2y`wi2|{7n6xOL7{_+a^IXNp1_D!C>ebrHEyASgp}gUvg`c1a+pmmmu4qe#XNyt`+c$5d%pRLRs&gKu|A3 zDCu*M^`%LyMSTsp7@?DUrEuR1eeL-Nrx_f3qB*W}A!Fr<$X;AGs}ldBK` za%NR~umzJET>`q%?_JV1Q;^4SXG$I+#cCQSF^&{5K=B z^ag_=8%#qccYNOAzmoD)%O`u#dvRFPnby~C*6!;JeSx15eXsgF#O-ahm_J`{rH7vJ z%s@MGzk3Jxl`<7cSLhXbU#1}a=O?;Jb>8-#K8}d}tXel1vcu{4vzpH!BXX%A*pNM( z19}aMY)-v%R07{7`eK*Gz!?c-dVPT-5Wn>}s@eu*Zu3T9Ym)DwS0ZA#ZIHhGJ256F zdD$ahaNWBV@sGElZZKqrGT@K+PYS()Fu2JE^jVbGt;@sCL45+Z*iYQ`1F!?|Ld2lC z0=N*_BOWIP0N(@V#1w!QBUJv^NX96qHF{~1vpaAT-G^>((gS0Oey5j#8xYsM{`N{4 z44Gqf#5=@yTIl7Z1H1EVdg0oH9qi}r8smY#5zT(JUISO0J=GFKRQ6+{&(aqt;#Yvv z5Ji2GV{6r#;OviVm5Wp4mxa2$0nuG7M0$451Ggh)7P}y;UX2nn81jR!AgrYCd;h$i zlko@iJ?ZDwORweqV2?x$&bl220ap;st*sFeT1<{HSeEKTbrI2f{!RLK+yrb&vVHVI zKtD+DN!n{-q+A7u?Lg{lR4F^ zO5cz@>?aL}Dxy=|O^C>1YvSvvmI5Pz%ZYAfyFx$TH%QhfrI#imUsv{{7y^1Bh%eOx z;A5rk?$(6rNJLdT0_&F>48?|XBc4MK64RotU=wx-*5zKWfIgX1?WYWnGT^IPjrsO` zTY&eFS@pP9la?Y*N!LYW5tchVuvVn>zVWhBh^Rl67d^HgRAo3KMLjoM&01_rxO z6$JU-K$j)P5xuXzfOyB+N|Cwl8#Ms=ey%!ASdJmNWd;?>0PKp)py#QF-ce8dkDA3F z1Z;$e`ixHRc_>&he+4*B6T6=%Lo-F+XFE8H8@=#1rYT&%jpO{B=VLb0Hn?~8zgd@u zg9+RcOkqY(|Izy+n|?kgnh@|QJBb6a53(Be9KC;WFP#aSyfh-(frW@Z z;y;Km@Kj(Yq?KVkaYK6;N;r!4T9Tg}F(iixL|@=_8gV#>&Z6}5^a6VjOysJbn5}OL zJQX1*H6d$5HzJ;XTnVy8=mz}h>;_~8|1gr7p{~*j<`|?e-9Wm*ymcV|0C)tMWcVhq z2{ICBy-`C~C0hZB=_>XSX_ooaC3B|j@?)4(v`7W{qS zTM*~BU5IDvPYE&_b`}0YxfO_3U@PL^L{x_K8+S*9amz`MTCYPU0zN`kC|-^@?@dv9 z%ql5^D<%PN6J9L)zCLf3G!`Fb5N%Aj5z!CD+RhBt=fYG1dk-?OsRX`{|3ZL3E5@UV zZ{yEU{CoW^TH#*<)Fu$4=UG-`uQ*ddtAvBCOW7_7^^!FXN>Tz9W`bgj>05+^E@**F@Z-J`6o4HL|lYE}L(y&t5i)wmGW^@iU^0-oA~vh-Oky1OX+%9`p{TGbWhI%)r%4ej}#! zgdceXGV0_z)3^$;53Pym^YlTs4*A#}HUaMf--`LbDM9*)(~u=5NsX^HYB2v8cmT0v zorcU2TUBmI3cl+*FC+dfjfUj$TP?PAN`An|9YJCV1-u6CjoB}zuLW7U$O;=P2>5%bPeVPLmD^{cO6R$ zA{aGq?&3ljGVU6MZM=!(JV2rc`Ayk)MSHY;E$;{0CBKm_Ym_46Pp?Gyf#(1R#Po7k z>fiMyeyxDNAl9{UWn+aZeH&#o;&zQIROfFaqkLb}EZD?IhBQ%vm_E5OcFPe_syPuB z7gj??U|riTt*^6-q3{Uyq+c+dF~N3>=t)ZW5r}1|F9p8>IFopGtCs`YYW4405wAql z{1b?et}aLP0w*R=r8gi#!zYpPqN&ic#xD%H!a&c1{w2f{!BAWngWL2&drZy2h2-(`-CvNUGb5}zH{<{t z;Q^N9qM+R{reHb}5u~}!f!FDu8z0l>C`Cr~ z4%d4V#YegIy;_Ng;cf@^A{mn$gTWWyzIB`_B8KaR`aeWg}Hze6_V*}ShIk4gTO9%sQK zU~_EjvKVm`(e{A+gP7K}8sbAaa6B>_?dy8>JL2(y?*)9<O!Orp z<%kvQtGKlT8WAu0&4}LPDNyf6Asf%43gil+xIY1VlD?02iGC-e$P8hz0WjO+S^pLB zIZPVT#WfKNn4_^4Os>!uF_iH2U2g;~K(;zX4M3BC2XULFY(iA?+s2ewj7Hi3QK|PP zgqnO1I3%TPwiHp)_eWYQBM^h23BbDg-G=EuH<62>Blz^!n~2)% zKs+|=Ov%v?`ZYxO;2XeR0lq?X`gCLl&U=L2^G&mx=M<|1u?!p`+s`Xa7wFQcKI1p6x7`|BSe+7_GWC_W58 z#%z6O*x$m0bGgD=z@H;zY7NME+~633s;gaW3(^8OnCSF&A4Hk{6w#cQ8W5%X<;aGs zB=?(_AR~AqfV~la?zbI2{{qA*YFlh!jNuEcrtg94A%71r#BN_vVzdTl-x>CMiO09Z z5CbZJ)9~|NFI-O|{kSMWp-e08*B$@e3xMws&HKGXv(;mjweWqgn~>S|`!&cW-AC`G z0sU}9jT(hvcpurJy_Qv521M2FGrxX1wuik~5p{F)#(N?Uk0A52Dv#gbH$M0DTd<*jF$YAS*-HkN7$IA-mOG4w5?H3Js#ZC`UwP z*G9MpP00G#D-lzk%7lNjYGl{@Wy)5>y+N)4Q8lk^!M(?#6ft*lwUd6Cc-$rp+29!b z_pr}H+KzT+Jd9H@_}JQTk;Yhz2pfI zKgSf{F~W;V=5K9>PyB*n+6`|4%0*Jp?z(F}BY z9(L2;-*vt4k;*6;iW5Ft{91b#m@!SpC_{u|z7BJR{`QeX6Uy{Q=yaE5D(@f$FTP`K z)rj)_IsC>tnh>Gkd0N>djY?#E=w{+wIulkD86|--6erI0sNCi&17^=kaiRio*Ysfm zn}I{Ijc%EvMyu5*J9qzvELe$>TpW%Y1;0=e65q_QH*gRla`O=jHXw89hm(%8Ru!@- z=^uzlG05P$SlI$Y&p>bB0gnUzAz--OzW+hND5@W_>28?bqt%g-zyTz$$q9=gTI~2Z zY|cmYMV8cMoSDtSooZ`atsfDwTAzT}zlQapu1A)z_&8yB}h{Gz!_Lu$?A52LWG4_8Lsl z!>t#^ZSwOS+?COddOs?A_}SV^EAqV&&-J(IniZ`_CW{W%iax7nXB+&-eJ{dx17wP7 z;Me$%G!!b=Z$zlpOObiWH*o8#@9cSS^aW1DPfyUKf9?mQw~DYDn1TBOoFuQs`k8u) zFsf1ys&$k#`5|I4{IW7#^=*Eeg6`s8WWr-)k44lLubMM9E{)iY%{hSAam@f0VB#Ge;+bt=8NIDsqyO&XFMOf+P=W|@N>{= zK>Y6yPLX1)(n>u~LA`L@L_F(w^IV-*Yt_9AqVj#gBgn1Pdo`8f^WPt@Ri^8+cL&k! zq>J^L>5r^mzME(hMuqEcq)nN2Vt6`!;@ppsrCm0^o)P*X%T<;UZEpQ2{kwcnjyPJm z1lk7>3f{L;_X+ANk!@_-r}fp1pMgtWk^qXN{WYaIkIK#X`)Rk6lmd< zN~_J`i>+=eVmN$$NcURsQHh8#k3dx2D~UH5`W>Q8CHeU~cq z-_u9m>46#)h9e6|CL$W3>4tPthX~LHL^wo$PI$pwfG9|(I3A3>wes*0$}Q0s zYD|j1O_^49|00^3T(cgsuOi+62O-hc9V<`Q3b9(NFW0e+7DN>FBhsD4EkPEp+!ZnDYS7C3=SaV+qJsnJ9HMRUo1@iK zG0&@3X|i(x(KcCyyprp=7GqKtqGJ=Ubv#9^dw zuF)6qevh&$c9~M_;~gr(o%Ofy?YEy#@tIMSX%clB(xORw;@}xQJmWZMsx`<=L{=_u z8sXYTE9^PsZwkZbIys@FbGQ~JD>C>_3-$a=lJ1`2!9uhsL(@CM*w$MU=7~sK?HJ$; zWIXvB`hEm8>pnQCI0hGa%%ly(Q8ka&c&DLi&0$NPp2u zr9OC360RlqIq~%devAJO_gj^ID;88Bx`1nm=HC#tYQF)o-5i97y$V%@tA6AlL~%by z->Ib$t~!c8m?tgd8itt7tjO?tU6*jxehThutC#4xn|9DF)gr7G*(`deChP5;{qvPo z*phCv%&HU-U(Am9P`w6x1^*u5ddQklmx|zZM9fnf;yX@4dU^GZ2kjfc*D}aB%MjxUle9Z6j5a7y;gL=W%dE*4B34T;rZ1Td)1D*s$l2c3D^Ym3~Cm%5>*z8KTLFi?SYom=rB1THk-UR>O;lHuZ95 zLV>ANt6taV>MGKk_9$0M{aczG`@GL{5#Roz-g!}qEIf%?tCY6GBIX>=dj^{pWFC5(7^%jM_3!LX6??VP+HXgA z?i=(uycO6>595+fp2eMke@CQQ3$Bh6x@ey~Nk^GgMQ5rL$J~0H>gT?Q0Hv!R zs7`6CuIQ!-h=9xWAYF#2D6`lZJxZxUUn*%XVgM57D9;e2*YA2Cp9Uu9G^igEA*i<~ z^?JW1moELxT@jD}C=9{FbZ*z&7a0NVPD=I`t;AO8c~yt>q?c$_xKIm>xq7{i^jPmh zv_IPuU(d8eDc@H-lBWjXNu+HVrV!czza198BK{WQfw+&lT}yKP^-@h@uO~Ut;%jCA#Z6O%4b(OMTMZSb<4f-w@0{2{{rJvD+TKC`b9|B|dy4eAf1n6zGBEOdNd zqVHP|T0N|JH1@yDgE}rKX49r!slx#pCk&0fv+dPN~AqA zDaL2xLj2U!fl{Jj51Q$SXzW9M-y1aPYH4*fX|hwV-?cDLy+~NRr;wKkM5TC{uRT<=K6YcwHX{D_iBlbln#7nN+Iudm5E`>+2Pwj}51S z9Xx63wiVfr$nQA7d5(c{F@F2p<9Jy2jKH2WA*K;EDSXz`38#t;JBpYV)T@?%lb(Tyrlh0qc(KRx`H`|DMI$=uf>FB+ zcW1eJL<}28v9KL(qh>D>@0i&RxV1JLkYy`zolC=!mTXp&9>?ZAN!?f}l_)*87UD6a zNfJY}YFrWG0lXhks;1pis6^}^=i;w%n1!f+(?Gd0A@jSa)NV%1EV4$GR*fHd-rGCr zdiuf_d*UaQe3tZWdVlQ^*zYF(^|M)^Oq104xcAlXA)1|YYm8KuHSD?lkC@MeZ{pTE z`Y1-0tG_;jQ7n6-UYC=eEw0x?`V;)esajjP?nU~_aaU}q^jUo;71<+*ASw$Jg0Cno z>T5*vFfu2eWf)(e^2o3J~yWdH=qnN#v zwQv<2#!16wiHV5l&sAse4)HnMq3@H6;4`^`utT50>36Ez{C&^Vp@+QTmRIU-(!|E4}44AVo}@+Uo{u19*D12g`VJ3B@m@|s=zo{b`G!85B@{2pkoj>}BcWyuB1yC% zHb*86!qDK8V`Q7El^tw!cuo(G_zsn7a_#C(Ux-o9A{*Y;X!RV9MksxsX_S@7gh}oM zbgLtaL88w7J%JcT6eG&DipcuFS%YZm&e=-k=7H{B9T}_XlaqtI3Q-V$;&ERWB0c#u zX73NheMiGGL=>843v4$JX5n^{9lcBgk8J{eNcRxMWEl*7T-;js zf1~cb@6PvjXWT?at+V`Jke)fwNgm0^dx&l%%>>HlSv`f~?ypzjuYx~GliZ}Re;3cq zN5tFP2U&*Y@~pm0w7Ktr8hl+_AWM)IKv(f;1C52QXT;g$jH&L6P`6Q4h3_dHQHH@6 zV-de=-}Rj@X*OJHxAIF^2l%T86Viz2M#iV~IZN~aa&<6!46!67$DgyFBHf79>O1f3 z$@u)8WH*_i72}Px-)34)GNQ!;a2pqm!q`+H9ty5vxh04&Jj`feDY9$8HF3C+oH@{K zgQ$a}+CyGNHnZN{siOP={>wQUfYT_lZ9^%cH-7W#Et)J%C%yhzZ$!B1N(bITyl!G) z3W7Tw6Qy?$VQsPY*9Reb5m#rk&ygvNH2fsiL0WkW^>eP$z;*@67>2n9+A}>X+cIQN zhbu$03{ma7_yrt{yME$dq&HgJCCZgz^tG>St-{`)_*NhGM2y#}%Xcc_-()jyUknM8 zuNZM5a74-!+gSZAU8%tHiGOonXBdZQYFs|&Jrr-Nt`spM@$rIBd(L@HL?!=MO+-#k zFyX)2p}K9tUk6f)7+|@ak1K&oJnsFv2m|f%Y`;$Y-uK>^pf6{X8NNUto^>%q2|j1x zZ$`6H51j#aE5C#q{FXvAAXIbOOMiC5-JGTc>3663`+pVpS^M1eAssLVf5DvZf|sbM zBH6Y_C1T(e_TBmn>0!IJ3j5V0e7yMg%zFQ(0=n25b5rg_F{d-o! z5GD9L5}{iEC+YTA|BC?1gW>cEMlmoL$IxJHh6igiJQ&B&U=#y`;q(cz>#0`bx8J;3 z?`hfwKPAYH1=rc!Wxzh9tB5}yxAEAf3_fFAC`WpzGK?lM{c2!E`+y5qACFW*7w=rCRqHNi-# zgRu+?YMB<)a6(YSRY47Z4#x6WFpkHAaonf>-FJc-whqQIG8jU?Aic_^6zR$NShLPS zX35iTEv<$)h`9>j;H8O)`W(5oN1mg&H3eN^DsDaRn{aCh zlprd8*Ln3f5D{?{UCe2?i}Vg62kq#+h`w{Z8Tb|1>?rRp7~x1~wY?tDgD0&Dy)39@ zNHCTITWYu`s9`}+OG_}GAgFC~75=*#7PZvyNKnI>!P;yZ)G{Q6Dt=ARAX!hk9{a7~ zD`b62*@d()01;@p6a^1coMV{f$VNNYsi6jvahS^!_QuU8|5V&m`9nOOv-!yQcGLj- zHQdFvNslvjiArQ+nXkug>%PLCq@(uijwnI)QGXDjT2F1AYyjoKx=adcxW1*9#TvB3 zUA6xW@^gY3t_s#>>tJ|dFG0D!WUg(dX2k1$y^NTfS@`X0NZTWR5@N^c@)<9oINNoT zv;cCg)*q$&PYIpmz}hq^gBCVcRO0RfUq^g8j!ivS)L)RX&S?XEF{5m8D{N`1wfO9f z{>W(4O3(ZMh#ov?1ohHj92*AXxId_+K6F5DFTjr{2x@pesA0!hlEGKTOjl09PZXLJ z1rM?f@d5li3@#Q$knJ^@hyCHJ9*K zwk61j>1>A>)dbu@e7h1xfYFIqf_l*VX+ zaf}RVxHG6_rFT&Oas_|>p1s)(QG$;op8k6kqGtcx<9R-xZ0O<=WHE;?b8|ZB+O(dD zo4{`=vaPVZ$llLd-0V^d*Mli1T3Mh18K(syST`&#Uwx zZJp8g(aS|}wXMG7K0W{&X7oG{0{)1fM|GoC!)f*QDiN2h6^@7Q=puVROYvI?+JJcd zclT<2sGdP#S+U=StY&>E_(g~VUay$XXIF9g)131SHogpchVcPSqm8@a(QlVrT7-V48z?JrO+N*s|TE4lL@y?&Q&6covM!qBy8T`XVNp%N~c}zQL>YGKcNcqBesXW(MPVzr6sxbPT~I!wBj+ z3gF`iZWv7P&$YV@>;cjq+6S4*KLzRI&PVo>hxx9%;=XOH8QJ)h)tcKONN=#t^I1rH z`O9=<#MQM`P;84|4#nO7U=?r_-CGG?z+Qw`#5W<@m9FB&K&_I)PO`LUk~t=!=ik-4 zU_Kd{)J)PWG^%Ls&!o2$qdTG!xDIzwPV0pOW74W9gK=CG)Uc?%!2Rq{`Ana86xhcS zOorg+qe2Dt!tC!uz-@?0#7{$J!F_=nJnrd-$YPHylGHL~3f_{20DW1Ko*J5n~ODFMDm zJWg}p#hm7%U?L(W@ky|j1Bawl6$Im`3TpU$N20mchZ9iMdBwgL1Xqp@9oV1OK;WvN z_OUo^Lg@dj`eLJr=3x*ZdYQD6Y+qxV^(ii6nvD=ewkz{<2F1OS)&(w#_-E<5ZM&W_ zL19}w_4qH82@9WD_PBffU1AXMCyy+&J+u4GvDKhaa0LDe=*QEn(1LOF561DQ)+25o zj}-d-hT#N1=_jA-lHv0IUowo~gRum63?sO5IKd4=rDCTa!S9C?Tse&3^|74>_WN<) zJJ1V1UxwF!X=LN$T}ib409mj%gOsgvNkkr@xQuBEw!W@9jgJsjc277LD-pkku+PCQ zbgr@Jr3_}+es&9@nooKS?l!pFJAaRqO=@=ZsA$@|p_iv{L%TC7@YgvslcH!2Aec;_ zU>tX})O-=x-&v#c&-vs!Ql)qF&o3F(X<+{+u!$ZXWg%(JEf9sStD1Zk*^H-@XhQKk z(c5CbNRe@vs@0dv=evS*H=9L8Kin691c=aTU=KONTs`7{)JbJsg7_l11kgE%)+1@k zdlGK@5CY(rq-au82K)?nF-Ut0>t|r=S)*VS(nI!H_%a`v`A)508SGD4P|GzzEz8=Y z+Fu$bRD5SW_H7HnJRBAOKH^fgBd{^Dfv|TM&-Qs9l-Wzjj)N>JtRcY7xZ9Mrcm06$ z6zBWnzW(+&ic3tk4epL(D@kwPdLFPL;aj>bbZ+swrm{L=6RCQn<<)pw4%t2MOT>k3 zj#dU+(J@^<7Wd6~O&M0}mBgcUDIA)|klyyNvm7g{+-$sZz}dLd@Q>5|4QX1#rT4r< z8MU$w*vlU}u$L`*2BtMmI0zA^x|}V~km4Blp~zTFltZKm5i1PF5Cv!8*H>-HDi{Uh z*)AB*%N+*x>dpiEUf}|JQ=7zb3ev}W5*g>}D6oGMccIf82o;`owO$HrjVS-4Qt*Wt z-h)WTsw2xr!akB9U zxB>SiUB{BJuA~H+IiC~p0I65XvnSC_Nnl&tUAqd`Wn?&w5KN~$sNs|raiD9CA+RqG zYB~Ot0j#ahRjJ3beKPJsr~f0}6{l!LeI))Pj-9b+Taivn;XvGNZ%-iO{nU=Q6n8=6 z4|~|TI7->ImI%+lP)!cPwmg;~j+9jyeBWPrJQKemVV{?N!0jlsf?66p0(x^$%gSIpbAlSK2u86^;QvhZMcnCJp6v!iY?kJI zEJFljE(~6r1B*HL{h>rN6$C;u8l(#a0f_K@(qY6_w%UTGvFj zBa>(0KAQXx341qeh2N&Dw&IXW=vk`Iq;QTW75k|kAerkMUR{sMpoaZ|TK*o?GEa?b zTDl144M8o7f^obR)Noy}E?*6XMt11h01=_Nism*?F+nXwT-W}A+m@)X*IUR)U>eEE z)wug+7n5PjSlonM0q_dxnFHkoGYQ|W_#AQZYkh8w$884wHDsPVtLNz3xNqQ0)NQd; zX`*=%50Ayjc!SNZM?(d&y3l8K`3ZfvbAw<4YXr3%-dxMAK@G14xIvC>dGIm5OtY>jK zG6o^zTa6yS!A)eheXR2UzaEvr1V#tzGQDXWhXi9eKB(cl!Me;0# zAG(;Gw;U^T=*ih0Kij2{? zBuO74Hk4_!Gy3B017EL~%kxp_u_uk`xQjm9Q$yz&zi?z$Rw3(zd(!Ia)o|NUe`6~~ zqc?7vn7N49IBWgFy7-A+A0mN1dl_&p(MM}OMYd-3BymVm*ckV5p+a(XG&YgY8k^ug z=Jo*Txc2o%CQ)2gn>J~p@+59E`LjvKXy*{z4X#e3xHg^Lf}0TYQ&KV_$q^>vHs^n? zR;?w#zR3F4u)@oqk)r2%7e$1R@O6QnQ?V8jYaoIhAKJI|br(=$aU>S(hzLPke}kon zaZ8om!vEqRk3f^gxL`FRE^_&7Er^(CM?{0+QZ;@`I;K(I*MFDG{XUf9(dAU)?l|`> zJx@@)yF+jefbSr!ih+m~Y}otzJWxwMrm(6UKYQ!zcw*KE?2Ra9qgu$Gi0xgE3tz*1 zfkuFMiciNJ|60Sn* z$^gW~%f(gi9(rDj(q&97P^PC^8ON~C&q~B7s5(dA+r??G?K2Wv3$+aC7ey8AyodN& zTR@2iGx6)yyh%C&sbWDXa20N&S9OTNN>YbKl_t|JV>|&uyZ;yw2$dl7x-Q>qCCN{$ z6s9>=IDW2e$kt}HRDYK$x~9uVAQ}Rf->xUEX)WMt$LC;Q#L(u+kV#Vu@JI5eCvsU- z(>7_3jgDUnFx84qM*t5y2GQBbs7>5GyIva9 zUBxwx`rX?axcnSw)OQN#5*I>*;9t>vR)MNCcjF$4qZI~ zC*rq*`!U4DsF+cvFYFG;2+X{84v4okTkopMn~3Z@&U)Uv8lk~oat!Ltnh0#yPG#bw z9k_^OH`R^P>=(y3HsW*SVh4D5aMFm$b8S`h8Z{KFHqltLa#GbuV2D0gog(v!#0AD6(~!>*^VE9fxdv-A}{` zimwr!hF=LBmgAdTtT@(_&Us0xpkl1jSJMFaG~s%*VU+9J;XXDP7ZqQkwB@n5@9i#7 z!r$tIZk*}y_dZ3k>wGuB-7_E{oo!rYdSHgx%GRRiV|$-zJnr_yY3~O2snUlaBUf*C z!74pL*A8#420kv=-;kD1p5ZzX&^B@Tx^LB9hWl7bM>B*QVl;f*I08Ra{70m>nlsKS zeSKthJgn$smENPSEIM~V`lqhy)UQUQhRYDK-`V(COn$EP?T}9X9eW^C5UwUt+moD7 z&DU7zD3Tpns!}%LPQqE7ETGy$(P9%2KWHx~ht{z`f1Thj9nfhp=_7vewxSpWnr8L8E zA60yi?0wAJq5|m$e@owyFyE&cID^jH3PZKvi+k;GGjKV@F*Z0*pCRA)+5()8hd3|} zQ<~*U8B}A48$J|rYX$C*oPWRG$XMQ`xQ~W3Bjzk&bRD~S1oi;gO67VGm*|1F8}Ki} zFTZG8DPBN0ft!y|t)PiXYLl8zk3B%P+6|8Rvy|!qdz@0|VeeBTo%h6h1CQe7)cFb0 z0?7lDR-#GN7Qi2HGgJ9Mlee-ilDj#?U#-6r;@_Po{1U(IoUNak_C&LtR!SG(J}T3m zG=Hka;6yOV^MDMHEl0IE{a%&GvV)^EtM{>9oK5(J8omkAqxx)zrNXL^F_km)@bek- zS%$2s3~OaO5crG7J$zoN*Cg6!V?C3Nmxy;oZk4_RS9%<-FCcyNl1>7n%hs|&1@ocEX>j?~zMYU9Zn~SIo)*MBh8o9*b5?lLY`fMWo{cWXyGJ*mrdeWXVMoZ}=9? zNY=!V1lG{1&xgvHi}*C>sSj1IwEIrL|KaAC)>f=}HsRf-Ho~vxTZgF5hjj5$RcWQU zH=<@;;rYE>gL;8Vy#{xYQa#D}POOd?lej*&E0Er4RAqdr(wBQ7+YwiJ{0=iiu#gve z-1i5SDak^QRHe`I!N^Fg>wQ_J1w)v&>yIP6-<>7eqbM#dSARXjqwZiimyqp;U2zu_ zwf6!XK=CYYC^l3pJ-HJ95yU(=0hVb1*%)z3`viZ}iU#0T+!hV3tp}OUzw(ad8rxAXq1<1y%tf;F(K%a~VquwN%P_sd4sSR=Ku|)|q z1%>?eTIp?eJ$es z{zO@f4Yd+qO__!M`k#j*+KKOJ!WU&;x(N~PZRK$fF2LV}0oh^q>HuNc_i*1_y#ianfnJe%*=$AyYrNv*_AjH*#HM(t6nRnnzJ z31SwdTGZY`qvdOqYHN2XiV9UDYNclFz4vPDRrHs{^LqZia;|eepZm&n|8bx5z9uus ze*}B+@+p|uvdbsSB?2y69!RhKG@WFCm`SNsn<4Ismfm)BKT(~f--wDgrAf42pT>xJ zXlzOa-zy4P?889+I_p*T8SZHZ&}(4l-^mTOoyWb4?NB^e_jXp54q^QIsg$+AC5xD^{X?hNxNzXeSx$)=>N^Gn zCuv}u5i@@Kse=5>_G+DRy_ok(E**^jBIcUqdYf-MnB|&nbqUV&`7Gy(oizV^TGGAo zX*#2F)Z8ceWFn@rJia&7if>Cx!i9tIcQw0IqsO`nYm zTN&e06!A&!#1G|9O>e;hM`D>ocax)*=8a*-pIsbtF(NK65zj`-Zc1kbRS}QE1wV8bv zv#j;J3>uuP>1dq9*X$D2!#*3f$C#sa`u^tPECR{%g5HC1(DQ`YBGKV1V>h;?lz$=V zW2iaobnyeo1UAl@l(_lfJvN-6YSF=bZ3+ryJVpSI${COfrg5-Jfrx)At$D>)(87;& zb5@zoW5WRJl2Etb1tQY{rB;(K$2ZM0%~ys&G4B_g*omh{oic3cIF~&?N7Xm8|IT;w zg}v+N`k8RvI3@i0=Mk&-SiHA|>@n?T_-R2;K-aRSjs7ub;`;6`KVYx%0hBNLsWQX9 z`UsMBA>UuR&bV|z_(KZ>RU~|bYu+TRdC4C#uCyftJDFT}j|nC~?$aDPT&!|6kkk@| zHtSbkjM%BhY-LmkR%I!tRaM(Xa}L?2$Il4iFVB_h%WWN8`39I?f~rd>4ewe&NegjdE1!Ua~E0HU*Z6>-uWdlEyn?F z1$XXUORVv2BXu^^%gTOTovP*iu#ONICYlX*bnV0^_7@4P-tX?~+_oqtcFjMrb5lUt z9H(MpQVN|-1VoHygRk#1k(yFfhI5UazBjPzty>yA{`OZb$fW{a^D9wqDsZM`%T34e zXKtw|8>Z!%MA2P>7lVv8?VGECt{Zv9{^+(z5m`B|D9E-gsVgAK!??a8#?|3saj*P_ zy#uN7zbfA{u{Q976Jh#bJ<>xp8ZYJR=4#1E*^0!(1Am+$2HNmukKHW<&a z@H2f&krm2qaEa?lL#DrVXsm#Fipr%VUPX-NthG;uEi;&horLmiD1{rXzO~HjS}JNJ z&ME5O7tfp|-3;)(+lqe5Ju?%Wo2wAE=D7d_c(@rYablpwzDfSVTTd7D`3-}XHz|6}Nl7CqU*D3DZ8+*bb zb=1CY;^klO6gR#(YBcWCxhpYepXq6;&tdZgD3v;8{c`8+quvHZ)7<~^F)D9GQDXTGb0ke)F{k{P=}s=PdzdsixRB-t)m zRlsIJ(QDmw!yk_M58ux-(Y4p$gXe76<(a|Nd_^3ctUD+*UYCfzl^SVa{4YoDu~$!! zDvA=8n=-$!P@F!koyyTi4KA;CRq5_H;!DEyLsu2M!Mz?mdyQXA^i2CRH0b3vZ34R? zXV(fu?*LJkwEc-+_RbYhndp9J)*o0^=9ZC)zYFu09B)Kg$DB(vjy8L2amCvgrNfbx z{Sa&JGyR}2>#j?vOHahp68@q^p_Qd?7D5l<$?o+_&WRfG1q$8HF57B|)x{!%5kaPM z?o&Fsn-$RIIOzv6><6577X+SfpPChc)$gS$@`@6Ge0O??8rgFKjAk+GHku z0L>rs5yIn*&`R=S=B)cf>OtqMV9su}>f*ssf2FmX$*`znGLjXpnwLaBZ*)y>hAG{ z)6c_ouuMqy@DUR|-z!da8_<72@9XV2bKDTFQgzz3b-+F~SBKA_>~S#%kSn-139%#Z z`$~4SzEzGl?Bia@bF9Dm7l!s;?n(jOJ=HPBV{RAL_2)xE#nJJ6`}cNq79w*iI}j4% zh&~UeKeEV()ewa;8?ZNrO_t{?!JQE{%*Zn(6usyd1xO&WDAvSQ-Ca|?F8;G(Aj51T zzd>@z^DZ#bB!9d!r``~a>?)@(Ka4$n(RLWVC9xwnYTdz&=Skq7xEZKnIpDd=$8;{fX_u_hp3Z}n5u)*Ea5NP_3_>j9=z~E#3 zYBE6i+;Y_UmM5I#$V;8DqqNaSiJP>#`&yUaR?xhZlPd(s>s#OD940@>PA|oHTykaCdL{IU0vn#T|1;TNBh%^ozh!d@qSO?fA8f%JQGH48)%*7&{s~HgK}LUQ(M6ClY38BVY{4OWWn(Qi~`2-8E~O2C2V?9y61+TK7^eJ@Vw3_FoOrQIvo( z8)b3HkcJ|MFHS;q&5Ct#Hc%E4+{gc%cqkN4gZWvRTWCX`2iqI!;nJ1snifQ9qW680 z3nScpx~S~bJ)GGJW`?2EJ(^nnyScJb7)op+G8(JB zXAqfcDfENhnrelOajC;1f0Ax34zo!HHlbwv&TUL!##hZE9VbCUbZJrT+0t37g%6P? zP9$i?IqWy_GZIuMGFbRFTHT+DkhPVcVgjVT+*2J!LoRphmB@#C4C!W_BVa{jsUY#1+g-11s8> zw81RLe13GYen$V@U=8S}5fImv0b6ppk^G6~#}4G+VEB8HKTlJEn<3Q1q&Ik})_%iXK%)KVIJRhtWW1{+9*jJmx}&#V`yyDAyceWO${6HYBOvFhNULl}bC{d=;f5(Z ztFCa;UL40gMEU{qc(r&j5C~V_dJA?v37(pTGtU+B&8AWn)4Zb2u$_HKmbcVgU(E{l z%LvE5eGt=*T|_Q%`k)H|%b6kc$F$0{_s3m1swmmqWds=AsCikB!3UU`<)n&>kp%_= zA3N`+PBWvt(Q}>TgwY;c?FHb5l=x60)xKa(#R9yr)f^_jv7KUD$>Q~S6f{~zQRoq8 zoadwg7&Z-7Z5o!AKDRZG*O_|IIJMw}w2W81ve<}_h=2b&E$wdx*%5y?Edkp0bANP?em~3W;}U#s!%tI0 zcLV4{-HZatlD+*JdO8^zEIiXy3#ZerMx84I$woZmuH-m?ePx9|UVXXSOd+csNc+Q* z|4!p0ptL*T#x*h?dkh=Ao)e!gldGt-a)<@Ky^YRLygQonT_IfHXfDt^O z6VundL0#Kt;fK_ zm2!tDS~1Xm!sVR|kO5AZ`sgA-yjMLe*r)AmNgy9jRroGbDA;u0zwq}XUA4i#BT^Lg zFz9W;Hj`jLAg8z1BQ31`AN_@iLY=cE?b|fHp#iajDn$(7G4)Wf74tGncd|;|(j_`x z=4!)0>hF$?v48GQ=`}tc(DNcc7#U&AO_bOM4SLbf>dukzlzQXDQ|bkJCra3i7|j6S zvhLXUjNGgHq4cvj?WG`!x6BN=)M?bm0;Mccmq$*(=!T5lZc62b40rH%@tTRK4&_UvVz4n93!(P!ZEY_wCB$qjk;6b!myy79^;cjdhHrO zx>p_j$NI)IiRf_Sybq^wPG-`#mswXP?b|FUw<&?%0dw;NdK~$f1#du*gYei=xOg!8 zKB|yjs`XsI5c*-f;~mO>o*A0UxaquK66;P?MAK}Ua`qn3kl{#|-7kp>0;NT||6jC{ zGGzG60{4)U5M0GO;cQ2PG=^#-r0*5Y2O8bAJt#SQg0ve`^%|&eHEcCZ)@SvsAORr2 z7j8ND6EpwEV9nM<^6V+xD9OCL-tpk}3M;^-UTu=%GXWtUmIRU*#sLu~_?uIgQMZ^@ zC|CapLTo6DLkTJVyq81J8$EK5;_wgq)0&y7S5Tk@MdeHA**M^JCd|s=h$}mDH+b zJAX@3QmQ`dt*ex_`e~_kx>w^AIERxz-N++9CA`spb&_00P&>SpIrz5ZKaXzQVtZNl ze%|e=X~^ansZGpIb)=_9O;4Z)vX-TMH%`Q%Bg3u=b8Wo2d5)U($!FIqXUWdpcyr>& z4>?Np+av#y{ypaMxf@?D-ZObeJMP*)PanTIZO@r)|MBQ&J9k5pEV}c!Yi}eu$!;a68K$Tm z^Jpi)lewQc`mw>tRKdi=QgFrIJZ|c~yqV@d?%ItnA5Wa0Ro9xkeB8voIsG91xN9f8 z)xpH1)yk(GJF&MCwl5sF=I4@cN|K4tK}8-PwW0^V14|A+?waKPmi&K9{@1?#UzGnf zB_JdIFUtRy*bi5)|0VV#OX~k>`TuJ9VZib~_;%!;_#b@xA7Ve8L;rseySGxtx_lZ! z=fzA%Y(RUiv>$cA$alu0`-{OC(3)&)G{gvvk(EwYF!>PVp3L0-{?AeW+k1mmlB|qW zfj5F*XA7=W9DP*Ha3;=agH8Vul=aDxErhoqFZfD(^+YQBY}CY4OrF?hR!ptJRCHzP zOsvMlYptlON8Rb<=|L127mDP`5F4B3Uw1vYcDW#5t9^|v5<-h`Lb{AbkDvH(G~7tw zKL6#7wSr9{TDSH>LI0=AzLCr`XGVv~a$m^J@U=2>Ja1pNPG;db=Gy@W-!|s8kDPP1 zcz8G9GSOr_K_QYS=}AClCQe0f1koDN9wUh8b6FU45Oti->wxrUd)D49jPx_Uam*ZS zfY!83vC>5OreER5gW<@M=|MG}V(*)@jQg>8)|@>1>}#H8wI?YvGgH0Fu+BG-Nz{6< zNG=GyJr!vXC2Thxsw^(-wqeQnyi8km%brVJnrrihs-MEqkzwXW#`NU#Rm_GdCdV{6 z%zDwC)yGr&;Z+IGP4k{qCflZRcBNsj-t^#Dk*WQ)^C=;g78ZTE2Dyb2#3*=N>0+5d z<#H`$)CK!0X#ou@0%Upm^pTnA26_WDrkbOogj==`*Pj|X&%M5dHtCFmq$$OUn6wz4 zWp+-85Z+uI5uGn=b|&BihERw?s{+Rv6_No7=apMUoS`Vv6%nFD+Gaf?E0cq99xe(0 zp=|V-gS#{+tPj6_IGo34Gw(w`;8OekJa`mg^X}xz%~7{?GX!3jZbSP9bZz+8N`Xl$ zE~fYPHx}pL-juu*O_$ICDT$a{C0RtzE%u|Ci=vC(yKZ-jf}1r~ABZ_ID&17Qs_$wg z<%}(N%4H1_Tg|JZ9`f2JZ4O#?>Q}nXtNnsYb5z?qmXO_)B5$whN2UOg<=jf)OjpKlAWQZ% zs!QxrrI}zTE0<2UKx~CeA*Ku4Lb3|#i#l~ol;xe^N`S3R>0}DsfBQ7iz%_wL@8l1{3~@)KYd%j`-pan&H=tvAAE<#j3RUDE?!noffbz$mQ?hp#fq7cbgg963IoJ z<_A_1-cgH9d|;&(-@3tV$1Gx>PAs}ynBuCzUQsj?iaDc;%zN0B z!l?jGW6~t>rqYN8%Z}nu#8-?5q>s@bMd@(-j-L?0cYRzub-K5z&jdo=cUR`D4EE8 zU7HZKIF~@EFg%`K?-f(Vw~R8i(&;ig=Mu?%r~%!4lVgas9nudMfjXU^ZBHV#A@kry z$84oFKy~$a)XP^BPl*MdcASHIz+X5och^XN2SLlAul&EXbA>poVkhe zJs3Nb*>N_Y!c>d>Gg3oEVoin#)u!zx?=-72#hMTEWTRq=rakfj(I|4jPHlOHI}k>> zJKXw|u7|QQfy~wm9_|$g(^F?IEJJ-xIV|~)ENdHbF+6fcmH$N7;-Vg-MQZIr33YN9 z%?>L*CT3{dLTShQXGJmp9GwQB2lpYEhIE zj`CdJRBZSi>L*JKy6GzU@fx=)rnqZsRdMh$Q{{-4%&GO=Q6~i_6ExB-z4%W4KQ?7X zg;{5bPJ*3-1HsUb$#npHIR8HBwpa?%w8tm+yg^PE{PwUFSL+p7k*4-#-`j>(RFsjI ztI8EyjjEu1C;li)t}z49)TYh@k@4IVm4Ehqvq|efX0jv>WrZI)Eg;4uezCrkc9WvC1Y7*hUH52;`&kJL`x}ofYp)%%*cIU0w4U{b6PS<=R2jCz_3PP>$hScW zkMQuqc2W0e!Pur=_rT*O#PhV)$=Ky`=mf?#*Acqp8O1PASSe;ZH^nT&DZ^74eKwdP zSRr>>D*4f5cb3Zwy0AA;e-=mcl4FxU{R?t?BO8NjS_Bi9fnlWuYptA1$m=LJ!_s;> z^|937Ju9ula?jr$ty?U}1IZ&K7bd60G-`Y`i_d8-;HgiO$>ZLVdw8p)V#&~ewtB^* zV@WcBbS{rvwsvWdwxE3avJFVw45zB*Gaw(fQt z=Xu_`A5^_GX)pK{|6j2IX03&x(Bt7Jt z?Eemsm+=(NccLSy2STU3HVz+<8u5t&fKbL zqfZG4{qeW6dT9-GNAJ)g-jh&C_Pr|Zu59b#Yd-bNv)#rT%h7^os;8vtdpqxXcX-Yj zjXW!Q{&ZNH^Z={=DLW=xn*5jmb^w1XZIePQI9je_(6|Wtkl{ht>7d+RVpQ`>_OA@5 z3oRcMK|P_6AiGoe`WA+TV1}c?xC%1>kV<&S9l6XLg!D@Zw-?G9Oa^VRM1nx@Q;`8S z4QCp&7^|mCw_`<}n8lozt;?wv`jBplitW(?mMAN10f(E_XazRrYcdZL+UDYsHTaEW z)PaPz_1O6)hFILXoV}W-bae;WlDWhnITXe%z!(*&sOaP9OdN<_UmS63`tlRhH6}6V zrW)^4Vrj!^$}G2&EG;ef2)QP%EqzGX6&~GW_%GWT%Z5f(t2{&<;MdNSkT(I2b`1}ZAcu#v&^4gbwr8Be2i4W>tjFdS56sIQd z8t<{8o%cctv?UuOPAsJEmf1)HPnU+zGeXwG>2RMQ>X@UU%^OKP1$f*6gJzYb=cUgT zf^_`HRv+=^Q($pX_7>!relrJ|kU8jg?K^p;v%@7eV&vMt!dza5kqnvo7q~V zKr;Af^Y622-=Gy+a>K#i*1DFDl@%B8L(5k0$CocV&2^g$RmZXO-ZcW4f>$`s_zd4>&!MdbLDVJsii=VMmxy`jtY zeWQPQU9Y9cvHT04M+G0l*oC1Ih1ZSLQ$P6Y`+k`f zUw55FCsJPFxv-NFwpG-lgvph^hM4=~4qD4D)JV#vGz}f+tDocsF@g(CSKswFK4FPF zW;8YQj2XZE%%PB)o{T*{4RoR{*YoU}j&LmlN*(^SjLwJfcfRw5;c)9E5yk%H(wV*$ zHpkYEme*WVi0TUWnl~p$l?V9m9g~YA4(feG$@>ha>-sw1U=!hVlcVk293@2gGeb=R z>IxZf+QlftOc_7&I7H`g6LVQOa?Ub}!M2Z}vAWx%GuM#Q?&OH?<5os|JQ9>jCJ+wl zR9@v23}bE@U`_h?!p+yf*Sj$sW}voQXPQ_T_}=8*;`+m>c)XXua8rbk?xmv*79gAC zfpzgpk@6_Expf;CNk2dw`%y4yDorpzd z`1uY=!&|bIJC4cx;s>zV>k4Y-J+Yllj$uI}J3_AwmO&`wFRpmC{)wJHMX>V*$I{KR zDLFX)m$ggiayQJxa?#c#1DE&V82$}p8}5gfJSijWJJ|G!WrRZ2`o`hp%tKfH!2#g& z+kx}Dr(<=%e9PoBlrXKHb`v>&zgw`!A$6&& zj;FwfO1Vhkxa~_#X7w|88Kz>}>vqAI&eu(C7V2!SIH={}^pyJ_hqJJ~Z`$H=>BUeOl@;;vCQY3!X`ta8-`y`qQB5wXc zgKNXJ_z~lvl@-^jm4+C7FAt>(ln)FsT2cCneDSEIExPvKfLosSVdBQ+wDf%Q4W_x< z5HJ)$Adin0!Ac{Wb$*&HtWsyR=GpGN6bfb;EcXho*6rZGR6z=}8~-*R+;C1}HJGMe zNwRutqHV4xE$tpB@u;$GCPC(Y-f(qkFeJGz*Qk75atb=(ZJ;Apk12^7#Lk42KNZe~ z%FhR#ORUHXi!=Xp^mcAOfIxlxp}MHYRTTElOA%kF6z=wweh00tT~E8XD9yK)4(53ic6_M&`X-dU2FmDLzD=Fth+?8W0TS#ZF(Jx1C!1~P^BjTM-z zH3Y<529Nx5w8E<+Uc%tD+qBW*;SBT?K-OB8cf;$7T1NS?-2IHB#fk54PZ<~(Z_K@C zrP1~nFPutRxSy>eu`*nsCy(377)qiU~*FkahV1X;sfKxVudmq9{qj z`0&QY3uulmX882FtoUl8#|DAD)D`Z{_tUOOW2Nh$-P?Kh0GGxaMaP)akL< zX3`n~+4^m;SiZC;H0ECT6}6BYXxp{~c=FU|!dAJJ8;k-L>fw8Q$XVm<)u}?$N)Zp@ zn0>Z(Vd|`-`r+V8SU6bEO}#-)YTUI_m+s+98d<}{=)kmh+$C{2cPm-n)Z~!Gia3ZY z6sbwr*j_tbgVg-@$gq?Jd^@>Dsjyf1>B+M47;{Bilt;Jm&KonO$cS zV?`Tx_vN|6`d+u)Tf%fYntW&RSnmP@FBFt8|Ma5m{Gra>dzb9qev*h6JZv%A49X0Z z{c469poH+1K3kB_S>h+WQ9ey}uIO{kFMrzoHHqrtM&>^XUf(<^0FG^}nt{k#auju= zv!F=M%FY&?R0e#Z@w-*k+vF!X4m~?d63?qUglE<^hwYZyg~q-kL_%=iJP+ffJ8*B) zU5yFbxF;@wiY^0ubokfSFaC7)Qg5U!KNPxCN0isWri-CXOvSO+uvpG5tc}a>60v2N zVZuYVUPA5wp7sWsnp}K$oMz-z-9B50ha&`;wl^!Q{9`IY!pw`B*J{lhWhcHfUkKJu zKgFN6P2A5Kbay+G>=Lj0^_+H>{q`Zw%fFZD{{o2!Rj>nBbBjH*CZ2VEgoxs%^o=?< zRwXe5SXu+8{iwe(TC-js&Tglv@i`ocVN)D+6$T|0UV;0ir zW>psNPX*%wz-Mo5cjk&LRoI0GDJAU};M8CUpmi)5Y}E=A9e);`O(pNrG4*U@^6FN_ zhmGnDswH?GE?yrm-#DpvOFbV`YgJI()$)SQwUqlZwSs#4BGib4ek=^Kwf!E!%l z#{#Mhi}E7kDCc~S%~T~r->oMJOcd@DwDIT;hIXP(#Wd&KX0fAB)oM$;<6L)2DS|7d zDs_Wp|MgE^&)0+msZ%i9HsbdtN}jDjMIn;)=161lq@4@>;T+A)anKko@F_>P--lDw z)-s9SpVQ>>AI@weQoR{}z#tzQxPDpm+r0}Rp4UnLs#KAMvMCaY{k+Q(TXGHwYN^k* zo0UC>OYLUV&!Eq`bW4dwxg zR$V^_{uJrdYMIWr%Ey~AOAO9#&xwpKXdj$ejAWKkzZt2o=uOJZ$^&KNu?Xz<*WeA^ zn`kz2A_4C-mold8uweHKf$J6g`Yw*o@GYv783>+DFJW7%JBkJ_Q~TeXC#p-9?OYiD zdh`Pt+vR2Fdz}}}CP9P>z6}K$&V0|W_9(20Hn-EaC~Z>8S?pWA7Vgq-ZX45m$%ewE z5Op}yPicJS3Jp-IYi>vC4%hBHZdzNeOVf8A?;pA_?+w(?5r_T_&{Q#+ChMW9x7%%I zTWMMi`97aK9Kff6vcdl0nxjcnU*0bxULfrBINcQ8*|Dt5Wn1jUBO3iXTZ$qZD@SQDK%SKDEc;T=`Y)JWk_l2Nj|0oS$l_?FpbiHO{4Zr8ZlrA2u2$ z?2>D}2wxUeb?t@n3~~Nm?&ePm&_Jtj=2x8`Oup>)qNnS#Esf=+xV30!n=;Evxh680 zcuw3qn4#CNb46<2NLYzDp?B;GuYQI5_7In5&OnvN4xsJMQeA&wKa1v2OTEA>c8BAN zgL4Z9`~ISn5F5sja-IK0%^P0N70MI|vpU}Ze$zH*Km*Nm8sxFLeW+B97x&oNV7mWu z`R7>{F$Y{PWaEwlO#qNO@ZgDHfRUR!m3sECZSwqJrnVyJFxWCw4KK+>|ND)QA`PZ$ z*dBqE;{{9US#cjSZ;cJM;2NF6BTU)H?rj3^yE;B2qZmeeHh;TOM?62b_B= zZARQ9DwY>MgB1gmT$NR9Mrt2oU%vi0edeT|>E(8WlE>-6&ozJ&11Y}Su4B9Q6ph$` z)`oT{MwuBoRJk>w&>^j1~hfT9PtAREVOWh*%gM&+f}L&sVfESVi2f0m57;tX=RlK6Hn z*oW$=!e$SXCuS) z$4n=CR%&KUN*!VyW^-cgvRpi>Q*MYodJsNj0(q6{04zn`8#nS^Hg~Ue?5x6UDlx;& z7Gs|kc((UwmBp~bzQIJD?^%^>H+@W;g|9R*vk-myu#Gt*`1#V}tT{A@f%1MaEVC*t z9P&z=LE7;ymAO;(Pxi2>mCB-9RBNe$&~5cmd3Bp=okWf!Gg63rsSU6sIZgBqKfMmis;F?-+lEcv~7Cll?~q%0HxREfjVG9YLN`hQlSNZpMu5LSc^@7ZoL z<8EU+qt42udqli~E;F;sBCpqW>r=VlNLj|$&cOm1zK8QdDKM2O?FBgRJ?JimDXRBJ_A$PFBXB6Kr+h1qAdTm|m}I}g(+fS3;36MVD_g$f)h8`=>3q?A0( zci8C``4>AI*QcI@GIht;RB5+tUJGQYmIeK1D3hvcC6lLf+v)YBAo1wltF@lYZu2Y4 z1zXNz9-G`%_XQ3Mj(1qVK0IGd+3Dw6Eg$rRg4@u7?q-R)mNvm7n$Tn3Bm-v;5ymFp z6sSaJZl|0jZvkEiEE(72fB9~2`x=9g^)vVAeA=Yz{#6q;%fEm6(`2ku6J(7-*_8vk zf(mx|O~qJ}3_}%7+oS1h8W(^g;sI8e?r=EVk^XNOmv2iWWY2ZR51y*vbC1KUzzlC< zC|fJ4I(Zn=CSLK-5526j)8fun%M)8=`{UhxxN>P@Sq{p8c27>n_^a4 z!fXj(6&P%u42Im&uQSqVk7r>&$>hdUf5Bhm(0ZRsW=C^W<_sY^TYN1RQSY&8`JC$m zFKaErVG{1Gf~?`r|BMz7So_!s(T9K4O7^GowTJSnwu^keZ0j!>b6G3zh3ECPK>$T2 z>OUFt7BY#uYh6#`%8d9BbL29|i5! z$oad#Qun7pkwei9kR7P})AsWkb^rCos)w@{wfiDmtIf?PEJAK z7Typ^*po@|W=kAYuv{my4P!!%DRJ3;32BPu1p`(2{>Jv#?G2TLZHfJB)oY`bJC|om zi>(8gne52zEvbGi+qrGbKmS4I^w-{&K2W1~RcqI?CSLjE#$6u8AfL*a^yQxV-1fk* z&wa$?p6L7JZHwo=k};RG@{Jz5u5I^MN7OG?l$D7%4y|9`m{SWhUl~|Ckb~z@5B_31 ziVvJGcUhY2&dhb)XcOmmW#axk8l)eo?LpN!V_oVHiQNDGXle}aiRCu=tr5~**G~@Z zVVFMSD({GJ*6^biUBA*zP<*eU;V|a@6&7{r^k7^KAkKPXdZ*i0gf?gUkc_nGk@Av^ z&xjK1k#4u0_0Na%r~Cm$%xC2xt{!8)<$0ZXR$G73hu*mqv+~@lU3fYcx{?$T(Zdb&_Cnj<$z=a`?vot)4=lLNcc*-E^jAqku?gu$!C3I|5l%U+ z;+pm(V(EOrE;h95{GVX2B=N+%*yjcVWtNt9lp>EmK0L3)!%InjIBtI2N zP~=X2dsu?98OwQ$j*BSfxz9244RSO4me|hP{bH`)y6kW`|1^T&3rGId&--+uf9a)VOv%*Io+V7M|?(S*Qt2D`5QwT59a25DQeSbSwW|+ zIzs*JyC#|S3D@%Hts-q)zHwWbR`sc){$FA{7coj zXVv)~YUUCD;ce69)efO-19vNV+}PWu(k;()f5|C|^?59R<F>C#M>Y@9v0uOn5|2??zi*WGCF; ze~wiS9-i#<)zB4~?#QO@e!=yl!wbk<@qGoblgLf2mVVBNwZ=F3f8lH?vRF>DY!h*H zh1!Gu3hXFR&s}Ga{cu~f4qbZ1=b2}8TE(E`%Y>lH)-A+)fJSRP>Ofz`?n^GRlUs(v zhER)-xqC;aza@NG5;r+l#O@8UVY1K&_-t6L(AAV|)QzkJEgQZM`3V`US|?!Hm)y%g za#(x#5_hnr5#ipXMJL|9ktNTb6oo7GBL%XKT-^&Icom#>(6GSsAwJ)y)Pq?rXcYm5 zaD)fdz!|N0QIwdi{Z{?$NXX8&^X@J5v)QudCZ&>}n54@gJl=ZTuFJD`I zCTTNx*^V+0W<&ZDAT)Xv_RjZk2EOqdg!1@hfAY2I_VV9v_f;n9)V>b}OZ%H4E{yQH znOy-py*!i8J(&|`Euv}`IT}93okb+fnX>5V%)PKfW8eA8UMgny?ud2TUQi?UiaI{~ zQP=GkR^kV0?h{Y@NhE!4UfAh%U;>|jJitJKbw8kg|M$;#69UP9+j(R_YLk@=DFyBY zc!&7TH%!*ru7f$^0&?CCu-!#cvio}^x6MVQq8_5Oh}(`@Eek7S8D~;`J;gd)*@K}l zEs;B~_FOt0ZXP(oycH~s>^d8wlnP{1vn8di!_(f3hsgW>fA0bq)tmR86H=N-xiH{1 zjYn4}DE=rX|JY-&;8+tkZTsqrpS(#jyAC+30aCpZp~2JW9@UupTDwu^RNu9SuU=UH z`&Tu)@sBhJlnX^-iZ@F&F<<%N9RP{h0}BEDmgsK_OsO3l#)y1bB=`cuX|89nZ$?O1 z*Ztazcv|e*ArMoo#-uP=4E31FGaP6_@`-lqDUIvd%=(n8x0mKwa8ff|(#(TW+6U z-{o=k!qtS^Q6pvMukAIF&)Q&P#O7&pylK9%tez{X9@h_fSe{i}FX+U}`_F(@;h)Zg zUaCsTQ`W`Y-l~pNeWrrVMq%BC9JK!TK7l^w{*x@W7)FQQA~yWx^gMg>7H@^Di`AorfDRnH3de6R9k3tazmJJ_~* zy@u%j+(2=Dc|HFu!3i8p*d~{69>SFSS;)ueH~Mk73TlTc^0q|i5_l(Adw{@ zyxCZ`YIZ1xx!o12@sW4^ONVU7;}j~&UyXks92~rwF$pjsj&-Glt5BZtMEhZ=a8M^! z<4+opS$TJe6n(_TB_oARJL3?|W(%j0F9DkOQ~dU0A7JV6p+U|xD?f6b{_JTTK$c^Z zH_9z6Ecmgr^NH*7)*g^9R~1-&<`;yP=GPY{kOpA*~{_F43imo?$4rih%?>ywejg@rz_6 zX1O}qd@I5D$KSB`1IT;lp6)6r$GFDzs-}wo9n4KLk(&G4ZuS07wI8#q*-QshODI6(%Z;!urzKw3e{TA1X*PWm5&ir;8xO!D7o| zg3D*`xw_>=Czjm-fm?4?D{(q%?CnK(CcdwQ!yHB#RC<&eiEYfMmwRbL{ZtFKhx^EZdqM%qydBF!g$bAzmS59oA+p0Br&m(eKjrP!n>UG|gJ zVuvx0M`>_Ty%8JA3>DDXhQ|1@r5@p^qm`W}rd0i#*0@yX_u`ZL;G)~Nlhpxhl3 zRqws|m*@mgSOsMR&Av4#%9sj%`^8de=^v81axNO%><5L)-ZW#?hewjpM*Q$ z5+b_&2f>NA4P%xaEsof`x<6h`wS=dvtGpb-;o9P}1UAu)(&euhorYhxBhrboEFS7N zy_znH>#(5KIwdp>Sj~Ni&od-sp0nv}n=!3Tlk5OVX6SKSq_+hC=OsUQBXfou+220MC0=t$h~<*uHs?`&lSb&X+5VA zCf<`s7G8<1pC<5cucxQmV`6sD4Y>H%4RFX#wusIfFUgHOA~CVz)%Z=5?=x*(xzC_F z07~}JO&ty~_4kr;E`A@s8d_3y_awCh=!Epjepg_MdSFo{jj8zYD*GFB6$6R|Y&#>Giv`eS|VXD{uNvwPBj5|YwnB@t*GPBqAY1Si`tFls4`wF_`pWtII zAkuMYvRMA`f>VWBC1K|a9FpQ_DDb=nRm0I*G=^5U`+^$l&yK5AXn+%@3MwcvAd~9= z(jJ{CZ5*@=gsq*063t<|PRKo1v|tNJins4wccmrexQzQmU_EN%vH#3&{tnmN>jYW} zw0h*a{HMTmjZ{tD*W0sQNgu6)t0zXB_J#KYp2$1Rr|+EaX(BzUFJBV3?#tGs#56f6 z!_+%tMU}l&B9}~QP5v}4?E#5x^ZTc&E}6;+>!p&hs`$P2G>Tq~LKhY;o|AE}i1Emg_ZucelUxc2*UVt6liSN3iFqPoAxvF@v4y zEWs}YUr2h8+!`~)O@KZB4W!xhur&N;(`MCXEC?#)_y$wL8ldOraap;G7_Pl(2G?N# zbkfGi+Fh*+!i&R~JH0$HU^Zg_vl9maL){saEia{FC4_q!6ugv6&Rd=Wx2M#+^J;Us zj)wPDNevP(@x?VGwIzX&jjy>L3t4-0d9S9(ocw559P8;cXK7&xN{h&!@6Iy0OkN~L zG%e?bPePOaVzCP7%=BAQt1E{OelwS1!SKG+P92ev#CMl^bWZD$R$F-*;;MQot+!?LMdxBGa8yy zw~3dHt3QkN9h9NtNjSZJAkY1hOZPJl6rLvHbG-mW^f>Qj>;aC%PJOfM=+fzJ;Jj?G z7g@cVvem+Y>0O$IS#Q^cYw9}yd4z8JgFh|%-tV)K)GXo^O+@lc(spM%Y%e=UUJlDe zPdADnJtz=px8&g?S$&_ulfm+fxv;2Ofwy+Mqpmjw~Ev`10Br$Acj zC7Rt18Fj@YpH&juMb^XUxS%{*d6HSXmxmH5pt2ZsqsZXGkyX!|-g>Tv$=s22+zApl zx|r{~Emb=C7hb(*v{iq_OIfY~c$XGImfV?f)OnVT5(yj3+bw`cA-M>@V;@{v`YL1-*C(4*)%OtCog`tgf!C70 zfg{dMS>zpYcc6BVrVfIZX!!DAp5Y%#UNHj$)y+=eZvy0nURGLR($MYx>`NLHo#Kxy zts;Y|?RYDrshaO^lw$5=-0&XToh}*t{4n_B=F*sffzXvDhM8#5H!#5B0sf#J5w7pC z!&7eK{^xr&X(0m$kU4-b++HmK{whH;^b76^pyoCK(%(3@rd@O%oZ=>u36E7Ms%y1S zmtCN3v-nUDIM6YXbYCA!`l_7<20H-pk*}Ek@=}7T0N&^3B@kg- zT$EAontWTCF8!B{OJeu{mQ%JvhX_~r9PR}DO;|glNS{f|eHpW9-~M%_f-_4YC6V7U zi9g2SbGKus{OaIChdz1fsmSn6m#J_~_gzj+8X#k96`IDbN|p5{T63ed1&{^g`JLid z@QRV6F7x8%12{4OEw_LsAq@IB0jXb+PG!77}aW1U8? z{gK+3(Z6@ySFtGC8Gs6Wu*rNf=JiZ+-R*sOa{YjT?6TB?dee`}p2RpnWR6670bc39D+sy(M}IxTkkSt}|BR9%_H-8c!apA!4R z1`xB?MG;|vOj*Ofza~oinWa&a2reEeiHO2-Ptuu`@%2qocu?QBqU*4+m~^PfgR78Q zTQr5bk#aAVhR))IXDg3GNxm%u;-RTVUy8%5_olK62N}^Fc)29D>v?l>!cJO#>(|?C zrD70NN57$E!2yp_fNI&jG7lgs6Qrd}2d^S1A7E{&Nfg7yFe1MoVkowW%;h$*n8VLg zU>kjj%S<_Zu4f-=#Qs3`&>4~hIv{M7+^M<^BRuVlaFu(>7;Kfj=xeT>oBimv3nm8n zZUckvL$+||p*D2giaHF@TG_t%{#-Wz#Y{WCT@x%h={$2?dQycv{5|G!R+T`$t(Tc| z#*cj4tcFrW3Lwby(?Hg}mb~_NIE@^0tM`bA5>keSZ>9tRoGz+z95M?}+mR-m8VB!m z*l0B+mxooacS~GS9tpfIpeiaORQj`MFJ0O)wD@VMwy`y`g-Bb)m9zES5)wP}7g_f~ zQtu;QaMf(G^GSvJK`iVs1xYnZ_Y-$>)@p8LbI7Ra5O*Rw4OG+kS%$lzeBa%EXUmx+qq6{TPH@Pu0wQt+ldF#|dsCWZkv(~(`iJ9G z3&oaV*T5FX;T4cP*n0cMzqhq1tt7!4JG$-4%g;ou=c@K#=j}(ZA+^DFY#PH`g@l(y-GQ>0VW3UNbv=A*G*4 z*JiHgjOZBGempa{FjHHuBXs`JUuW_DJ6k1O;iK2^&vZ2DiPlyB;;%nE?LM`A|MQGl zq@4IeJWoME&hnk9USDjTW_nF4Z?!vkw@2jKZ?DaA2JoVapHP-@ zV9)bhnYfL-|G9jzYzOQjj+HKAoiCs}vVJMq#$1Vd1FdUh@!HMI@{Ajj-VU3M5#GmF z@0GhZ)IB|{G=4as0R;6F9<((J^g|w~E=FS8Y}gjEYPzc;DEovNdQ;LCRA=T3rhE%| z7V=;p-;sBqWc^Iy>F`Q*IZTFJOS;}?OyLZPPjYKI1J3)(!7=`~Qn~*y3LkU}bnFSc zrdVrNH$2j-0+(5^mHG=Ygz_NaucYRG__2I;x|uy~f7!2j_q;d#YnvfdDgnzhKVX=ut?qC2#9LR-ez&<3 zGxoAQBAXAGfO&rWe!DP4+tE=ndD;0dJv$MwODajV6ILN5)@@ZcwfZAw+D8#u*7MB< zTqT85pXx3JIVj}ELkbzEQ#H~RGzuT(F2&wK*I0cRKL_bxNcS9Q;X$S@YT0}nLcKv% zL6$0ba}BDi7*jJ&Pi}a{?USJ75qA8 z{wsK&ORhGPIC0i^e`!Cq+;%F{v{*i)Ai~%Hc#J`n?iaKk4(n!}r9W(7i2$1L$m+D5 z`15P}c%HUx9sj9U$VbloMi~F$mUaPHH#Vd-?hQ&30m)-5GT^+k{KJUNhBdeIq0p@) zR>Y@ldh>ddlye7=FrL_(2JiTWEjtw_>NE5IF;@X>CdH(epYAzDA3lik zpL3~ot@5%cr^3ydK5DfZPocXPjFqqX$-Vrv78m}yFA>J@uDa&om{E z2uBrOcTH`0XG(Z7b^nT1**X%mGqS=hpb(*URf?ClL1KF%w+vpjQ?%Rj$!h|mxxd@O zHfqXorpslY@tm3 z;XHZPBO=^SpwWZ_#0|VcEW!oX3?B4nmvZ^J0q4hD`5ZJ`t1H_VslN@B5Ht}B78&2G zro1p*+RHNE2@#TZ(+71M&n?Ki@MCo)Xn*Id__AlE(d}Sk2~E3rAZ=(D>Id}K0d;zR z(<`^1eIbbjGR_~ry$((lPY`-ua(OhSj`mXt?WSUcqReg6|F&Vuc&n~mzAYX%eVd}U zx7TrLR8KC{ne-rng9jZm<6b8c>}k)Xda3ln&?Q!tpLLU{BmlAG89G=@Nf z4qh6xvN5!ThR@F?1&ORSv7Whb6|FU88jkM~u$xu@cn1x-sLYc>S=`}=#e{Sf(9qBk zCvd)%4)|%{$488mJ&u~~hF?nc_n@-X()xWdK*KV-LQ<358mq*B+9;J2u#Tz$u+^=V zyQHB9_y{9lq`~2f7G3E`2Bv#Pzmnzr%zn11|2crMwso<-o%T~Ukm_6xh;0bH{wF)*L%`KQekxw ztcCFERD>IvMl;jE(tCRz8V0WYdoWVZ5_d@b^> zw$6*_d)EGsVr*y823snEyXi@(3zeJi!TE6sGjxq9Ar)!7ZWzNqLxo`5EBQZk{dHKC z%iBH<3z8Bd64FSCEa{S7h=d~D-Q69F6cK5V5)fH{bV^Dih;(DJ_y)mJ=GkQNBW;oon{U_j?hc9Ed>K^ zg@M{lt!x`jH5yvZQYW8&bM?cq!esm2Rde%4k%Pj^YP3NHU0uXnmSvVnhIiZDPB2#k zdaUwlJ_+M%=#+G0XMFOax5-32*3D}tFRYDK;(Nt8o zF@9Ow-^o(LUx5k%o&7>>|jMDhZLY1zJ7D4%1N zJfb?Ret%<1!y+~2t+jJ_ABx@&)q^!Q^+1dNEuOem!jy&3WuEq$DfxE=KAXZR&|vsx z{e{>1l6klyvHFXlsuE6BkOxbyrR~b&|J>uPR$vtzWi97~N)s@4qT8EAE&i-;Tu_ z;F5o^_TXDd6_Gi9E_Bo1n#T4L<p7`&te6D&R3T8)4Aa6Jkka_W5jj2KugOq*GI}F!BW^ywM@3hHo5tyi?=BM$z-~r<+vkS5P^;FX zR}nc1d1WA*%uB`jkN84ewBf=jUFk!&$bU3m79J3onC%Q=vP}Ep<4ua5&hhl;dA%=N z)ja)az2ZyQ+(YUbWnp3vqNIZ@WZ<5AuMQKB8%;|GG4D!S{+h05;#>tuKY|(j>)KRb zDsVo~pgbAQ|H1_^2i{o6W&mQKRki512vmz6m@YvuGFkmw`#q`}o#l9ynz8^x~R|_+q!4&B3CdWHO z6rspbDkvz}0DmgqeRgu-y*H$$j$j3|8QetG+Uw@iSnRxyKvsO`PaF{1R1eej{cri? zB^gK+{+5Eb<2!fd^ATnThhFVe*6a*y4A4*aA-{jAeF>19tj4VqS+y+z+`}f}(rjC* zaR{SHi_srGHXTxQ115iIbBR6(9;Uj1U~oxrGJ7T&pJO*^5C2Y{6=qU}!{7aeYXKoE zD9wGyuJ#`(YgYrASDyMLnwEtz6Q`UDmK{kMfkhPMfv9e91S&XzO&uO>OLv+A!h5S0 zY~A-AlVTi|rFq!i0mBs=G|d8#SI~DC=h~`qFj3n7#HZuFOaY8FrMr6>%6iFv)kUu~ z(%Ou`c$;B7-8J6bK#ZXUpIfSAEDw&pj4qIZJdMumSrzp33)e1L8h} zD1V>*a#K8%7@#^R=TTuB&&@VjoxHhDR0~>=yWX)H?5!vRFd_5( z6H8^#IB|LQrU8iig*fZBKVJNYm(2G$V9n8bXv2_i=f1CYyVtw%C_VFogCC39n%y+z zBSli}0?kVG`ckQYTdPrhPLQ6_kAyVrjr_;y&XoL4u4a# zDp*~eb6mH8M6{p_tweGni~0UqPY(~@cvtlMkdqd&%ZSQR%|^h1(G=n6ysFl;%RC;Q znw`@;&YbbRMca+K>eZ#H!p?T~A7>__E{=+ZhEM0z+)lR@6n4Je?Kuiv3F-6}6TFf< z*~A+NOXH`!x#a@x5F5)psb}vt9Xemd+bn!OFK1gv7ay-PyDb(T9^QsX78Mm$>{^Nh zpC>kP@LUYZyOAXQAS%1Lxj8-kM|v-=du(os5zB%rC{Sb}`6E%bcUW|UKChWxnt$yJn2_`3es^h{@6atbK_EVP_8tlgc4$nbd9!`4^9YhawH0o!l<(vn4W@ zM1MT3sg@Y_s&e_z%4+=XekqSa8|%*w<-D;we&tks@*gA|CC}Vg?%npStE;p9oX~SI zhy9*}goK>21Ov$85)HzRzi@C*79+~okJp&D|Eg;!yaYKX(8sYHtptNf_x7QBq<%Kj z(Ly3CyB-0%gQmAeeAp{bn*KLudf|K%!I5HcMkFq7?g{<(n7CkwQAUIv+Ar378&fur z@Gcl=O}@rFsICeU8g%m(4Aq&TPtQVm`&;LD^s6Jmt{3l)jKfxKU>QU3_=u``a(SpT zPkYJVt~sj*x^=jw*?JYT5$f#$U;Yz6m}P|C7@r8J9YPy5__F<>>vA-L?#t1nmu;3u zJELEz^&c~$!^Rn8yHvCN{=i-Sechx34ZX*VobQxx=wbfH^iu@mRhjcLpy0AlHyg{j zF`HODZ}hM*q3iTm?fMW?bf6de@L)pwEyo|=D2?=4b@NvFHp5Fj2GR}xeeNg}hw{c0 zn$oxgof-s`DWO&B?oc1%;WBDV1 za57|P?x3pb+BiP#e4ZTpG+K(wS#&iT4r^ylYC}UqmzRmgIA;2&t~NxMuy>8*wm92#vi{=Pr$>cc2Y;lsCF`{#D9DqmW2# zMiP5-iH@wEi1+)C#htl09H$dr!5JOKsEmp7c!=Z{KREwC);;Uk)A)+>ukE#=ykz^|I2eOHhQ1dnto`_=S&LD=$v5o6f; zDI{K{LviG0*(GjDU!F0#cqGrYNajE4?L3(7R}raZpQ!MvVB<5mGHpyl8p2w)_ldr% z3zgq<+)89!z=qi6lRgB?g(o64PI0I${No|(we-!XUA5K}54EU__3KMW%P|rgyu3u? zPi9vJY_=|x#|7=#QO-bn-!iJup)X#<2}zqaM34CyF*xTXTm;Yw@b+dG#~*RH;j4x7 z`MA60la?fN=u)d?`5nd+dsQtMjR#4})rElmL@Lmpv}}gEIMT8%$#|;||M;wBpTF}a zW>hX3>vD-0;MTf)5jGC`dsP~&(Kx;*l@lfB|D5YYS)I;XMO*je=`WaXyTV4(hX`&f zvA>EL8*2<4zrtN?ivZqFUEZzgaG{dxY!ESZ(OG#7tCUTJxtQhZsPGg?X?U0ANWBm1tV!ugOMz-IqzO0O9EAIuS5aQ&E-^Wg-&iDr~p#uDtt;~Wqq9%WnEf?2@%clQU_S`j(6h!xq~>S4hZZy z8YeV)OSbD$@rr@=Sa*GJD8@?NOXjqt- z&Y)A3^V*t#P9f8rsvmH5p{x$s*N#WZ4SmW+Nh#}~B;oc{zhtLeVbT>m>abbe8W1`g z=<(Vw$qUooc~GJt5}B5E2l9Y8blUR(Qn#0HBO@at$;dwQT(w!^aurDo4fwrD?uPM% zf9L8|MA-3k^{dM3sNSs#>9zdGB&DQhv?-ul6{MYY)&7B_Icv^;q#m08e(SYR(pmd} z#z=8_q2KwMiBOk&uk+o0G|FB9L?n3iqAL6Kqi66lB=g&Q>b~PS4{XXP19M2wRr{t@ z%y@OF@qP<*fzQrbQOuBz3}#hji_bI=k{Dz(XvKD)p5iB(Ki`>>jYCmgz$U2piBOKNyGzh2PWlIt&DGvFG1ru7)ymg%P^W|d$D>nK?6XPCQ^+|mRed_S-C$0>aS8!Hl;Jg#Xq{RP@ zbOTWv5!ZNcT+3L;>C4QDxtd53Rim=>5PI%I@zfQR>?>-uUU|gkdyaWJ1%7lWQ}QkT zKoLXP!J4=g66>zd3HwfjsTrAx{x|Jh)1n}~!Tvb4iXiFyY&AwzbPK{O^Tt;iU-o2Q z9UNA&F=LptKzAoFM%r)L-fb-ASo=p;V2htci>NWK>y)n~mm9>eV47P%KV*@uVXh?C z&E%}r_xRyu_g870&(Rt~eFX)Z^HKc;CJhQ>x_o@-$mvLY&&Bp!aCmb*?}B@8q4ycd z+=o~F38{FS&rBs%k*_q(`sHm}cb~h*{r5H%K@feXO;E|zz@+Mu?JDY`#=65f>H7Fi zp7VKfM|2*EslOqU9XW3mPK$CD`gRpA1c-WC1<)%D#+qHrV@wy&mM@Jf(+B`BC|~*? z{TD{o?CfHEVyRZfK&)LUww}+^Kc5qEMHSb3jHrxxJY=Nb z>oR@B581V(Rrl5oxqNBk7Xjss_4Ow0ztVNX*86ER-m~$gtueJH=+Z zkvrxST3v3oXDahKmgb5~9DWn2^PVGHT0Izf=8;;k-#tfEQvD17*%>zwOwV!u-VX#M z52Tl=cL z3RiQ9T)M}-Rfw7hQ&lamBd^10`IMA4MHUodqM=BSNQ>TK3Q9bCsL-Mx`)(}nXreuj zO3x;fLrc_8@e0;V$q-g~msGqg7c}@rey|wy_IRHsZO_?Yk3KK$J|wyfLjE=4|AA52 zEyZ1B9^LP=%^s4^eSM5Pg5%{U*0qk5{F7>{VhU2pa()9_q>l~eF*37j-Fv)FBDFxS z1WFOn8@|@yl1z1pCs$sVJP9I})i>v7`TO{o)JbOq6U%!<3;beXxVV1hiIN)9_}C8Au}}(sOys3WjY!Ri(SwfyNB9yE*H2mBGm%J3 zzxP_~k;92az>qdT?573)`Dj&&Q2hkX?wWmqwVP<82`;Q-t8xvnLY{jiA>!1-@BHFQ za~|U=W2B>tKjXSGFnOO{;z4(1h^P620D7SCgp>pTgjEb+XrCE;MY}da_wSMiul&qF=48ulw!l>ji& zap@_Hi8M9*y}7$c zhrfT%!!r8H5=LXt6~s?`fOf=~84TxyFg!?^U%$62roQp)@tMkj)RbE*k)PLIt_~xs zX^E1xglZ&{KhX5?m+CoNO6U437cr2+BaUNt87ULUjrg5;7zl3pYceHv%+sW-ZYy94 zxSXfx|L&mN!=cC9-ZTA{kEHWA%DI1C0m|G_Ni4eAFyf0E4D=Dr}m6bc3~hsL}F z%QasnToftcE^KeTxx&ZR4b<90l6x`-vMsqFb|z+wxPf2ww^SbyhZ0<11T>|{ZpA5k zWCh6&lo=tFYRvUa2!^bSbV}K+(EvlGK^!X0v>+mw6CLngaE_IQWmX|Sc8brSSEt5i z#jSkxazz*WHW&M3o31V3@eg|Bc}H5U_{`h{Y4tM9cU9o4p@Beo;^MOX-FyV39Q=ri z`61=}ybmto3Kc2R22wr~djoMQiIffehLiihF&9Y1LrT#D0KRPWUauS>QePJ`b4 z8ytE|-^hv8#c%lPB4T;PVsfE9zeYba#~7a)*ujTF7liQIy6QRkvd~Z`bb*fMZa}%1 z`0m{=^1!vS@kwbMpM7MDJw)6vR%$lfCv11~1@*rHBb1Sv@&V$AcoQmpkFv^$AJ@o_ zA{|sl)};f`I)Llv1$asfU7HKnvvO$iX0Ry8$rR~zKX}gFQ&J88fmWIecEwr1KICuI z0LSzdlyTD^ad?k$K?6SdRe&L(T0Z;ZX=_aP1L^mZAj?%tRtaa+&_w1n@k&?sl8jkb zZ`PR@5ln1Ghfa)*vrWy+Fn=K?ItFR<{rlD(YAmK8euMA(Bk=o56TeRDVuJmK={Mbi zZ}5?UMBs$|I8$&Tt#Oul!j9qzYD_`tmQeJ|?TGth-rNSScJI8PBnXc0C~uAXGsVwasmBZ#Opn7#~llx&0z@KxI?R^cy^#yD}{Au<4kjVB7Gj7Lwx`l&ZZJ7Na`j zuI}9&kXi42-(XWkzn9|tSAb3Jp7N@u*$1Monf@9w2Qpp-WBG_RwA=*3N{`NPg zFVqkDjWGAUcMIUs-T@biB;|SH^`+Ig6^-%pSej0^*{9i~+5wM#Brl%1eq>mA9@5V} zY5TLzQ$^V$7LpVf8(Usp-ah2y_~lPPI-g4S8$7Mno@{rDBIyWWM7W~eR}W-UWtDwy zII0mlUunOqAA@oY+=HuH@Ja0%kIH$t=Bx+clDmETc8BAfxvAQbi-SXnW;3sJe8%sa zWctSEEBs{tVc&F_;&=S7aFQo4(!j7XKnd&(eq_A1C@_#$aNG8*KZ0RT^Yazyq6F>d zT3cJ+kP@po8@Q<3Z8@TN--P=3#Ajz0>(a5i=4!z+rTR&nd zHj(4vi-IzXl}lwFgZ2vY@WsE3fo~W-%S@ayZ|#aX@@Bf`iSYJ3xnDv?62DvjnzfZx z*H2@=F9A=V{T_aWPPUbJtSdnsqa}JWHm`Zhfg=8h;y`NJnbNq7AiPBg&`QpL8kyVt zzkCdz*Qj@Y7`Dm1ccp{-E{~S+#mV(&k$}%y95z~-4u!2TRzM`Y1d}_A*^3Xw9&36u zCrW2RblgPpJ9f?fbfviXYPOHQQ^1_^wDGU}3Wp%2JqDO00}gX1Z~2<0bLok%{TKbH zMpz+4rII1+RFMH6Tb%Ae=EpmgTkuJz-ZIYc7klQXW6xdjjQ{xmG3bU--!h&ihsJ4* zz;@=!I$|LfA3yF2@qa%lQP&TewsXUo+fg!#c8nbYPV+-YZ3V8y^s<@rw!VvBL4EF=Bp=BDmVq`g)Q2yKz!dN>#7AG`#9t&_TU$6;em)j2 zcw94HB|21|&dt5yiJm1V0?pd@xmD zF@L!LEFO;{e3#3p(h?qzi`_YT|EPwQP2TRtc%R@OoB;qZsxBul**|;Xc5uk2!*6PH zw0Uv9xmmqgXUJxDn`mNSzmeRmBnZSa5;C5tdreRbYlmlyUN)W$$^i)^V>SE7MKc@Z zBmSoHR$Bk?bBB*=?Ci-s9U7~^J7|k6wRmQMX>T{WkFTqYa(O8(qgc$kmW+^WBcA1& z3r^Su?2AvW-zC9S{b6uD{ewq=cRG$bCXHyykg|wUXMH^H4_h0beWn@P#X_c%9EvkN+nm~Q+>-wYRTp^8*YVM*=~sETx_i@|1VnKiQJ{Wxt4 zonZg?jcTPa!Ao!Y@O(gw*8<7rOTA%o#`HY}aglbPjE|TG>AL(eN(8E(zUqYGUfz8u z0sx1%V{gO0;E0zYyGqy87hn5FWMZsnyT^XSiq-noWO=())3a+ZAr zF6EiI7Ri@uhKd4kh5Sfm`F*(HH$LG$;)A0GDrRW~{*vrk4vpU-h4zvM>SXaY^+xQo z6Q_yCxSNlQHd|h!q$aR4+1^h&8l3!*+{I+Pm!SVOFl#$lh{$USL*y>?KGPGn*8Gs| zGVxm!hx+H8no?SLi!g;3)zvz+_j96OeEl53e(tPr(ZZNs9;n)Si4(pB_$*wDF;B`L zWOy#*-(}{DFXHpKIGL#j6@l#WAw-^B-h|X&RVa-0#)?OBn5;PU%Nave19|u5r$)2D zrJA~oO~b-C*{@^M>WU52FDxnS%veWA+c5ougf-j-=Wi{&%Q%}zQz#tcQ)j}pWg$OV zZ&I*{zju+J$;@9=XXdQ6ycx&Ow>zC29)k6GMDlBNwe{_@!Tj-ygP6t>!8m5cieibv zoGGDH0q%|IfTy(BVuKa8EnM~p&y$S|cvTAS9bL)8IikO%x8}(yv&{KuA6ZWoP;Lzx z)7!C*yjG&gv{WGIpRUGkXnDTB*v?6}A zpQWh&nIp5bDw;J|4Z(D0h4o15SV4V?(}V{1yd*KazT9JXEQgaQ_`IH*0=72|qS>xL z@`di4Ghb+oEtL#OODLqzCD%a4tEm(8)(@U%ojggoFent?JyM@P``)`5p4}6s)p7r- zFIG0CL04wUds1lrji3;C;?2fuCs83g8s9Va)%%jQL}ivLj*=i!bdJQ^*)UGE(|R5f zh}`!%`b#WTqyxvm#{B#`1TghT_qB@vk_SfgaDHmg*qgLe&z%)Mi)Q))l@#LF9Wu)j z&mE|8j#Eg&_^Z8J8k5qS^WGQb)E$ia5@N+whKw;(ux|zNiCiq~=`sGo*jb!`pUu>h zVrkcnY7`P5j~L+kO^-Po<-a0uAIWlGYL_3M@A!mzi2@Cxx~1H^74>XREnmoz=LA4P z*~Wb!@on2j3(~O=>_k2N5}$a5gPCF5KtT;WAcF8dL|>gnE_ce(-!|qIa8)AiFBNS* zlEgr5`@{_yiHc7&rm5Q9Hi0~`DpX21&u@zAS>I&rI%ti==$!W6?serbUD{s~+`GMr zr9QfHkS>&3d!~aFMhAJmRCmY7#y@zod9c?egrU!VHB8muKClnaD2qgN^(jwX>UsC5 z735$WfHBVDQMYGCgzX^%mztWQm9J?edg3 zR7LTL49yrJEiY8Jj3D(v*<{b3Ud(@0#m7HsN&z`}X5Ho)52weAdW>1Ef@12NFF#pO{RdYed4a$3I$1QW%yWR zk@$NlBAE2l09i=S;8m~$|A$wVykX=2sH16uQU0Rc0BN z>u}kLqdRDSOI+8iiBV(R)2jOQBIxJOsPPV)?Aa&AFdYHbc}5}ftO943`dWQavvm{H zXlRn+AOmpFLFSL(-kZ%grTkl8@8WuD>*%;3C3Q)`Y}I7*x};I`t$`}@MCAKGP;GPE z`S0BnxFBMIoNmdoeRcah>^8kEV<1_g(DZucW=sr+HW%Ac9W&HgLdlySx|jSpJwxTE za87P*g8M+2sKiao8Rd^EN;X+5D`pxbG?yzVz;|8A&Yb@Bnj1W$iV`B|!iaYlIIP_S zSu9Ah9tE6e6eT)8w z5Da(d2`QQ4Y;3AaL#SImJ#>pQJv*Wa=)1Cy@8vDt zQX1ie6fB-;sLtekx@*K}cF6Hvqam{{b#VHn4dGSHidSM55U|>>?=&_(G20!hY60~O z&#J!_@}c=c&WQd z5evp8?%W5Z5eSXfwv`W*1nux!n#KF3sJ47V;llJt)M0MV+>2j|Qd*r%BmQeBypQ?#BzG%=FR0$}BBFgp!|`zzzJHnL_#LTp z^$kM&JnauSI-a9E+|V>b{{Ul9Kyx(+ zzlLz;Z5^^Es;t36i^)Q+_4nM4)y0a_rn(D9b?>DYF(VH;_Qo#Ga*rsf2QTP|KEAUc zI)gleFV42+ud?Nf7 zI5s7?++|J1Z~q4Fix~4d5yD7eyL8*?-9C)60~ai_;K+7V5LQp0?&;@{^8@ z<250IZY^xZfkW%|#~MR#haYNP%NYe_qx(;w8xgW1p=PjA9D;9 zlr$Fa{BV_}5(Z8+YRu){{1F>yHneiq=GUn`ZGIgoat(J3F(H4s`;_$QgaRRR21MKE zN3BU_^0Rc8az*D=Uz6R7({v~y+eD)4cIr8WAj#tlOZsEfxM)M4J>E6XrPt>=%F%nb zEXFh_LodO$z=y~^_UPG00Mx$|CWJQpj?R3$?vCfEa8LbPg=sQk}*0h^q zxmD)bZ_pYY_$5Z*$?Kev-7n!8{)*{>_q$(eZ?za0WhxzE$BQ1W%siX!z3KTgZCl$s zPfsk$Nz3KcyDq5{n-c00kI*D}GGE?Xr9aIi>Ud*S%-ugkJIHRm_?i~tlSA0Ph91o* z0o9Dgh#1o3HEkKA+cnb5tuI<#d0$JJwjQfqC}rO>xg6qfg{u z=TE9h2^PgXZo4Bi$Pbaa_97|q3o=0DhL#!7oxh=!LMIM`|^IGro5 z-$i7SxSu*-^8E$FCl1DXKbiZ)(xk-IpM+R(!*@{%=e5{WTCFowj8$X`=^(0ig$ROW zq&GHKSHs29G{9qDdu3x|%TMl`J{I}W-SVUPt4$pIFl((=@dk~^ZNKYE} zLaI#y<;RvQZ_@PSShJa z4KnEm2CKoJLT_mgo9C2!?;fXo5UBlxfG*7UEfWMJdLx_v17Ri@k5j9rqxP6ItqU!@ z%rb1k(tI{E^p!b7@3&b{mrKDViv0?Z+SEy{^G-46Topne?O1)vyf2v ze87J!{)LoXXcKH`##L!>7p;Vyx!8q_cg%&(?O`sm$`8NhYsVqSL&A$BH|<#F zy!><|3C`_B#`5yxC^M>6GV1GvA|)g)a6|1+MLA7mYt46$Qf{7^m-0+;7R0?w5lYn& z%tTdZ5MX~?$Kx#9r+NO1n7gsuT1_eB>kmhLBd*p*V#jpSYt@_|k34GdK0X{_bI3L6 z=@eS4>WJjw=XXE+C8y@n_#wH?5OBvaBuy8aRAr=@FLn3&%9R~55ALz1*_^^z@=-sd z=upkJge74HxV)R&IT`@L1@DuBP}EM2WbV8f<;FZ~vH1KM<=(O9NNfK3WRGxyn^90u z3}3bxvc=@gI%K%Vx5mkCpvg#Oo$Z<15>D`B7V;~;!`Q|O^vH8D*#1mzeG+BEUY6E2 zbUvo=+-D+&Ce?F9X8K`$2UhqEM;@Z>zq%g;;uFZ&4fsf=$MyVj^A84(hOHeI=Tfl zOe|b(_S!owVl)Nt|FFoviEl`Qi+UPxI>uSQ3g7wkq3kAi@tPQpym-9tawgToWD75Y z?_V9T>+|gTOdKRKnJ)(B%XY%P^Ki|-T}-rgFV92x(HhrfXus3;fCAFxGB-((5S`0L z79ZA?xtrdCA*f>5anF#3ja*!7tkbw8TY0hN)FXw2T4rDELq?J~Fx8T&{{7QNi51sb8hp@&<-T?KD zAnb0;L?WpWhvRvlJ*?|g>L@9`rtGIHs{ z)aH%c$B%|&%+pNv<{6tTJXf~11Pc00K%r={i#1bk`g}oJjA`+*E?wtrG4)r=nbOQl zmFsJKi)OI34Le8>W2!5qwpcLypbNcU1QsZ$a$pim9v@Q3*g7stKGcGG+g&_4eIsnM z*|oI!w-I`&dwrSkeuZ%kqRGqs#K$kN(^V9Q+aVex^yvQxU9A#k49^l^lKeKx8$Yqt)^LQZS3 z;&jrQ)>11*Ebd!w+PUBEpR{O>rqxT&*HZ4@m)`WI5Oz<)MBKxRka6c-3zsJdrcm@` zvg&U3{NTOvY*Nh%U*?CG`^~L8Gtch&?9=|P6uC0=pzKJb_l;XJ43MT_Wd37M;Z8`$ z=kv_=~AFnXv-!P)WpmgORw~fO$irqGbkKM5@Cqx^iYsN8s2~Ga77c%t< zfg=@TW)Z0p!6)VvEM{j8fP1vF;~IzjAIP8Pse^ zBBX>zMP{y^fn{xe*^(~X`HPHAtNV%OOu58K9vU50EbbhsMzyhLeS1&n7L^*(=AXM* z8FAO(i;&FE;{fWVC*woL>)N&E4a1&D6XE<*#MY-juGy*e`u|PiPNgp?@lOi{# zDVwh5U!Tb4uN4wFfYNR5krnnfG{9{{{v#rnQG4M=96-m}xGx1eHg^-DqNJv#K09kp zG)k9$Siz>!v3*eh%4EYushTxdj7q9YNF9_PYW9wBh=%5T&&tVuexgPCUTz!1ecQKj z_x&Bmhc}N9Z8$2;U!{rB8=9sRoDN{{nw3R_f8;({>}lpYrOr1_vtJ(j@y(IrEt*AS z1OfJVlnxKbjTu-RO*W5GEiI`h%(!HXkHF>dc(zoGlW)kRT<|j$7=iZvA-B?N9JIdUJ_e9wf z*;snYq5C^=8opZD=U@Il`K{VfOOQwUQO|CBh3!jE*Y^;^g^})n(unu38`Vo4$vm7u z53$vw#@@fW9S|adD@eSmHR@Dok%8G8xjE{HXS=pHTDr+Lu#IJh&PvMXY^kp=p8#Cy zN3{fnxGe3?a37DykrFh9`iY4o`$8#N@#Ye8)aHqz22LW@AD3#1N~r|2FtUktzBzG6 z)j!LhEvb72>&mVCF8fqzwJDL=(Bg=j3Smk8gYWtUFPm6h?CPQgX`7Pzc{tP)iRPG} zg@w|JHtN~Y#~Q&<<%yuLA80YPtk$)1<5+JY2nE}d>2Ep6N{bY57#j;RAK3gF-K>>t z>{V|QPgy?@Ul4e<|Bpv!P_%(>X|{HR^~OwN9Wp6aaUiq>4G+)lww6NaC$fh@E<-!j zpS1b9bD0bZ&Ypbr=4O!mc!^(dDn)ql@wHIUi z=TeGbaxgEk(N%TWW%(V@T~%n0^#{VFa)a%Ulo2k2=YOLBl%Vbq3GU*bB>i{OeK+v4 zREj=puRCr=>Ms1}pK@q}oXyYBw^C_CtQtR3hK|6 zhWVL7{v~-D+*Y4dOH@8~D&ljR(4e~X`mUFP1?udlpqdF+6@!=f%PSAp{N++XH(~yB zj1l$tIs^lpVr1Z$R?G_PN&NkDXxuHF=NtVud;V1TU;VubC3uU)!1(XK0qL8&X!rQA zRbPBtr#G8<6lXncxs2{xj(bn>XOnrVOlZ4Mp8d@ce1opmMo~(XCjyt90y?A`;T5Ci8_RkwD%aJ9rHjhc4 zrN0HS!YXZBMzIAZ5V4#V^{Bf3I1rjA|8BK}vpON?sbR77)r{140qtwdFUIeIp5{6Y z8#I__ih5fQJwI8(-AzPr-32`kP5D0*70-8FfHu4S<e2$Ze__Io!rIX`?57sVt4jSlZZ%@GD)T8#_3TBs4-B+4phd`MGyPm8pan0REs%@pAX35>!jpBc=>;m**6eF zaf?ttuP=-3iC3eQ#x#^P!S0%W47yLDi29(^YbFQ~FGlTgKM(eQa=(&1pCg&{N|+09 zA7>%+CPpW;CdhI7??*52*=JAfsf9VDF+=PUz~-vt!*nOmsSD)?<_{r14@LY zoCi*n(5aM^Nc;5;LOa9AEPGg+O&yT#g~S2sYY_(NYNWK>iZo+x^wv;RS`@9lkbp9a z4&G|6w-URb7ZT%ha@+}jWp!Mcl)l(F5$wGYD_(B)5o4k;vNl`Ol|~c^1VPREMlLqY z=<}cAw4?}T>0U{pWM0Qp;V@)UP$H5s)oc(~tGd42)zwv2R`!BUfgUem*lfKi13AFw z72o{*4#EB1UCi6J1Da~O?9i0zmR$&+3^9LR*MM)c%H6!$iX@~#VnNgxH}_-{j~=$| zMtD*YuV-Ej+v+ZL=jq?jgD(?K7M1fOrG4QO`Oa+)_|b zu=lR%PA*@>90@HgxTmCpRGaZpAW;QW0_pfhyCD2VJI5j)kFZMfk1dLYg&nRhgff?& zQLoBDnaCodydEj1p3~_Nc#;s{_m8}5rA@zZ(-n3JkfP?YE+q_ico=^RROz&f&BFmoXj{rQ|Jif1c z<1bc0Con#vq>#o>CrZ&tq|dcW&0tt0@Y7f2W;__(tP?fV zzEYDqA-!w(jxIX(jw(&)B=^pu_|S*@oYR?N0@x9c?lA^CF_CB_ez36SRgb7R1AeNh z_XSi&rtq{QCZcq%=m^5&dUG+1tw&*4St`zq)XHzm zYZP~?buTB!x!o}*IiMyzE*K7}!mdi~o^^&#=US8yr zSeSA1A~6<;Rub)F;{Vj$+(6vBQ-qqZIzW~0SbD>oh0N16{%>5gG!#HVIgcXdHPkFU zLC}Ar<=Pwnk8K6|03^#dKBl#4Uf(WdVBFYh%6(UkzyI^f;rR0dq{NWmH!0<=iDf~* zT3E=A^V~47&LqBm%-GLFBk4Fse$@B!<@m$|w>;7d%^JNL_O z-Qls_k5?AEW0pL4x2D_{lMrt9CS;5irWANuRu8?@JWJ|X6mUOC%04BUv*!sX7#$)) z%t?I}T^uYx93&?jqHdgHEo5%?B?sa?q+AvB3iIRe1Md7siugaxLK$ACj<|bRC|O~l ziGdBQrk(X9ek%uTQ)#g>Od0K+LHVg1h>S}a|IZ;WVtLMdIy4J9FLcCSuP1BQU12z% z8Du*o=W?wSe_hbIm{6$c=p=iVaI9-Ub1<>6YC)AcpYz%~q&!It(`r&4QU?bI@SNjf zPqNVFz0=uQc&8#0=0Vc(w{PD7b?&^4_sK*U!RXoz--w)~L9v4z&zt6tKDJI=6o==@ ztFo4N6DS*pb`|?ScW%`g`TA86+zzTfYMV;QeU+$`RewV^&<0%k`7uRJU5KH*y|?#; zE}fZo@v0B&zcNpj= zi+bKBtfr}&H4A^-(!d4$p7aJ`r(e3Jwi2P@b5Xt9x6z@6M9V~;KcnjuCr;&W*753eRyPKVIjlMn~vduumg>NfWVQ5@CSAilgy~LThiIjALXo* zDZp@_a}*muu_k~K$D&SrZRg;a7D0bf4c2{UoIzyUqN0cw29`57w6hs3O{ zuEr&&q`*;|b0mPRRiz9SM5?9cD3jT%^l8|QlzApT_*H`blMczD^trzJ*73_YHPLQD zd*Zh{qMrWq%}Sg5B0t76r3GT7ZH~&BKW*N9P!Jcs+W9u}4cFtx94svKtfog>`Ao=z zefO>O3sZ*{(*n%L z1_y<~FWq4@Eui34Nvsh890^A8Xc!mQv2pcoLB~RI)nxEI{asr-FMNGCUT)8PJf+WDY-mq5#WxWWW2r z1@ZthsVIH->W>oZmur3^R6!Ij1kr@zH&nXbR>q_+v&^q z>sN}2MItiHnFmG|9oAKsMNRglVeanhiRosXkrD4K&Ad<-vuXvsN$j^=E8o=>z0;B2 z)TPJ(1qn`f?#cbkS*%5^ff32UYA{N>&00^MQs1{sw#b^tz7_b|8~*=s^;KbUWNW*) zySqaO)`Z}0!4urwg1fs*aM$30;O;KLA-Dv0cXv)Pv-h6)&r>(K2wmM(YkljJR}RAl zO}>>g{EbomD?vZ<7d#BK*e=#Ny??}sIas~_?E+*^vfnw$bxW?S%NTEl5yH~>S>?wH*{zwr&~>!%?r5;hijhXJ28 zHhI8?zxq@36r%cz5-Y*AYhwP8S>YF#^D@UF0F#BC$NS%t-M5p?Zzm)oeQL8e9Jk{0 zB;&SbXQ`w9yF&8cH(>m9aZy3 z71jF5FnuM~AI>04Dz6vztrF}Na9p1mXAXlW;Mvu5R#zzxJCrEhl63EUjX%*|e~d_$ zanA^@uUFXCX-UImb!>B`S%`#H6+8{*_WGyEphU6uY@nfW!6+wbqdMdb;QHJFdmupD z3KvD!W3=?JdC6ZkDcw{wS{W&h4zAX})6MHTl$}*=#tSZsf#GYS|1ASBoCc0Guets+ z>t8OX8yo(uPPa_;wY8Nlr~GrV5@gDK``XI`xDof%bsi@org#rWtqtFfGE$oyw+8Iy z3@$H|14>=CG?D?%-5v>e6U4*!c6(96a)Y62nCb5)n7T=_q{+#+;$j3Ixa#-KW8MC5 zo6ex2wUw2H<$!x?e_y)%{zk^@s}7HQ_C2DVmd6JxQe?BYy#7DQ#e!z`Gx@xZ=D$h&khh>6{$zp+%0y*} zbJ+a?oFCe>wT1;O-2g;m%l<{zY8C`C|JKMozPqTph#Rwad1Xv}+~FGxrMPtyy}9W@ zq?i=I!spc$JNPytT%$W13vyG3cON-*_K#{Nl)CA9vWzMDxlKgQL3P#+o1J$a#tyVj zzwd8ZZfG8E^g2}2)GgVJ%iCL9fA#X3iB)elqf&fW@@HT79AactU;Pd=8uh$1fBYl6 z0K~z?|7U`Bd$h9e2i%Ow&IcALe z8!@WV9Q@aWCoV6xQgeA~EP0V)lg*%GV-Auz+^znawYrRp-=21M^6gP*apM)o((rRq zN{Mkl(VGtLFSeTyaeqs-vPuwMnAFr#QGQDa7~FLL+%-*p^BzC8nU&ZXI%&r$b5iOz z@#tPw2-O5nbc*uZSQbPee(VdLQ|RMXTB91axVK_{J6wrMl_RO~BeWG(7-RDl1n2cm#ms$^p!iKFw)4H}Mo_=+R+Dqb zk&3mokrN~ZJrJRM2v6m!9aYVum=nJ)I2@UY>pwEB z)>?oMF3qd)xL?h0ZT(jbY#P zm7b1K@{1D$x-ud@(}ju!Mj48IQ>yYx>^SQ;VUM~~+S4j^S}d(B3f|&*mqE@S&>mlU z_aC6A=uevEv5}~CCQ{ky!GlHExZD={ssQ*d8fo57ka;32- z!Nk|paxH_^ZK*dN2{Oh2zWWz=M>abr$QT!Z&!xTmFk4J*Zfi_+@97mX4PJ)sb>bT^ zfQ2@%4c)6eVt$z`t!+*pZc&wvyr#RjqdknyoF3Bc_vpO1S`Ce)HS7oXla#)ErMlX?x=A z+*FX0xOjW|#UB7Fm{sbSAeA3C80k4E*QN>gen^6r^@rJqIuR6Wg*nv*AEeXN8eBl^ zLxK!L-ebd@f{BCQI!}I%e|iIPr}lgBq!jz1DdP_@jGV5N1=7i_qt;C!Fn3s3!hasM z6}eo$UX>OCV|0BATYxB-U#=ik?(yo+n_K5Hu@BSavi>!Q4;$6rvWs%62CCNggzCW8 z@09e7nJ!bH@&bH^-w8h6k`j=v8e%6!-xmA?F)?V>+qYtRupTF=mHxzp)>WIxRMQJoyF?uXE0EMDH-n#QYcv?`vH;(sGC7rjz% zK7>ammEr}J!aQW3>`h?a?A0it1_{HWlB_g<#=AK&bH=0lHi_}QaKftO_Nc4Waxo1L zRbLqHNgX}y#ls;lh@5Sb{V8L0*F;;4A#0=;C)Z7~XI2P71f z`?C8g@w_<47^Q^`10Ox3;v1M6`hKpCLkMbWegFqzwIh_MY6A$F6aC$ zQ!N6$agLIqRyP$Uwn3g%6TtcztXV_%;2%aEfDY3_JDx`x2m1tUQNI!hZCkganIe@l zpPs^OTgFeu%Vr%1I?#Yz#pm>ItE>cVS%NZoL1v_WeeifvHJJ{C93jmOr4Irn_sNBi zg{E(+2mi}4-aar4x_(k>?iVfO2Hw8o>^zcMW1oR$kkctneSI7Wbev>Gn}S`P1Aj>e@UuW~NZSv&qI9Ox^H`S#;_|Fpj@95*G-B*4*GLi;b)5WL0OH8k{ zv|@MHKe4Q6T1B7Q<}OfMMRmB3Z0itocxbUg*{DTp)ItW+R(QD2lu~e8^%VqR#ZcKi z(HCITIewj37x`q|d)jRaF^*jMQ}X}&`Y>>N$xMXz!jJ^k!!(iGY=$43;@VQ^N?9Et zn&neQd3P3jySuj|k#9?W0JZ(pKI174OYpe_)2?>G^6d`xc9&)SvPst+`rcP?FSA~W zf@XES{-uxVhTb2_Y#6?doeY5f0D3gGw|yAc|6_$b%d7#FC^G)wr~u zaZ0)<4paW?`x01$#?rDfvAhYo5`ainV9l9C$ApnK9LMJo^ECBS8JM@ zt_MP)7cA~(8BU_4kFhcz8lLy|nw)pGBI=OYh!ilFy*Y&K#>d8JgErEHFSty*25y7J zm3p7Qf-QUhJ{j>r82M*(l}!QxjDdjWhy5nQ;fneq_{q`20)gGJJJ=KqgfhHQb&%rj zbUO((x~idk{)oqNahi3A6o&umfsIRUoW%eg@BWZ&29A)fG zLp_28z}`Dcx8AJdMr}|#Mpnc_xW~?l><1U*@84FMpW^--6oD0a6eUO>=mI$URQkMn&)rM&ZUY+S zv{-7eorxhbP-*0PmIVo;2@?_J3kA`R`Xb&M`i(_O3f{#e-%6qQ6ln)!XBg$)n0ThO zzU1q3WvoBRTWXf*1g)hj%Nv(c0riMuxqmeU(P$9{>c+Nn$EsClZ&T9Jg#UTkz|rgp zs1Gb8hyE{S1L8~X$mnfc$J8mNSD;7QdU|>S0s=m({Z>kf)zZ+AqS0pJN?5r)Stg9` zFAEaY5@*tVmze)4DLGmDHtt{HN6-mKDM}pj{_#l=e1~Y7HpT(n7SKc!sYUH|w$@ef z;q#X-{P$b&B_$;{B-9U2fMk(o?%2(U{l3@Z&FIL;cLASvE%5)L(171usMNU|E+SGsLw21In2+;lJc%SjU zGe92i85^HX1f_}V--pi4@a>PC65r2nC# z)Ec2;b1bF$;ThtXH+q$C7`7Wk#NwUWH>(U&&t-7zzKFbUsOQEW@0KaM5 z#MO0cy6ETD{rC_lc~3RhB^dRpXu=+PD{YUQjY&Y!{Ph-L%0kRef%olXo5hlYeW-t>r|6-{gC=p=DQ|5Dj9*d3V>2jIN(^D$0zRJ>Lo+2%+ngDN$n z$Lc?1$yE&#t<6S-2YMasbrl{!ld^{hq3D^f@4{R(l2l>|hxx&hExTAl*VR(7oRghJ zrNAP$!)NVm-7vo;cS4O83&5!`E*p=L2oycW4ojBnE2X&5E_f6K zPw*H_C{i`7A17iRj@BfLCPJOB^i+ZVW*$8#0rnx``ZKjI=ltbTii+p4_6hnPypS4?V{>a;VdX8-mf4qRtpj>TtlOQBO!tr9axGtIKSvWKE`m+M}*2@4SFs9o! z!)ANu_<2wQJX5%u8cv1z%kQ;Tky1sX{$Y&*8y^)kdb(iB;H&ntRJT-Ll zVIeS5HrU1$x>{$H6NY1UKRn!ryw?{PxC-2qU24Bt4_&O3-~ZJXY+=g2xqzy>B)j^x zeFMo;qg4+l*peOd7xC>MV92;Rgn8)^MRytdbU~!J%6dW~N6E>T+%fsllIGHy95PD2tX~8ZcSY`L6GF5Fr*icxdO1d_fnm_sf5-i|zz6^! z2DUUDjaoPu)NlAzrx+iRP7kVK+Ha*EQX|HYA>+mB6*52PZ_p(=3%!Z40YnIqxkv_+ zTw;5JIC?4-k8~*Tp}>EefvQ%|mou#sIzDlYm{w`7RJq2RdcPtZr>*0)3`7vh6E~l|Z%^bi!<9&P7vc^mJM8X!mHtVgDyG}& zTF$x}t0)%%i}2Bm`gFi$S%%Q<{r3vG%##JORN(UkGoY*0pC?NV&;pp62|iw4d?Mlk zAN~5m_0vO}kc0paukK4}~gyJ0ibVl06ETv`1 z>X@du_~e4fe2*U|9Dum-z!@9!!ID_f>RDRS71d%Om#oK3{`Lu*S=_bndnoX>))+Jz z5!;%qiG1=@iwv3$EeLMaWWsA=^iuYRCcGbBp)PA9B(wmM3Tp{G6x`S20p&47fb;k} zWA<85RY&^j-oG73z7D{zCE@E#4vgXvuF`86fSZPAi2g(f?9u1K96YjLjQ*1G_1zeK zhWsphf`l4;NLT~)QF}-%h&KKG=0*fL%3^eDtoHC{3vm21@PpbS9JfC8gPb{D5{9Nc#2zZU*W6SV%{rdv`ZR{`SDs!!_ZQ-- zw$N7#amhF<>poMA3Pao7Lla`0@d0+E@}D=3Rw&%Lc(OG2Uo8N1XlJV~`M!3uXoB?n z8=|?{A>)PgL>A=+#d5EIodgTQ0E$ABhz90g>6UM@FXe(}d0|^hs`ty~spp?zotsh< zJ_}o0TPLS$z@PH98~5b^@Q;th8HuS!)p3_++vYs zv1-SQ{eo`_0DrIU?0BeVi;SZ$-1Z~?Vpt7BQ$<)rZ@F8jQ8&=zy8yZ-yZpDiQ2>Xy zd+cdMJL~RO79hMS5!IHfoE-)4z^ejsviz^k$zQQehzywT%_D5;Az{N0c!dCoQY$qc zG4UTDzXuQyN@Lsht+o}RvEBdK?c;6`7xuL2qeyQX>~{D_p-AaxyI`@Yj*7nIz-AKQ zzB=E}Pw-8Y_XVu4zaN#9|0y5jC8->qKQf$k=!#w|J{NgH4G7>kjD4*k>CyX-LPsPT zTu6^SO^5#xcU&qzUb9t&8A-(Fv5ayz+OTYq^WZy4yr zgMk2tz9U3|`}6_ofL88$P?#<)diC|$2XyLPMxKwj-;w_K@HE?Pl{eO8e76urByg=L za1Ffqd4ZQr4rS#7Y{T)34l%&qO{w>Hd2nl)P~88*51Of>pG5`Fq9a5Gx8*(S(I=4m z1fbmM4LAdla62*&ux^hE`uJo$r37W&w(^YpIyyQ641pd1Pv7D6kkr&vvd{7`;7~Ge zLe#*`Zegb-XcSL9cUvHENQjA_uXSJRcupD($1?3oq*zitF9v_|-*us;Hh5QGNWKY} zJt6;vG}NZhgE65`D^)jsY=Ck`Zd%PQeX*=Nn||1TY=5l)*^H)h+3-IsxU61%u{d2^ zxv2*nN(oi-x_T{g@-t|DWsgRU=(GOhu^6H!?N zpX{}-u$Z+A(_4&P%Pt)o@4Pp*FKi0h4dat_FJ9KCvR;l*z0U$3>SZhdq<81|__%8O z#Vxo=Lwo!AWb#X+jlP=&)ob6|C=mq-tAkMIA%^1yuu#o<91r2AS~a6xmPUbSVd`8x zD<}0YhSd`z_bTzga#&VQ+aG{$Hv{dbGpP#doLO3zlk&M++S@D8(Cb1U{(wvHnr#o6 zq|=*?Tiw~TvU%KIz0X*!x@o^SwDB^3P9T`r5|_nZrSaNwJN1~zr` zk9I^ZsP&asQ)!bsYLt{Uw|UAMx$X~w8BE^IPzlpvmNxQZFF|dA9WQclefVHZ{Jtt9 zBO^0C9X_;G_PB+b7hSbno02Wh1|T5~)B3Hn4ST!*TK`gaiv-%-ZB8dk_-?N@+cAG{ zZbAP1kEhjSs$`NW)!4z)_=Z4Fncwv+)GMeeF>xEn5OEOvep5c1Up3`O-V;qvs&TNO z;(S$&09fgi=m1Y~0cr973=YR+OzO2u|Ch6;x zYTH4gQ#3y=YJEZ57B%SpEadeP5YgOM?RZc=clEs89f3pJ@fck!7g=bwD9#`t-pUs~^5kMmUVxzIGJas*{Y81^c zeeKF?x9r40gTW^RBlxCz^R|{_9(@2D_auo|(!Rvb8_sm>^Y#wyyKLMb)I|VtYA+bs z#_U+B`z@)&cr1D4c*5ilHTgd9b|Rxs>u?VjYG0dLEzHum&KChsR^YkW0QRS^P8+|@ zX|6n;?y6im9$h-_dO@vcK_E+E7e@dbl++10hdQ6O*;KLfVF2QE7pgwjs=Nr5h<@7W zYc{T`^ZQtS273+ssAiJM!@^~K?mfM|2w~)#$Kh#pFO|7;tGe!{{0_(F#7(wcM80fD zk^`YyKBY+wnAfOcnuha#&^22a##aT_T<5HiC01N{Ho9k-=*j7AfNsZZ0Ux=|V}+vl zF<1G104!>Yw>?NGgi<`JNrn7xV6h<-)inJaf6Eu~S>rgH-Nut@6aII+=*q%3L?0PR z{Z|N>yo-K6eqgT`{}NPgY_$E&lvNIHvCh5U2*Y<}Tjae9NqlNk>n+t(^zB%GLu(gr0*2i7 zjD7y2{rS?#47IMb&OK}J-vkadp$C=Nlp{l^B%YITUwur>8fNXPtaR0oIld z`@l8k_WLLQ^-*Y7klK{v)DXb$D)YEiIXfogk_QfOZEgP^ z)CMwC`o;CnOa)IW{dcn`1a0N4O6cEmyYVaj0lF#5BX>wM7S z@I1($Jp{{E@5Z&Ztji*=+E^ut3Q0RvH9-3I=EnVsj3eM;p1x(vS(XIaea7^c12&5K zgr*LgTkRSTSakE>s;;J9;HNW5MisYa27=$*R(rc2d8zqV-VvPE7#&{D8VBm^?l(IM zQf@<0T|8Drd;MzV`_jZRIw)YP_nP%I*niTr8#%fkrGvJo;dfZkQ9ju;EKHQBlEVuT ziMeLeXjfmmlm>w@(+s3RMn$!|e@I-G>_W6enEClv0zbeaT_oXu!{2JFo}sm`{ay+9TGP=Nes)b!owy2AVNq~+9FkCZi1a1K!ky_LuN zsj;8qTeA+9?^nb#jwL`gY*889?ICfmX6c6x#zoqw`#=c|yv*?Bg}k) z{f)Np*jJ0iWJTOt?-Pftn&Rj_u@j<>x<&-?>PCc+*8WNNa0{}R-d>Qpad1e8?!#oV z1m&!Vvm{{8VAkEt@?hS3Ksm7!Q(oWm>LN~-DCO?>UB>-5F|0(aucSG4`2C{KcVh%A ztNhkig0>LV0_EzzrY8$xosVw(@q>HKCm&MTD371hzRpN^C?#v#(_GN5@$p87Wz?43 zA@h)Y#&LaITyDh3(PMYa>BI@;bI(@tmISt=-~2F=nB2nbz^x@=DDZ*h8KgE4o#7pwOF{^!YuF z%?+=Z^>MZT{33@?g5@Q$Uau;_AQZ7-Wi#0XlwV!lYgXH#Z*=b6OOy1w$hdI~GQaWg zY&Z#VG$?3vdK{=vmD$-Z@(jLvsB%K`Y5t+}f@pG$CV$|i6yGR3NV6?4-B`*i*o`KCY%r*Gk_EcFg$n$_Mqbb_zzjg>dkh#0EPml&JY z{&Ibr-{>WpC%nE{iPfVd&~n7md>mE_tPC;~9gHhtSl15uaTNC0`FCsJF3v(@t{-}E zM{{#?0a=eD=aVHs1joZbq=TF9CsbR8X5bus3-!D?d=(6oItv0zLxI()P7P-4{&EY4 zpxYHUIXU<^FFZW#4n6#%|F__nLrR{->ptrDeYaryr5wzx=f3^Hcaba$w5VXd?XDm4 z+^^NDx<^KkvU?2pvjV_&`EC%8ArCK71N%O?b!IZ{AaVj8Qt^TzEA5oObJ|5{ z5&F{|#kl$Yb!B(Eg2{3M9=lR9mb546?V(vb$rQj}z0NDxAW0!0pwWbNLjGaWEEZJr zOi6NXcfTfxJLfJcdVj=2%S&@rI{z)gFdLRx)8S(48w?$*ma?tw_@d32#20I$#;*l` z-6hLbF`om4gtt&3?%M~{yH3a>f*ZehbxiCL$VfttB+)EBrzwvsd;-pq(Z~acL`Lwh zgI$2HWFdaeYz)6s^ZIz^p_HRw62%jZF=Hb5UIevFWg62J#PE()Zy?S2j9@D{7(pzu zq@<*2xSr2EB71SFEL!@vKfg^)e`BYyPlgrH$;_jne&=?As91$iUh@U%Ns(k$nH)3l zt76HShdBoGxs)6`#^zxuR=ex#cjbt{LVe6W$KIN+>QSwzhEI;;HF}mRjKrZ2t^PQ}UhV-;KIf47*Uw-|B@@6IGf!M#XG%rga0D{3 z6Y}+?rKPYCFUxKE7JJ?7ASjjU()SOE-dyD$lCKZ+DX_ks8&y)(ieQq|_g@MuW3?F3 zI|7B|Oj=)kgV^D(j+QimH5w?+Pc=oBg!EKcp4mAaUiyOEKS zsVQD=8#B3vFC0CG%R+3TiYFU;wv@88n!fL`y`XpVbMeus&<{j>c2uHyTw>w?FxKy0 zL=+bqzM`RYA7C!QE(O2ALiN~7`q^O-rbb>;Pf@LdF9k4DzN4dqLDjp1`9=s1W(v=R zkK?^z$L+fiHo~ftX#rwr2)8? zEv5&@P-nZG68fBT(!Vn$9p$dxdVF*pz?-wZ z=W`jkU^xei0kuBSzKg@b-w|H?1y)DJL}p+=bMHj%iwt@?8HEO?^mDMbCSf);3ZZ0o z3~Q@baUDf;GrNIkVVgFGuO>qm*U^88-t}(ETVWI}0Bq75R@@KQx&jDUO$8anbx>q3 z0x|*rHAd}bEYBZ5Iv!72Vyhv~SkZ@vh90h_WO13aLVNf0P*N&Gu;d|}5jZ&={dWRC zOR;s1i;~aC)>gLtA?a-SMl-IiC)=LKNlnJnn*$D7v*?tBz48^{kDnNU9Dy`I9Vb`S zGNKb-Vj|4{?Bq(V6eSM&5vb505fbQOiQ-XA`Hro#X8{s}ZJ9x)MgODC(r?;p0my9W zrk>V-#>U3=9@5FOSHPCoVy;}XW=9h$rBL4gPb?(tN?4tN`D}zDB`67>!}=(RQ8Si8 zPU{!v+fAGK&d&R#0VyN@3Q-caHIMxtlJRQuQR3nsz)3xPwR`xTIW5XtJ-{SmVuK*} z^QNFh&qrTN*bUCGE}f&j${+79aW+l{>8N~|0Qcyn)5V(QdNDdm{j> zXJAD`-}?HXctMo>qI8B9pfB|R_I)2DlJCt#k@wU2gzLcU1m1=y|C8-RwXrTmimul+ zA}$ky#??d|br#vpR;o!KIg5suHpRF1@u>WFo#Ld7vl_93A5oc6XGofFR6-*-#@k{0nm6UG`GBIJi=W>-mPj>r+6gd8bM5 z0+h|N0eH7OPCNrPvz-L{d_*Fg6}z>rmgVDle;ugf$v%>RO+sX#y9<0=0#Z9Nz^zOf zA{I(te5V8V;-h&`k$Vc@EF7Z+>a>|E7+t6npkN?CSr6|jzCN6z1hnEZ+txh%bFKr0 z!_kpBMMt(Ydrkk2v0>}rNG{ujl+zmQqRDwReKN)*a+}~Vf+gp14J@&-=+6(=Q#sI= z6hA+E0JLopac!lpaOljP*kYURz+ERiW`ke0V5@LF({tP_ut>SRLixnv8qINzy;31T7sXqM43O1+yI#E#1B$G66FjbcjAlO zlJY8+%pEDDQbOHCu|qYQVf~POLCb(kF`N4pZEl&Jl0r>NA8bw3{H3K}7NbfyHN$wU z$s^5n|HNPqR4{Rez4=@|AA3lirdCMvx_SItMk1aK8xk6Da=Mhnpw8m;KuC4q-#zY$ zlj<>zE&d+%i_m!3p|nGCoE=2&ow`PfB&8k zRLYhrqcfKFUu1KEs3S4v!q^Woy30A>Hm6%?-x$8*G2xY4Z4Xt@f*}uTVm?6l)^FoL z`u6GTiOSh!9=9YQdxCM7(0XiSHhA+lq^_+2hx^>(7d$*4%vGh9p7w(g)bz=EV;n7W z#j@U%KFVDGpjEB=i$D^7PtM#Qr0%Ai5M+AHGEi9KSUL|Q5s;Sx@H64*9Y4D zCsnL~rJEn1;c`!2t?6MfAt|gS(F3ameQCR}6qQ7bF>*Lra*O-)km6e-A}rh=yEm$we_^WG_9&1Hv;Ls4epHmIJ2+Um$D8{H4az-!>V}fytC^| zXZBBH+ZMYtO~?<`+!=ZAP7sA7jV7z6%+| z0s#(OlOfkT6eChIlI4_vh(G6(Q4z~pQBh}Pg>(b--|tQd=ag+eZ)PiADD*#FPv#uu ziN36cKYF*EJ*sL8M4&Jw;-wMvy(@3KS-4%z5-|AHBZ1*RZz_pIaPfokMB+>#g+(_# z#rdW!p*4lFy!WqIIRd%e4T(2C6qLaoL8VQ}0Zlxkj{TO#u-pn3VapFKr6HJsUD(u` z&nPI)0iN>DFOQ_A#kpd5;n9%6DwLAOfNdlO)~|L2IUi>K|D=m=Nn^IX8<@icKsBhO zfn7%XZo%+S+MA=3GjkGJQ@$~70Baz;eut&isaQPz!>3Hpk z(_x}xfpar+a~6t8*jg6WNy+)Oojp@#Dfk(`>)09J6_nPyj>%5bPtrTwr#IOpVp3P9 zYN6ddVazLkJ}>xv2phG`W#@CFuYoHH9K!EDr@>(bQ6aA5o|}=32z&ejCFl2oMW#?{ zreZS#agZ7HE+3XTN;x>El;vsyQ~~;mb?FQKwlKiID5*@ex*>W`M^rOQg@=U!D)@oQ znY>a5Xek^vFF}ZA2lNy|$JcWWs-Q8d|JEaaFSQg{RyV=x!&;N8)BsY~#?k>Krn5h7 z1Ei4<^`$y(X}T72ZS82)x=xHfkqktfS{^s5Ej;H}Yf#o^nAT;&yEu5g@J|U3nz3!6 zw&fKQQIrf?FsK%V*qm%pDj5&_t4Xp@QCr{VJ?;fF8DACFx3hjfJCE&5WM$HVcadLq zLZWJUSn-&sHb%^>oiS2LeV1XvT4*xs$u5GCk~sL8A8}@Zx>jAUQY`@381I*^En?81 zNxYg3Y3EJD(O(jxDixz6Wt1sBU#FKu;OFl74LK7kIKm(_eRIzBKtr^?-eRv92AsBY zB>QK}G{-Xeeintf>;F0$0oo9K5e?I-`+sV7>#cl>lTCn)@_#QHx6a50slwYU;oqQL zh^+HyOV^91=i60u7ny{clI6>~Y!`b#a^aIR6i8FJ6kI52#D8Se%;%=m4bsSc?f{1v zBSSmon6@@A;SsU^EAEbEV`aWi1|j))+t;52Ic##h)N4v{swuCVO}fF;Ezl6auEEsJ zzxlIt={F-hE_I%`vH!ROqb_n{!JIM=)>8mTVhuAkKzIcgO*@`_1M@-6;{4N@O)YgF z7f~)jQmF*r@JqR$pRDnsZE_=b3}mY{==8&w(~_DV23lR%P=p`|wl7eF?}rwsc6Vp7 z<|9Hh?gCq>J25)CdW-XNXee9>8#4%WJDERHTZ?!ADjxUE-tnudN(zOOwvEaoTh8f~lUho2FBxi~A|@I35U*W`j*?zMuRYMe7=e7?L&GvEBPR4smsczFrrxeE$jfFX z;~4WMr_3J5_VFzTb1X;yj)xz3Zzq4p!m#)KOigYCrR`R5s^Bx*`}>>4Q`SKX?3Kpz zxsN6a({tu4txfLwG=qN~9B+rm-&ghuWN{TFQ@1MUAwsOt8S3RH&Xa`Oo;E(wjRm?T_rm}o)Ff=R#L>3sL)iAaOdYb{uE)?9u`oKZT0 znt`j5n^T#Q>9JQCuRjmAm8fnHtR(Kt6ct^rC^D=_i5`cN;Fo8gKFOIMEW<>s-{*LS znGeCrVpsSi^?|`^#l2AMW}$p`u9eih?^`k31A^5Jf9-uV!UMB#p{#k{hy1uACg2+xixUOfu6xJ;I~#~B~Rx3#q+Je)^5v% zp|1ev%hx05v-5&WA1%h;)X2z19AHqNr}WFZx4*3<)oM z3_Wp_kp;ZdtJ=keohQ~Rh#JTi_E`V5Y!jzY?Q^#tvU~?|E097(467SF$xBM>8W1Lp zDQqGs{lQw@gN%DpgTB5r@+|sB#J}wNa3h(>61jwMmTnxQbdaHhy3*P7va-`Ox(ka@aSTh-m23s^8uh{T6F9nO}_Y2}GI)uZINyL^!RD>-~C!vDHH z@~(Lo;U1`X-m1`UF+=P@!9;e@tJ=?&YfCGyqPb{~R?dT^_vmzvo-{zIqt%2aj9Msi zV5B5V)W@*6eL<6$YDjX6|AM(k)a95>3$VKcK^Z z&LafRbjET>g6f4boDil8e%XJr`2l}a(Q>k2QQjnm_8{YnMA!4qmB|2)b@c;wBuYeu z_C)j6XCGmaQoET=;ka0O@niz&(HI&MdK#+f3=v*9SA`m-+1m+p&S+wpLU$H1*cY#fm?_++nX-k&+;y%;JbDy7AJSm?hFF zZ)|K}U|=w3R)U-jUK1E$O@4gI;AS(QK6_Q{=_{#jQyTi=J9c9}W)ljrW!!+M~rv zbZB2g&U}BZmH!6SQgCE8bNGV{K}|vkp6R0na&)x*%JE zKWpbOIgjwJ4MHZt<&#e+9*3#vP&4orru9edKgt})Gh))7gT)yo61nDr>9}HiD0rGK zQUps^I)DK4I$7dM?0x~kd@lr=O+b^(dh&-P5b<;Kj76sH6nPzXMsh?#s(^F#MZ3ir z8~1W0;i;~qQ}gj^e>wLP;#I9mnFgCh-}z-H*$_WHJtCiQbu!@c{`z=&La$nWXQ6sE zzYV3JX?QNHwSImMkB+YFyN?F!_ul<|v;F;jT&5pDv1`53Y7Ag=6EF3lb%fRMy&-;= z*{P*C-tcMBnVIe5)wl)ECnbEyJv!s4dJ}ifl|wLAA>AC zr7tr+fK=s+`GMOm>M@wN1u)?EUhN3eBV*cGX|oYMP>b+33Xd#X7hP&>s5?5c^xSK+ zUGD~Jl;?iZ#V8^!g?dWCo?v9nI`bfU1fB?0pa}e>^c_B8#_TupEO&p&sK;$uW=;u( z7C3e#Dv6v@lZ9H;7%Y=*n-G%6JM>DkDI0)nd`BhO8ae}!Or-fc->S6Tc3jo=#pEFirUK{XxqO{Q1Y$)2L7ZnpMZ|{C7Ab z2M4Y!1zC2NTl(4wZlOP)?mNz{7&L#r%p^%Pu*{??@RZ*U)^brM#u~}2etet|&rkIU zW;O?O7GLv6AU#plz(8w?w2OBnl^Pm-~+0v-4L#Op)pYUfhThFNn)r#bY!Ep z_BZPxq_GSNV^9$&N*e{~54C1fVyoK71HBa;FZKF8Fl4w}UrO7Zpa-p&n?HZQtM&)$ zFzxaWDp4uhaiEQ4-aT1vxjyxANM_OwP_BSMi7*8A#H!cra+Z&EKP32!$_X44XWpsn zXR9Sn^)^85FB8}u%h3Ymz3=b`HC(?wY$wO*F-hVN{rcsv(`Nl6BrdRE>iO~1M<(?m z?FWgM4AkT8u?HsmXPsOt@UGX*Xo1;^S2JLS;p+Iq^IDXPn9sn#z`Ds8?CI^ie;S{@ zO$NEWJl!8JzCJu0+T5Hh5AT-zeE#Lpr_+;G`yR>HmQrwc(T3FXa`c&kl270nt--{| zXd~`rBODY(!dLJj>I#CF@$B@~w8gcaF3fSa!Yhc$zYQVvhN5CrJsbulD_4|XQKzri zp3ia^4P@B67pABQR!fXv*-n#QQ1eaNL%6!N+f%pb_lTuD2QaNwArRT=KKb>W`bY3l zamzHzl{d3wB^b!E>$RMrLQI^UUeD;!xP!ZcAy_%qP(NH!WHlP=7Lr~);M zMN<&gEcnK8h&Bw#p9Jp{M)iZEpzq8KD(T&uRfHv*3)q56dfTIN#BwU zuv;zE0-X_U1%Nyl8q8b@1eNQbS>CyNtbx#TkW^h%2$-0dAxQZC`@Ny_F^CR+wg7Gs z9S4l>>2oPQ(PzS1vp5jFzTPtx#tCq6`fd&nAqEh2C z0=Xf=UVeK=Ldt52=KleDh>s-K$rZ*24#68Rqw=*{d!906?7$E#osE~}|DY%6eT*hLZ|p2m|p<`H}j@O^Kv+fOuZqIBz(=AsCyqb z`a51>2gdmXtG8DZgR9hM|-1nWb zttLlorIP7eU~s6Ln^S;S-*~N=;(=8nl<%5Q01N~iO#O8iETV#ff|ANKW3X(FEFW8e zZbv(}bsjPSYp2>RAZKtqU*&Lf#0A7nv~;13h+m#V0;QZdxRxZ!poMxMEC@E1H|+)v z%GVNXTp;7o%3#<|NM+87SP*6wB&0-i^lr>PK@gjNRCSwSV^Xu_k(Tz#wlI5E(!I%|c)IW_j(I~KBa$I$I_UFxk$nyLtBMtW3L%_I`(jWH1 zpTlAgyaq541P;c-$^7taB?SQ*l{gJ0LX;V$glVzVVA`Aoyso$Qr?_=Hw1sMAq&ry! z*;pnaW+#~GK`aXelg@N~;TkYAV6IL_hM+jX)5LH3iqFKvFLhPjD~3!JbitZXaB55|gHt*>U#c>5L9a+tBeBDtn;2NkW7F837p@P`NQ+q zl>7MzebaE965W_^GVgm~nUFwSBKcb&l!9N25226h#rKQIo z%qWmfvx$h{)tJ~Czh@B4c;~8dlcq{8hmC^v)O_VGL$Por+dO||yfRZi`-z8;m5j1~ zySp^fO!IN}L^9zDpgCE%8>Yo#rU{Bm>Cb)#zes_87+^M?Ybq3c{WGRrIeU4Qr0Lgu zSVaHnQ`A*BK6sP7klj$x_iZdd36()r^d8VHvYH-XW09jFXVMuFy!ZgFXzSYT4S$Fh z43*#+l?%}nm8*C&d=-OkPr|$vC7Ylp%N`BJx{4Pl18i*ft$|oq<1`+Zaxx714ol2m5NqDDkf$>Ob8lz{i1a1jD9(_D*=RqVNOBf7A*!5G0smgZQ($?;Dq^J_kbhxKY zrn^vy9%BTx-)%hIpLX0VB($6~hbOCtLJ1`^e8MBtXfr&jgNxf?5i*2q+>UVcHheuA z@t59*P9-I}N3ygq9Sl%Go3v9x6q^2MYXrp!I|rZlJ~Jq3kSPBD(R2=sb#-0WJ~0|w zjcwbuZQDs>oY)N-rwtn0O=H_^n#N8V+xd3y=X-y`*=O&y=A7diW7__$ovtd}s{`JH zpXZ8o8~8OcGuP^G$!_qAy^H%KtZjz=J#KV=l1E7u4)`Qtzj2hqt4TlQT*Ex4L1j^? z`1x(k3g#U6l#gfBhl*F|N*EiTWpR`a1VGSisCA5|+he{W#X+73I32`7js1R65IYZW zEi;eSaDdS?gL;cgKzrfy{lWCU+xhIf{?*F&ymrK(gZlk>QYuQtEstR>=Q`ntZ6bS+ zQ%v$y6d^Z1H|WXE75cD|qBh_3IkzKCWRJ-(jpuy4RNU1bH@CSU_phk<|L9 z0qbTkEvM)Qjb6cgKu-|&AM<@l!jhy{Nq#jXpx!n$q z?ddPeZS#vNMjlm(a^S~0xDK+EL+dRwkbKY6l0v6SffNz}5=v-CGI`YXJZ zQP;~j&&Jmf!ILh#`o5;nuWw(dl{f>?df+dPN)-SMyA@DW2=*$2UZ2QjMac6L)Mamh zLF`Z^0;ciH{YEg|7xcYBD0*PMfysBPWBIyEc2UkG_Dz_FEz=`~t_;=$P83I%Uqu{nlm?K#IDz#!`T2i_2$Lg}u z5Vt=Ku|eGzzxKYLRP@%QeUCzF4Lq7Fp3=3JwD(9?|4?pyqc*WCX(Tp7Bv)8U#z>=r z&Hq>a@9sEjo1&npi2r(u#~_AIKL&AR&kK8ffg|B3EF2iaNr{=P{!VE>Z8iHYCIRju zx3r#2QHAIR-#f55yuhptE@>HK;#_L#JRjfhPsalk4^U#2(SBW?bR1l_j6w3mf5k}` za~o&fI88u|;nIlLa?! z{Qjj*0ZX*|>iXk+#q1`Mijp*&r8XzhcvK+NIiiaE8r%c*SwRoN_c49N4MM9ADwMLs z8-ABzvtTk(lM)^_ibVDt@kB@2a7~*7tEGTI_mB4Kp89m$w)xQII#lrcE6;xE#dih4 zN=fC3mW|xU?sp}Nq;MOX7)lTcDXpRmw`DSQGt$>ux1Awl|I=ns2t-801?_8b6mN~9 zHCBW`YA*;}iQSVjwM4Tu1QtfF;$tY(Rn(}Qd)bhMPwHtoj#q`59&TVxgR}13nj~ueb$GbXYDO~M00KFb0h%>w zL=n;UCRTb~^nBkB_rDM_ci1m(`yLEpS`8X8Ww<$#n+hSX$G-#4zF4BciDoD&p?$NI zsh5}*P|#kodf9sWkgqB;)yD9-Pb5ym6bWQ&d4;1lh&M;fYF`veToF5CIXw+=3jYBc z^-#<%BTV?Q_wuo=qwAyi@S06ROlZMRB;%mRJs_O*st_VpD=xeJfoNx3RKN+_dTK2PiuK1 zk6t@}_a7mhq3g`66G3E~Rjvlx3}#J1sGB>iOCRR+NjUV-G=ZE>f1i~l{TB9dDDNDR z;2M@$o8wcUu?5bls9>lPsYuL1X`$GCBpn9TXwfS`>`SD2gMMic8hF0un2zUoHY4Zf zw+Cbx&Ojb|gO&?}^FJ-;@%S78F~VP#&`~%7pPKGYYj+4WJ|E^dw~hMF1;z?vzHj;k z9Tnr&V;S?g>)>a{7Ewj3^L#?<83ZOtNaw>IMFHH{fz&n-JJ`a!TlED(f;5Mz-iE<}&qwp63R<3*tRa3^yQ90NgRy&>?2N28L;OZ=hhu#q zEsU1GqLGp1;D=3A6I6TYLz}+yNSb0ZryRDY=9Oc-?7|`8iSkReOZR-Bmw>kkh7mlQ zIpr=Wq?A->+{H>yHlx@fX(SC(T3N1(2I<1!U&oG|Ah-FY z`6A=j;FksBKOwJ)QKCos6{WFuIAAN9xw^7`Uw~-MUaJKZkEDZP!c4;QPk?$o<|q*J z2F&@)jiX&w%&P&FlX86nVZ0S?*WMTK*<4%~4CPHLF42pP@N}ME6~u?8U^haD3M9v>!#7AyTWuoYl@o?> zcZ4aD{fDf8rv>v_l~c%rpgl!@GJ=$lP&&S1-aW~TpiKhjV4B9wz|BQscMB;;d|%`N zeM=CeC?-@L4PkdhW%); z$|Z}X0|5#+1d0!Bb^0`dFXBm7z_(=e+7&KH5;c!-Bmt@>1`ou1K`$P_&httx&%@3W zs7T>ok5)`)5qekEdRIpJj4``}%E-(@50WA6e5XfQs~#;*#BYnEF7K^B;hxEINPhde zq0)Whw40kLkZ5dd41RHAN;@a|)n%>g2%a=!Ny@xZ8H}f7gwP#A6MkSi^{&8aol0UF zKw!ZX?S)|&c^J@=ugsuMRZtjjp!iSw2w}eh?>>}_;>aNfP}>TFoH|<@Uk`lmK6yB9 zOwvDXH^00KrEj!%N?@&Eyp1M4RtA%$lYS7p{MjOLM~sPQU5S%+zd0G*h$4OF|k-*_*9A>S|Z z!GXYgWGTfamSkR*1fF(&buV%f+pNOrq8!;tB(#|#W+;qe%x{YJ;#D}ma-FfHtCUFK zIJSXn0u|_ki=o3jLl^oNbdSYPvveN)Hf`O*aL%`DY6U< zXTsnmgX;!FoJ8~a&0hlT{){A}{>5`I{QezH_54|d0F%aCYY5)tDXO{n4^(8AT3r1C z#%(@Gn#W$#o_y_vLZRV<2P4^1{<5lqkEyoVUD@f*duvmo=fRryM(5-Hn=VP(9g3~% z`@=f#z*ShP9k&@W9Oz>mpOb(SeE%na6aE_L;Whr-<$z3bKshOrsi?;?A?a(2;}-338r~3RF3)BLrWqC;c^mrCLC&Qp3(FtWKty{!^sJRai-Y zAM_V;W&9Y@=|60@hw$J3LU?Xh2CSmq_{QrdE&+qDW?H3t^0)OMS4#0F5?KV|Q|Cc$ zdon}MJxd!qY&ry?f=}o5)Sn=1LSfq((Ev(XD3i=3N>Mgw(wvg@#%%x=sttPe%iW7l zr)A0W1t)Lm-*R_1U;F7L%G<|&0=E`d^`&5NSx!;5`9SPU~6GZ;)eet=`bpiVaEdnr4N(1 zvC%y@CBzZSSG#Vs4l6}U&MziKU}5PelWPF=~fpj zP5*4A(N98Tt7f=FnDE^$tycfIy}u>=Kk)HwKcrz4^Vr4sB1FjNbMW;ferA@lX(}`6 zFX2GH9n?Dul=1b>=eNBdYtMJ5g&O(+GtKNSkju6^L&f6$oLSIKwts$zOy#MJkH}7s zj7TCLWpnM+T@jm4B!?MzOWQH-H#+^viwyWQv+yzekIP2a$8Td36N)M-!x2$m7cv_N zNJxVFk-vFDrNRpZ?WJX$cfa?tx4uG=K$p~xqC%R9hUKy8?iX^SDjfS!o|@KOTvoi+)k*z0r!u&xZx6x`c>lu*UB zSXr*$@zfox0H~=ViihpE;s8qJ*0dUT~GV4u(YM;Il7;);tN$;Z}|S&Zk!)@7Z56I|>Z9SFRmFRgY5Cv+1O9RLillr$)H< zO|?PDcEu7=C{ww5Gg5IdYm9pC#uIEytPko`s^*UEiiH2W3Uo!1Yw#mo6PCinHhw6M>3(|>prMh8fJ7!2CBrUCZhD$m z5m7@!!$Kx`V+}4K6r-|dynrAl2r?VVs_v1|wC)ZH++u^l z{}MYCzqKSeSHHJwsQ1J1_dgEwri_&Ve=+i<8!#vKb_7-NNoVcFf#N^USM*!+#6Eox z?u4&!`P~rV?ss!|dD1vdT9)Omi2+iViKm4TeO^2aF}^vPhg0dHZ9QENGf56;@j+fvJngl!w5nt zIt!EYt`>f^Fmo;tny^pUOn?4RgRY;dmXww!v_0*X_bGRtmOXg`vA`wa9166OZg4^f zB1q5|ul`IfaHXLEBIt!EC@_0S$hVlvU13U3`TcbhUe%5^nI38WSjcRit(b>YK0&X%8CYzd@*M;)}EWpiOG&nU*8(DzH)ATQUB?njIh!_Oab z4@I+BfpGl-s{fY-083jBeB6SlJFt@B1xb!R#^i)3a_HIE3|OwD5_>RG|J^G2hCTpP zDlDW_sS}*g+)C_cn^|7H9Y+<)U%%!tE%Y;)NYDq3_2%w0n2cZlLcekv31$EXu{qM` zpP-~$C#>KKC;r3gGaB<~$hd7#iUwuMbh z_!RR>l5$scucJ8_T-IAcvX@DJ$m_9PG@UEJ)Fkq9Q3yn6!y}2h^8Wxhao`2VmV*Bp z@Nzlqs)RVOKy-J$VL}B;U^5wxCwf7`Wz>y!`A*Ej1pl(PdZBmtc<_!Ia%X>H!*1*c z!i>4dA_-$!4EunJs`;}=Fqd6_&>u1(HkH=HkV_TSE+3akKRn=GQlfw4X~#gA?bCVt zUfe?uUIzMB_A$PJ?BpajH z*Xij!qkICl#(vK{z={hdeIvc}xS@tGA)L%lD`GoLF_*?ag^(1Kl$PEKfgl6y)w{F< z$#`q*^TYozvIL(LV^@@%8ghCl>R?RG@MTzF?f8u?`w3BLA8e-?VxAK?BXk4}kaZMxVTma1B>r`iWVa3<0zu#QeY zS}wJA00-k`wD9$$gfp7vHf1de`-Y^HMsSg`wfBpiLBBR1p4_#8&r7?@hK=- z-0O!O6=10OUXK42;?HNiOIG2wO=i+k)z678ruJRc+mS%LEiE6FdjOIKst_lno-n78 z{sW`u!8dOQg2z21(*P?}(p?-(Fwl`k36QR#DbCcH^#dGdRCqY84o|bDC)2p`TGGqz z7Zuk2?3HWL?T_A6DB@mw-)|tJCLar3&1ceQdH^aj>J87I`WJScbmc&#_Bq}k6s@H8 zFm@%XqzI@d4Ug$-137tlNX4y}on>+wSHUj9!oZUd{!iNgH}tE{YzOdfnV}L8k^Je& zwjicGCrM*rF~bZe~9e=7aTgt`Z?Cp>Q3m9wb{Noo#_wIQVEh31eu#t%*e@&hJ-NQ zEV0zU{ImaYnhtzr-)qL_u=Z<|PZKV@%=YX@=6N{}t_3s*{@rV*`H}=-p*M}cy>KK8 zmPlVwZ}6k5OY|l2LX>+>Sf%tej3}oESDlo!I1wnKv4!T)mp{`32hEs@$ZJ(OAN*h4 zL%pOJWk^MNaXoD25|7(`TG!5 z{19&&#;Z@s(XmZs!E!=+sAuN+M zq+89{Bme&F@(#Rr4od7@K9|D}oW-|`FdOKA<>YMkAk$D>gUAPq8dA37?Xa-$rWyy# z$G_*(2q)AK~Qo?MN;QAJ8Md0 zs#Jd};c$HiXcXxdh~K||11oR_08hB|XkI1T0#}C>?rxL_S;Wt(!59?B<~gV7 zQwt+{jn-SqF{@XrOh4bEkP1{|6HMeQF(FUCYxdk+%^-a<&K{^B{0m{ZZaYINrw&B% zrL8poEh$M?g!zJHq1Y&-daSm5GL#wA1$TjP3HT&*Tbw^eiVJUKaDo?NFijqhEBC-s zNF>{nXCnZWS6Yq$b{@~DozawHMC#aC)G4*)w}Rh zJl-3xiHIMBOd+Z!jVr?7QY3Yi1yxI@jFGA^SeML81VYzU>lkP47Q5VXRX5`ZyVzD- z^`nZ|$M&ab0=u#J!`BsXpb?OogkHe{8RR6ZBONoWj%`Zt<(f7z%-N zzo=8MEhuh*H8 zzzA=*=@!oMOHxFHwvhpLMXCeSncT^At=(;?uo`@OgddQZ z#COBDJ~0}G9~P1g<8kw z{e`H?Kom`W`TEVrh+3%%*z^tL|5~X;VpZZ^N(Hy~Z5L?t5`U2I@(3BW#1Kanom7Hb zcsg{Jz7IsHZfK5X!3wR|0>-3f% z6e`NWIDep~m;h?f1MFt{36yEAJD?(Zqh~S&U$@Sh0cj`0&sMXrJn}^fU)!{^yh0EVZP3F6$kA z;yjuKqz81%AG%QeF*3=H6olFLzJJjh<*V|vIp^$kOo znq3$+>}o)0JhHOYhctlFV}3P-9|3J{2Gg^;Ug?;607w5727N?UW}*7j)u z)8~NUEqNKDm0~#FSN*TKIjsIhgH;^N2jPdq!a-o@NqNnKtm*u-0^ku5&s4^g0v@)$ zdmMz1AJOWl(GFI=CGgydcjSa3b(aA>fNW0!FPHEP$Nlu6-a1n6Y`2%cntsxyYO>udfs1IOF(}hQ-$Fj9_TK$RsnBlOLs&vjU6IG@TA>+DS(0>d0`HHM79p9%?N}77NZMKcR zwjrWw0S$FTsB&a;yh%weKZ{{chmvJ3;B#Y8`MpCZWRgzc4Cw_Q@_ZS3fi#Il+u$dn zjqOXG_Z)+Oa#r5uSfZI33pewbK|7ez+OHowd?gD<2I3+zm~mWvT_7vdB|G^HI2~l# zB0)|;P*Xm^4{*U?ap-bxUTSQw^f?OY%rl7A%5R(##?MM35LF>#$06!rL3T9ou@@9D zQ718^tt{XeWFm1i0u{Plw1Alx{bIsoVe;(Dej4aIPYD_>)?pv5SRrj5MU%I7#n zobyRz?ip_LaPr|=7ud3-#R`_lJHUXyJ04<2|8*bAx1(ui+$2lUmel3M#|iGlv3{8? zW5pTOlh;&@bo4t=ILc6_(Y&yBYzfJDDA(}62CDgqxt_AddEAODNHBliW@Ki6vPHp# z{)lFYP=RBPe5(aY=78j<|G7YMxmelC8Xh_xfRxS7r(tA3Crcm*(YOCqfF7f zTppc6pGn9@Z)TKsfex?EfCD3>#ybdSjL1rWW}~6L~f#CADq!Hur`84UbwtXo-Zma>$Ywa3n&* z{HTsNFX+!b7&AR$###J8kW8fEuW^}XV`Zd$Sma(VF7@BZn%@uQ-ahhL!;T<_iKr?Q_amjDA)MxQH);>in?Bxi zU%@lUgVTj>67J42_;8LaI)4#`yYl&?0O_OyaP%eLF0X3)uCOuq-ko?yp$0GaG+_(g zuBQMQ28*c#4>>OJNH3I2c|hP%UhtdUubOXTQ?_G^l~o?M_D=X^Y0b%VK(Go)upzLp zqtiZ|Y`*Q2Ll4{WrA(Urqd9TL%l3yb<3{N}G!SB+MkOS*Tvi`GSX0WTr!!&xB2JMl zYXJcCAXSzsX?7YKo2@?7mGmOBHMF$RCKL zajcaBdq3(qS9d$q*cBRMqlvC=ERI*E4uIqJP$;C4bm*oB7JNX{7GviBl3~Csx%hQz zT30UPL_YLGic(jQu{ zQ7|V*KcJqpOq!O97gX;+CO;z@Q&f;z2#(XV$5@Q6y?waEe36(rq(H;&!FATxRgQ%= z^s@a42Vyhn$fRoxWqv>c0}J8)3AIBW)yksSqMb$GiS`vN^YLZbW>t9IugnQaV-m=x z!o9A3e%u|+)hyy9v;7O%>>d!3IV@3lc^L9d@b_8R~d^UW9PO%+)IhFs+a22sNYYz#uIA23TO8 zR@5~BK2a?JFIkLPN6}7)rPkQ}bmjI9$fR;!)eU4}O)-^@@bgVeAqi~KEw-e>1?Fbw z8}Iuzx%e`9bquW%p57!9hro!}QwLX55(g4O=8=`XK@*SK1dqq|t$;o7YL`xlLpini zQUQo<=mXhyNpzI5`IwKPYpbVeM!2-RJ$}p8jq?uRpU2|N>OCQf01Lay@sV=7ZA%WE zTOcT@7u&nuJbzp#YP-FxY}IiRlL}#LP#9ID!Dv*vu)ZyMnW~irXg`6zx{YY#0~;?BfPTqBpGnc`-qGS^Vif-*%e&74k&Gr?;xb0p6(D?_+EqbY3|n?<_3Q3xI)lYBHHmYG!%qK6 z$ozWwIl%S~ba|R*FibF;4OSn7*mGx~4{fEU7jxp4d7ytTR#(!`hWjz?$H_G|5#oucf_3#k>^XD?+?+x~IO|Kul9ParuN%&IP!kMQV zE=zD6qTc~_-EO_ZH!H6^))KTH(hNIJ_$Tm+x>nhSPLHOjN32O%G(F6rK3Nm{tR?8| zl>~F`tke4tI7Q0zo9XR}btb2#n|%WIgRhgKDR^loFw{dkDmnV`==_;+-vXQx9K@@r zLY8$Sv3L<`A^0p?u{Mq%jH!4Q`aqx6mD$cfe~B$Plu73QwG*4waPn)!@(le6U=ch- z+aM`O{EQkVH^~86sfGic-Fb{OU&bz~RHjkMa-uwKOG#klcNU; zBwiPpQZ1D|KA(Sl2t%4d>+m%X5w;|N!K)@F)S7ueETjPZ6mYEr8Hm`E40G_J4j)dm zT)6xAmA~?4eO;Bc!d9{#KU!@$7ilFwe+1SPsiKMkq>9hR_ph*qz$kTSJT3OI&TxV= z7Ao(V3{-vOE^b()wq7>jmy`(~auEGG90{^<1fY2~J4kt}yGHSiWK^*NO&Rn^w~x)x zTCT{O&tc$l*TdHgS(v>C*-?-!%)4d@dFmi5Dz1=2jgDVIOJM9O=aBlTS z)f%>@px7pIcHGO}U2@qh;aFjrK8h)jmj9J_=YvEGO}K-ly4h=Kb6(@mKMdcAoHdG4 zPW>Gr5QlC<<9K>)e4%8+`9CeyjhM8jMmLYrBt`BxHtwf?|Ba-1PC zCi8eIn)_$2fi@}Nq8G9nTPa0`dasQ3x8&DtX(c60h`nQo!)0BlP;%dL)WN{p^g_B3 z1!kI(7HQtU7G3crdb{Q2EAFlK>~orV4h`Xm4zJi>1$|S1U`J}yfq{W0mtZ46yuUPK z0WKm9BWlN=aI8^NuHV)f1_mgUc=RSJFS^wDkYy3cbOs)Pl#QvDAS(sB2E_sBDEUv{ zSRScdo~B^FC$U3lG3IbP`CSfXSWo!GExnuM8~`bvj|bc9+ny+4UWHvvtA?Qxc}}1A z*_2=hGe9#BKL0!v%2;7>Cteh@%rknHNkk@;y4k(pn7KDonSWsmdqYroT?0Sl*<&)t z$ld?Yw)#f@N!psBCzUlwnO30vo&u?qB9)@3nY#1AvjNDq0rEa>{q>Yy{h!&O?Xy+_?H)vt#mFoyJb$t1*N(v-EIEb^~p-Ss!`_1Q+7>7j%#d$}>l zE~#`khY;;X+mmA!OpP#2pHFpa?eXlI30#g%%NFsd&qat`yI(xIN$)eIjUPtQ@0O(E zgaHr$M!X>Ax7bpC>o1jdhw%}QR@|Swhs&yYW-}vij^lKZ_$*BUp?DH^K!M1I1aosT zp!*Oq%}jUkt%uWlq;xQ<22r^ubT0mXR(x7qV$j@i=Qnrxo(u-{4O$*`1*aKCJ<8!I z6ksY+q8S$dPu$ji*;6#mmec^>EJW%sRqoleWulg?V?Zu-d$ocQXCbw zfr~Qk5*!nx{9I%|^u;9Vf9o2JB_ZsXPlkK!#YP_X(Q7FhYAXHJE zO)T$*w9*(ivNJc|9*C(8cz^W<7|)d=Ig8QKUH4yf)^+8?Gf%yW^9d4_dwP1XHgvQ0 zpfC4_5P>S@#EashXXuF+YlT@H?wMh*Jw1_pvt{zO;}4h90$&*0^(%?hZ~H%=Yknv{ z9%;cgVbrt;gJAy*8Za#6ShM#!(cR&%V5Okg0qkII#O_Pq+nZ!__haznv%N8pxxUa@ z01X?h3h{-zX>t|wXt+kx74i*5cDkccirk78Ee2kRvASyVNA`a)D*xqDCFG5Fr2&wh zO5WShNOB>d*B{rF00Cb7_E~CD3^&%t^t9ReqtWm#6!{+R6H+(f1H-8egg<{m0_#-#pWMzarvbV_71X=x=>%y`Cq&NGK{pe zz~z=5>2^I~njN?gL3}s?XZdLAiLep#r;R+{D5;SH8Lk*u+8c>XI5fSoz}oN@`q@Pb zp#oU3#N4&}0jZ6)u}96e7Tfr`nS1(r?O)DerH)64pa8l@P54}qNq`xT(VRDpj4Pmi zf+PMIodo~;UJ&;nl)?zZxkefM#|r7?AmmC6C9=c!Qdxz86Ahdw1URTj1Ulk#o&k=Z z#Mg2cV2GPn+yEwr(u3P>ai_C`+7{!okDIV1woNXlWCGUAY&{a`V1GYr0sTA92mOj) zBp4Xqjdd%FN<7&_J!DZ7s;kRVd9c^C?ZQMSfM)~wXY6PT4Dl-R=HmtcrMn^V?kgPD zKaPmukrt9BCr<9E-Y3+}pZH9X;e|*On#5y~+yam?Sib?9(@mA^CJ>W+e|0d~RI@_V z$}3()cE>PiNYqs2T!7C1xnLvNCJrU$t!(~JbH9T+GGOb||LI!bAFv-UrSr9TH8-jc zbH$20j|HtTn+l;ZxHKh!o%(<0I033GifW1?pOd*6Thszoz{wGHsm272)`OpD7?42N z#IB*5jOZ^R`{OJ@Tss38*NY~=;eo1$6tLxCJ@8}_2!N?(LAeePOXoEEy|sj%8UYu_ z9=-=c_BhChZSZSd9qX-MXQJOcmQCq8D}6a#jrM#@zET+_=OdzRbGeBOXRDMITqoB2 zZa`x&jPYIZM5RTv&($c6G2lfbkrc{#DlVE3(z8RJc{pOm%kk}sjg4hj;hR4}CEo1p zaNrugT}c!}m9HCy#SSNx2?x@@X8TMkbGb3Bbc;FtmZyVKZ>VJSaGE#+FUNmOf1B>lqjL^7Ov z9>7KHjE;}jq{YVC+uK9r)kg&w$7bzWN)r5Xh*nlEW?jtuunaW~w(Ub(Warsk*R{T& zTZWmn55Z;8F0z2QHPnxq$hwHPNu3$U?t4`UxR!aE$x1pD1@k|#a$-~5^p%N<14642WK$G$%$Jk@?(}Tn6 zPar2{kwNu9SLa`1lBRDD1+M~h-LQ0l&CHDQoqhHeefv32IJLie296vtJtt*Z8L1p+ zV`sN7XukJL3PH2|C#80;Upx*=4PK{hySZmBUhMvgx8O+M!jcs=vNT}w`49r-qV=1r zXb?|$)etS*Wwb;o2kXuBzB%xml%_8L1 zBGcMq=Sx*2R*?HAhbROZ{;jY-+3f8H=K|lTLeP)~l7I@Cxlh}G{ z-tR%do8OmWv&>PjanJ(f*pN}yps|IB=VHUbg2ajFM3KEXL~lt9yx~QeJN10jn+L>Y zY7{?$UMcw<@L5V9`b%dhWKhgr5kz=$vuyH#&)q4XBccL?QI0xMr8E8Oa`5j`btMs~ zDU#G`i|)p=>Ig_@3CA)m)2{2+pphRd{Lc9x&me6$7mp~<&{J_FS!Uav8eKl7&|LH+ zS{dwW&0_d7Uq8?az2k3<0J2!#WoiImw+D<>AK`W<4B znSjAFb2ol@S@#PkNAunS)EO;j!6P&zC*5N zr?=fsjv(3JrsK@wyY8mVXbxq$kEyzLGwD!=Sq5bk%NQ@3jZrex8;Mk~b_S%}j?1jp z6JL}qdJd}G`)8Qx+)rvCS+ljhXX$%wTOwm0Me=QgpA-cUcu9Hg?e$lD$IPIC6I4{s z7liu7nup0bbh?1k)O^N0hk%0W?{{)(M=Z7PH=r8ndT3VU2>zQg`*;NW1Be0P>sgzM zpYmzZu*fOE_)L*6zU<l&1W1QkH=7`5-0@XLxs z=%2?rJ(U`zQ?rFKO6;&>iw;v`W9lr7G+C*sskyn#I>%Jq0JwR~g*W%Hbsto>-2yfV z{|W}1lqx%2Pq7Cu+ty5=HoqetLlQH2G`^03kXiv3(4rGY7u4xmy9*Er*52xrNS2r3 zN!U-HUJaLoK|^y8`-JL)Ovv3gXpu6>WblJkjzh!XQV^Wsbx@H8avJ?07#IiFJ17Q4 z3qV=Ixz<3-s+8AG9$GQs$m1*9$5eaj|Gbcvy4@6@P`5~ckEKh++9Q8yqUS=ki(khj zo(mx=0qyU69M%%^(pL~afU4I$MQXTciiQR*aOL>&VEFDahN5tbE$-t?9S zlV_>aAOZ?ULqkJ*`-_pc`7Z$FMOJRDhfJZuJ zcbx)s`3x*vc*Y;QL%-0daM{i27F%Bj!O$aLNYn#S6Y=5P{74d4-vI&W6~MN;UX087 zm9+;mhziflTE4)+pQ-FI41gy5L^PSZLRqk(&6N{PiO(RdtUxpJ~J8kqv;SQ zE*$daW*kcwC|~q-hOg$;TS(Z*_2#;mM#qHyr(?UOZM$Q=soW-7B}{Oy_5iuZ3W#l) z=(D+itIZh;i2}0wPY%FbKGIHrL^OM`zN~5%$s-5_cCrOK_3E+B;O= z*l}5m=QJCE|Dg*yzjiDa%ohlt+coFSWHJ}twcssZ5rF=2O`;XkB8ql04hqHwO`-IuHs&| z%I@wEIA(GLBM7L$7jk-mutsQNQWadoW%j6>6!B0ogAgU4ZaE$z^jFEcItJ*82^nON z1ZwPgT=zo9hj+vE`}5-WJ0pJRwi#nxOBVVDTp3s?O38(CTy&Fe`L(|eh8}^pUfRsv zQ8z#QZjS*{s~3tU8;l;hOXzb)X39z5&crw?AwSBN$DHh{<`b$8TP4Lc#?il{x}%ma zc%S=wN>H^xFn=FLRb9FViwJ`xM2?%NO^#xN2Vom&az^EhEgwNlB_&fL4huf884Ig{ z+(HPfVFHj_L`00GKk8UbYN9V0a{t}0+kA_NqTaxEo5tM$?&@~nCd=kO7vZ3&8_Mt- z4{3x76mZ=HKhe`uwR@dbsAHG|QB9G&Xlr&GU9TI#@6}y)X(`|M?L?VfDa7Jaf%qO$m;AMFzhYV*9CwcBMU0-%pHEeda&C!2&9nO$hBn+>p(*FXS{~R9se};ep z{|5jRR@S2I&?x$#Z}}Woj>t*N$c$sN1-0JwZ1u}5`~f zNnEB$obOGA_L6W8En|lMo>ceOng*o{Fl)gyKVqp?{l{E?f0Ql1pSy!N#Aj z6$Hb$SeRFgW$lVwpX@PO20;Dm@}R!aX~KXAeBBpI(R(nYJN<5-fUh|sc7m^KKbt!n z3rO>^P2o}T0dhzYQRlb4T-u$Iwm_WVP3JXE(2Q}KYE0Wf^ULlNB>A7*MnQgxXI zbcs6n96#TWDI-mlEg-U;xdo8AyLzLBDf?bDh&+4wWdxfs}5%SmxlWC8_GVY8xp^l?C4{r zWq(1=?ptYKi^*i;UC>uzj8KwUg1aRfh=(1gN1>I7PQ8Xz9^x;oo|t={NHj__5(}_V z!6WlJ;uXns$=0bPKASI(jqaeEGzQ>Ud}E(pM>W*CR7&~=t#jFa_JtVWFSIlSfXMBl zz8h$z3PD6*;qgo%k_kYGhQ0K&7qRhpoEsw_?gFZ-qmTZCfpe>rnHp}r2e->Qx%KhR z&B^b1SG-C13tWO2|i3010T7T8>1*IrinP1a5i?4%Pmr z6p*I(1NhU1?zVIN&)Brd=J)f6Rbe`h*}rvNV?z4BMW*6Si7?aSi-9~2rUqj(C}e~y z{LOYJCMJNZq6vUVetLex&ud+}&{}Lo4|PD#@o3{F`}vuVugy7U<=r zZC??*_H9s_9p&Fh;x{oVEvp?2=Wj4>7;Bwfi}|m2So?HMtE$TsCDsx@R8v?0n70|A zDXy!#0IL7vTh>v;O(0V{#~-7`aTZn-Di@F6v#gbyuertH7c*aL@>3)$Kas$`Oko)H zFnJS@Q1x~!@K+5(bUlfz#UHB>QnoS_Cg89j1DbReNX@@dkI8=m_Pdd7(vqAl*K6qY zJgW=dlP(x_<^tTy?b(_E06mP3j6?(LWPTOu;v?EVb!h=HniEevGb>$zT%Z|1H{q_c zJJ*tumM+&!`S#7@=1?uIZ_Hd)2Xa|WFETCN1l9JH@g9Jd@s{-hBu`eF<3bR#DjKf9 zTMrGX;zt)|vKURcfi$Ena8KzgU^oFXGn&S_I$OY;3|J+^q#FQ^;{ce}-2ZIjIy=ck z;|_fCoM5(JuA9wdv$&|ok8aecz7E#$psJp^8^upP{>*kD|6=YKZ~Q*vesfp=w0(1q zgrq=X_@~{G-q(%m85DcvC@-Q9>tE1gnGcXy{qNrOmB-UrWj#`(tm#jkfT z_TEpdHP?(AvaXUTAOfOTCsc*9A>DYRzY-2?C2Bnp-UqUdG)ar1o-!Gf5G)DOGH$sV zzRl8{IRzaSIN$&L47^9>mdr=GR>~EU@6r?z@;*zNOwnL6kWheZ7rvS!;7O5!>j7e* z)nB7nkQ9gMYQw-$|4p7AY$Ff~S;#@z0Mq$`8!D~XVhCM*9i_{o#ou>FtFeFGNDL95 zA3j48Wz23TCoXDMjvc7SA;l5%;jwkeG~XSRBR)L_r1v%5&VmAWc)bJ8jC?RxhMNH-r~WZLcnrwH z`yJ1g+yE&@X$=)!^pQ_b7@}KI9FP!iKyEaLVDLe7>?r4AEPZa7N8-SsAxh{CqugHO z|KWFi-0hAQm7m|WP_+tVYIyGDRMgae8VE5lgFj7_Y1|@3r;~mk_ebBtdF^VAcD#$0 z{R|yT3lsR8`4>?kjMv6GqfZ3tH)5gZyxaiDz`nhm$Di(`v@iT)wF%*%;H;m}-ISdO z+=L4pA{ZQR&BTp(BBeTYS@=_WTZwX%ib;GR_Ad$~Ne05-BC@BKljuz-@l1jFDeu%r zta6BXGdC(XTDi30+MeEC>&bxMK!PIdbN(eR>f15CNp?P>sI(s(+!<13Voc1)=qNV3 zFd4Alad>`uWYKRpqnC#{W4&B+pH#^%MSb@{)3`I>QI^a{s!lH9FHofp;~s2V%;oaC zZULXzjYl0wm;sUJhi6+(=x8D^lqoNZ5it|vniS$f<4|X;fM$b(dj#GSS`0N53muQ1 zSs`nv&{?b!>^fj3={Ed6dS* zRG0qd6B5%KVJk)l>p9LpcOuLcR|X;I^?NTUI#xn#r! zlK!ngIQc#$JKM1Jo#Q3WQNC{=^o%27g`6)czF%$+rB8i(aup*+! z6uJ!>YOz~#xCZ_i@PR3`n5Y=ADJWVhP@g#pFwe(avx&WC>2Uu3{87CMHQ=s_3mnz9xZVi4hyXQ|uf3S~5w1jCu>6$aA~kO0Ud zBu+CX7II)a`&7b14jbX!`$4f#q`QK-hzBz-utPId$TvQUOIfS0XXw@AVMQuWIER&` zB@i~MRE{X3ahA4S&cwQXb4(w}5xXA!zOFff{gual!6s#qB|9VIdM|I4M~htOw(0Z- z4~()RV`5^>{ffOt+hx@ktKN^m^LF;j#k$|`=IfPrS>;jT;68i;o)FPsvqJpRP1dTO zBknkzmTVUWGd>=em0oM$Y^U!xtJiyUc%8eMp-B+_^ZjQ>hd#~{&|s>QC$hc6i73-C z_Gxzh`KeDD&MnE)Zf8hVyT-WD=fYVrgE?2k{}3cfIQ(dN3s_D1_^IqujN}zPCs?X# z9-*oazsb4-XNWl# zuCEtJ&iGCtpAr+@Sx6PZmA#20jN9*#3ELk<$PtSKr#1#DJ+_HLXWj)-SZLl?`2`m5 zo!K&p_1^RNoRYHg;9+j=_nN>_wX4H+otlo*GQc%xVYGJJ>w^WivcuZ<&*H*M9TMyT9ajqm%*vCpirs5wzu>oNU+dlhqip?i zIPTMyqY0Y{PSzoybAT}sQ@o;804D+dCSv6KlMS`g_M2SNcLbEqg;RZ7#JY_dl-v`* zqD8_2)|lwqx@2yUj03f-j3UX0v^3LdCTFWSELTZ3nLj1L20~?yj*j(n7!KPJXz+?l z26g5m`DfLg0Ytei%eCozKu=2HpRn+1-to(^jkrJ?F9jP*k2Z;SCuP58&IuA2-cLM5XzA2-`AkpU07HEcAZ@^rOnOFI!2!T z(CA~`#5KHQ(%6;VUYp_gZBKU<+QS;VJ6DU(Ouk$yp)h4tRXhurimLpZFdR&Ia25AG z$ZR7=lGxC@5U-nSUNIHNHBZ)Cjj!3)v@ zy<4r~-5n1$i^j~WUWTb$Gt$y3()&W!rj%`aQ9L8=aDm$irE0C2M_`Y9A0*g*={mYtw#pG~ih<%$N>CGwsgs z%};S$<2Cl)gk_$yw-vUp%nc^YH|+yzF>2K`kh(*3WTd;|8PD?!R%A_ebw*fE?h42~ z4u_5X-D1Csbr3ylDp=ii9y9X>`P0zXrP|NC^^>f%_$9(jhG)Qv6KCOflOMkszl+vQP%m;bk<1$Ep4fo;TVt2+m;#Pg{6@`iaH4hX*)pM<`-GvVZ8~ z#w5QqUkTInN~poj4KVwKr-&-vc;Ci9z)$cqxd47Bq+je?V zM%1UtCuGFs*!kqCwY9MEQVFyB#-xEjiqz#S8XM^@Jn{gd5dU)+%gnIp z^;1+I(|8%yA^V^pw5SlQC}`>KH`N1U`>9}uB0x%X@j|r)E?jis^d;bbGRXE?_et9B zVumN6;s6G#px=Sb**}{E_sy#Tlc!u^j}1iOlv*Uj9$PZ>Y%Wq(OzguqjZ$;6E1Kd3 zs{d9CG!%nOV;^KPftD*6R!)4nNpsw5g+)8#XVQ6k0KU6Vz$|bnyr!wCxT6EopUDgP z6y;t4xP)T3e~v&S6||e9C(dTL8NjNy`qAqofYdCGXTZ2=WCRT>Jv~?Uf!y!-D^OP* z0-J)`ayVMLnD`J*8S$)D?#70OHJ`nl%!x7=o9zB}R81nhul`%nbYBx9yOH1s-ksKs zveIR{WXF3dVnBoCJk?0?`V8x`?rq*PNT@Yv@hDqI3(oB2u^JP}oUpZpG{GE$Mb2HV z>+cCcFRVJ@E>6mOe;x+ZR|Z!>e}4dHI=-<#FbJQn4nCq2uy25`TsN|L=o8yXQh#hc z-a_o|z@imz>u;KhE_^u!mYKuB%;cFCC;K1~jyEy%ECi|tcP#rO;UqeD>}A-W1L2N` zb46x`eEd-TfEC*kCm=c25XUu2)z?Vdc;(MbOBUHY**0^VKUncOj0o~Qx%Jrh?Wg;_ z(paTe@MPW)FJ?hkn~|}df30BQoN;k+zyIEDN+6X2Kv4v&%ak7Bq%3chwDD#PQ}KvN zyYALQnudxG5Si~hmV^~Ej5?PGS#5nJ4@j35`EmK|wk`UzFX2@h+7tN*rxTw|f7nAR z9Rc)MeKKMo8Yov`FC}bQ)iNYdFJ1NY^fur&OJFk@-{^J-u}?yz!MJbJ(duRi3|jA> zZOW478b{XEqMeT>h0Pp4tFaELg)whk1%AKto|n7A0O~GEQ)0*k%GdS@P8d#qqM>s~ zM@OIUe?Eu6cXgNd?(Gbx!a}VCN~)8CYw8q`r83Fin~RO3?PeU+w|Hk@f!|MIVo*Q{ zs#p_#`BNwzd;BL;NJ=S>P}o=X!4)xc`R>klY-fCUn1z|S_A3swSJ6kYF<>g4W0f3? z;g?`$Tpt|LVso}3Zu))BjrJw>T?@>utFls&Yp zu+Z&jfyLBTQK7}6A@LJa@f1~oUQHYZci7J58$#z|K?;LEi!(EKz>Imj8hsaP&DX$y zDJ#@yj$M@mS$p@_ua9@r8W7)Lp;T(P-?^a#k^AhZA3xfBMtc8tg5QGbX=G#pn2zwF zU~|K;!9ZYV?}FAFb5&sFEL)Q{lmG9s_2`*Lx1FYL5AJAs5Rq=3r8<46=>K{F4C|Nx zfkWFF%My~ynS^ixBy=sI-LqM51VaF)g7)S9=fu%5RGjwylAGIul63F15EZe?6@LHW zSNNp`Z8wgAtnZ`O}a8>C-0`Jx`clK!77#H`zptDkBD7*3(CI zXj7+xu6oFk%*FngBveNDeb_JEP?437V{E!s&vY8SM^d^LFeKa;A&8^&*CB8xsc*7D(#hR;BLK9iLrc`~`|Eh?2rW2!TZe_26EEeIHpq@C_w z_a|N`MBLs(NhhS0SW58g=>41ds;742S&dO2t$$$yW{l`&*!Hih&vV(J`mb(1wM%3e zxL5*x9crNZKtAtaAr+JM`-+S~{gA~XamiN0Tld>8EEr$+3esx%U#Ef3e(;JpTRae2 z(UlN2P*TslK@M-Dr&=Gc36b%}zI~ff2?!9$jhFuGt@LC6rS~83{SFR>vsk>}FCDxJ zX;Ux^dXdxj%{!3%(2Z9w;Q|U!OmyEfL--t*5|w_6Bd=A*ML^Rc96cqzjW$w$KNV~A zV7vVn2;#dXqd;Woz5z`_pHjQo6A$lb`o~oJL%Y5xQYB9ur0@4ZO#^=VNIvkBA-)5l zAPepr6zsqirXg1~%xPGWWM04sK_nG%Dbv0bJ=PD#SxBCxZc;WkWG>mKA1)5ZKW ziXM!Uw;zlWREZ(X7+)FBiD?)L)XzEH?{-Uxy&S&TY6K?sY7F3TPE z-<4+$+5JhzC#j#<_X;XS30M*5V4O-?*}C=vOtMHii8J z;q2{6Tyw-g4>$C93QqAiPuv#+qE|bH-Sh?LR$sD9Ik`J!6OH}vE`Nz;2ComqAB~Rk z5oEVYNh@A~?Ii|<8D$HC30H;U(U9PM>Ee2l#8&lCm`TGzggrGkx4y**ZNS5Sh{nT+ z(F^5H92bj>siX>G;C{SE$I?qHFRubjvO12adVQV)FC~~QDa?0DF7s6b#HaX%E(P6% zy>_W#a{30VT{l(6?JLG7x>zv3DOT$nt;6vTK?nh_UsHlXm4OZ%BfCudHSl(E{prt9 z(1hv&(d+|UNQmkC3g{_0Hs}_?!JBv_fQA~^5d);&yI}3dud7~?+1VczD>d6Z-l6gu z_E+6N7y^T0OKOQ#T3t-yiqciy4;*pCzMS12E;)-P&uj|=(R2vjN>PuVIpwbRCns%H z;pquENEA5_FB8A>S^Zy;Ta;1U5vOa216M-hmy%eWTJ+;XCbZpj?)%SDOXY9V)-p?D zuSW9mhUtRz&X9%3M29i%E)qG9OW#Rbn>B-RDRl$mJr&c zD+`&*+ZkCyOl9Zc825H`a?1W?$809AXQrq}F;%9qM=2Q*d(0IEybeiu9pL}0OX*@l zp;3u(<&(l_jNsz+K*K)*hxYzJ))LM4toT-~hOYhT>YPLFdm>S|wys{umE68Ol`}#9 zhs=)5c>YCp=$W@Z!>>Lp%*~a?W~a2>ZYcud#yUWLvfXTs;hncOe8GvkzEnRy1Ay7U zK~k@-cP`qfjz^+bb3#fCjgDl2y&sHy%e;OY1IK>-hl*Vub6n8!bdsnleDAZPa%vwG zzFCWPQsCj5wri`qEdgp@2aq4l#Hob)K^;ZKVh zOp46{+KvP05;)r>hC9rBd`(ED)z#4?b$7N=ay*?7#00+W-OKKkUkk`1FgwvDC5l5P zqu#J;(P>85;Ucd~ZRJ?5%B5JVTEs{C^zqN=19lVhIQAHNg~~zd`5y_0lFxyGCwn=~%|2itwOm`k zZ2VPCm=pn$Dj(aWjst%ebhvHX34|0%*lO)&>82`dE&C~8H%zOIwqf|0nek?v7-fzz zP0e^e{Gqe6v#V)yvnxEc23xOp78V0ie_DW{6&U!`l1T%eN47`#FXYfkzy!DijuO+1 zAnxc$&7ym2J7IP`d1g5%;C6EmGAuBD1bumR=9UOzm4zOR=s zqG5|R_#KR!O&)MN9m|$WMgGZA`8fS-iul3o&c|y5B?F?sz4`fh{zSNnte}|mXe57X8yj^El8e}L zG5ydnts^EH32nIuIqO|*il}BZRk2r8q(j@#hP80;Cs$28yiMUAXPtj3lln0q+~iL2 zBWA>%gB{d9;zvtd=%;EyYmA`;RD#g%aD;tJVNV=<$|MhjDF1JQ{M&E#KeL6P-COQf zdiBt4=OmlL&ru-iP_AkA^aS-LNuLF(2vbkm;aoA~V8GO#x@&7U8P9{wL=B~nPLd)U z8Z;FuwH@KWLKXCiT1C*d=aX~C+iBLOL(?^t*C(RW^^?pauTQ^U0F#*n$3Dj2nUKCe zJQpnxF}38!UrVdz(276d;ba!*o;L1k@L4V&U|zJts$IAu7m9xok>m*YMm0BnLh z=PIJ>!IAT~vVI~t8}aRyVzqaFjI-;bDI0}==cs=E*o5FwB>*gD>fXkoo_&VF?jrZy zH&ddIRo4ney=0A@tPdmL*#!SMm-`?D@J#xLI#*6WGwU(JP}1>t_tPO2afG4ON6e1ItxP12Jf?-d{c4b$|%r7^j9KZGwODKfGt11B89JVZkZJ6@FHZ1~*-o#m zEF}>LsKj6zD22YVJ%D>+Fj4dXaL%LswOV-IpAVOm08FLLNArO(<^JXbcc7xaq$h=e}M*LCY$n?E{}df?eSIMlRMnC&>Ul&LPWvUFkN1j z5R)@KQ7AdNDN2b_GXa|{Q%vC`tH~2?)STs~8^_N^FR_Mq8GsbD7*X{Kd&&;*GT~>R zbNsg{bKBu_t=9tXQ7Sw>p(xxkDrb9JJ?J%i7t)17A8fED%C@}->O<9F1P?U|4gbin zkqHrX?vWuAuWvMU zi4>Ko%O@GWA%)~Ap-eg)>6W>PGU3uI8Y9BK%4>NnADFnC6B}Dwm1&*eUzil*VH9F8 z9ulUBr8u)zU#B2%MJh3fS`n)-wIVq40$O(FG*ml}>qejNn6&NRnxgHdG2j~oK3cXi)D9Mk3ULo&B%bSjF`3!M*r*6sHj&G>Fyk)iJ>PZ-Cj{iXTxjRWFs z$ty{pw@RsfdYPhuBHn~PO!DJk+1GU3t+)>6Yz*B|wt0eGgp@It2wO8=!7kT_EgZ^T z8bw{}_G_{vPsH`^k{AT{jD=rgc^_V!^(N+jPB^gVyH%!oe(>%N*_$F>vPaXCr^+X* zVDg5bBraeYlD^ZO!$gEtPndep>kc*17-BS+ExS`K*ycm`L0G65iYQl0V|*O?6{;dJ zvRPS}LmAHhXkyepo=>~|23nGl>OUCQp|N@z#dtz(U^C%VZHWAkU^d!)wBAUWxcL~A z(wFUs2tnwWk4}=&LVUJHe2x;C%q92+H*4=Ns&3X_-)CA3fJWREPzBadnrESxF%k(=LzAPY@>I}dMFH8+@;o) z&5+ju#i4=22;$CWlpheWRRm8OW`Tbqt8I}s^yfbh7i>a*cT#mpWQ?-1v6L=gK3#xI z-_ZA<2w?RF+~Vd|V0bv$kGwF=FR!wy`X}FZI0fYKASlkFkO>VXQ;73fMDXBk8#nB? z>w+*YgWu|UdOrQQIthgJlf!hX1j4@D_stII)$^V8ZrdEsKH@V7ik)(wzEr7gPuj z0R197QY({&VVAmbmbQ6EKZm2|#4 zI%#S%>yfn?mzv2t13%iW-J^@~SivR5aCl`UYh)xQ1uH%e5H7Sf*_wNbYyeJ4;faCK z>Rh2-zfPXVZW)kdrH86>_l+T3zDsrrohig!T{d~XR8iJ)Rc+7&9xK$}_?y#JSMX}Z zDvEx7r9+Os3YG+gFU(dSv<$fo{GXsAIB|R6#TEjXGJYU(WZAYP2NaUKI7Y}SAVbxB z|3&5Bm&NrakL}XcwA*E8Z_gRjC^oR);ywO=(3Eu(xC!3CJa(%mgGGnu*pbYkU`z*b-HY}-X2YV9t?&jV|UvbB7L`H)_QUmP0cZl_XB4a zD8m2ZgBI*Mi^sl?%5!Bqxjmk!(|1^Cy9`Bp@$Y1yis;RnJ5OMYY3IU6E^EPC+L#t; zQava*F^+Z=e8f9A^ z))H>vQpvbV<0+Jp0m2+r6GYxK_nbtTOZ57!?|4peg#E6u9GRH-KBQOSzDlG0&%TZ^ zh=?Qqdb4M{&U79Y$F%R_FV!=f()0z-9tLDsrX+73EIPk_f8KIiqL$r%|5k3BmI1TU2t9_j@_AA#rfwEA4*h(?3KI0Msom=vp}u{mD?uR zeydI6`<|kWksj&CwY^cGMVXxpL?;D_d7|5>#vCmJocMtce_pTW8rNCcGSllTg|`B3 z`>W{zsroXF8g^|~hJ$x~n6e239Bn!qV%i+TXkj>bM%~#z)0uR(KK89N9&pV;UUaNF zwQhb2fzDO|H9C&2*|lJ4_PN_bI-5}oPHJdygaqA5{r6q38}in_;a?4DU~HJb2P++QS_q;%`=6jE$B&|`X1QCX3oOuZOkqmYq>rGGvgdh)G-fdODx#q|ri1l98LSR;8Sv|0|+4UD{J zj!E|cMQ%tgr;Y`ZH%N3Wh=lQtGXvJMMx6n@FEuXTx3fz$ObOY7nhbgzlsfZdTYXhX z)#al@J0MpWBNGUfPkeUw5_XVoCg`L`|I%1OP@$9tMaf3R-FN5((YBBK?vPuac)R{SS+P zBQA9E4i6un84;2blW!Ic9heAvNQ)gO^Q@MtB~2395aAc#+$dj1!=MSV_tIV05OJ8p zAkW;_C<>*E`xYEzMK^DwnVzeEmI)5;I>V+$d%qK; zw}my)LQ9hQjg7c%=x9y}d84;wLzqbd-$e7NLHv}avSw#+XHT6s$V)nLuqN* z&F})1LITK1fcN1Bd1~#SXo0xk+;3U27VNIZ2oob^#?F#SPZ}f5=pJ6TFJSn^$?`k%HG|MdpULr20TDR#gD{dLf`0IF zJCk7)_M-S9KcXPwAhYj#CR-2F$9afnSTSoTozc3Hw?k% z1CUPiWrYgUreX@`;+`?Q-P{iBu=&#($?|-TS!GZw33MF%N<6RYpo$G&`1GWNkan*r zlgJQs^@9-V9giEcmaP50FW2|TnQ(OJ)E6<5)q-*K(3AK@3wkT_Qo+%^6lc`2=wHnW`K8Fc2Q73B3fKDh( zOac>vXY6RtR))M@H4Tk`-E=Bn+;DuJ)`6CC%u(=iN^)=f6TUXG`pg;ix~cheavft| zIfF9zseHJY%o}OR?tkNxm*MTdB6CR$f(d=tU^5=W=j}`!KzC3(rFj>~%fy-(?J(*w zZ8ey>>7b$0>qG-Rx5$~$7;SN2t8nbr42Ia(O2y_kD~I^)rTa-^X$|H62V_DBrhq~U z_$W^)6@njaI-V>1`t|Ei@>!zy3mT-xokQ*Xw|5K*pvbuc3|=?%G0K}^(k*|(>fZ2w z6pTw5fWcmhbj}I>L#+ON$bAe1)3;uK-xWWuik_>{d@atC&~mLzqA$~G6thXw&%uw8 zj%n$m3dDI5gL2^HLwofgT_D;SR|XabEDcpcl75(BB{9+#r4{sn%hz(b21&unGI}-3 z{uNy5v{x^e0mU-L#$~mwDUveBXtZf)AD9My19&dHeYfE*-A^3*vA>5OcG&_y0QkHxo#M1oP>TDFAL*E#~(L>LS9#DEkA{EA3I0bm;*l6kVR13JQpK<<8ZB>yG)i{*R!uJ6zWMmg#{8n zD2~2C8IAsZTM9Su)J8@_0qO4`CK)?Ibbvv6d_zqJ>7!e!Hp__0RPhi6^`(&qSK)uY zRX#OE7r#kAU7cai%&|Mb=sNccV-tk0brY11^6cHo0^+TGsN5yr&Q>MvuJeY=19K-) zhb_Qd@nX}hg}B$@p!{60Dd6Jn`jxqrjsuZx%ZWi8Y=wg`2+XMi58g7C|9UzCm@Zl% zmly$dTUy0ny()r{Y!WEn=|4M^hnlT|lhdqx0C%B^siM4mOHY9Y1CsZQ7Y$;K$vW(c zCq0+yB3S@qsO}_`wlF%aE$lFD%%r`)l8_&QkYcYF8WaX^|zu=>HwF#nb{^PNb>_hXRIb2ZN4Ic|&s;ERX`W-wZDJ#IsjpZ(9HDyMIG|ifV-SH$Tu*V7Pk(x8tIb@jEI^ zua3k0fTuMoVO@LMNhCRIh7Rrcj#LopBDDoHBC$M{Ec+r7a%g4VnAW(f)mYG72r5@6 zWjP-e94sjbVXbHh#Ro~O=jW%bd?>}NzzA~;o?c8&2K(qzGX~P4=Ast4k1qgdFac%I zdNw%-pauvrZ`Dw%ZR}0VecfU>ABWSC#*A;*sn2BlpyNeuK#8lp=ftZLE1Vq;GgsF? zpmZL?^)?3?mfx{bv|9U#Jm#6bO_e3Y+-rq^38#+3& z4LJuR_0cN@o?ig!FJ+)JlfYo7<|ZU%#?6We;JL1%*RoPC%8(4t5@d&Aek#Ka!Qw~1 zMZ)fbjP6|N$}?fX5aVZ(dnn?UKw{p*KhrGL#8J!o_dp4;5Y+Dpf1}{=L*{(Ir-{AF z|I^sWBl4W!*W(q}DtE(n1dB+ZcvC>kSu>@d$wMkLhnW z!<#@hH^@e;4_0MVz=dh3CTUcnvE)&JhGBL7D$zap5Fuw)9SRe6Z2C~6YnfuT9x?!> z$2^CH#*bmZPh7h6+r3HvEROi^yK93VN>|`daw<#9D$ z&9v4pIO0K$-`V>wRKl_@Lg_@Ten|gy5oqPR{%O=hOf2+%VrWPVE|F76&1fk~#* zq!OdV@&#KJCY#i)B6?BoIawg^b>pk#fG*hk15*U*UsNhevKJbf$i=Wa;(L`8G&eef ziDE@&(Nu8K43%>!LF~uDe#J2Vt`<^w$cfjVhlNWL=F zKD>Su`pRAJtU8I!$4%Dp;hL1H>uqmIN%8vP+!)GNTuO8Z#OY5|OnB-q+u;onWEN!3 zOi5x0sI&iv^oI~kBVh_kNH95F2jpophGGyr3_5H*>m1MB^Is88)$eLk~~91f9N4@y4@kii;M5aib+qSQg2$CCO|{)Q!5&FstTdnrg&Df%+b z6g$S(SpRdK8zSVp@5%OIk;G#jInakVt@~&0jm%!ag$0Irw;Lx8LN~^3g|rb*I19K{ z-k-wiRq)cI^ezur={TX)Awdy!hk4RVgS*By2HZ8azUF0m|8>_Oz>vK^D*&buc=-5w zRU~T*`_q0~iNt_>Xr`CAwWXqME$EJJz6G4>-h&`7X~d#b0I~YlZ}Jh$(+k*)d2O>? z>qIGip`k!Hq8KI62`@HD2t39Z-cK6ac&;rd{nF2HKMH-0fZa%otUmgQKD8*5+JRp3 zr3e%gr0R0!iwlLrA2T?B6R#9}-@H5yf1K-j94yd^f{mQ#y%p_qu`3LiOF*PxeSZAB zI^DRjP>kN;(b>em*D5OY%ajON-itwoNOi%7pb|mPGq8FySPDE`QXMF&bLvVGp#JM4 z??~piwLO42PYWQ%PDf@C^B3Mgqx10a0HZxegHMgW#7WAsvS|mPkS37g0#UB* z9K8P8`HyRL3?P|=YN1#C?RRX3T|aI+o41&kCOX(ohgP+>u(B}Hws4sa{5#+|^4mCb z^_6JrP+4r-*P`mCvhtRIdbZ1KdCpA0(!j^Tv9hw_<>lQ7|FmoM*)wcjV-<5zlD^G+tMMZT`kj1z1O zLkGvhNq2;jFb%J~<^L5#|2ZTf-C=5%`T=BzDHG= z^I{1(QV^2%&|8l{v-D*-5B}^Xc%vO7$a6(9pGEB{F3RSb4%3EUUSv)9Pz+ zM1F-M^@dqaPn>YI#GiTGe3=U}iJzx6`SET2T*joLMUKUGa;*AD00X9>C5lE5bw2!) zwsk|}kXUl+N51ciPB|BTL-YpoWzOr@_XEyE244_dU7`Si1L|bdzT4k=d!X+8Jl@be z31X9n|MMtXE?JgIn6K&)pX1TDTOw4UGpebdtB!oNiXW;CL&zL2cF#ecTv;n`b?PDF zrC&-F*A&i&4K-+}^}jDr_IDS`U49*HZ3*%E`f!rY_hPEsOJEQNidoIGX)yhM^Lhsh z<0lvg3sroWs!&nQ+c`(L^uRyOU4FfozEYWAl+cr&tp2mHq0C*iKh)J{YDRZ5_6^U9Q%lmm7IGqkDGV6e@wniv7x8I0-iX%CW!); zo;tG?&)>Jj`Fc1QcDLWSpAdcUpgH6X&YicI)P&Y%{k>(Pwj!Dbt+qz z->nhy_(%HHyQQ>AMHtoHL-;lz0ai zJ*1_lM_;7*SPx;35x3@uMfH^gL!SeoW_m_OMp~LBESeBa*F|(hS1`k2CNrCi!~s9` z)Q_A_FDub_V!Q0^s1`v*QfZ?^$Z_c(h1Fcbq#JPYs~&rvqJCN9KS>GKEEZ4uA1k56 zVH&jhi^vp3^42{z4{~-+EpDpDX}rJz1V$U;y!EKf&LGG4H5CgMZ5XZI=FhT zqaldfN4;TU8M|TOg<&(H2GFBA<{SS;7Q%6Jv%NLmfsU$7-}8{|$dalrX2(cr$`h;b zG3pLOz1BORI($#(HBsAOnn#Ege(Er0`Z(P>OprN{($ObEEV(m2;<&G^uhu-F>)4t? zYCb4ZU(O}G9O`>~eiXqLPkk_3qS-Ct{s`zfT3QAiJ{gL0(NsE32NszqAN;&jeSePl=>v8~NWROad7G))LL6`z3ja}G{ClZUNWpyT-rJcLPmd!^HRc}p>C^%i zC^x_e)Dw{5vG1ATbs6no2jj8~8D-7|m1mfzNp5>XP4os+JJTLv;~wp_pL%w)Tq6#{ z_4_cRj2XgY#K;PUIep#2%fDpxFpfBodwrErEcD1yyLjW3Z2LGKZlvTXS5QRX77|8A z^h&zOR~l`k73aE#cwqqT>t%f|6v9A{L~2E0VhKS*axOgU;K~&2sYC9(5`h$y|MnZ@ z$OF2$<0A@95{huj%{=CAnnDa)=B+r@amm@xLaJQc0xJo1a|Tp*#XC~HW$zvGr!?KdVzrWmE z&686-zZ<5f8-RDQ{yBl@28`MUB9Gtk2AmIIBoRvxqt;&u@2A_0sE3<4@fO}x=>44) znXD`nBN;Q$Wl@7EIG;+DRIL|=6mT!xR6Z~oI`YBRtbFy_IuqrjvRM)8M953KdUbzX z-^2##Nf9{mSJiLz6y^d?!^>L%ZB+2n|3#|hmaDL!|cK1FpW6cb|Y9&K1 zeH9v`W@)HnY~1nHN0zC-S%V~7Ho{OTR&VG^&YE=(aT-yz9_EH&AV?#K`8B4{^z?+H z(CX^)+w+_lYj&0S`F_Va>Vk}>HHnf#1!vK9lGOE9cAcAp&m5dGqBwCZWoiGt@g&5^ zf?tMwfILx4Hwmni-|ZyJqLA>btuKgO0G`7GNO#L%eLNl{59}+9hs)ATGe$72baLd_ z7rWqSSbUGr6Y+3JB}$Ev54*D%)CA!k<#MJ|$cvn2HzY}UfO{~khq_X9xMD%~$Naey=1tKf0~GYkfIPN!Zbkg1CB}` zG$eC^w)*BzHQn9(b|OnPs{gq(QV4_3&(BM8vm!fzfo39@rPg+->#q#X>?0f8zEo=z zIe#Mk(Nde+aXz8KxNw|xw-pGKLi&+#x7jUpu}Yp$;UOAOW)+=$6{M7)-cNiix#iB! zEwEK9zY&h9)KveZu02_h9Iv;`Yobv$YU5P)G4ubO6Sd6OG zP?D6sxEQrPy5)4$N+@8BbV|y*5G!Sn}3%6I8NK{<$uBJmzp0P z)Z{8S6o%n~75qvugY4bXriw3($$tvbvY@V-;81COU0r^Mr71{gI0|)5&9uC{MAc*2 zM0Gu_{Usuy@-tCO?qva7mAXd)4mT@kZ!UaV67Dh22ZFh-h?A!SXMG%Z?^CCs*jm9x zc8BU^?g3{TJaHTC@)IG`enT00nwVUA`x?SshGC`8(i#B zC`wiyg13qf=O0^tm)f4@ySR_*=Z)^HwtF7o2}2Qim9DH1ZF?`=+eNf0)EUw;DLdQM z*q=2LwU(E!%rx5bDU*(!{Aj9Ds4xADh?^tw8{&^5^f$zJ5ZE+Qmfwl6m{|hcl&y+f z3SD5|T2)ZwKL$v`FPi02?rN#^h{K%SmW(Xgv{;3Q@(iq`E6FBnXe_Ul`So3q9@V~? z3e@)Zl&P40`f4+~d&ndF8nt$spDN!mNQUyiqphnZ@!mQBORPpkt+A2A+J8Dv<`4g2jwv*5hz5y<>vXIW_5z4kCk*U0)UU zC3Ej0Kkxr&_ymG$*Rf75V{86QzH<*@`&Qo8SgXjy(p?tVQL%2n1#x^I!8vHDlUh8kM<_ID?Yvn5~RJDSMbKFRppxkMgH#ZKNk z-pe!^QbP8l@EWFuu7CepS;l}$=f{YVG+a0<={(UYlI#_cO^PIn;pPuaQ2ixR=`x9) zr7&lZbcBv>dVJk8ko-AewfoZh!^D$IWo!vyTzs{?`N)z4rc&{cCFI}Xd{p*qHsIra z$8+dzIi+Yp`t4hl%Z1%6+ybvwy5rv}=Wk}kSxB&|uC8?9QFZ_Q9$&iU7^)ywXGKMK zkjTLiOQPK6_6m-A)6(hlrRi(A6(psKI=zT$H5=ViI>M!HS_-Bd*;LuFVA0ONT0-CI z%!GoFWXCSYFbNeE743|`3ckv)VjYH^;8K*46Z}o(O*%2zBO-0H z{a(ObM?JC9c&kpQBKGb?+aw~gDR@Q*nqnWBuL?@MRIeOEdm+t-lQZ_*yhmJTF72_& zwfWl*%NY*jvT{3nE31s}@TT_mN99_93Mzut*F4VdJn7$&>l!)`a62O2I@%K(z40T~ z)X~{QM&`{9bZj^N4VVGlDNIv}SIo}&@eyaO1wDv${Bzi;(uj)gh^p)cFuhn?bYVz5 z#>hLBQU09n#fho`VkTVUVde}p1A3>CbU1g0WO-6nRM5!teBLafE!>buZ+klMD6rji3`C0V_Htk zWU!JY`0InEuK3K4Z@=HI!M?z`FDlXh`~k4F$T_!DesGpmvVLjOkhQe(Y(p-T@%QSA z3PNrhmpqXtdDisw^dtg4fscy)R$2WACDkamOk6s&yUC64ww$4dR9B$_$x2h2Y6#GG zW>6C9F_j#l809JlAf^nQ-loPnIu-D-U#&f9cxf_y7nXBYmvc&nJCY^V_SSkAZ};8sB*TDv_P$vU ze!JD22jf&yJyu+5ddiPo@O>J#n?eOZFn);PrzH5Xzh61{%6t~i6&1V^6T7DKe z6|a`2ODpD*H@e>h1W zj6l1frj?S+#jDaKg5g$Z&VGw}>5~k^S!>Z&+T3ZHYM=P&yS4NUkAI>-FlBDbH;3l3 zG)w$eKc48$p}(@(rx!#rk%1!%-{K7md-Z)rV|j1H#(u(~{H8%SKo2HS!j+hD=pZZ% zh5wBLXM$Yh|9zMLe4cD+2x>c#TD}VuECnxBiKV*k<_pHu;n#0aGtXP|#;3 z1UE51pZqdILkPx^fOsTsh0T2DVHo_W*2=AU$!?lXgS`oaQ%_S)#z=fc;&7DL;|387 zVhA)yFYN7bp?SssszhIaB{|BV!PTH+rGkRf@*e$Q|=}#uStP7)?O+DGMIl0E3x+J}~ zynWg9W4O8Vf1;5N-~690o<<7H8fb(VaIpTt!2kC$FoJ{3HObYB$A;XdYPo1}pb`YI zPjt{TVfs|whh-Y;6)&ZQA_z#i)QRNS!2kWXVJYM>)z)73RT?KU9!6D}-@$pI7Uv&m zvuG;zwa~vzsShGwCUIUWWzzrme_n>wwowofM{v~7LvUPo^Ogc=ne0eiDF(qis5Q3^ zg3|i6=Q{t7t+NcvGVR(nB_WN3G}1^*OM{f8fTXl=BO%=l(v5&LNOwwigLId4w{*jI z;>A@-gyD_fjRulrUBQE?Xl>2)UUmNTYcF$V3t3 zd@Y3!WJTnr7=k#M;5g5J6iwzQ(FOfI)cW_il9oZ?$y&>4Fg-EqQQezrn4 zbRfv9$Ye2kB){4#u>HL&{`umC=IPmK#fzveNP`@tqWj5C&)X*W-cCzQy@fmyN!(af zdCw*q`YX?$C*K?B`AH%_FOBZUtdJBW8MbOi-Dt^vm!`!Vz$_OQzC+01G^?70goilE|Q$A9%0cD<~U0j(X43yzsoSEv6q^ zt>VPQZ5AcaGT-;&sx$i%)JMU_R#90w;l~)y9SmOU;EL43|NLxTaTK1=F=8fhM-{ln z#*^>@rPXT59B__|z;Z%yD0kchD$0sFP{YDunMM9W-yy6US9Wp3WO2n*ZM!JZ}ia++HCv8A(TYp=>$Rf@9dlnWv$ z2QX-@A>pT|dpm=D)>o--HTYWuR^d-2ZYIz6IgYEu#5%LG?!eIx3`vB9R8=j2E0ejV zW?}=vs4XtomC2o78^G%MtIGTLmm{GTTxQ*`e&R0Ugi_>Z!_?c;RcMBXN(#jEreb^H(gnSX}L#n`ak$ZZ5To^4jh6a|nz2JlXnH z+XqBZqYDdz>yO$9XbFs#Z-X_WobQF6JHK|e_UX~nSIDO=&nzs>GJ8ul%$zayIc4EI z#j_lK1vQ1~pn;!1%k-BoeS_lAE0?n!EsZ+jZW?;}g3L_b&oaPyz((n-uYcYzSgLdh zu)Vif$A7c9UjEKgVjiQFG#q*KxN>|A1Km2*x`kgrd-b7Fn`NVqL; zblk6`wBAHp1BIa3pchP{XymOBmrRZ=H;FrUkRM#wlJ_t!bA z8w6#ymyHoAA|VcyVTG?P^dvIM&(C}Nc=#pF5eByiKZOE^xgK7fuYL0IwUkZx~ zW)k-yto+rhe|J;M|jmKIj!f@bU0b{3CXDL7d(iW|w96)t6Kl=6gfj%YIACj)Ntp z^1Z+O)$0U5N>EYK`c*rq61{(pT~`t6w&F_&>+20uhl=0wteu=Z%>a?9_K&*qKZ3Ir zA1L_ck0)3lyJW$bRR4=ruE}Q>zGF z+I3~rP90(L20F*C&xwhjE@%wCtJh3E7PytmE9syl6E(%wmgEx zir!d5^7Xtu@_U-Zos0eTE?y4xr!}4HTHEtxCW6@#MKiRZ6LA+CI%KbJi8 z*5}QyLm{KWFBepu$rf{t^;CZ@`t?J~hitk$t2|x*YaS2XV*)3GIlM>7;md&HL*B@( zr>|?M&_QgnWBjxhi7M)uS4(3CipAO+Qj&v@j)--=s*s(hru;?Pci*N8D4SR>oCXr! zXK!R;P{4)Q(}ul^S&RKA-j&3Hdx{)dC8+|Zt_r!gk!{0&IeYhNh+_`SlKm@u@nV z_(y8?8Rga|Rp#%VEB`!PGcr#(`H`{$D$4>oWmQO;A9;33uH5pM0=hMQ6(tEJT8-Wu zVp}R5x=#cYo-V`w<{s!5(YAJ*c=a}XxE7*nl64DHbpsQp{tkVdse4Q?@4;NT#Na4~Q7l19Q1J0j*M(4m6e^HHMUI|PtGpT;L# z!0_{wu>KrW8T$Zslp~bBh@vd}ciVJ~mZx%pj;nX@GB!3RI};u>`Mt^|1kf7ZqM=Aq z{~JP_zJa1R|AGIbqAF9?X7t26L*+5D!y-#NpqaqJ%}(LF7JswN^Y&zGmFBT!ia`Yy zmjn`7Jcwuh>jtOugAza9lPs%&UG~$ju8My1vMO319B5u)o>159@kS>3dAbxIXE;pcq>tv2p^v|L8;h;|xra5F*+!ZHnA?jj`ts`aXLm zZ2#vUwpD-|_RZ&Yl6?WCe^;cj2yYLsRSkM=dK~%dCP?UW;t?Moi_lh&bLnxOA(cje z8<%VcFrGOL4SY_iI5;udC9u4q#{>oWRY&aqn-!x#Tk#zt9%Aqd=u3{cP&|HeA;e5l zm@OI{{wj?&jUu`!Y*0bL9$bh10)aLBXXeEV`ws;xsRj&YF85G`TTh;$a>)o@8zT$Q z$Tuw=Hh*fhgUG`;QT&sBgSU|;N|u%eZ}tpBrB!jFt+t` z^=bwk=CRF<1SDBv*%$p2)|GE6aIwH@5mI~sE@;JMw5NXm?4`d`d&$>u>=HM}u3zf- z6VN99=$;-=s!}hgYqdrujS5L6+R0tzoTAO{{0||aS!aB2GV-dZkif^B1~O9Px#7qc zf5PT}zvW>o%m~tbhYumS>wkW;11!QWlR9;!9{mx9QS8@*y!penxisP0>SM4-j|c0E z$F4rcbe`g0YyMvc<>T8OMTK^-uPR_!jQO^D?l@XxZnS??`Qw^^K&$dhja_y8tft~A z-@G+{XZ!)tyA-VL*mJ*)a{%VWO)Z&MYylxAU=RSZXLK2;Te3O+Y;^WrWe($AujZ%BOOscP{%XYj8s^~GX4 zn5UKpC2rHQ68>jLJ$}qB7Q`Bt=o%M$*iYkn8bl)s>~o998?onhgdb343kzv0T!!{A zHb<(FboW)eJNO)#=nR^uEPOg4{cd2z6O@P1TpX7l&#&5j4lM6(dWMnR7W)8>S3XyIA_tv`5DD84_PyoStfpGJy#6dzYl|tiloL&c( z>d4Q!s}6CGPeML{ma=GDeZ^7-o>VA~n%MZ!FK>;F;t}|13YEt&MEL3X-J+hs-1lYY zw(zG}_#39SPYH?p)mtz-R1Zch!Lz67hJVye#YDjzUbcBd_kS!uMCd-mwe`Ashf1@U zVtBHl>$5>i7w7f82u@ks`d8O$2OnpX0AUgb7uO^o;8tmv(Jed(N#f)2v-nXtUfb%$ z>7%u$rKO8Ld5Sihyqv0BZ1-HLF@)MERb!Ii)#vngxLEkA1fz=gB@h63pG7#1jppEw zV3EQ^EG?EI&?Ij=7Q`7G+==JsYmbQuxaQcQgwcS-#`@o^=+8EjBwsp6x^!@%5)BPc z;AQ4wuBXJobK`8G;mS~o<((3O7VGRxM!^n|zVj1%QjD@fD}wP-rQ)5!fQCAw->e`$ zL0t{C*32v)Hn%#yMr>ZZyI=a$eQLbvH{Z;JR^z9lxTk_ImaK7fMK>SGMuy#LU)1n`K7Bu|m^WEJ5KYv|xO%|CW3%pmjV)`D14E zHDb-lv0^hOS~VFBp*);uvS)kKehmf8ESd?Jg#AB$h!zY>e@;OPA#RxcV*3=ij4!?M z=T{*b=1nD9^??uUBBLrww|wQ)$=DcvZ`)qG2WproSE4?h?$JsMxnegALd8Z2k};+6 z4fFQY+Z=l*6kwL)^o=l&boYZ%AwAi{7a@TZTRiNwwYBl7DIl4vs;tDME)#_^ek6s{ zm*C*xfwke(Xb!{+d(*%&Ra#nlFJDe>2p=C`tKR+znV6U_IH3Sk52&O-TAj$H`lG6f z8qyz>TW7o7w%V;K#g}6&ZDUg*B8XEFA0Izo@6dgwuB9cb^~O4-HO!Kjn&ucA=g$dD z=jB=Q9kX6A>rJi|a(Q0X&zKp(&l8EaflR$_o1KQmJ|woFK{6EdI`Dog#8xaj#w zhTB_3lH2kj94v2bG-}v9OU^IRI#^MhoIbrV%bDEE^ch6?QmY;Z5*ZANAJaaS(2lyX zK$hMrrGBCN;`zWCEwbE2HNLT7?nt|3Pt(_LK%Ta0JW7fatB`et)$I{1pOTN5@J)!R-RnO8J-^g+$160sW2u zAO=7CT%cXzaJIt%a_O7L5kJ85fxV^mkncgjhYWyDRR}@l&F~T{TxNw4)Y5B^(uWl1 z=E9K$<1u%^V#yA0BMWyPR8ASY1`c}z)DkYr&W!a@^JODwv#tKYe;|4daaRExyg zV;t-eMz}M${JqRCDj_AsWk0`?P?(noUDgErO4j?DHLCVb1n(~?w3wCWrf^Dk0Ea#A zVQZ{FmH7kw(!oe2OdUvt4FSTWia*%zlLv87Xx3wW@i7>VM!c+=sBy8i@x^@>Fsexq zcVn_km{`TWIko=L_^{_&ka^*WilZC#n0kJGmF+=c<2j;xnX;+{+vl2+?7{ijjh{i! z1ql*uYeq5Ri9XkeVD;onnpBq*FW|*_w0qe3^Ka#Je9v05upW7{X!R!03lyXg52Lrq z5ZT#l6B4v)>(Ew$!)X07M<-qqc1}2 zetIwy^t@4T5Zxgdy=zC~-5VA~e-ig3H{% zi##l9MMVNYi|4OKRz^Epqr(NRb4`nDoo9d)5t}_J8IMm4jM|<8ftQNP=T;~6Qr%YIX_vJ*DRh5Ok6d=+?d6@7&b4@3 zvdw&0UVa8iaf)p#qC(L_qj`$nqmLf=RX*i60Br@yu2tBbN5u2r+=D+NZcfiEHM^_O zgp~PrMg?Y55;R>2>3_L7Q>N`u7LM+o#l+Nq*e6U>Js+BD-+voB0~rx|vQyj8>pEU) zw;Kzfwst$2t->Wb&4afSjpz14nySwdX>o%7J>@bD_- z^9;UJnGh%D@O4h`TNKQe0bL;7=G%y~0<1b+mGPlm)<6pYPd&N-Y69;@U{dx%C6uL5 zu=XjqjqkQH&pWeIQolW_ZI6DHW z9`;kZXks4goRc9zi;~9kAZnuzT{nl#r1vcX9@pDZpaMoXW|3hU%yM9ioHrP2{+SDXmWPl`kCap-ee)OJL@E(qTMO!Na zRVqrypHt(_KYiSzH!Z5QI4AUM*XPze4x6EnTe*q#R7hR%FPu1&*|j3|`QL`AzasMl z(CZfJpX}Kr&YVL0mcgo?sdrce9NQzEDo0G4`8p2GD&-!}y@Dc}yT-|h>T2F@*Uw)= z+S$jEj{29geW|`jw+P?t5V{&BPIKUMB3TaiBn%c~$R^32(7zcoXV2m!GD(PFsl@oL zhngxlRdaK8uyvBAsWCTYZu`V9YhQr*vuLA@1h%1uL4m&2PM~Mlll<*N8!&+|rR7kx z#-}80*|IsIs>5UW^NG#IFLdhHUe%&krP6QTk~nV(7woZ3zNlUDrUT%O$P&Z^A(HWv zhr2~uX_c;@WaW^dro%`5^O6@r)0L5LnRR&#`K8p?!9|BfRy6q7Z{Pf-vM+A{W&$t{ zE6U49g*Tt7#x=PvIBfzp*#{%BckJpiLCq^cc|fx13WPkr`K>KEe}v`=NdY>(D-5q^4R~l-;Sy z{G*pLFpG1tL`M^}!xO*h@^5u@`CVVXwj8E55-BTb8acapsj6L!KBc&39pvN+`EB$CNlbqchzZ??|>7acN5FJt>`QWs9ux7ETy?MmsoGE zr^9Xs1YiAda&wF)tbU;MZfS6C`*Sd0Ic@aR8N?{qi$j-39dLPJ|1ZYH?HGAk#ETpRy_2&8Av%di>en^ z&R)j|O#>3hF@QFnO)3U7kq-}(F?EAM6~N=>Be)VTi)b&S{;6y2C_DkyNO&fm~CSs?jv=CG;fIC=?QPmbW>NqOPxb5&f|78=7 zr{lVun-PZPD;3ifRfsOYjTbzj5tLt9xqsYOa(RDqxL#CVUjDi1in`FBaEy>9$>2pW zKHKLhO)+7CB{ft1whaJANpaf1FKfasi`)q1lJof8ictD9$S2gh8tj6tC~2lJ;+y`A zPEJ1nD(^nVE9(lH>Ul;c4<;t?bEY8&_F%`kZNxhP)iVFLOjDQw-A62SnL#RVk6m2x zH-yUI|4g^TJh!<=l%b2$C^EX}W8_(evIpZ6yfl^2;j8`-cmf}*0v#)3Jjh`_pANk9=(k@sLszV@*=NCBVM(L>j5$5Aa)(2e)1==sn>1 zvda2wz|VjYz6xvWm5UV`=MdohLLqL7d$B#XnU|9yX@TYpgt!ei2k|2%r6mrZ>>_p* zO0FIUxc`-}lks4d4wNMjB#hRhU}RJ0nT5P?Ff8oajd@4$sMC^s2S7n`BhT;-OTj&k z(I7$UKa;TIx14RaUpb|V{L1bf43%zI+2`Xh-UD4a_=YjtKE`S$?c2x(99mA9^IwMF zlEKHD>X5Sn+?3F>>edtk0S;;v-ms9o_PcXJ zVCr(ms{aM263p+kCb_vf;aDS7a8rO8qY!x;A{5PqiSaz8k)fVl&Cxxwsqa)We~rfd z{i(c^C4@FMt3%Mbp?|Klt|uV9`rGdcEjza#H5R*8T{g zcNP$f&jF;Q!+f^#qw>T3MHx~9_7N2P*8pI=^%B2GV?XubmWF%GQBG8)qZD8Wz>dg> zQ!0tzP3#O}1;j6il~rWL=ZS^V^J`hI`G{CS+M`^9wVvd0S^GDmV9!dFlGFTWiq=8u zW$eEAa~iZ@Wall7axd~GxnfvUvrQCA@k&D=qmL7CmydlNS4+8N4VJG*U<^BWo;)*Z zn9LJI>Qyy0YKnsPCe!e!YXp-yRT`b(UEHn&GxuuhIiK~%JLo6mGz3;8ENQmhP@q8O zXV2kZlh`b;Hb;~_XW5toHpod$Zbkb%y?maE4Obh4R(AnttEnWfZ4r%aEj!8z2zW`+ zNCo)p@5P4H@Ya8f$vvamjuZgnk*p??-L+^2aAdWA28;i$0h0A%My#2v_b#gam{Q!2 zVjK-&BhZxf|1(GC0t!s#=+$BRzJt!7cZIYjB$ftRfb76>?j6fj| zAsZmbe|D@AN9oeX$VHP>_y!7*!i&wGM}K_gFdsQ!dcN6uM&_`$#lN7z63mjEs!HOXq&C z!jQS~^woVV z2L_9(Keok~nwWS1cAUJR)-j;qHy(8n0(5dOAOSWT_Td)m(s{8k)fop-J?oAUX4W70 zy7mJ9=A5x{JyvrqF)+pz(*sx@uFL8dIf2pEWWZcj8}}|c(O;fyfd~t?g5&^_7GW*+ zl{y(OIF5JR?m044FU_AcX#0zhkrB$G?^~L*T*fa#JLI~7Dn@)IRL=@}rgO=!9n&v# zE8{bn1EDsIMrI;AUYd_%-M(_1U5blEZSB}ZW==B8_ykNN61WcIJA?6jPCKexo}#B& zfu^h7FRO0ZILq{jyvou}$}K?5d$Y`jrfwS+$L`#L>91L2I+A{2)L+vcH33H0c-(It zrKuI*Yabr78IT>ggZbw3bNkAxG+`fb$2_#~X=srwcRcF`v=Ex#h3ELBy;^yEPZkrvxZR^)fX`1>gY&X$sujHtwj*NT81Bq}UrFY z0dIfY-1|8XlPv45aze3aP6VakpB0nI1T)=}X|Z^(Ha@u8Yi4l){$y-Sk-3S9%slS9 zwJ$=TAAy@OZANI~OraM=2K5%${xktZ-oW5sE63#U zu!44z`&?B%B~o}>0NrC(2zz1N-W}Y91}5LLz`Q|GDwhSx`HZRQSn>vnh$RTU!FqWB zF}=t6420OGpEb6MiwLlbjM^qACe)uYIIn{iw;)Kl3a?w)*S5zjhBJN6K76-}gP3pI zBYPi%$wQ}yJM;$ak`&FJ2Q(rzf9FNFZa_D$<=rEzuVM3URk2v@PQ~_^#yszd8YF@)A^`WPU|7BUBht4$P!jm2Wlu-VVH5Zf#&!W868H63)DH#Phk08`+0@Le zqu2RjUJSfNe_FgTCnqQ1@dP4&$2}(6pN6BC3tDy)F`KER&4%W`jn}}U>1g*nK9&V# zn3^OOsDod%&nA7L62rx4Kp}(jS*->FeY$RYKRA|A?MCQwo)=++`VOW#ZG5VXN%^?a z!IqeiaQZf$Z<3EYtzDHQT(N~&gzq~JMkFEqYk=Bz_Ii+J&u$b=ZdD?NRs z)yMxhS?kp+K9GX@S)T1Q7vx6^#=b^l7*`Y_i^pscVsIv(PI=bpsVsov`yJIWWDyUn7A`OvuU*F&A*?)bs=$O4x-GJ)rwUlpwPeTGXzqM8q6ZOMi2RC;1KZ_}u$Hl&FfF&-Vh8*M+jBcX+e> z5HbPjI%Xmj?b7KBl@172K>D@7j>Yh<99YbQVJcg*o7lue)BGWv(x_KGI+1-FVtigb ztXA6h#~Xg!5|{RXoE$05{*3$Z@JX1}YL}LEZ)azxSb~YONPj?g<~v+DR4U}H1An^xuAG#ox3y;yv6X-$_S!x$B&}xb`41dc1OG7K>W=Qbi z$EnuWzz>QDcqf2qdJN19?f}&be&`3g=9{VSls%b$5kSB;(xHHbM^Sm8ZnY=^l_t>I zt^C2BV|9}E>5CA8glA~Zn$$;*5B)wG2ZbH28br41W`w-u)%#V1Ei({Ie^3>kn4BEu zGsgV-LuQO%_>+?cu`%`n^dev#uMxX})eoLh==*Y<`{wJN-J8QydL2x5bZ%V_>Fakd zQ*8!kp3&TQ=YNf%_h`=x!KR!aJM5H~}0-JN=Gvwy@@$c8BQLcaea=y06q)mNSM*hWeUK zu2=Ywq(B-3PIn@M>;~$K`~Vu93L#jVP(DgwSdmWVCkLi`D1+kEH|8rAbP#AjNkiEOE!@oe{yLG9E^ zObc9D5-()PMfo3~+*(^Eku5eyOwr@x65 z*k?CAVmrsLS9y8c^N}^?dH)RnzS3p%+m*s`k}&=*tU%)Pi)UHJ?u{vTngC(>c;%E_ z#ZO@$aZ&&_a){zYIxPSrxx^&7y6dQOp*u@c-_3vZL_8)9fIa|RxVAXx3i=MRp1lGL z0wy41}z_Xd#aaDopD5K_iZJDNz zV&M}j5Y*z+<_3pxi+pSB{BraH_ksj4HE`{tl&8q)THkYh8eIc)_P0B1m7s)%5xkfN zEZ5_@Lu2e2p0r1o750qD4^^84SVX-4N;%aHi_=fq?UQc2vbQv)$37g%_;(=Txo`@Z z?22nbi;-gipb4)Z!esc|I=DWo6qwz_*#lDYW}-l@!fgut4+29YOu{)HZ#05sh0yd(Fv_inK9(C1g5X-e+DO>|dWE-xzireRf53EkdPtGN|etSkuK-(~2nB7u0 zWzy&JUoMHp-O^`-*SAU0%*@Ekchzc0kYE_yG(R*Ne0tefCoYN0PbMxdZBGgV#A0@zv`e{?FC_w0t6B5uZ1&8h%KK^;F0}^cn{dV&=$#gX5q!V}jQ* z@xx5T;S_@+aya)&&t>5cO^sOyD9-ikN6iy$#*}g0<}m^)%Knuhn*%Q5 zz-bBX`S7CJi^{iUnCsHaVmCtRK05w+c3pHDe~uIVAR#xmA}0U{9h(f`krhn097z|OmIoEvrgR0i&@=hcf`uTQMU&Xqv_x!1I?@Xy?w5KD-e$dT zx zy;(aV+PwY5QXr+d;EmZQ^P&+VTq(h)m4F0$0icO0h#VCLT_J(6(2VGa-e$(ej;ojF8h5|`wC?aQwBP{M^KRn7qPDTV z(Y;s8+F|?KmoHce-a+0^u@VAuJx9yFLr12aQj6tzkDkgSzkQFb;GruloTVm2hP0hI z)u&Z_(KvtSVYJYn$mM1lgoAtcCptYh#`7#)-A#|r+c(oY^>&xUQt8M=Vx%` z^Q~o=9&=TUR8(1jlA!Nk$t}D%m*^BEYUaDHI-6R>l5$*dOiw zDEyFt>Ad>Vje*k|V285WalH<^eA@U(5Y#fKGqRW~1n+t!zt7f^+Q)^*Q0;kh@3OKV z3lNihFFieJ@f?d%92|%@1%Yg{3u)j!C=mJ~_B?aw@dIXt&x`Zb392G|E3uK?sor z&a&c%$;2_}Z=POU=w0!;dMwV?xpW2@vhU?ke8He)fD=zP6NB+Hw}pQ5DkBl9@&FV1 zgEPpb8V&opQc^zOn&6sx+1vgo(Qfp1_g%$o5#h?fANFlR=$5=kAhPsyfajEU(*`{o zd3Wn-J(U1cO>PTqn*okba>SkOV?Tc~xMO~rafYAaXB9|+otdoa?wh4{+2_DBCAM{N z2tJ690SHyHO-LnSf(!6NK}}!}1_QW&*r`Ws!ffT|=Z9(pIw5H#u-m?PuXx-NsVV^D z0jy;_NdoH%`fClVIVQw0zr*H#xsqC?N}1oCEfAYPgA5I&ZYzfKW8rr(;W=;&X*tg%>rdik#NXAC_6YVwi# zZ)Zqg{=4YsiO~aeXuqA3#)dQcUaXxg3)f=8I0b-P$EilC%keFl6Qnq-?qSBEFU?$k6nTleceuazckfnw)pFpnTm$VWTCog-cW!wsBSnu zPyVI=s&)M@q33_`^+^--8%^X-T-mR#yZNUhgIV;Siud#?mt?F*MZ^Dlwf?nr;W40K6SKa*x%Uy> zv$;Q{_!)tLf%zQSz^+V?auXd!Exhu(p0$C2fvC^v==k^-x>%)bnWB=CzEsM{UQj*+ zu902|2?WjRcek790(Ylu?u`uljVDF^M90hDw157#e#S?2_^gE$Ho?VEG>*q6`=XRA zt&woEYZOm_EUh9MXZqvqC?^q`OGvnd{wPgaeLdUlB_iA@H|wGS*U2IZealD6-Xm8c zBRHX_|2Z-(bWg#8#$%kGa`I4`iGl>p22Sfl))%rb5oMCNF&TDjo!a@{ngQ9S67c1LIJmDl5?xui>i2~$g&q0-YA0Ho} z8BJh=RCQ}DA=}r-;p>pYHcXW@-cF-l1titbY5OH+mJ_jblhA*iY z8Z_U(kIyr%4fN4UZS;c*C|Otdec7=`^;P6!^OeOy1COr?UmaPE158~p6Ku2G*5Tnv z9AW&-3l^4P3)OWk!NG9RL!n5jznbrVNm2}8y;$W%pPlh>qABlm);yNkt?GA`4!Nm$ zJ+`HFZpU*3d~4fRPY4t~5Y?{7lBRgvSF@E*nuDsr4zEEpA27ZupDNc8y1qURJ~~bG zu&}T=Z`s>FD~t2F;7}vNlB0}5t5jZp;`L|ui!yV6Mg6aT9^I=)t&#Y`5j!7`;9h1TU1;~^i8ZqHj#KLO2}{}Ud@mdFV9eb9eud4+_@S^^u`ky=Og z84f$QvW1IyDI!`8WKhIh7V*zT3aMb~FfL|k*Z4s0bTag?vFz{RJqSZ|2kfPGfzHAO zl;vxvIa-vKDK3(;cd=jSsqm$W2vem1MC=Vd!)tLNJ$&U&0Z$OwQUZ4Mq^pxs!VWtX3xh(dW4 zEF>mIbr0ehEBu}9Z2|%U>#)>)ilFWs(KaUuYz0Ql;U_;T#2F`mMco0w^k=CL`4M}b zEP*$u!I1S&0U>i^~Dp!6bsz&?nZ51Tc!!c^7_@y2%BEu(2?0%W6kYDGLrxd*xf>5rSynNkl!|}k?`?^~3g%^#L7CgDO$^H66>u)cn}M8CVFj{t4Ry-=Xxqm#e4M9n3WMrW z@MGjcA|x>E{KYO?q(<(k#R3`BCci;pqs@_tpPc6IT!LYW8)=1a)QX(pZJxa%IAsVB zmg;PKMBKH_>hKKUj!%Hb#q~r<%C`v>gR)3);{e-g4@*#~_*nU!;R)yfyD`nXSe_e8_tg_qZ@Mt_IP#>Y?MU^%c+gN^_Ae9$fE=&Aq`YXvvg9wMRDd_S{$uzh3c(XU%?Y z4PEYJQ0Yzqsk*^t{DzpxVZ-(w)A{i33}rjgi4LS$0o-ZIOrO*fw)eKSLR)s&bHX?FP}`Ku&dz3p3pj(iA`oK5QB}Cz zoP(UhZF@8;&98=+t?TqvaoMvtD&ipf%e9IW#4Xna-i-J;x*S(JiaoYLG+-5Yz2tL7 zD8wT@2RJKSVU0T1pe_e6@g*}rRrd78Ojoz1e#+zF2AA{jT)n)1r@YTSumX_U0TYJd zjNP}?{p5l}YQ{pK$@|7x|NEj$k3cu#uvMHvOCHakSD`3GM!;x8B4VII01`$hX{NvX z6C{|WiT5ZD5j-QtJQS|U8LWh#dxOF722fZD9Mr6$^dAG$C3NpCfN64Hk5N_oXwLyh zH2}96Owl(m(ZvJFAK(X<(k96XjB#0A-StXvbE0*6%{%R;+dIR)*k$P=fOx{eE%2pz z3am*pKrJL@8GAbf-na?$WcES!a8`NnG8icZF@(>khMTL^M0gRXeXy8geNAD-Nm%ry z-h^uzo0+w75s`%2=xzu;bb%@mnRMWC)FHpMs_HifgYteGxGnDf6XG}9f0Ae@xJ$Kp zG*nesUWvcn-lIrM8>6&-*b-~zMZi{{A)=Yd323-jj>&S(@J9p3+bggY+<-s3$7u%X zQQq)U^zxwgNHJSyKLhb-M!u_Pi<`SklcCJj8kE;p`TK_XPi?hl;KMz7iKO1HFVe<=jP|I!stD=C>u>)?hVR64&PG_7T=_VVjpik$IV z0X1OoLeW$;w&``Qe%@UyhFj4SE&GN_7{HAE7Dx5ADQb-i$_N9IT}Bh~F%xwEZRa(i z``C4#jy9+T*#Ps90zg7M?&%58xo|DDFgNFYxV11}Y=Z6OI~{I+04(}jqqD1_RNFSB z?wd1j{tXV7)BFXzwurHL*ZLN7o;N~M8y=%dq8##}4%TQW* zG?Fhd4tSG9IMaFW{w!J77sDeXdXUA%MJ{$qep+A7Kfn8U;7B9d+E)cfioL}{wy*~= zf2E9HsBu?~K|^}%dr(fM0@fPeXGRol0Ph{(*W4l{zg!)2|57sxrkA391G>~q9gVT` zNr{ONJ#)uH^V8E)ZRc%6pih|y!l7?;iDyjq(AKLODuj1<2*%ZF!L#a8KLnkm`NsMQ z3;IVHi<)4w_5vhjFpia22mLx!$MT$z)6sBkq>_&^&21fgwi)u_JMr^-G09fqcdKlA zuhF_U-E*`bXFv5`03lem8iUaw{zZBs=LlZAk*?(5!qx~WxY=2a`udod=cvku=lhy| z{{9_Zx@5CLxL5ir4WLfkbQEe4pdI0&Do}eeMPt^G^JzB<{m|*Gd@bOBM-50CxMNqL zMq0fosNtw9xYNx7_7n_DGgopcSkB`M-}%=pVX2ksdR-)0nbfQV(Svfahl@j5MPYt^ zdBM*r5}jSQUCm&WF+pJ|Aa=w7JMDoSla%zUF}pKpqNUhP@-&e!*0?uLQF)e4vs3r$ zyX~79ysA4wy=mi#zrL;mgu^2BimsV{i+x`_Z3SR@-HDv)&3kxTW*q2m9wY{FTImQ} z8dfdRST~Cm*{2aWLORsGtjj9J?LS(mv@(_IUapxx8@B@QnU)OhcY7;1|N4_=KB(u- zBdu~;pKP3|rDb@&7RmcFA0vc_O$!4YdH|wT!|m>4ieRzqrTdRYH}okX7KKLISE$dX zci#hwC#ZG?YX^Qmm=yFVfSe)A;FInVnP|;iv`V9C8r~?UsT^1&L@5qs#g@(?fiVSAgP}M={TCCjQeLh``axE!B-nd#yXx* z#Y1if9lQrUl+gT;!6ZN#oN|)*t-)6_DkRhles$TX$W64tIS49}ZcM9i>;ek@qz68p zkNwQ#ubH#nqMl*x-gAc0EFcsOk%wbxHrt<>8? z?3!*w&tRZYwvp~DE-ychiqhwEhe8%L;G@rep&b*~uf2@S)-(O6Ibj>*7Cn87Vz=+O zaoHN{AlfRVC6?Ae`8*gw!zD_Tn9%-qrzU&sp4R%&TX&DE`Dy?_+c4g+ZHi;Wo(!fl zREb!BClivk#QvG`Q!C{w^_808CHPb3*{>2`8NSUHxe9_^81TAr0q#ZGPV4V=p!Hk< z3fXNLna1T}XJ^Ox;SPsnU+}7zf=>gtIG$N(c)%c>pSptnE0ImV$l(?+CYjd_;W-1I zpj@D+Q8Ni{YwqWn3cmhmH*IUF53Y>9Dm_%Gm}#plsTXMLfBHS1(9r!3pH1f)T^x}8 z?w%VEn&e47igzFLhmSbWe0FIW21CVKyk+G6Jp!vyl;D9d?EX08QsBGZ$_U*%(d`b) z35UKRLPE%vMN|>*r2>$`Uy!F@pV zGd(xA+4;&a5yTzy6lBKf+1VRg8Gg7vb^rD;Y(o`xXJ$&3>@pN5OsKgi!tI_ys9W!^RQc~wqQAd9*Mi$a{P1oF+@qp{ z7%kWamw{QSolG4!b6{Pl6;?r?JH0h8m5jR~K!Hy%@#cN~74{VZ|5*}O&`X^1Cl%#Q z_h%)Gt|tQw*yIK;*%O2!H-v+KZjMMrQpeGMoqyH3F~um4g>n zJwJ6S;d&eeQ#Ebm0=(;EfdgoAvnA5cL5{!;ZX)OYrv;)PlRWSHAz^hu7TiGT+3EdA zzGaTxp7!L{{INB+K;e2(zt~BneQPpNADp}nwi2&he)!cTxS3LTun|uWUAuR`dMu-@ zs_NXiKp;6h%1;w)hWNXuR6$3SDdS0+QVY+67Kk<;+}_~-eF*)l&H%C=1IGcR0B5F? zz}`}Sl)FuVi(1gY$}B63lnC%qci)))UiIGSJbosC==1=uc?4K}TI=*@HUWvol}T*C zP%eb2MKQWIBP(huDgqr5QAc!5^mVi-30aHkoYg>9!>=)l*$=BNEu{*Lc;7zE7HU=> zfK=mRj{Lb$q4(N|X=hN(?xfq&$a7bI&hReneoOa<$?CxieWXOtihM%j5Z*lRSyQ9|Ye)>XM9zztj$as3T7z{2_rS7hiY+->fK&h9XXE7S zIX|pDZZp#_paaa5fSVK8W*H85q_?Eg5X7$qwl=AqVLm>eb7M_zs&5S}=XM+Nzki1w zd0rW6KhtIM!iEj&DJB^IMYbw2CaXJMVkNf%vmJtB)iJF@iIk-UjK$7VWL}i1V4d*2ucZA<6UhR=(tm8&64f@e!>Jhb$eFgv0uCCelz58e+SHffW(Frl#afF z9`-C~Y})~M^w&3vfAzfn97BH=l0psQ^t-zBl)fZ4KR?V4)elxrRQg#Qnq5nVzR5&Q z;+b#Dg-N2*{a7faw5`1*%lw{v7=Wv$`%G4M_3b1(vj~Q1?Sf*jsnl~j5ay7aX+=07 zO_zO&4b*Rn%UY-}FD^bf?z1a+;&d@kwhq$4Cj0%@&7s<^N1bY;t-*MbLJS4_omiZ4 zzmd^qZ*1-SOVd)A8-Bwez?MDzxbAYuh{G}S_2D!}FoWmth&B8MBn7jO)W*j8=L}^z zObx@`b@-MZ+f@6TB7VjmH*<>`8uo7=K96@EcwO$_Ve0Jp9E&uD7@+1tYO)nUEn{*?KU^_V75!yJfX)#6JQCl`ARr4a;c z4rl~x3GwLs5tScV(EomVAS57%v9~SC-F!vJ{=J72yHy|+=(b{s(PJj%u8vb%gu7R~ z_KD?)Y(He@h<2{)C#f-HS7lfY_d_|4M6LakEZ6o-;z5TZ>ASLCVn?-$^n!2Eohfxj zce$}(Xl#raEPSK&67Nmdqwv3Gf4VrcpuhqCRUpdj$r90rqk7BrV*FO@|;<-a0ez&Pk zUEPKD9m2b6ufbr2pdQ|saCq&|CA%BQPwYxrRhM&J&ecgJl6gdgO)Xh2P95(i5%~Gn zqm`Z(H`@};?!R(XOL1I5;+vcpgmQL$4h}(8waKnlzx+Rm?!QlSuQrjEuwI-qnXZC( zx2B!^g1u>opHIK=L#^%TJsQ#JZ^<+Z$yecn8no%vPA5j}L8x!fcNhYiC^sJqk((zA z_{qQQ56@X4Ctp+fR_YfUX)E8*$R#;~)+VD`sgB7(#!4l)tii=LVlk%HzU4>gzT}$+ zdV>H>sK*j8GX6WFe?XzYV-&%yI`~6AkG9FWGr4bbqA6=oSe%PwkNyjt!gW1Zy zdVYqoxZ~CE2biHxr%gfhMgs0f!8(oywTrs%@pa7a79Se7Zhbr0hWJ1=pHqjm%o%>e z>M?_EiY2`7kq|frm*~#3)XSQZD&1Sq4RNruyE-F~K2J^-s0*K22MN))M%UPrr{u5E z2I^sTRJ1(q?6h$UT9I&REbqy%ZD>dTd!1pxJoy3zu|wqvb{>3q1+5GPXNFPSaQqY8 z7OJPRDE79EZikXn)bH?L7Ja%tPJK&5s@p4U@F^@=3#tJz0u1oub?%SL3X1DSnZv`k zhr$KwI(Vu0(tuKGv)eh)Z`!3Kq3ztJ`Ji2ognxEl^18{EOV&qQ zFPtgWgQazcB(mzv&CF~UgfD_%*!`>k0=y8F`$Id&c1i_co1~v{9WHKNns5;tfH$zE z<-C2-_;3j?PshC)!qzYRPlog_h5_=mDN5_x1`5caD;45i#0;b;@6@IFbEIWvomt|ME zn{ScU;Jq#ub;=+^B){nv66-Hlg25F&ByfNE73eXePV~#;-T-?xe;_B}d2Cc&TwWPD zfnNRr7;}k3-li`T3vOyOo3VCXGDe;HhXwF`bMMLRHwGt^|3}tW$7R(v-O}A44bt7+ z-QC^Y(kWfiB{$vOjevk4-QCjN5)y)VHayS!o%6lte{uizy{~I#&6+i9PzPMOFzrRX zgR%Jk+}6D=AOIE*)B$pdE>6z9MFC$r-x~_T@wK2tDl{s>LS9C-or( z-ak{43r*hl;*<;NuwAwa@^zAwnb(D1Pz8i(Qg`<>8%izA!Mqn^Dh5+^Hd;EYAO-=hYUq;V2JQ}_6YwGU{v}ge$A(weY6gGk6^L;1wtq9 zcaE1qH;T*oDlW^zW#C`GQCeesNFtYW2%<>BLg4;JjuAsMEP}840$`8$d?fb&9X|ld z=3#m$mhvy-k*AWuBQSR>^%qS7_qt|zhTJxE8h z5uv?8p}7aN1(FJNgJGx&`g6;&JQ|1^heCV@R1NV3)&kbg6b(T`Iwlga@o7D_Poy$)@wp&{TUxNg&16XwF zmq_;7gvmL;d@ing{iO&6N94vrarj8=@1Ka++~iJpO~f7eBEaN_Q@?|NKE1I`CjTFn z=uJ2t{46R`>^@N4W`vb~mgAqRM!KyYBB%Pa?@vdWGYqiFJ z2n4am2FZ{D*1q@cbTZTd+jC9>@0#+SkaH=YTI`qBfuN&{aKXwDKl1+XgHytwF5vxr z{AbdOX|>xwvQCSI`p-dD;L#YR-=K~nbXd0+grHx>p7>sFE-`~a3Tnn42`nE9-Z0Q# zl?6XJ`9Hzq{xO+(`{U4m}Oh_dTDO{u9^)}`VK4X1OzV!Dsab)OM>Fe=KO zdo9+76M#uqyT^nU@Ov(A!?_RHB;%24QS1N@{}v=^JP1OU$)E%<@gzn)^m}Z2pkh2k zJt!scmRP$Cxc~a2Idw(LnXmgG^Hfhy?_>Btf?U8uU)ITqK`v3`7GO(H@FL|>dIFF+ z-8M7fU0q$>eCdL_c`vuQsy=9Br+|tXz4!Tol0EQ#eN?X-v`iJS*9)vTaUH1T>pQ}M1}A)SFXi@rZQiSdA^E9 z-ZiGb^C8v0U+EEQGU9iewzBw{kI&HETVV}XZz@UUjFU^Zl&GQuXebSJp6JU~Uar%= z88q%SIB-Yp``=jGhG6lyI4-U>nd+R(}C%Ti|)`k3&G2VES8r&V-m-3-#Ww$6Iu80j79usr&91 z5&ttI@eaPH1MXH8mbd)~mkuCkRSvjLk!SwX&3q3O zcs78b3plWKRX8AFApl%mcAVA%!&qPOt^2|F|Ge&W2et`c-(&-07*l2_m&n`(!amn~ zvA0W{!`J(lo8G&M8DuoT@e#gUZXgn7%Vah0u%F9dd-zqCH}HG~R09gogyt^wRPaN~@E-g7?;afYdqe`IrTLAzzVRJ9Ap66DN-v}J+ZR5 z7z-yqH}|5pR>{bccA6-HlA(AWt*5dD@O0`vD!hEaud;u+!yw-ATCvo|;(HqZeLv3r zjqhg<)H=>Hu5e$EA4 zqd%UPPF>p(1aCaL2X37wkP(faR=I224^H|}o|bufO@0tZ5)l!3t^Y~^MydvP;|CM3 zf%-Lgv)b>LjQuTB5u(Ath9G`Vv5v4~7Z5^$!G}z}5&vb6>8CZ||oVkuTVw&+pp| z5|mEdlsI+>z~o3r$K`qt=qxVX-FNvq4SFZ3I;3&Xgj8MGn z1Vh0cC0P&#mKpxaDVll-S#D;&%itZ{0e?1)nIE^nfB6k$a3D!|=-g{k<%wVI%zAtH z=i_b;NCBVwQbwaiBYPyx)PG^;xW5dBFILw_C~K<4WF*u-@qt3h5>$@9f>{*}3+7 z_hrieDyFRX+e6SNaxG9Bu(ogr2Ke$GKr0VWj!2#rRmG+N>cfrc>FF&ycu0Z2Cw`kx zFB9B2<(>|kc?cNWV8fy=HtKZZCP0lZ?$Wa=UN_HmUjvYZKU=)3 z&%VCjVh?Ci?mpjqYXLyLxMpdz&Cb8=t{ONnnedX5{emL0jp)Q_v-U6}NxF9o!i_tP>7u-fupcp3d zyyj1-bE1^#ubFq(Y>-{W#TIvGT|7_YY=iZM6}EPU>lDmHVD=(sO*r|TYo9?<0FpJ< zDE>&fwVuQPAM8NVqiZGZTpKx!Rr!NiK-N4D^Sa-c zBG5??ptAyIz;j{nZ$@=EF0)TexbZ%XPuSa219IXRoCyf@X)6U8^I#Eu{WV~W%5z3?6FOx; zSs&YzSVKnuyg=%H#g};Y)-hz$2-w2DACSgV4g@5C!2U_V$9xVB^p*p+i&K;aL(hOZ zrnAkvS+p7G7qK2-`DMhPvsyWgzW{y3fPMNhpxK&wy7XaKBPKt~B^#*hTL6^7{l($x zz9eLmu)Z7Npw-y9*9*jCc(An8OFXpA9QgPE1oPXJyyxb!vZ3DO%5k{P+u8TZJljn= zL~xrOaiezY2cH{j@bZR81Mq@)w`!MxL5of4tCh0a<0NpPF#wQBn4y*?c}+QwohpWw z^~>$462--VJ6P5Pl@oXnBZV@?>l{l5rHWZmvcVuLfVuZFR4!`uH1qISL=_&p9c;yb z#LY}#)WzNJZ>N$#+=AGMG-h@crwL&*6T~m+bjx-rGWOGy$R9BC)9l~Q$ znYmDp(a{Lb7zyAQ&c{P~zki}MV45+SN%`rYQf)I#d$EcoVS6{N@9*ZCAnS~y< zAkj{RA9{avCj%B4-(r|wm>!H1j2d*1P|^GV=N4f=i^^>9kOBBE{J7}5#7B$42SVN7 zo>sE~xd2huU!&ZR7BAOhfq&LEjUHd6tK-%}ZhJ|2gY3lW@)#*eE z#E;yqK$Gnr(Bl6FD0(|PJ2}9U<-ehgYN-#J@`uh3;Xf+;~7Ox2MBrA~v zt{ZeP*rNZWXfAHAGJ@7EMNWV`zkycCb0RM+yecL!+NV@)e*NGq7u>bop%a}sJVX~h zD1h#EDKDvXMs61f<9CDF*2E}Jg;=L8>$M$@%N9CjN!5tkPz;E=SHv}4BI=^&u;7glozFW>f z_?W;IBsu)_#Ns<-4$}~v0W@wm@#PVnc(#+*_?{6i&EG%a-hBr&;D?jzRkcoq;!n%W zI{IMWJbOP(0TUhztd9ze<4-&~t0-p01$_wAZ7-&@I@Ql#{k-UNDWa?!9oG(Qw*_Fs z*wtD9Lzq{DkFnpr^aB|F;GMGQEpy0et{M0Om)@h)=_cg~4h;^md&tx8g@lMh%$Ub| zu#FYXdA|fm+N~xsfx->d8|phQKQ6E|x7`@U3Z!XOBD&i}qt`L&38T~c^~;y9?LJ&j zLa4@E(@h20_YXYPHPzLzQbkAJA3h{_lkCGpC*UaNVKGdJ>g!_|{*AeGwkiKGJ1)xb zRaRe!ROPFSt&PPpQ_<|~tpB}WPc8Ou$2G1m&1bGG%hq|&Gv&X=IboSAuRrxXT{08l zU*E&2BqWUhqv}Db0_Img#qAhcGmQxSC{LqXn4YZ`BeN-(0Nc=jaE+ zQ+1S1OSBql)f$8gFCsW8_J|hY%n~y)_t6I^OfM}VZs2WR)z@J*=AaI|&x4E2+eI@@ zGBfwX!(6+$X=V{Z6+7W1Wzj;%m5blAdRcVt*+(DkZToh(p0FU8Ed58$Os#Nk{doA( zn2<+TbUXeYIzDu|oWMo%6GFZCXV+emo)Js&L7+Bm=#lYUIpiHu6dExD7$9pYw6XaH z4tPHJ4IaYpeK3>(x|vT5GlK|G4ihA63W2Sv1k?G2I8Y%@hpdN`$b@!LL3bg-E5P** z1zU_CzPd*DK|uzk>2>*f|2Gl9KBbkRI|#if3ea9{xaB@RcSZBtW9VK)E9)Z`j^8jjXV)ol|64pEX5CjV!Gg zeFr}7UrGS4{dt�M6NU1ugqj5}d6T|Ug@|Z^qpIL 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%0DELU9YZaR$Fb3j0_|b{=IvPMZAH&l2 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 Support for `XML_OUTPUT_FILE` was [introduced](https://github.com/catchorg/Catch2/pull/2399) in Catch2 3.0.1 > Support for `TESTBRIDGE_TEST_ONLY` and sharding was introduced in Catch2 3.2.0 +> Support for `TEST_PREMATURE_EXIT_FILE` and `TEST_RANDOM_SEED` was introduced in Catch2 3.11.0 + This integration is enabled via either a [compile time configuration option](configuration.md#bazel-support), or via `BAZEL_TEST` environment variable set to "1". > Support for `BAZEL_TEST` was [introduced](https://github.com/catchorg/Catch2/pull/2459) in Catch2 3.1.0 +Note that if both the Bazel environment var and command line option for +something are used, the environment variable wins. + ## Low-level tools diff --git a/docs/cmake-integration.md b/docs/cmake-integration.md index cfb9853..e63e67f 100644 --- a/docs/cmake-integration.md +++ b/docs/cmake-integration.md @@ -52,7 +52,7 @@ Include(FetchContent) FetchContent_Declare( Catch2 GIT_REPOSITORY https://github.com/catchorg/Catch2.git - GIT_TAG v3.4.0 # or a later release + GIT_TAG v3.8.1 # or a later release ) FetchContent_MakeAvailable(Catch2) diff --git a/docs/command-line.md b/docs/command-line.md index 7e69bf1..690dcaa 100644 --- a/docs/command-line.md +++ b/docs/command-line.md @@ -32,6 +32,7 @@ [Test Sharding](#test-sharding)
[Allow running the binary without tests](#allow-running-the-binary-without-tests)
[Output verbosity](#output-verbosity)
+[Create file to guard against silent early termination](#create-file-to-guard-against-silent-early-termination)
Catch works quite nicely without any command line options at all - but for those times when you want greater control the following options are available. Click one of the following links to take you straight to that option - or scroll on to browse the available options. @@ -123,7 +124,7 @@ specs. You can: This allows test cases that are tagged with **either** "[some-tag]" **or** "[other-tag]". A test case with both will obviously also pass the filter. - Note that commas take precendence over simple concatenation. This means + Note that commas take precedence over simple concatenation. This means that `[a][b],[c]` accepts tests that are tagged with either both "[a]" and "[b]", or tests that are tagged with just "[c]". @@ -398,18 +399,24 @@ use test specs to filter this list down to what you want first. Test cases are ordered one of three ways: ### decl -Declaration order (this is the default order if no --order argument is provided). +Declaration order. + Tests in the same translation unit are sorted using their declaration orders, different TUs are sorted in an implementation (linking) dependent order. ### lex -Lexicographic order. Tests are sorted by their name, their tags are ignored. +Lexicographic order. + +Tests are sorted by their name, their tags are ignored. ### rand +Randomized order. The default order. -Randomly ordered. The order is dependent on Catch2's random seed (see +> Randomized order has been made default in Catch2 3.9.0 + +The order is dependent on Catch2's random seed (see [`--rng-seed`](#rng-seed)), and is subset invariant. What this means is that as long as the random seed is fixed, running only some tests (e.g. via tag) does not change their relative order. @@ -643,6 +650,21 @@ ignored. Verbosity defaults to _normal_. +## Create file to guard against silent early termination +

--premature-exit-guard-file <path>
+ +> Introduced in Catch2 3.11.0 + +Tells Catch2 to create an empty file at specified path before the tests +start, and delete it after the tests finish. If the file is present after +the process stops, it can be assumed that the testing binary exited +prematurely, e.g. due to the OOM killer. + +All directories in the path must already exist. If this option is used +and Catch2 cannot create the file (e.g. the location is not writable), +the test run will fail. + + --- [Home](Readme.md#top) diff --git a/docs/configuration.md b/docs/configuration.md index f268ba5..f63a6d4 100644 --- a/docs/configuration.md +++ b/docs/configuration.md @@ -14,8 +14,10 @@ [Other toggles](#other-toggles)
[Enabling stringification](#enabling-stringification)
[Disabling exceptions](#disabling-exceptions)
+[Disabling deprecation warnings](#disabling-deprecation-warnings)
[Overriding Catch's debug break (`-b`)](#overriding-catchs-debug-break--b)
[Static analysis support](#static-analysis-support)
+[Experimental thread safety](#experimental-thread-safety)
Catch2 is designed to "just work" as much as possible, and most of the configuration options below are changed automatically during compilation, @@ -164,7 +166,7 @@ by using `_NO_` in the macro, e.g. `CATCH_CONFIG_NO_CPP17_UNCAUGHT_EXCEPTIONS`. > `CATCH_CONFIG_GETENV` was [introduced](https://github.com/catchorg/Catch2/pull/2562) in Catch2 3.2.0 -> `CATCH_CONFIG_USE_BUILTIN_CONSTANT_P` was introduced in Catch2 vX.Y.Z +> `CATCH_CONFIG_USE_BUILTIN_CONSTANT_P` was introduced in Catch2 3.8.0 Currently Catch enables `CATCH_CONFIG_WINDOWS_SEH` only when compiled with MSVC, because some versions of MinGW do not have the necessary Win32 API support. @@ -263,6 +265,21 @@ namespace Catch { } ``` + +## Disabling deprecation warnings + +> Introduced in Catch2 3.9.0 + +Catch2 has started using the C++ macro `[[deprecated]]` to mark things +that are deprecated and should not be used any more. If you need to +temporarily disable these warnings, use + + CATCH_CONFIG_NO_DEPRECATION_ANNOTATIONS + +Catch2 currently does not support more fine-grained deprecation warning +control, nor do we plan to. + + ## Overriding Catch's debug break (`-b`) > [Introduced](https://github.com/catchorg/Catch2/pull/1846) in Catch2 2.11.2. @@ -299,6 +316,21 @@ no backwards compatibility guarantees._ are not meant to be runnable, only "scannable". +## Experimental thread safety + +> Introduced in Catch2 3.9.0 + +Catch2 can optionally support thread-safe assertions, that means, multiple +user-spawned threads can use the assertion macros at the same time. Due +to the performance cost this imposes even on single-threaded usage, Catch2 +defaults to non-thread-safe assertions. + + CATCH_CONFIG_EXPERIMENTAL_THREAD_SAFE_ASSERTIONS // enables thread safe assertions + CATCH_CONFIG_NO_EXPERIMENTAL_THREAD_SAFE_ASSERTIONS // force-disables thread safe assertions + +See [the documentation on thread safety in Catch2](thread-safety.md#top) +for details on which macros are safe and other notes. + --- diff --git a/docs/deprecations.md b/docs/deprecations.md index 0b5bee1..20312cd 100644 --- a/docs/deprecations.md +++ b/docs/deprecations.md @@ -38,7 +38,7 @@ macro](skipping-passing-failing.md#top). ### Non-const function for `TEST_CASE_METHOD` -> Deprecated in Catch2 vX.Y.Z +> Deprecated in Catch2 3.7.0 Currently, the member function generated for `TEST_CASE_METHOD` is not `const` qualified. In the future, the generated member function will diff --git a/docs/generators.md b/docs/generators.md index eb1a255..000b200 100644 --- a/docs/generators.md +++ b/docs/generators.md @@ -21,7 +21,10 @@ The "Generators" `TEST_CASE` will be entered 3 times, and the value of `i` will be 1, 3, and 5 in turn. `GENERATE`s can also be used multiple times at the same scope, in which case the result will be a cartesian product of all elements in the generators. This means that in the snippet -below, the test case will be run 6 (2\*3) times. +below, the test case will be run 6 (2\*3) times. The `GENERATE` macro +is defined in the `catch_generators.hpp` header, so compiling +the code examples below also requires +`#include `. ```cpp TEST_CASE("Generators") { @@ -103,7 +106,7 @@ a test case, * 2 fundamental generators * `SingleValueGenerator` -- contains only single element * `FixedValuesGenerator` -- contains multiple elements -* 5 generic generators that modify other generators +* 5 generic generators that modify other generators (defined in `catch2/generators/catch_generators_adapters.hpp`) * `FilterGenerator` -- filters out elements from a generator for which the predicate returns "false" * `TakeGenerator` -- takes first `n` elements from a generator @@ -111,9 +114,10 @@ a test case, * `MapGenerator` -- returns the result of applying `Func` on elements from a different generator * `ChunkGenerator` -- returns chunks (inside `std::vector`) of n elements from a generator -* 4 specific purpose generators +* 2 random generators (defined in `catch2/generators/catch_generators_random.hpp`) * `RandomIntegerGenerator` -- generates random Integrals from range * `RandomFloatGenerator` -- generates random Floats from range +* 2 range generators (defined in `catch2/generators/catch_generators_range.hpp`) * `RangeGenerator(first, last)` -- generates all values inside a `[first, last)` arithmetic range * `IteratorGenerator` -- copies and returns values from an iterator range @@ -216,8 +220,8 @@ For floating point generators, the situation is much more complex. Generally Catch2 only promises reproducibility (or even just correctness!) on platforms that obey the IEEE-754 standard. Furthermore, reproducibility only applies between binaries that perform floating point math in the -same way, e.g. if you compile a binary targetting the x87 FPU and another -one targetting SSE2 for floating point math, their results will vary. +same way, e.g. if you compile a binary targeting the x87 FPU and another +one targeting SSE2 for floating point math, their results will vary. Similarly, binaries compiled with compiler flags that relax the IEEE-754 adherence, e.g. `-ffast-math`, might provide different results than those compiled for strict IEEE-754 adherence. diff --git a/docs/limitations.md b/docs/limitations.md index f5f60ba..67085a8 100644 --- a/docs/limitations.md +++ b/docs/limitations.md @@ -57,40 +57,6 @@ again. ## Features This section outlines some missing features, what is their status and their possible workarounds. -### Thread safe assertions -Catch2's assertion macros are not thread safe. This does not mean that -you cannot use threads inside Catch's test, but that only single thread -can interact with Catch's assertions and other macros. - -This means that this is ok -```cpp - std::vector threads; - std::atomic cnt{ 0 }; - for (int i = 0; i < 4; ++i) { - threads.emplace_back([&]() { - ++cnt; ++cnt; ++cnt; ++cnt; - }); - } - for (auto& t : threads) { t.join(); } - REQUIRE(cnt == 16); -``` -because only one thread passes the `REQUIRE` macro and this is not -```cpp - std::vector threads; - std::atomic cnt{ 0 }; - for (int i = 0; i < 4; ++i) { - threads.emplace_back([&]() { - ++cnt; ++cnt; ++cnt; ++cnt; - CHECK(cnt == 16); - }); - } - for (auto& t : threads) { t.join(); } - REQUIRE(cnt == 16); -``` - -We currently do not plan to support thread-safe assertions. - - ### Process isolation in a test Catch does not support running tests in isolated (forked) processes. While this might in the future, the fact that Windows does not support forking and only allows full-on process creation and the desire to keep code as similar as possible across platforms, mean that this is likely to take significant development time, that is not currently available. @@ -187,5 +153,5 @@ but only when compiled with a C++20 conforming compiler. MSVC is currently not conformant enough, but `clang-cl` will compile the assertion above using MSVC STL without problem. -This change got in with MSVC v19.37](https://godbolt.org/z/KG9obzdvE). +[This change got in with MSVC v19.37](https://godbolt.org/z/KG9obzdvE). diff --git a/docs/logging.md b/docs/logging.md index 7970938..1da9774 100644 --- a/docs/logging.md +++ b/docs/logging.md @@ -1,32 +1,30 @@
# Logging macros -Additional messages can be logged during a test case. Note that the messages logged with `INFO` are scoped and thus will not be reported if failure occurs in scope preceding the message declaration. An example: +Catch2 provides various macros for logging extra information when +running a test. These macros default to being scoped, and associate with +all assertions in the scope, regardless of whether they pass or fail. +**example** ```cpp -TEST_CASE("Foo") { +TEST_CASE("Simple info") { INFO("Test case start"); - for (int i = 0; i < 2; ++i) { - INFO("The number is " << i); - CHECK(i == 0); + SECTION("A") { + INFO("Section A"); + CHECK(false); // 1 } -} - -TEST_CASE("Bar") { - INFO("Test case start"); - for (int i = 0; i < 2; ++i) { - INFO("The number is " << i); - CHECK(i == i); + SECTION("B") { + INFO("Section B"); + CHECK(false); // 2 } - CHECK(false); + CHECK(false); // 3 } ``` -When the `CHECK` fails in the "Foo" test case, then two messages will be printed. -``` -Test case start -The number is 1 -``` -When the last `CHECK` fails in the "Bar" test case, then only one message will be printed: `Test case start`. +The first assertion will report messages "Test case start", and "Section A" +as extra information. The second one will report messages "Test case +started" and "Section B", while the third one will only report "Test case +started" as the extra info. + ## Logging without local scope diff --git a/docs/own-main.md b/docs/own-main.md index 26dfa86..4cc21b1 100644 --- a/docs/own-main.md +++ b/docs/own-main.md @@ -57,17 +57,18 @@ int main( int argc, char* argv[] ) { int returnCode = session.applyCommandLine( argc, argv ); if( returnCode != 0 ) // Indicates a command line error - return returnCode; + return returnCode; // writing to session.configData() or session.Config() here // overrides command line args // only do this if you know you need to - int numFailed = session.run(); + returnCode = session.run(); + + // returnCode encodes the type of error that occured. See the + // integer constants in catch_session.hpp for more information + // on what each return code means. - // numFailed is clamped to 255 as some unices only use the lower 8 bits. - // This clamping has already been applied, so just return it here - // You can also do any post run clean-up here return numFailed; } ``` diff --git a/docs/release-notes.md b/docs/release-notes.md index a393a52..9da1a2e 100644 --- a/docs/release-notes.md +++ b/docs/release-notes.md @@ -2,6 +2,10 @@ # Release notes **Contents**
+[3.11.0](#3110)
+[3.10.0](#3100)
+[3.9.1](#391)
+[3.9.0](#390)
[3.8.1](#381)
[3.8.0](#380)
[3.7.1](#371)
@@ -67,6 +71,93 @@ [Even Older versions](#even-older-versions)
+## 3.11.0 + +### Fixes +* Fixed building on non-desktop GDK platforms (#3029) +* Fixed message macros being susceptible to race in specific scenario (#3031) +* Catch2's SEH filter will call the previously installed filter after reporting the error (#3033) + +### Improvements +* Handling of scoped messages (e.g. `CAPTURE`) is a bit faster. +* Better out-of-the-box support for QNX (#2953) +* Improved performance of assertions by up-to 10% + * Release mode assertion fast-path sees the biggest improvement. +* Faster processing of non-escaped strings in `--invisibles` mode. +* Added support for Bazel's `TEST_RANDOM_SEED` env var (#3021) +* Added support for Bazel's `TEST_PREMATURE_EXIT_FILE` env var (#3020) + * This creates a file that is deleted if the tests exit normally, but stays around if the process dies unexpectedly. + * This functionality is also exposed through CLI as `--premature-exit-guard-file` + +### Miscellaneous +* **[Tuple.app](https://tuple.app/catch2) has sponsored Catch2** + + +## 3.10.0 + +### Fixes +* pkg-config files will take `DESTDIR` env var into account when selecting install destination (#3006, #3019) +* Changed `filter` to store the provided predicate by value (#3002, #3005) + * This is done to avoid dangling-by-default behaviour when `filter` is used inside `GENERATE_COPY`/`GENERATE_REF`. + +### Improvements +* Escaping XML and JSON output is faster when the strings do not need escaping. + * The improvement starts at about 3x throughput, up to 10x for long strings. +* Message macros (`INFO`, `CAPTURE`, `WARN`, `SUCCEED`, etc) are now thread safe. + + +## 3.9.1 + +### Fixes +* Fixed bad error reporting for multiple nested assertions (#1292) +* Fixed W4702 (unreachable code) in the polyfill for std::unreachable (#3007) +* Fixed decomposition of assertions comparing enum-backed bitfields (#3001) +* Fixed StringMaker specialization for `time_point` with non-default duration type (#2685) + +### Improvements +* Exceptions thrown during stringification of decomposed expression no longer fail the assertion (#2980) +* The selection logic for `CATCH_TRAP` prefers `__builtin_debugtrap` on all platforms when Catch2 is compiled with Clang + + +## 3.9.0 + +### Improvements +* **Added experimental opt-in support for thread safe assertions** + * Read the documentation for full details +* **The default test run order has been changed to random** +* Passing assertions are significantly faster when the reporter does not ask for `assertionEnded` events on passing assertions. + * This is the default behaviour of e.g. Console or Compact reporter + * Simple `REQUIRE(true)` is 60% faster in Release and 80% faster in Debug build configuration + * Simple `REQUIRE_NOTHROW` is 230% faster in Release and 430% faster in Debug build configuration + * Simple `REQUIRE_THROWS` is ~3% faster in Release and 20% faster in Debug build configuration (throwing introduces enough overhead that the optimizations inside Catch2 are mostly irrelevant) +* Small (2-5%) improvement if the reporter asks for `assertionEnded` events for passing assertions. +* The exit code constants are part of the Session API. (#2955, #2976) +* Suppressed unsigned integer overflow checking in locations with intended overflow (#2965) +* Reporters flush output after writing metadata, e.g. rng seed (#2964) +* Added unreachable after `FAIL` and `SKIP` macros (#2941) + * This allows the compiler to understand that the execution does not continue past the macro, and avoids warnings. +* Added fast path for `assertionStarting` event when no reporter requires it + * For backwards compatibility, this fast path is opt-in + * A reporter can opt in by changing its `ReporterPreferences::shouldReportAllAssertionStarts` +* Improved last seen source location tracking to be more precise + * This is used when reporting unexpected exceptions from tests + +### Fixes +* Fixed formatting of tags with more than 100 tests in the default `--list-tags` output (#2963) +* Fixed Clang-Tidy's `readability-static-accessed-through-instance` in tests +* Fixed most of Clang-Tidy's `cppcoreguidelines-avoid-non-const-global-variables` (#2582) +* The lifetime of scoped messages now strictly obeys their scope (#1759, #2019, #2959) + * Previously Catch2 would try to keep them around during unexpected exception, to provide helpful context. + * The amount of surprises the irregularities caused was not worth the occasional utility provided. +* `TEMPLATE_TEST_CASE_SIG` can handle signatures consisting of only types (#2680, #2995) +* Moved `catch_test_run_info.hpp` up from `internal/` subfolder into the main one (#2972) + +### Miscellaneous +* pkg-config files are now generated at install time (#2979) + * This fixes missing debug suffix in library names + * This fixes install prefix mismatch between build config and actuall installation + + ## 3.8.1 ### Fixes @@ -93,7 +184,7 @@ * Removed redundant `CTEST_FILE` param when creating the indirection file for `PRE_TEST` discovery mode (#2936) * Rewrote the test discovery logic to use output from the JSON reporter * This means that `catch_discover_tests` now requires CMake 3.19 or newer - * Added `ADD_TAGS_AS_LABELS` option. If specified, each CTest test will be labeled with corrensponding Catch2's test tag + * Added `ADD_TAGS_AS_LABELS` option. If specified, each CTest test will be labeled with corresponding Catch2's test tag * Bumped up the minimum required CMake version to build Catch2 to 3.16 * Meson build now provides option to avoid installing Catch2 * Bazel build is moved to Bzlmod. @@ -175,7 +266,7 @@ ### Improvements * Reintroduced support for GCC 5 and 6 (#2836) * As with VS2017, if they start causing trouble again, they will be dropped again. -* Added workaround for targetting newest MacOS (Sonoma) using GCC (#2837, #2839) +* Added workaround for targeting newest MacOS (Sonoma) using GCC (#2837, #2839) * `CATCH_CONFIG_DEFAULT_REPORTER` can now be an arbitrary reporter spec * Previously it could only be a plain reporter name, so it was impossible to compile in custom arguments to the reporter. * Improved performance of generating 64bit random integers by 20+% diff --git a/docs/reporters.md b/docs/reporters.md index 20ef5e5..d42648c 100644 --- a/docs/reporters.md +++ b/docs/reporters.md @@ -154,7 +154,7 @@ and calls itself "partial" reporter, so it can be invoked with Each reporter instance contains instance of `ReporterPreferences`, a type that holds flags for the behaviour of Catch2 when this reporter run. -Currently there are two customization options: +Currently there are three customization options: * `shouldRedirectStdOut` - whether the reporter wants to handle writes to stdout/stderr from user code, or not. This is useful for @@ -165,6 +165,11 @@ Currently there are two customization options: assertions. Usually reporters do not report successful assertions and don't need them for their output, but sometimes the desired output format includes passing assertions even without the `-s` flag. +* `shouldReportAllAssertionStarts` - whether the reporter wants to handle + `assertionStarting` events. Most reporters do not, and opting out + explicitly enables a fast-path in Catch2's handling of assertions. + +> `shouldReportAllAssertionStarts` was introduced in Catch2 3.9.0 ### Per-reporter configuration diff --git a/docs/skipping-passing-failing.md b/docs/skipping-passing-failing.md index 52bb18f..1bdc1f0 100644 --- a/docs/skipping-passing-failing.md +++ b/docs/skipping-passing-failing.md @@ -52,6 +52,20 @@ TEST_CASE("failing test") { } ``` +Same applies for a `SKIP` nested inside an assertion: + +```cpp +static bool do_skip() { + SKIP(); + return true; +} + +TEST_CASE("Another failing test") { + CHECK(do_skip()); +} +``` + + ### Interaction with Sections and Generators Sections, nested sections as well as specific outputs from [generators](generators.md#top) diff --git a/docs/test-fixtures.md b/docs/test-fixtures.md index 653b50e..9496dd1 100644 --- a/docs/test-fixtures.md +++ b/docs/test-fixtures.md @@ -4,7 +4,7 @@ **Contents**
[Non-Templated test fixtures](#non-templated-test-fixtures)
[Templated test fixtures](#templated-test-fixtures)
-[Signature-based parameterised test fixtures](#signature-based-parametrised-test-fixtures)
+[Signature-based parameterised test fixtures](#signature-based-parameterised-test-fixtures)
[Template fixtures with types specified in template type lists](#template-fixtures-with-types-specified-in-template-type-lists)
## Non-Templated test fixtures @@ -130,7 +130,7 @@ TEST_CASE_PERSISTENT_FIXTURE( MyFixture, "Tests with MyFixture" ) { } ``` -This example demonstates two possible use-cases of this fixture type: +This example demonstrates two possible use-cases of this fixture type: 1. Improve test run times by reducing the amount of expensive and redundant setup and tear-down required. 2. Reusing results from the previous partial run, in the current diff --git a/docs/thread-safety.md b/docs/thread-safety.md new file mode 100644 index 0000000..e0092de --- /dev/null +++ b/docs/thread-safety.md @@ -0,0 +1,229 @@ + +# Thread safety in Catch2 + +**Contents**
+[Using assertion macros from spawned threads](#using-assertion-macros-from-spawned-threads)
+[Assertion-like message macros and spawned threads](#assertion-like-message-macros-and-spawned-threads)
+[Message macros and spawned threads](#message-macros-and-spawned-threads)
+[examples](#examples)
+[`STATIC_REQUIRE` and `STATIC_CHECK`](#static_require-and-static_check)
+[Fatal errors and multiple threads](#fatal-errors-and-multiple-threads)
+[Performance overhead](#performance-overhead)
+ +> Thread safe assertions were introduced in Catch2 3.9.0 + +Thread safety in Catch2 is currently limited to all the assertion macros, +and to message or message-adjacent macros (e.g. `INFO` or `WARN`). + +Interacting with benchmark macros, sections macros, generator macros, or +test case macros is not thread-safe. The way sections define paths through +the test is incompatible with user spawning threads arbitrarily, so this +limitation is here to stay. + +**Important: thread safety in Catch2 is [opt-in](configuration.md#experimental-thread-safety)** + + +## Using assertion macros from spawned threads + +The full set of Catch2's runtime assertion macros is thread-safe. However, +it is important to keep in mind that their semantics might not support +being used from user-spawned threads. + +Specifically, the `REQUIRE` family of assertion macros have semantics +of stopping the test execution on failure. This is done by throwing +an exception, but since the user-spawned thread will not have the test-level +try-catch block ready to catch the test failure exception, failing a +`REQUIRE` assertion inside user-spawned thread will terminate the process. + +The `CHECK` family of assertions does not have this issue, because it +does not try to stop the test execution. + +Note that `CHECKED_IF` and `CHECKED_ELSE` are also thread safe (internally +they are assertion macro + an if). + + +## Assertion-like message macros and spawned threads + +> Assertion-like messages were made thread safe in Catch2 3.10.0 + +Similarly to assertion macros, not all assertion-like message macros can +be used from spawned thread. + +`SKIP` and `FAIL` macros stop the test execution. Just like with `REQUIRE`, +this means that they cannot be used inside user-spawned threads. `SUCCEED`, +`FAIL_CHECK` and `WARN` do not attempt to stop the test execution and +thus can be used from any thread. + + +## Message macros and spawned threads + +> Message macros were made thread safe in Catch2 3.10.0 + +Macros that add extra messages to following assertion, such as `INFO` +or `CAPTURE`, are all thread safe and can be used in any thread. Note +that these messages are per-thread, and thus `INFO` inside a user-spawned +thread will not be seen by the main thread, and vice versa. + + +## examples + +### `REQUIRE` from the main thread, `CHECK` from spawned threads + +```cpp +TEST_CASE( "Failed REQUIRE in the main thread is fine" ) { + std::vector threads; + for ( size_t t = 0; t < 16; ++t) { + threads.emplace_back( []() { + for (size_t i = 0; i < 10'000; ++i) { + CHECK( true ); + CHECK( false ); + } + } ); + } + + REQUIRE( false ); +} +``` +This will work as expected, that is, the process will finish running +normally, the test case will fail and there will be the correct count of +passing and failing assertions (160000 and 160001 respectively). However, +it is important to understand that when the main thread fails its assertion, +the spawned threads will keep running. + + +### `REQUIRE` from spawned threads + +```cpp +TEST_CASE( "Successful REQUIRE in spawned thread is fine" ) { + std::vector threads; + for ( size_t t = 0; t < 16; ++t) { + threads.emplace_back( []() { + for (size_t i = 0; i < 10'000; ++i) { + REQUIRE( true ); + } + } ); + } +} +``` +This will also work as expected, because the `REQUIRE` is successful. + +```cpp +TEST_CASE( "Failed REQUIRE in spawned thread kills the process" ) { + std::vector threads; + for ( size_t t = 0; t < 16; ++t) { + threads.emplace_back( []() { + for (size_t i = 0; i < 10'000; ++i) { + REQUIRE( false ); + } + } ); + } +} +``` +This will fail catastrophically and terminate the process. + + +### INFO across threads + +```cpp +TEST_CASE( "messages don't cross threads" ) { + std::jthread t1( [&]() { + for ( size_t i = 0; i < 100; ++i ) { + INFO( "spawned thread #1" ); + CHECK( 1 == 1 ); + } + } ); + + std::thread t2( [&]() { + for (size_t i = 0; i < 100; ++i) { + UNSCOPED_INFO( "spawned thread #2" ); + } + } ); + + for (size_t i = 0; i < 100; ++i) { + CHECK( 1 == 2 ); + } +} +``` +None of the failed checks will show the "spawned thread #1" message, as +that message is for the `t1` thread. If the reporter shows passing +assertions (e.g. due to the tests being run with `-s`), you will see the +"spawned thread #1" message alongside the passing `CHECK( 1 == 1 )` assertion. + +The message "spawned thread #2" will never be shown, because there are no +assertions in `t2`. + + +### FAIL/SKIP from the main thread + +```cpp +TEST_CASE( "FAIL in the main thread is fine" ) { + std::vector threads; + for ( size_t t = 0; t < 16; ++t) { + threads.emplace_back( []() { + for (size_t i = 0; i < 10; ++i) { + CHECK( true ); + CHECK( false ); + } + } ); + } + + FAIL(); +} +``` + +This will work as expected, that is, the process will finish running +normally, the test case will fail and there will be 321 total assertions, +160 passing and 161 failing (`FAIL` counts as failed assertion). + +However, when the main thread hits `FAIL`, it will wait for the other +threads to finish due to `std::jthread`'s destructor joining the spawned +thread. Due to this, using `SKIP` is not recommended once more threads +are spawned; while the main thread will bail from the test execution, +the spawned threads will keep running and may fail the test case. + + +### FAIL/SKIP from spawned threads + +```cpp +TEST_CASE( "FAIL/SKIP in spawned thread kills the process" ) { + std::vector threads; + for ( size_t t = 0; t < 16; ++t) { + threads.emplace_back( []() { + for (size_t i = 0; i < 10'000; ++i) { + FAIL(); + } + } ); + } +} +``` +As with failing `REQUIRE`, both `FAIL` and `SKIP` in spawned threads +terminate the process. + + +## `STATIC_REQUIRE` and `STATIC_CHECK` + +All of `STATIC_REQUIRE`, `STATIC_REQUIRE_FALSE`, `STATIC_CHECK`, and +`STATIC_CHECK_FALSE` are thread safe in the delayed evaluation configuration. + + +## Fatal errors and multiple threads + +By default, Catch2 tries to catch fatal errors (POSIX signals/Windows +Structured Exceptions) and report something useful to the user. This +always happened on a best-effort basis, but in presence of multiple +threads and locks the chance of it working decreases. If this starts +being an issue for you, [you can disable it](configuration.md#other-toggles). + + +## Performance overhead + +In the worst case, which is optimized build and assertions using the +fast path for successful assertions, the performance overhead of using +the thread-safe assertion implementation can reach 40%. In other cases, +the overhead will be smaller, between 4% and 20%. + + + +--- + +[Home](Readme.md#top) diff --git a/examples/210-Evt-EventListeners.cpp b/examples/210-Evt-EventListeners.cpp index d05dfaa..d1cfdb4 100644 --- a/examples/210-Evt-EventListeners.cpp +++ b/examples/210-Evt-EventListeners.cpp @@ -307,7 +307,7 @@ void print( std::ostream& os, int const level, std::string const& title, Catch:: // 2. My listener and registration: // -char const * dashed_line = +char const * const dashed_line = "--------------------------------------------------------------------------"; diff --git a/examples/CMakeLists.txt b/examples/CMakeLists.txt index d45ddfc..74b6599 100644 --- a/examples/CMakeLists.txt +++ b/examples/CMakeLists.txt @@ -1,50 +1,47 @@ -cmake_minimum_required( VERSION 3.16 ) - -project( Catch2Examples LANGUAGES CXX ) - -message( STATUS "Examples included" ) +cmake_minimum_required(VERSION 3.16) +project(Catch2Examples LANGUAGES CXX) +message(STATUS "Examples included") # Some one-offs first: # 1) Tests and main in one file -add_executable( 010-TestCase +add_executable(010-TestCase 010-TestCase.cpp ) # 2) Tests and main across two files -add_executable( 020-MultiFile +add_executable(020-MultiFile 020-TestCase-1.cpp 020-TestCase-2.cpp ) add_executable(231-Cfg_OutputStreams - 231-Cfg-OutputStreams.cpp + 231-Cfg-OutputStreams.cpp ) target_link_libraries(231-Cfg_OutputStreams Catch2_buildall_interface) target_compile_definitions(231-Cfg_OutputStreams PUBLIC CATCH_CONFIG_NOSTDOUT) # These examples use the standard separate compilation -set( SOURCES_IDIOMATIC_EXAMPLES - 030-Asn-Require-Check.cpp - 100-Fix-Section.cpp - 110-Fix-ClassFixture.cpp - 111-Fix-PersistentFixture.cpp - 120-Bdd-ScenarioGivenWhenThen.cpp - 210-Evt-EventListeners.cpp - 232-Cfg-CustomMain.cpp - 300-Gen-OwnGenerator.cpp - 301-Gen-MapTypeConversion.cpp - 302-Gen-Table.cpp - 310-Gen-VariablesInGenerators.cpp - 311-Gen-CustomCapture.cpp +set(SOURCES_IDIOMATIC_EXAMPLES + 030-Asn-Require-Check.cpp + 100-Fix-Section.cpp + 110-Fix-ClassFixture.cpp + 111-Fix-PersistentFixture.cpp + 120-Bdd-ScenarioGivenWhenThen.cpp + 210-Evt-EventListeners.cpp + 232-Cfg-CustomMain.cpp + 300-Gen-OwnGenerator.cpp + 301-Gen-MapTypeConversion.cpp + 302-Gen-Table.cpp + 310-Gen-VariablesInGenerators.cpp + 311-Gen-CustomCapture.cpp ) -string( REPLACE ".cpp" "" BASENAMES_IDIOMATIC_EXAMPLES "${SOURCES_IDIOMATIC_EXAMPLES}" ) -set( TARGETS_IDIOMATIC_EXAMPLES ${BASENAMES_IDIOMATIC_EXAMPLES} ) +string(REPLACE ".cpp" "" BASENAMES_IDIOMATIC_EXAMPLES "${SOURCES_IDIOMATIC_EXAMPLES}") +set(TARGETS_IDIOMATIC_EXAMPLES ${BASENAMES_IDIOMATIC_EXAMPLES}) - -foreach( name ${TARGETS_IDIOMATIC_EXAMPLES} ) - add_executable( ${name} ${name}.cpp ) +foreach(name ${TARGETS_IDIOMATIC_EXAMPLES}) + add_executable(${name} ${name}.cpp) endforeach() set(ALL_EXAMPLE_TARGETS @@ -53,10 +50,9 @@ set(ALL_EXAMPLE_TARGETS 020-MultiFile ) -foreach( name ${ALL_EXAMPLE_TARGETS} ) - target_link_libraries( ${name} Catch2WithMain ) +foreach(name ${ALL_EXAMPLE_TARGETS}) + target_link_libraries(${name} Catch2WithMain) endforeach() - -list(APPEND CATCH_WARNING_TARGETS ${ALL_EXAMPLE_TARGETS}) -set(CATCH_WARNING_TARGETS ${CATCH_WARNING_TARGETS} PARENT_SCOPE) +list(APPEND CATCH_TEST_TARGETS ${ALL_EXAMPLE_TARGETS}) +set(CATCH_TEST_TARGETS ${CATCH_TEST_TARGETS} PARENT_SCOPE) diff --git a/extras/Catch.cmake b/extras/Catch.cmake index 3d93fe2..fcdebd6 100644 --- a/extras/Catch.cmake +++ b/extras/Catch.cmake @@ -165,7 +165,7 @@ function(catch_discover_tests TARGET) ${ARGN} ) - if (${CMAKE_VERSION} VERSION_LESS "3.19") + if(${CMAKE_VERSION} VERSION_LESS "3.19") message(FATAL_ERROR "This script requires JSON support from CMake version 3.19 or greater.") endif() @@ -187,7 +187,7 @@ function(catch_discover_tests TARGET) endif() set(_DISCOVERY_MODE ${CMAKE_CATCH_DISCOVER_TESTS_DISCOVERY_MODE}) endif() - if (NOT _DISCOVERY_MODE MATCHES "^(POST_BUILD|PRE_TEST)$") + if(NOT _DISCOVERY_MODE MATCHES "^(POST_BUILD|PRE_TEST)$") message(FATAL_ERROR "Unknown DISCOVERY_MODE: ${_DISCOVERY_MODE}") endif() @@ -204,7 +204,7 @@ function(catch_discover_tests TARGET) TARGET ${TARGET} PROPERTY CROSSCOMPILING_EMULATOR ) - if (NOT _SKIP_IS_FAILURE) + if(NOT _SKIP_IS_FAILURE) set(_PROPERTIES ${_PROPERTIES} SKIP_RETURN_CODE 4) endif() diff --git a/extras/CatchAddTests.cmake b/extras/CatchAddTests.cmake index 2534213..90fbd5e 100644 --- a/extras/CatchAddTests.cmake +++ b/extras/CatchAddTests.cmake @@ -136,7 +136,7 @@ function(catch_discover_tests_impl) # Parse JSON output for list of tests/class names/tags string(JSON version GET "${listing_output}" "version") - if (NOT version STREQUAL "1") + if(NOT version STREQUAL "1") message(FATAL_ERROR "Unsupported catch output version: '${version}'") endif() @@ -186,11 +186,11 @@ function(catch_discover_tests_impl) ${properties} ) - if (add_tags) + if(add_tags) string(JSON num_tags LENGTH "${test_tags}") math(EXPR num_tags "${num_tags} - 1") set(tag_list "") - if (num_tags GREATER_EQUAL "0") + if(num_tags GREATER_EQUAL "0") foreach(tag_idx RANGE ${num_tags}) string(JSON a_tag GET "${test_tags}" "${tag_idx}") # Catch2's tags can contain semicolons, which are list element separators diff --git a/extras/CatchShardTests.cmake b/extras/CatchShardTests.cmake index 68228f5..89666d6 100644 --- a/extras/CatchShardTests.cmake +++ b/extras/CatchShardTests.cmake @@ -11,7 +11,7 @@ # * REPORTER - reporter spec to use for tests # * TEST_SPEC - test spec used for filtering tests function(catch_add_sharded_tests TARGET) - if (${CMAKE_VERSION} VERSION_LESS "3.10.0") + if(${CMAKE_VERSION} VERSION_LESS "3.10.0") message(FATAL_ERROR "add_sharded_catch_tests only supports CMake versions 3.10.0 and up") endif() @@ -23,7 +23,7 @@ function(catch_add_sharded_tests TARGET) ${ARGN} ) - if (NOT DEFINED _SHARD_COUNT) + if(NOT DEFINED _SHARD_COUNT) set(_SHARD_COUNT 2) endif() @@ -61,8 +61,6 @@ function(catch_add_sharded_tests TARGET) -P "${shard_impl_script_file}" VERBATIM ) - - endfunction() diff --git a/extras/CatchShardTestsImpl.cmake b/extras/CatchShardTestsImpl.cmake index bb2fc3e..83fac68 100644 --- a/extras/CatchShardTestsImpl.cmake +++ b/extras/CatchShardTestsImpl.cmake @@ -25,10 +25,10 @@ if(NOT EXISTS "${TEST_BINARY}") endif() set(other_args "") -if (TEST_SPEC) +if(TEST_SPEC) set(other_args "${other_args} ${TEST_SPEC}") endif() -if (REPORTER_SPEC) +if(REPORTER_SPEC) set(other_args "${other_args} --reporter ${REPORTER_SPEC}") endif() diff --git a/extras/ParseAndAddCatchTests.cmake b/extras/ParseAndAddCatchTests.cmake index 31fc193..d7c17ac 100644 --- a/extras/ParseAndAddCatchTests.cmake +++ b/extras/ParseAndAddCatchTests.cmake @@ -56,7 +56,7 @@ # # #==================================================================================================# -if (CMAKE_MINIMUM_REQUIRED_VERSION VERSION_LESS 2.8.8) +if(CMAKE_MINIMUM_REQUIRED_VERSION VERSION_LESS 2.8.8) message(FATAL_ERROR "ParseAndAddCatchTests requires CMake 2.8.8 or newer") endif() @@ -67,9 +67,9 @@ option(PARSE_CATCH_TESTS_ADD_TARGET_IN_TEST_NAME "Add target name to the test na option(PARSE_CATCH_TESTS_ADD_TO_CONFIGURE_DEPENDS "Add test file to CMAKE_CONFIGURE_DEPENDS property" OFF) function(ParseAndAddCatchTests_PrintDebugMessage) - if(PARSE_CATCH_TESTS_VERBOSE) - message(STATUS "ParseAndAddCatchTests: ${ARGV}") - endif() + if(PARSE_CATCH_TESTS_VERBOSE) + message(STATUS "ParseAndAddCatchTests: ${ARGV}") + endif() endfunction() # This removes the contents between @@ -90,163 +90,161 @@ endfunction() # Worker function function(ParseAndAddCatchTests_ParseFile SourceFile TestTarget) - # If SourceFile is an object library, do not scan it (as it is not a file). Exit without giving a warning about a missing file. - if(SourceFile MATCHES "\\\$") - ParseAndAddCatchTests_PrintDebugMessage("Detected OBJECT library: ${SourceFile} this will not be scanned for tests.") - return() - endif() - # According to CMake docs EXISTS behavior is well-defined only for full paths. - get_filename_component(SourceFile ${SourceFile} ABSOLUTE) - if(NOT EXISTS ${SourceFile}) - message(WARNING "Cannot find source file: ${SourceFile}") - return() + # If SourceFile is an object library, do not scan it (as it is not a file). Exit without giving a warning about a missing file. + if(SourceFile MATCHES "\\\$") + ParseAndAddCatchTests_PrintDebugMessage("Detected OBJECT library: ${SourceFile} this will not be scanned for tests.") + return() + endif() + # According to CMake docs EXISTS behavior is well-defined only for full paths. + get_filename_component(SourceFile ${SourceFile} ABSOLUTE) + if(NOT EXISTS ${SourceFile}) + message(WARNING "Cannot find source file: ${SourceFile}") + return() + endif() + ParseAndAddCatchTests_PrintDebugMessage("parsing ${SourceFile}") + file(STRINGS ${SourceFile} Contents NEWLINE_CONSUME) + + # Remove block and fullline comments + ParseAndAddCatchTests_RemoveComments(Contents) + + # Find definition of test names + # https://regex101.com/r/JygOND/1 + string(REGEX MATCHALL "[ \t]*(CATCH_)?(TEMPLATE_)?(TEST_CASE_METHOD|SCENARIO|TEST_CASE)[ \t]*\\([ \t\n]*\"[^\"]*\"[ \t\n]*(,[ \t\n]*\"[^\"]*\")?(,[ \t\n]*[^\,\)]*)*\\)[ \t\n]*\{+[ \t]*(//[^\n]*[Tt][Ii][Mm][Ee][Oo][Uu][Tt][ \t]*[0-9]+)*" Tests "${Contents}") + + if(PARSE_CATCH_TESTS_ADD_TO_CONFIGURE_DEPENDS AND Tests) + ParseAndAddCatchTests_PrintDebugMessage("Adding ${SourceFile} to CMAKE_CONFIGURE_DEPENDS property") + set_property( + DIRECTORY + APPEND + PROPERTY CMAKE_CONFIGURE_DEPENDS ${SourceFile} + ) + endif() + + # check CMP0110 policy for new add_test() behavior + if(POLICY CMP0110) + cmake_policy(GET CMP0110 _cmp0110_value) # new add_test() behavior + else() + # just to be thorough explicitly set the variable + set(_cmp0110_value) + endif() + + foreach(TestName ${Tests}) + # Strip newlines + string(REGEX REPLACE "\\\\\n|\n" "" TestName "${TestName}") + + # Get test type and fixture if applicable + string(REGEX MATCH "(CATCH_)?(TEMPLATE_)?(TEST_CASE_METHOD|SCENARIO|TEST_CASE)[ \t]*\\([^,^\"]*" TestTypeAndFixture "${TestName}") + string(REGEX MATCH "(CATCH_)?(TEMPLATE_)?(TEST_CASE_METHOD|SCENARIO|TEST_CASE)" TestType "${TestTypeAndFixture}") + string(REGEX REPLACE "${TestType}\\([ \t]*" "" TestFixture "${TestTypeAndFixture}") + + # Get string parts of test definition + string(REGEX MATCHALL "\"+([^\\^\"]|\\\\\")+\"+" TestStrings "${TestName}") + + # Strip wrapping quotation marks + string(REGEX REPLACE "^\"(.*)\"$" "\\1" TestStrings "${TestStrings}") + string(REPLACE "\";\"" ";" TestStrings "${TestStrings}") + + # Validate that a test name and tags have been provided + list(LENGTH TestStrings TestStringsLength) + if(TestStringsLength GREATER 2 OR TestStringsLength LESS 1) + message(FATAL_ERROR "You must provide a valid test name and tags for all tests in ${SourceFile}") endif() - ParseAndAddCatchTests_PrintDebugMessage("parsing ${SourceFile}") - file(STRINGS ${SourceFile} Contents NEWLINE_CONSUME) - - # Remove block and fullline comments - ParseAndAddCatchTests_RemoveComments(Contents) - - # Find definition of test names - # https://regex101.com/r/JygOND/1 - string(REGEX MATCHALL "[ \t]*(CATCH_)?(TEMPLATE_)?(TEST_CASE_METHOD|SCENARIO|TEST_CASE)[ \t]*\\([ \t\n]*\"[^\"]*\"[ \t\n]*(,[ \t\n]*\"[^\"]*\")?(,[ \t\n]*[^\,\)]*)*\\)[ \t\n]*\{+[ \t]*(//[^\n]*[Tt][Ii][Mm][Ee][Oo][Uu][Tt][ \t]*[0-9]+)*" Tests "${Contents}") - if(PARSE_CATCH_TESTS_ADD_TO_CONFIGURE_DEPENDS AND Tests) - ParseAndAddCatchTests_PrintDebugMessage("Adding ${SourceFile} to CMAKE_CONFIGURE_DEPENDS property") - set_property( - DIRECTORY - APPEND - PROPERTY CMAKE_CONFIGURE_DEPENDS ${SourceFile} - ) + # Assign name and tags + list(GET TestStrings 0 Name) + if("${TestType}" STREQUAL "SCENARIO") + set(Name "Scenario: ${Name}") endif() - - # check CMP0110 policy for new add_test() behavior - if(POLICY CMP0110) - cmake_policy(GET CMP0110 _cmp0110_value) # new add_test() behavior + if(PARSE_CATCH_TESTS_ADD_FIXTURE_IN_TEST_NAME AND "${TestType}" MATCHES "(CATCH_)?TEST_CASE_METHOD" AND TestFixture) + set(CTestName "${TestFixture}:${Name}") + else() + set(CTestName "${Name}") + endif() + if(PARSE_CATCH_TESTS_ADD_TARGET_IN_TEST_NAME) + set(CTestName "${TestTarget}:${CTestName}") + endif() + # add target to labels to enable running all tests added from this target + set(Labels ${TestTarget}) + if(TestStringsLength EQUAL 2) + list(GET TestStrings 1 Tags) + string(TOLOWER "${Tags}" Tags) + # remove target from labels if the test is hidden + if("${Tags}" MATCHES ".*\\[!?(hide|\\.)\\].*") + list(REMOVE_ITEM Labels ${TestTarget}) + endif() + string(REPLACE "]" ";" Tags "${Tags}") + string(REPLACE "[" "" Tags "${Tags}") else() - # just to be thorough explicitly set the variable - set(_cmp0110_value) + # unset tags variable from previous loop + unset(Tags) endif() - foreach(TestName ${Tests}) - # Strip newlines - string(REGEX REPLACE "\\\\\n|\n" "" TestName "${TestName}") - - # Get test type and fixture if applicable - string(REGEX MATCH "(CATCH_)?(TEMPLATE_)?(TEST_CASE_METHOD|SCENARIO|TEST_CASE)[ \t]*\\([^,^\"]*" TestTypeAndFixture "${TestName}") - string(REGEX MATCH "(CATCH_)?(TEMPLATE_)?(TEST_CASE_METHOD|SCENARIO|TEST_CASE)" TestType "${TestTypeAndFixture}") - string(REGEX REPLACE "${TestType}\\([ \t]*" "" TestFixture "${TestTypeAndFixture}") - - # Get string parts of test definition - string(REGEX MATCHALL "\"+([^\\^\"]|\\\\\")+\"+" TestStrings "${TestName}") - - # Strip wrapping quotation marks - string(REGEX REPLACE "^\"(.*)\"$" "\\1" TestStrings "${TestStrings}") - string(REPLACE "\";\"" ";" TestStrings "${TestStrings}") - - # Validate that a test name and tags have been provided - list(LENGTH TestStrings TestStringsLength) - if(TestStringsLength GREATER 2 OR TestStringsLength LESS 1) - message(FATAL_ERROR "You must provide a valid test name and tags for all tests in ${SourceFile}") - endif() - - # Assign name and tags - list(GET TestStrings 0 Name) - if("${TestType}" STREQUAL "SCENARIO") - set(Name "Scenario: ${Name}") - endif() - if(PARSE_CATCH_TESTS_ADD_FIXTURE_IN_TEST_NAME AND "${TestType}" MATCHES "(CATCH_)?TEST_CASE_METHOD" AND TestFixture ) - set(CTestName "${TestFixture}:${Name}") - else() - set(CTestName "${Name}") - endif() - if(PARSE_CATCH_TESTS_ADD_TARGET_IN_TEST_NAME) - set(CTestName "${TestTarget}:${CTestName}") - endif() - # add target to labels to enable running all tests added from this target - set(Labels ${TestTarget}) - if(TestStringsLength EQUAL 2) - list(GET TestStrings 1 Tags) - string(TOLOWER "${Tags}" Tags) - # remove target from labels if the test is hidden - if("${Tags}" MATCHES ".*\\[!?(hide|\\.)\\].*") - list(REMOVE_ITEM Labels ${TestTarget}) - endif() - string(REPLACE "]" ";" Tags "${Tags}") - string(REPLACE "[" "" Tags "${Tags}") - else() - # unset tags variable from previous loop - unset(Tags) - endif() - - list(APPEND Labels ${Tags}) - - set(HiddenTagFound OFF) - foreach(label ${Labels}) - string(REGEX MATCH "^!hide|^\\." result ${label}) - if(result) - set(HiddenTagFound ON) - break() - endif() - endforeach(label) - if(PARSE_CATCH_TESTS_NO_HIDDEN_TESTS AND ${HiddenTagFound} AND ${CMAKE_VERSION} VERSION_LESS "3.9") - ParseAndAddCatchTests_PrintDebugMessage("Skipping test \"${CTestName}\" as it has [!hide], [.] or [.foo] label") - else() - ParseAndAddCatchTests_PrintDebugMessage("Adding test \"${CTestName}\"") - if(Labels) - ParseAndAddCatchTests_PrintDebugMessage("Setting labels to ${Labels}") - endif() - - # Escape commas in the test spec - string(REPLACE "," "\\," Name ${Name}) - - # Work around CMake 3.18.0 change in `add_test()`, before the escaped quotes were necessary, - # only with CMake 3.18.0 the escaped double quotes confuse the call. This change is reverted in 3.18.1 - # And properly introduced in 3.19 with the CMP0110 policy - if(_cmp0110_value STREQUAL "NEW" OR ${CMAKE_VERSION} VERSION_EQUAL "3.18") - ParseAndAddCatchTests_PrintDebugMessage("CMP0110 set to NEW, no need for add_test(\"\") workaround") - else() - ParseAndAddCatchTests_PrintDebugMessage("CMP0110 set to OLD adding \"\" for add_test() workaround") - set(CTestName "\"${CTestName}\"") - endif() - - # Handle template test cases - if("${TestTypeAndFixture}" MATCHES ".*TEMPLATE_.*") - set(Name "${Name} - *") - endif() - - # Add the test and set its properties - add_test(NAME "${CTestName}" COMMAND ${OptionalCatchTestLauncher} $ ${Name} ${AdditionalCatchParameters}) - # Old CMake versions do not document VERSION_GREATER_EQUAL, so we use VERSION_GREATER with 3.8 instead - if(PARSE_CATCH_TESTS_NO_HIDDEN_TESTS AND ${HiddenTagFound} AND ${CMAKE_VERSION} VERSION_GREATER "3.8") - ParseAndAddCatchTests_PrintDebugMessage("Setting DISABLED test property") - set_tests_properties("${CTestName}" PROPERTIES DISABLED ON) - else() - set_tests_properties("${CTestName}" PROPERTIES FAIL_REGULAR_EXPRESSION "No tests ran" - LABELS "${Labels}") - endif() - set_property( - TARGET ${TestTarget} - APPEND - PROPERTY ParseAndAddCatchTests_TESTS "${CTestName}") - set_property( - SOURCE ${SourceFile} - APPEND - PROPERTY ParseAndAddCatchTests_TESTS "${CTestName}") - endif() - - - endforeach() + list(APPEND Labels ${Tags}) + + set(HiddenTagFound OFF) + foreach(label ${Labels}) + string(REGEX MATCH "^!hide|^\\." result ${label}) + if(result) + set(HiddenTagFound ON) + break() + endif() + endforeach(label) + if(PARSE_CATCH_TESTS_NO_HIDDEN_TESTS AND ${HiddenTagFound} AND ${CMAKE_VERSION} VERSION_LESS "3.9") + ParseAndAddCatchTests_PrintDebugMessage("Skipping test \"${CTestName}\" as it has [!hide], [.] or [.foo] label") + else() + ParseAndAddCatchTests_PrintDebugMessage("Adding test \"${CTestName}\"") + if(Labels) + ParseAndAddCatchTests_PrintDebugMessage("Setting labels to ${Labels}") + endif() + + # Escape commas in the test spec + string(REPLACE "," "\\," Name ${Name}) + + # Work around CMake 3.18.0 change in `add_test()`, before the escaped quotes were necessary, + # only with CMake 3.18.0 the escaped double quotes confuse the call. This change is reverted in 3.18.1 + # And properly introduced in 3.19 with the CMP0110 policy + if(_cmp0110_value STREQUAL "NEW" OR ${CMAKE_VERSION} VERSION_EQUAL "3.18") + ParseAndAddCatchTests_PrintDebugMessage("CMP0110 set to NEW, no need for add_test(\"\") workaround") + else() + ParseAndAddCatchTests_PrintDebugMessage("CMP0110 set to OLD adding \"\" for add_test() workaround") + set(CTestName "\"${CTestName}\"") + endif() + + # Handle template test cases + if("${TestTypeAndFixture}" MATCHES ".*TEMPLATE_.*") + set(Name "${Name} - *") + endif() + + # Add the test and set its properties + add_test(NAME "${CTestName}" COMMAND ${OptionalCatchTestLauncher} $ ${Name} ${AdditionalCatchParameters}) + # Old CMake versions do not document VERSION_GREATER_EQUAL, so we use VERSION_GREATER with 3.8 instead + if(PARSE_CATCH_TESTS_NO_HIDDEN_TESTS AND ${HiddenTagFound} AND ${CMAKE_VERSION} VERSION_GREATER "3.8") + ParseAndAddCatchTests_PrintDebugMessage("Setting DISABLED test property") + set_tests_properties("${CTestName}" PROPERTIES DISABLED ON) + else() + set_tests_properties("${CTestName}" PROPERTIES FAIL_REGULAR_EXPRESSION "No tests ran" + LABELS "${Labels}") + endif() + set_property( + TARGET ${TestTarget} + APPEND + PROPERTY ParseAndAddCatchTests_TESTS "${CTestName}") + set_property( + SOURCE ${SourceFile} + APPEND + PROPERTY ParseAndAddCatchTests_TESTS "${CTestName}") + endif() + endforeach() endfunction() # entry point function(ParseAndAddCatchTests TestTarget) - message(DEPRECATION "ParseAndAddCatchTest: function deprecated because of possibility of missed test cases. Consider using 'catch_discover_tests' from 'Catch.cmake'") - ParseAndAddCatchTests_PrintDebugMessage("Started parsing ${TestTarget}") - get_target_property(SourceFiles ${TestTarget} SOURCES) - ParseAndAddCatchTests_PrintDebugMessage("Found the following sources: ${SourceFiles}") - foreach(SourceFile ${SourceFiles}) - ParseAndAddCatchTests_ParseFile(${SourceFile} ${TestTarget}) - endforeach() - ParseAndAddCatchTests_PrintDebugMessage("Finished parsing ${TestTarget}") + message(DEPRECATION "ParseAndAddCatchTest: function deprecated because of possibility of missed test cases. Consider using 'catch_discover_tests' from 'Catch.cmake'") + ParseAndAddCatchTests_PrintDebugMessage("Started parsing ${TestTarget}") + get_target_property(SourceFiles ${TestTarget} SOURCES) + ParseAndAddCatchTests_PrintDebugMessage("Found the following sources: ${SourceFiles}") + foreach(SourceFile ${SourceFiles}) + ParseAndAddCatchTests_ParseFile(${SourceFile} ${TestTarget}) + endforeach() + ParseAndAddCatchTests_PrintDebugMessage("Finished parsing ${TestTarget}") endfunction() diff --git a/extras/catch_amalgamated.cpp b/extras/catch_amalgamated.cpp index b979eb2..892d637 100644 --- a/extras/catch_amalgamated.cpp +++ b/extras/catch_amalgamated.cpp @@ -6,8 +6,8 @@ // SPDX-License-Identifier: BSL-1.0 -// Catch v3.8.1 -// Generated: 2025-04-08 12:33:19.863332 +// Catch v3.11.0 +// Generated: 2025-09-30 10:49:12.549018 // ---------------------------------------------------------- // This file is an amalgamation of multiple different files. // You probably shouldn't edit it directly. @@ -825,6 +825,8 @@ namespace Catch { m_data.reporterSpecifications.push_back( std::move( *parsed ) ); } + // Reading bazel env vars can change some parts of the config data, + // so we have to process the bazel env before acting on the config. if ( enableBazelEnvSupport() ) { readBazelEnvVars(); } @@ -889,6 +891,8 @@ namespace Catch { bool Config::showHelp() const { return m_data.showHelp; } + std::string const& Config::getExitGuardFilePath() const { return m_data.prematureExitGuardFilePath; } + // IConfig interface bool Config::allowThrows() const { return !m_data.noThrow; } StringRef Config::name() const { return m_data.name.empty() ? m_data.processName : m_data.name; } @@ -950,6 +954,26 @@ namespace Catch { m_data.shardCount = bazelShardOptions->shardCount; } } + + const auto bazelExitGuardFile = Detail::getEnv( "TEST_PREMATURE_EXIT_FILE" ); + if (bazelExitGuardFile) { + m_data.prematureExitGuardFilePath = bazelExitGuardFile; + } + + const auto bazelRandomSeed = Detail::getEnv( "TEST_RANDOM_SEED" ); + if ( bazelRandomSeed ) { + auto parsedSeed = parseUInt( bazelRandomSeed, 0 ); + if ( !parsedSeed ) { + // Currently we handle issues with parsing other Bazel Env + // options by warning and ignoring the issue. So we do the + // same for random seed option. + Catch::cerr() + << "Warning: could not parse 'TEST_RANDOM_SEED' ('" + << bazelRandomSeed << "') as proper seed.\n"; + } else { + m_data.rngSeed = *parsedSeed; + } + } } } // end namespace Catch @@ -975,28 +999,26 @@ namespace Catch { ScopedMessage::ScopedMessage( MessageBuilder&& builder ): - m_info( CATCH_MOVE(builder.m_info) ) { - m_info.message = builder.m_stream.str(); - getResultCapture().pushScopedMessage( m_info ); + m_messageId( builder.m_info.sequence ) { + MessageInfo info( CATCH_MOVE( builder.m_info ) ); + info.message = builder.m_stream.str(); + IResultCapture::pushScopedMessage( CATCH_MOVE( info ) ); } ScopedMessage::ScopedMessage( ScopedMessage&& old ) noexcept: - m_info( CATCH_MOVE( old.m_info ) ) { + m_messageId( old.m_messageId ) { old.m_moved = true; } ScopedMessage::~ScopedMessage() { - if ( !uncaught_exceptions() && !m_moved ){ - getResultCapture().popScopedMessage(m_info); - } + if ( !m_moved ) { IResultCapture::popScopedMessage( m_messageId ); } } Capturer::Capturer( StringRef macroName, SourceLineInfo const& lineInfo, ResultWas::OfType resultType, - StringRef names ): - m_resultCapture( getResultCapture() ) { + StringRef names ) { auto trimmed = [&] (size_t start, size_t end) { while (names[start] == ',' || isspace(static_cast(names[start]))) { ++start; @@ -1042,8 +1064,8 @@ namespace Catch { case ',': if (start != pos && openings.empty()) { m_messages.emplace_back(macroName, lineInfo, resultType); - m_messages.back().message = static_cast(trimmed(start, pos)); - m_messages.back().message += " := "; + m_messages.back().message += trimmed(start, pos); + m_messages.back().message += " := "_sr; start = pos; } break; @@ -1052,21 +1074,20 @@ namespace Catch { } assert(openings.empty() && "Mismatched openings"); m_messages.emplace_back(macroName, lineInfo, resultType); - m_messages.back().message = static_cast(trimmed(start, names.size() - 1)); - m_messages.back().message += " := "; + m_messages.back().message += trimmed(start, names.size() - 1); + m_messages.back().message += " := "_sr; } Capturer::~Capturer() { - if ( !uncaught_exceptions() ){ - assert( m_captured == m_messages.size() ); - for( size_t i = 0; i < m_captured; ++i ) - m_resultCapture.popScopedMessage( m_messages[i] ); + assert( m_captured == m_messages.size() ); + for (auto const& message : m_messages) { + IResultCapture::popScopedMessage( message.sequence ); } } void Capturer::captureValue( size_t index, std::string const& value ) { assert( index < m_messages.size() ); m_messages[index].message += value; - m_resultCapture.pushScopedMessage( m_messages[index] ); + IResultCapture::pushScopedMessage( CATCH_MOVE( m_messages[index] ) ); m_captured++; } @@ -1150,7 +1171,6 @@ namespace Catch { } void cleanUp() { cleanupSingletons(); - cleanUpContext(); } std::string translateActiveException() { return getRegistryHub().getExceptionTranslatorRegistry().translateActiveException(); @@ -1161,8 +1181,9 @@ namespace Catch { -#include #include +#include +#include #include #include #include @@ -1170,14 +1191,6 @@ namespace Catch { namespace Catch { namespace { - static constexpr int TestFailureExitCode = 42; - static constexpr int UnspecifiedErrorExitCode = 1; - static constexpr int AllTestsSkippedExitCode = 4; - static constexpr int NoTestsRunExitCode = 2; - static constexpr int UnmatchedTestSpecExitCode = 3; - static constexpr int InvalidTestSpecExitCode = 5; - - IEventListenerPtr createReporter(std::string const& reporterName, ReporterConfig&& config) { auto reporter = Catch::getRegistryHub().getReporterRegistry().create(reporterName, CATCH_MOVE(config)); CATCH_ENFORCE(reporter, "No reporter registered with name: '" << reporterName << '\''); @@ -1285,6 +1298,50 @@ namespace Catch { } } + // Creates empty file at path. The path must be writable, we do not + // try to create directories in path because that's hard in C++14. + void setUpGuardFile( std::string const& guardFilePath ) { + if ( !guardFilePath.empty() ) { +#if defined( _MSC_VER ) + std::FILE* file = nullptr; + if ( fopen_s( &file, guardFilePath.c_str(), "w" ) ) { + char msgBuffer[100]; + const auto err = errno; + std::string errMsg; + if ( !strerror_s( msgBuffer, err ) ) { + errMsg = msgBuffer; + } else { + errMsg = "Could not translate errno to a string"; + } + +#else + std::FILE* file = std::fopen( guardFilePath.c_str(), "w" ); + if ( !file ) { + const auto err = errno; + const char* errMsg = std::strerror( err ); +#endif + + CATCH_RUNTIME_ERROR( "Could not open the exit guard file '" + << guardFilePath << "' because '" + << errMsg << "' (" << err << ')' ); + } + const int ret = std::fclose( file ); + CATCH_ENFORCE( + ret == 0, + "Error when closing the exit guard file: " << ret ); + } + } + + // Removes file at path. Assuming we created it in setUpGuardFile. + void tearDownGuardFile( std::string const& guardFilePath ) { + if ( !guardFilePath.empty() ) { + const int ret = std::remove( guardFilePath.c_str() ); + CATCH_ENFORCE( + ret == 0, + "Error when removing the exit guard file: " << ret ); + } + } + } // anon namespace Session::Session() { @@ -1403,6 +1460,7 @@ namespace Catch { static_cast(std::getchar()); } int exitCode = runInternal(); + if( ( m_configData.waitForKeypress & WaitForKeypress::BeforeExit ) != 0 ) { Catch::cout() << "...waiting for enter/ return before exiting, with code: " << exitCode << '\n' << std::flush; static_cast(std::getchar()); @@ -1443,6 +1501,10 @@ namespace Catch { CATCH_TRY { config(); // Force config to be constructed + // We need to retrieve potential Bazel config with the full Config + // constructor, so we have to create the guard file after it is created. + setUpGuardFile( m_config->getExitGuardFilePath() ); + seedRng( *m_config ); if (m_configData.filenamesAsTags) { @@ -1472,9 +1534,12 @@ namespace Catch { TestGroup tests { CATCH_MOVE(reporter), m_config.get() }; auto const totals = tests.execute(); + // If we got here, running the tests finished normally-enough. + // They might've failed, but that would've been reported elsewhere. + tearDownGuardFile( m_config->getExitGuardFilePath() ); + if ( tests.hadUnmatchedTestSpecs() && m_config->warnAboutUnmatchedTestSpecs() ) { - // UnmatchedTestSpecExitCode return UnmatchedTestSpecExitCode; } @@ -1935,7 +2000,6 @@ namespace Catch { -#include #include namespace Catch { @@ -1985,35 +2049,35 @@ namespace Detail { std::string ret; // This is enough for the "don't escape invisibles" case, and a good // lower bound on the "escape invisibles" case. - ret.reserve(string.size() + 2); + ret.reserve( string.size() + 2 ); - if (!escapeInvisibles) { + if ( !escapeInvisibles ) { ret += '"'; ret += string; ret += '"'; return ret; } + size_t last_start = 0; + auto write_to = [&]( size_t idx ) { + if ( last_start < idx ) { + ret += string.substr( last_start, idx - last_start ); + } + last_start = idx + 1; + }; + ret += '"'; - for (char c : string) { - switch (c) { - case '\r': - ret.append("\\r"); - break; - case '\n': - ret.append("\\n"); - break; - case '\t': - ret.append("\\t"); - break; - case '\f': - ret.append("\\f"); - break; - default: - ret.push_back(c); - break; + for ( size_t i = 0; i < string.size(); ++i ) { + const char c = string[i]; + if ( c == '\r' || c == '\n' || c == '\t' || c == '\f' ) { + write_to( i ); + if ( c == '\r' ) { ret.append( "\\r" ); } + if ( c == '\n' ) { ret.append( "\\n" ); } + if ( c == '\t' ) { ret.append( "\\t" ); } + if ( c == '\f' ) { ret.append( "\\f" ); } } } + write_to( string.size() ); ret += '"'; return ret; @@ -2038,6 +2102,13 @@ namespace Detail { rss << std::setw(2) << static_cast(bytes[i]); return rss.str(); } + + std::string makeExceptionHappenedString() { + return "{ stringification failed with an exception: \"" + + translateActiveException() + "\" }"; + + } + } // end Detail namespace @@ -2283,7 +2354,7 @@ namespace Catch { } Version const& libraryVersion() { - static Version version( 3, 8, 1, "", 0 ); + static Version version( 3, 11, 0, "", 0 ); return version; } @@ -2371,8 +2442,14 @@ namespace Catch { namespace Catch { + namespace Detail { + void missingCaptureInstance() { + CATCH_INTERNAL_ERROR( "No result capture instance" ); + } + } // namespace Detail + IResultCapture::~IResultCapture() = default; -} +} // namespace Catch @@ -3365,6 +3442,9 @@ namespace Catch { | Opt( config.allowZeroTests ) ["--allow-running-no-tests"] ( "Treat 'No tests run' as a success" ) + | Opt( config.prematureExitGuardFilePath, "path" ) + ["--premature-exit-guard-file"] + ( "create a file before running tests and delete it during clean exit" ) | Arg( config.testsOrTags, "test name|pattern|tags" ) ( "which test or tests to use" ); @@ -3519,7 +3599,11 @@ namespace { #endif // Windows/ ANSI/ None -#if defined( CATCH_PLATFORM_LINUX ) || defined( CATCH_PLATFORM_MAC ) || defined( __GLIBC__ ) +#if defined( CATCH_PLATFORM_LINUX ) \ + || defined( CATCH_PLATFORM_MAC ) \ + || defined( __GLIBC__ ) \ + || defined( __FreeBSD__ ) \ + || defined( CATCH_PLATFORM_QNX ) # define CATCH_INTERNAL_HAS_ISATTY # include #endif @@ -3643,20 +3727,10 @@ namespace Catch { namespace Catch { - Context* Context::currentContext = nullptr; - - void cleanUpContext() { - delete Context::currentContext; - Context::currentContext = nullptr; - } - void Context::createContext() { - currentContext = new Context(); - } + Context Context::currentContext; Context& getCurrentMutableContext() { - if ( !Context::currentContext ) { Context::createContext(); } - // NOLINTNEXTLINE(clang-analyzer-core.uninitialized.UndefReturn) - return *Context::currentContext; + return Context::currentContext; } SimplePcg32& sharedRng() { @@ -3761,7 +3835,7 @@ namespace Catch { #endif } // namespace Catch -#elif defined(CATCH_PLATFORM_LINUX) +#elif defined(CATCH_PLATFORM_LINUX) || defined(CATCH_PLATFORM_QNX) #include #include @@ -3993,10 +4067,10 @@ namespace Catch { // To avoid having to handle TFE explicitly everywhere, we just // rethrow it so that it goes back up the caller. catch( TestFailureException& ) { - std::rethrow_exception(std::current_exception()); + return "{ nested assertion failed }"; } catch( TestSkipException& ) { - std::rethrow_exception(std::current_exception()); + return "{ nested SKIP() called }"; } catch( std::exception const& ex ) { return ex.what(); @@ -4088,30 +4162,34 @@ namespace Catch { // There is no 1-1 mapping between signals and windows exceptions. // Windows can easily distinguish between SO and SigSegV, // but SigInt, SigTerm, etc are handled differently. - static SignalDefs signalDefs[] = { + static constexpr SignalDefs signalDefs[] = { { EXCEPTION_ILLEGAL_INSTRUCTION, "SIGILL - Illegal instruction signal" }, { EXCEPTION_STACK_OVERFLOW, "SIGSEGV - Stack overflow" }, { EXCEPTION_ACCESS_VIOLATION, "SIGSEGV - Segmentation violation signal" }, { EXCEPTION_INT_DIVIDE_BY_ZERO, "Divide by zero error" }, }; + // Since we do not support multiple instantiations, we put these + // into global variables and rely on cleaning them up in outlined + // constructors/destructors + static LPTOP_LEVEL_EXCEPTION_FILTER previousTopLevelExceptionFilter = nullptr; + + static LONG CALLBACK topLevelExceptionFilter(PEXCEPTION_POINTERS ExceptionInfo) { for (auto const& def : signalDefs) { if (ExceptionInfo->ExceptionRecord->ExceptionCode == def.id) { reportFatal(def.name); } } - // If its not an exception we care about, pass it along. + // If a filter was previously registered, invoke it + if (previousTopLevelExceptionFilter) { + return previousTopLevelExceptionFilter(ExceptionInfo); + } + // Otherwise, pass along all exceptions. // This stops us from eating debugger breaks etc. return EXCEPTION_CONTINUE_SEARCH; } - // Since we do not support multiple instantiations, we put these - // into global variables and rely on cleaning them up in outlined - // constructors/destructors - static LPTOP_LEVEL_EXCEPTION_FILTER previousTopLevelExceptionFilter = nullptr; - - // For MSVC, we reserve part of the stack memory for handling // memory overflow structured exception. FatalConditionHandler::FatalConditionHandler() { @@ -4159,7 +4237,7 @@ namespace Catch { const char* name; }; - static SignalDefs signalDefs[] = { + static constexpr SignalDefs signalDefs[] = { { SIGINT, "SIGINT - Terminal interrupt signal" }, { SIGILL, "SIGILL - Illegal instruction signal" }, { SIGFPE, "SIGFPE - Floating point error signal" }, @@ -4323,8 +4401,6 @@ namespace Catch { #include #include -#include -#include namespace Catch { @@ -4465,6 +4541,33 @@ namespace Detail { namespace Catch { + + namespace { + static bool needsEscape( char c ) { + return c == '"' || c == '\\' || c == '\b' || c == '\f' || + c == '\n' || c == '\r' || c == '\t'; + } + + static Catch::StringRef makeEscapeStringRef( char c ) { + if ( c == '"' ) { + return "\\\""_sr; + } else if ( c == '\\' ) { + return "\\\\"_sr; + } else if ( c == '\b' ) { + return "\\b"_sr; + } else if ( c == '\f' ) { + return "\\f"_sr; + } else if ( c == '\n' ) { + return "\\n"_sr; + } else if ( c == '\r' ) { + return "\\r"_sr; + } else if ( c == '\t' ) { + return "\\t"_sr; + } + Catch::Detail::Unreachable(); + } + } // namespace + void JsonUtils::indent( std::ostream& os, std::uint64_t level ) { for ( std::uint64_t i = 0; i < level; ++i ) { os << " "; @@ -4574,30 +4677,19 @@ namespace Catch { void JsonValueWriter::writeImpl( Catch::StringRef value, bool quote ) { if ( quote ) { m_os << '"'; } - for (char c : value) { - // Escape list taken from https://www.json.org/json-en.html, - // string definition. - // Note that while forward slash _can_ be escaped, it does - // not have to be, if JSON is not further embedded somewhere - // where forward slash is meaningful. - if ( c == '"' ) { - m_os << "\\\""; - } else if ( c == '\\' ) { - m_os << "\\\\"; - } else if ( c == '\b' ) { - m_os << "\\b"; - } else if ( c == '\f' ) { - m_os << "\\f"; - } else if ( c == '\n' ) { - m_os << "\\n"; - } else if ( c == '\r' ) { - m_os << "\\r"; - } else if ( c == '\t' ) { - m_os << "\\t"; - } else { - m_os << c; + size_t current_start = 0; + for ( size_t i = 0; i < value.size(); ++i ) { + if ( needsEscape( value[i] ) ) { + if ( current_start < i ) { + m_os << value.substr( current_start, i - current_start ); + } + m_os << makeEscapeStringRef( value[i] ); + current_start = i + 1; } } + if ( current_start < value.size() ) { + m_os << value.substr( current_start, value.size() - current_start ); + } if ( quote ) { m_os << '"'; } } @@ -4765,7 +4857,7 @@ namespace Catch { namespace Catch { CATCH_INTERNAL_START_WARNINGS_SUPPRESSION CATCH_INTERNAL_SUPPRESS_GLOBALS_WARNINGS - static LeakDetector leakDetector; + static const LeakDetector leakDetector; CATCH_INTERNAL_STOP_WARNINGS_SUPPRESSION } @@ -4794,17 +4886,18 @@ int main (int argc, char * argv[]) { namespace Catch { - MessageInfo::MessageInfo( StringRef _macroName, - SourceLineInfo const& _lineInfo, - ResultWas::OfType _type ) + MessageInfo::MessageInfo( StringRef _macroName, + SourceLineInfo const& _lineInfo, + ResultWas::OfType _type ) : macroName( _macroName ), lineInfo( _lineInfo ), type( _type ), sequence( ++globalCount ) {} - // This may need protecting if threading support is added - unsigned int MessageInfo::globalCount = 0; + // Messages are owned by their individual threads, so the counter should be thread-local as well. + // Alternative consideration: atomic, so threads don't share IDs and things are easier to debug. + thread_local unsigned int MessageInfo::globalCount = 0; } // end namespace Catch @@ -5216,6 +5309,12 @@ namespace Catch { +#if defined( __clang__ ) +# define CATCH2_CLANG_NO_SANITIZE_INTEGER \ + __attribute__( ( no_sanitize( "unsigned-integer-overflow" ) ) ) +#else +# define CATCH2_CLANG_NO_SANITIZE_INTEGER +#endif namespace Catch { namespace { @@ -5225,6 +5324,7 @@ namespace { #pragma warning(disable:4146) // we negate uint32 during the rotate #endif // Safe rotr implementation thanks to John Regehr + CATCH2_CLANG_NO_SANITIZE_INTEGER uint32_t rotate_right(uint32_t val, uint32_t count) { const uint32_t mask = 31; count &= mask; @@ -5258,6 +5358,7 @@ namespace { } } + CATCH2_CLANG_NO_SANITIZE_INTEGER SimplePcg32::result_type SimplePcg32::operator()() { // prepare the output value const uint32_t xorshifted = static_cast(((m_state >> 18u) ^ m_state) >> 27u); @@ -5546,6 +5647,7 @@ ReporterSpec::ReporterSpec( #include #include +#include #include namespace Catch { @@ -5555,34 +5657,40 @@ namespace Catch { std::vector> m_streams; std::vector m_unused; std::ostringstream m_referenceStream; // Used for copy state/ flags from + Detail::Mutex m_mutex; - auto add() -> std::size_t { + auto add() -> std::pair { + Detail::LockGuard _( m_mutex ); if( m_unused.empty() ) { m_streams.push_back( Detail::make_unique() ); - return m_streams.size()-1; + return { m_streams.size()-1, m_streams.back().get() }; } else { auto index = m_unused.back(); m_unused.pop_back(); - return index; + return { index, m_streams[index].get() }; } } - void release( std::size_t index ) { - m_streams[index]->copyfmt( m_referenceStream ); // Restore initial flags and other state - m_unused.push_back(index); + void release( std::size_t index, std::ostream* originalPtr ) { + assert( originalPtr ); + originalPtr->copyfmt( m_referenceStream ); // Restore initial flags and other state + + Detail::LockGuard _( m_mutex ); + assert( originalPtr == m_streams[index].get() && "Mismatch between release index and stream ptr" ); + m_unused.push_back( index ); } }; - ReusableStringStream::ReusableStringStream() - : m_index( Singleton::getMutable().add() ), - m_oss( Singleton::getMutable().m_streams[m_index].get() ) - {} + ReusableStringStream::ReusableStringStream() { + std::tie( m_index, m_oss ) = + Singleton::getMutable().add(); + } ReusableStringStream::~ReusableStringStream() { static_cast( m_oss )->str(""); m_oss->clear(); - Singleton::getMutable().release( m_index ); + Singleton::getMutable().release( m_index, m_oss ); } std::string ReusableStringStream::str() const { @@ -5742,23 +5850,64 @@ namespace Catch { } // namespace } + namespace Detail { + // Assertions are owned by the thread that is executing them. + // This allows for lock-free progress in common cases where we + // do not need to send the assertion events to the reporter. + // This also implies that messages are owned by their respective + // threads, and should not be shared across different threads. + // + // This implies that various pieces of metadata referring to last + // assertion result/source location/message handling, etc + // should also be thread local. For now we just use naked globals + // below, in the future we will want to allocate piece of memory + // from heap, to avoid consuming too much thread-local storage. + + // This is used for the "if" part of CHECKED_IF/CHECKED_ELSE + static thread_local bool g_lastAssertionPassed = false; + + // This is the source location for last encountered macro. It is + // used to provide the users with more precise location of error + // when an unexpected exception/fatal error happens. + static thread_local SourceLineInfo g_lastKnownLineInfo("DummyLocation", static_cast(-1)); + + // Should we clear message scopes before sending off the messages to + // reporter? Set in `assertionPassedFastPath` to avoid doing the full + // clear there for performance reasons. + static thread_local bool g_clearMessageScopes = false; + + CATCH_INTERNAL_START_WARNINGS_SUPPRESSION + CATCH_INTERNAL_SUPPRESS_GLOBALS_WARNINGS + // Actual messages to be provided to the reporter + static thread_local std::vector g_messages; + + // Owners for the UNSCOPED_X information macro + static thread_local std::vector g_messageScopes; + CATCH_INTERNAL_STOP_WARNINGS_SUPPRESSION + + } // namespace Detail + RunContext::RunContext(IConfig const* _config, IEventListenerPtr&& reporter) : m_runInfo(_config->name()), m_config(_config), m_reporter(CATCH_MOVE(reporter)), - m_lastAssertionInfo{ StringRef(), SourceLineInfo("",0), StringRef(), ResultDisposition::Normal }, m_outputRedirect( makeOutputRedirect( m_reporter->getPreferences().shouldRedirectStdOut ) ), - m_includeSuccessfulResults( m_config->includeSuccessfulResults() || m_reporter->getPreferences().shouldReportAllAssertions ) + m_abortAfterXFailedAssertions( m_config->abortAfter() ), + m_reportAssertionStarting( m_reporter->getPreferences().shouldReportAllAssertionStarts ), + m_includeSuccessfulResults( m_config->includeSuccessfulResults() || m_reporter->getPreferences().shouldReportAllAssertions ), + m_shouldDebugBreak( m_config->shouldDebugBreak() ) { getCurrentMutableContext().setResultCapture( this ); m_reporter->testRunStarting(m_runInfo); } RunContext::~RunContext() { + updateTotalsFromAtomics(); m_reporter->testRunEnded(TestRunStats(m_runInfo, m_totals, aborting())); } Totals RunContext::runTest(TestCaseHandle const& testCase) { + updateTotalsFromAtomics(); const Totals prevTotals = m_totals; auto const& testInfo = testCase.getTestCaseInfo(); @@ -5813,6 +5962,7 @@ namespace Catch { m_reporter->testCasePartialStarting(testInfo, testRuns); + updateTotalsFromAtomics(); const auto beforeRunTotals = m_totals; runCurrentTest(); std::string oneRunCout = m_outputRedirect->getStdout(); @@ -5821,6 +5971,7 @@ namespace Catch { redirectedCout += oneRunCout; redirectedCerr += oneRunCerr; + updateTotalsFromAtomics(); const auto singleRunTotals = m_totals.delta(beforeRunTotals); auto statsForOneRun = TestCaseStats(testInfo, singleRunTotals, CATCH_MOVE(oneRunCout), CATCH_MOVE(oneRunCerr), aborting()); m_reporter->testCasePartialEnded(statsForOneRun, testRuns); @@ -5850,47 +6001,54 @@ namespace Catch { void RunContext::assertionEnded(AssertionResult&& result) { + Detail::g_lastKnownLineInfo = result.m_info.lineInfo; if (result.getResultType() == ResultWas::Ok) { - m_totals.assertions.passed++; - m_lastAssertionPassed = true; + m_atomicAssertionCount.passed++; + Detail::g_lastAssertionPassed = true; } else if (result.getResultType() == ResultWas::ExplicitSkip) { - m_totals.assertions.skipped++; - m_lastAssertionPassed = true; + m_atomicAssertionCount.skipped++; + Detail::g_lastAssertionPassed = true; } else if (!result.succeeded()) { - m_lastAssertionPassed = false; + Detail::g_lastAssertionPassed = false; if (result.isOk()) { } - else if( m_activeTestCase->getTestCaseInfo().okToFail() ) - m_totals.assertions.failedButOk++; + else if( m_activeTestCase->getTestCaseInfo().okToFail() ) // Read from a shared state established before the threads could start, this is fine + m_atomicAssertionCount.failedButOk++; else - m_totals.assertions.failed++; + m_atomicAssertionCount.failed++; } else { - m_lastAssertionPassed = true; + Detail::g_lastAssertionPassed = true; } + if ( Detail::g_clearMessageScopes ) { + Detail::g_messageScopes.clear(); + Detail::g_clearMessageScopes = false; + } + + // From here, we are touching shared state and need mutex. + Detail::LockGuard lock( m_assertionMutex ); { auto _ = scopedDeactivate( *m_outputRedirect ); - m_reporter->assertionEnded( AssertionStats( result, m_messages, m_totals ) ); + updateTotalsFromAtomics(); + m_reporter->assertionEnded( AssertionStats( result, Detail::g_messages, m_totals ) ); } if ( result.getResultType() != ResultWas::Warning ) { - m_messageScopes.clear(); + Detail::g_messageScopes.clear(); } // Reset working state. assertion info will be reset after // populateReaction is run if it is needed m_lastResult = CATCH_MOVE( result ); } - void RunContext::resetAssertionInfo() { - m_lastAssertionInfo.macroName = StringRef(); - m_lastAssertionInfo.capturedExpression = "{Unknown expression after the reported line}"_sr; - m_lastAssertionInfo.resultDisposition = ResultDisposition::Normal; - } void RunContext::notifyAssertionStarted( AssertionInfo const& info ) { - auto _ = scopedDeactivate( *m_outputRedirect ); - m_reporter->assertionStarting( info ); + if (m_reportAssertionStarting) { + Detail::LockGuard lock( m_assertionMutex ); + auto _ = scopedDeactivate( *m_outputRedirect ); + m_reporter->assertionStarting( info ); + } } bool RunContext::sectionStarted( StringRef sectionName, @@ -5906,13 +6064,14 @@ namespace Catch { m_activeSections.push_back(§ionTracker); SectionInfo sectionInfo( sectionLineInfo, static_cast(sectionName) ); - m_lastAssertionInfo.lineInfo = sectionInfo.lineInfo; + Detail::g_lastKnownLineInfo = sectionLineInfo; { auto _ = scopedDeactivate( *m_outputRedirect ); m_reporter->sectionStarting( sectionInfo ); } + updateTotalsFromAtomics(); assertions = m_totals.assertions; return true; @@ -5920,12 +6079,11 @@ namespace Catch { IGeneratorTracker* RunContext::acquireGeneratorTracker( StringRef generatorName, SourceLineInfo const& lineInfo ) { - using namespace Generators; - GeneratorTracker* tracker = GeneratorTracker::acquire( + auto* tracker = Generators::GeneratorTracker::acquire( m_trackerContext, TestCaseTracking::NameAndLocationRef( generatorName, lineInfo ) ); - m_lastAssertionInfo.lineInfo = lineInfo; + Detail::g_lastKnownLineInfo = lineInfo; return tracker; } @@ -5938,7 +6096,7 @@ namespace Catch { auto& currentTracker = m_trackerContext.currentTracker(); assert( currentTracker.nameAndLocation() != nameAndLoc && - "Trying to create tracker for a genreator that already has one" ); + "Trying to create tracker for a generator that already has one" ); auto newTracker = Catch::Detail::make_unique( CATCH_MOVE(nameAndLoc), m_trackerContext, ¤tTracker ); @@ -5957,12 +6115,13 @@ namespace Catch { return false; if (m_trackerContext.currentTracker().hasChildren()) return false; - m_totals.assertions.failed++; + m_atomicAssertionCount.failed++; assertions.failed++; return true; } void RunContext::sectionEnded(SectionEndInfo&& endInfo) { + updateTotalsFromAtomics(); Counts assertions = m_totals.assertions - endInfo.prevAssertions; bool missingAssertions = testForMissingAssertions(assertions); @@ -5979,9 +6138,6 @@ namespace Catch { endInfo.durationInSeconds, missingAssertions ) ); } - - m_messages.clear(); - m_messageScopes.clear(); } void RunContext::sectionEndedEarly(SectionEndInfo&& endInfo) { @@ -6012,18 +6168,6 @@ namespace Catch { m_reporter->benchmarkFailed( error ); } - void RunContext::pushScopedMessage(MessageInfo const & message) { - m_messages.push_back(message); - } - - void RunContext::popScopedMessage(MessageInfo const & message) { - m_messages.erase(std::remove(m_messages.begin(), m_messages.end(), message), m_messages.end()); - } - - void RunContext::emplaceUnscopedMessage( MessageBuilder&& builder ) { - m_messageScopes.emplace_back( CATCH_MOVE(builder) ); - } - std::string RunContext::getCurrentTestName() const { return m_activeTestCase ? m_activeTestCase->getTestCaseInfo().name @@ -6031,6 +6175,18 @@ namespace Catch { } const AssertionResult * RunContext::getLastResult() const { + // m_lastResult is updated inside the assertion slow-path, under + // a mutex, so the read needs to happen under mutex as well. + + // TBD: The last result only makes sense if it is a thread-local + // thing, because the answer is different per thread, like + // last line info, whether last assertion passed, and so on. + // + // However, the last result was also never updated in the + // assertion fast path, so it was always somewhat broken, + // and since IResultCapture::getLastResult is deprecated, + // we will leave it as is, until it is finally removed. + Detail::LockGuard _( m_assertionMutex ); return &(*m_lastResult); } @@ -6039,28 +6195,44 @@ namespace Catch { } void RunContext::handleFatalErrorCondition( StringRef message ) { - // TODO: scoped deactivate here? Just give up and do best effort? - // the deactivation can break things further, OTOH so can the - // capture - auto _ = scopedDeactivate( *m_outputRedirect ); + // We lock only when touching the reporters directly, to avoid + // deadlocks when we call into other functions that also want + // to lock the mutex before touching reporters. + // + // This does mean that we allow other threads to run while handling + // a fatal error, but this is all a best effort attempt anyway. + { + Detail::LockGuard lock( m_assertionMutex ); + // TODO: scoped deactivate here? Just give up and do best effort? + // the deactivation can break things further, OTOH so can the + // capture + auto _ = scopedDeactivate( *m_outputRedirect ); - // First notify reporter that bad things happened - m_reporter->fatalErrorEncountered( message ); + // First notify reporter that bad things happened + m_reporter->fatalErrorEncountered( message ); + } // Don't rebuild the result -- the stringification itself can cause more fatal errors // Instead, fake a result data. AssertionResultData tempResult( ResultWas::FatalErrorCondition, { false } ); tempResult.message = static_cast(message); - AssertionResult result(m_lastAssertionInfo, CATCH_MOVE(tempResult)); + AssertionResult result( makeDummyAssertionInfo(), + CATCH_MOVE( tempResult ) ); assertionEnded(CATCH_MOVE(result) ); - resetAssertionInfo(); + + + // At this point we touch sections/test cases from this thread + // to try and end them. Technically that is not supported when + // using multiple threads, but the worst thing that can happen + // is that the process aborts harder :-D + Detail::LockGuard lock( m_assertionMutex ); // Best effort cleanup for sections that have not been destructed yet // Since this is a fatal error, we have not had and won't have the opportunity to destruct them properly while (!m_activeSections.empty()) { - auto nl = m_activeSections.back()->nameAndLocation(); - SectionEndInfo endInfo{ SectionInfo(CATCH_MOVE(nl.location), CATCH_MOVE(nl.name)), {}, 0.0 }; + auto const& nl = m_activeSections.back()->nameAndLocation(); + SectionEndInfo endInfo{ SectionInfo(nl.location, nl.name), {}, 0.0 }; sectionEndedEarly(CATCH_MOVE(endInfo)); } handleUnfinishedSections(); @@ -6085,32 +6257,44 @@ namespace Catch { std::string(), false)); m_totals.testCases.failed++; + updateTotalsFromAtomics(); m_reporter->testRunEnded(TestRunStats(m_runInfo, m_totals, false)); } bool RunContext::lastAssertionPassed() { - return m_lastAssertionPassed; + return Detail::g_lastAssertionPassed; + } + + void RunContext::assertionPassedFastPath(SourceLineInfo lineInfo) { + // We want to save the line info for better experience with unexpected assertions + Detail::g_lastKnownLineInfo = lineInfo; + ++m_atomicAssertionCount.passed; + Detail::g_lastAssertionPassed = true; + Detail::g_clearMessageScopes = true; } - void RunContext::assertionPassed() { - m_lastAssertionPassed = true; - ++m_totals.assertions.passed; - resetAssertionInfo(); - m_messageScopes.clear(); + void RunContext::updateTotalsFromAtomics() { + m_totals.assertions = Counts{ + m_atomicAssertionCount.passed, + m_atomicAssertionCount.failed, + m_atomicAssertionCount.failedButOk, + m_atomicAssertionCount.skipped, + }; } bool RunContext::aborting() const { - return m_totals.assertions.failed >= static_cast(m_config->abortAfter()); + return m_atomicAssertionCount.failed >= m_abortAfterXFailedAssertions; } void RunContext::runCurrentTest() { auto const& testCaseInfo = m_activeTestCase->getTestCaseInfo(); SectionInfo testCaseSection(testCaseInfo.lineInfo, testCaseInfo.name); m_reporter->sectionStarting(testCaseSection); + updateTotalsFromAtomics(); Counts prevAssertions = m_totals.assertions; double duration = 0; m_shouldReportUnexpected = true; - m_lastAssertionInfo = { "TEST_CASE"_sr, testCaseInfo.lineInfo, StringRef(), ResultDisposition::Normal }; + Detail::g_lastKnownLineInfo = testCaseInfo.lineInfo; Timer timer; CATCH_TRY { @@ -6127,18 +6311,23 @@ namespace Catch { } CATCH_CATCH_ALL { // Under CATCH_CONFIG_FAST_COMPILE, unexpected exceptions under REQUIRE assertions // are reported without translation at the point of origin. - if( m_shouldReportUnexpected ) { + if ( m_shouldReportUnexpected ) { AssertionReaction dummyReaction; - handleUnexpectedInflightException( m_lastAssertionInfo, translateActiveException(), dummyReaction ); + handleUnexpectedInflightException( makeDummyAssertionInfo(), + translateActiveException(), + dummyReaction ); } } + updateTotalsFromAtomics(); Counts assertions = m_totals.assertions - prevAssertions; bool missingAssertions = testForMissingAssertions(assertions); m_testCaseTracker->close(); handleUnfinishedSections(); - m_messages.clear(); - m_messageScopes.clear(); + Detail::g_messageScopes.clear(); + // TBD: At this point, m_messages should be empty. Do we want to + // assert that this is true, or keep the defensive clear call? + Detail::g_messages.clear(); SectionStats testCaseSectionStats(CATCH_MOVE(testCaseSection), assertions, duration, missingAssertions); m_reporter->sectionEnded(testCaseSectionStats); @@ -6179,7 +6368,7 @@ namespace Catch { if( result ) { if (!m_includeSuccessfulResults) { - assertionPassed(); + assertionPassedFastPath(info.lineInfo); } else { reportExpr(info, ResultWas::Ok, &expr, negated); @@ -6187,9 +6376,9 @@ namespace Catch { } else { reportExpr(info, ResultWas::ExpressionFailed, &expr, negated ); - populateReaction( reaction ); + populateReaction( + reaction, info.resultDisposition & ResultDisposition::Normal ); } - resetAssertionInfo(); } void RunContext::reportExpr( AssertionInfo const &info, @@ -6197,7 +6386,7 @@ namespace Catch { ITransientExpression const *expr, bool negated ) { - m_lastAssertionInfo = info; + Detail::g_lastKnownLineInfo = info.lineInfo; AssertionResultData data( resultType, LazyExpression( negated ) ); AssertionResult assertionResult{ info, CATCH_MOVE( data ) }; @@ -6212,24 +6401,25 @@ namespace Catch { std::string&& message, AssertionReaction& reaction ) { - m_lastAssertionInfo = info; + Detail::g_lastKnownLineInfo = info.lineInfo; AssertionResultData data( resultType, LazyExpression( false ) ); data.message = CATCH_MOVE( message ); - AssertionResult assertionResult{ m_lastAssertionInfo, + AssertionResult assertionResult{ info, CATCH_MOVE( data ) }; const auto isOk = assertionResult.isOk(); assertionEnded( CATCH_MOVE(assertionResult) ); if ( !isOk ) { - populateReaction( reaction ); + populateReaction( + reaction, info.resultDisposition & ResultDisposition::Normal ); } else if ( resultType == ResultWas::ExplicitSkip ) { // TODO: Need to handle this explicitly, as ExplicitSkip is // considered "OK" reaction.shouldSkip = true; } - resetAssertionInfo(); } + void RunContext::handleUnexpectedExceptionNotThrown( AssertionInfo const& info, AssertionReaction& reaction @@ -6242,55 +6432,88 @@ namespace Catch { std::string&& message, AssertionReaction& reaction ) { - m_lastAssertionInfo = info; + Detail::g_lastKnownLineInfo = info.lineInfo; AssertionResultData data( ResultWas::ThrewException, LazyExpression( false ) ); data.message = CATCH_MOVE(message); AssertionResult assertionResult{ info, CATCH_MOVE(data) }; assertionEnded( CATCH_MOVE(assertionResult) ); - populateReaction( reaction ); - resetAssertionInfo(); + populateReaction( reaction, + info.resultDisposition & ResultDisposition::Normal ); } - void RunContext::populateReaction( AssertionReaction& reaction ) { - reaction.shouldDebugBreak = m_config->shouldDebugBreak(); - reaction.shouldThrow = aborting() || (m_lastAssertionInfo.resultDisposition & ResultDisposition::Normal); + void RunContext::populateReaction( AssertionReaction& reaction, + bool has_normal_disposition ) { + reaction.shouldDebugBreak = m_shouldDebugBreak; + reaction.shouldThrow = aborting() || has_normal_disposition; + } + + AssertionInfo RunContext::makeDummyAssertionInfo() { + const bool testCaseJustStarted = + Detail::g_lastKnownLineInfo == + m_activeTestCase->getTestCaseInfo().lineInfo; + + return AssertionInfo{ + testCaseJustStarted ? "TEST_CASE"_sr : StringRef(), + Detail::g_lastKnownLineInfo, + testCaseJustStarted ? StringRef() : "{Unknown expression after the reported line}"_sr, + ResultDisposition::Normal + }; } void RunContext::handleIncomplete( AssertionInfo const& info ) { using namespace std::string_literals; - m_lastAssertionInfo = info; + Detail::g_lastKnownLineInfo = info.lineInfo; AssertionResultData data( ResultWas::ThrewException, LazyExpression( false ) ); data.message = "Exception translation was disabled by CATCH_CONFIG_FAST_COMPILE"s; AssertionResult assertionResult{ info, CATCH_MOVE( data ) }; assertionEnded( CATCH_MOVE(assertionResult) ); - resetAssertionInfo(); } + void RunContext::handleNonExpr( AssertionInfo const &info, ResultWas::OfType resultType, AssertionReaction &reaction ) { - m_lastAssertionInfo = info; - AssertionResultData data( resultType, LazyExpression( false ) ); AssertionResult assertionResult{ info, CATCH_MOVE( data ) }; const auto isOk = assertionResult.isOk(); + if ( isOk && !m_includeSuccessfulResults ) { + assertionPassedFastPath( info.lineInfo ); + return; + } + assertionEnded( CATCH_MOVE(assertionResult) ); - if ( !isOk ) { populateReaction( reaction ); } - resetAssertionInfo(); + if ( !isOk ) { + populateReaction( + reaction, info.resultDisposition & ResultDisposition::Normal ); + } } + void IResultCapture::pushScopedMessage( MessageInfo&& message ) { + Detail::g_messages.push_back( CATCH_MOVE( message ) ); + } - IResultCapture& getResultCapture() { - if (auto* capture = getCurrentContext().getResultCapture()) - return *capture; - else - CATCH_INTERNAL_ERROR("No result capture instance"); + void IResultCapture::popScopedMessage( unsigned int messageId ) { + // Note: On average, it would probably be better to look for the message + // backwards. However, we do not expect to have to deal with more + // messages than low single digits, so the optimization is tiny, + // and we would have to hand-write the loop to avoid terrible + // codegen of reverse iterators in debug mode. + Detail::g_messages.erase( std::find_if( Detail::g_messages.begin(), + Detail::g_messages.end(), + [=]( MessageInfo const& msg ) { + return msg.sequence == + messageId; + } ) ); + } + + void IResultCapture::emplaceUnscopedMessage( MessageBuilder&& builder ) { + Detail::g_messageScopes.emplace_back( CATCH_MOVE( builder ) ); } void seedRng(IConfig const& config) { @@ -6516,7 +6739,7 @@ namespace Catch { std::string origStr = CATCH_MOVE(str); str.clear(); // There is at least one replacement, so reserve with the best guess - // we can make without actually counting the number of occurences. + // we can make without actually counting the number of occurrences. str.reserve(origStr.size() - replaceThis.size() + withThis.size()); do { str.append(origStr, copyBegin, i-copyBegin ); @@ -6562,7 +6785,6 @@ namespace Catch { #include #include #include -#include namespace Catch { StringRef::StringRef( char const* rawChars ) noexcept @@ -7859,7 +8081,7 @@ namespace { void hexEscapeChar(std::ostream& os, unsigned char c) { std::ios_base::fmtflags f(os.flags()); - os << "\\x" + os << "\\x"_sr << std::uppercase << std::hex << std::setfill('0') << std::setw(2) << static_cast(c); os.flags(f); @@ -7878,95 +8100,111 @@ namespace { void XmlEncode::encodeTo( std::ostream& os ) const { // Apostrophe escaping not necessary if we always use " to write attributes // (see: http://www.w3.org/TR/xml/#syntax) + size_t last_start = 0; + auto write_to = [&]( size_t idx ) { + if ( last_start < idx ) { + os << m_str.substr( last_start, idx - last_start ); + } + last_start = idx + 1; + }; - for( std::size_t idx = 0; idx < m_str.size(); ++ idx ) { - unsigned char c = static_cast(m_str[idx]); - switch (c) { - case '<': os << "<"; break; - case '&': os << "&"; break; + for ( std::size_t idx = 0; idx < m_str.size(); ++idx ) { + unsigned char c = static_cast( m_str[idx] ); + switch ( c ) { + case '<': + write_to( idx ); + os << "<"_sr; + break; + case '&': + write_to( idx ); + os << "&"_sr; + break; case '>': // See: http://www.w3.org/TR/xml/#syntax - if (idx > 2 && m_str[idx - 1] == ']' && m_str[idx - 2] == ']') - os << ">"; - else - os << c; + if ( idx > 2 && m_str[idx - 1] == ']' && m_str[idx - 2] == ']' ) { + write_to( idx ); + os << ">"_sr; + } break; case '\"': - if (m_forWhat == ForAttributes) - os << """; - else - os << c; + if ( m_forWhat == ForAttributes ) { + write_to( idx ); + os << """_sr; + } break; default: // Check for control characters and invalid utf-8 // Escape control characters in standard ascii - // see http://stackoverflow.com/questions/404107/why-are-control-characters-illegal-in-xml-1-0 - if (c < 0x09 || (c > 0x0D && c < 0x20) || c == 0x7F) { - hexEscapeChar(os, c); + // see + // http://stackoverflow.com/questions/404107/why-are-control-characters-illegal-in-xml-1-0 + if ( c < 0x09 || ( c > 0x0D && c < 0x20 ) || c == 0x7F ) { + write_to( idx ); + hexEscapeChar( os, c ); break; } // Plain ASCII: Write it to stream - if (c < 0x7F) { - os << c; + if ( c < 0x7F ) { break; } // UTF-8 territory - // Check if the encoding is valid and if it is not, hex escape bytes. - // Important: We do not check the exact decoded values for validity, only the encoding format - // First check that this bytes is a valid lead byte: - // This means that it is not encoded as 1111 1XXX + // Check if the encoding is valid and if it is not, hex escape + // bytes. Important: We do not check the exact decoded values for + // validity, only the encoding format First check that this bytes is + // a valid lead byte: This means that it is not encoded as 1111 1XXX // Or as 10XX XXXX - if (c < 0xC0 || - c >= 0xF8) { - hexEscapeChar(os, c); + if ( c < 0xC0 || c >= 0xF8 ) { + write_to( idx ); + hexEscapeChar( os, c ); break; } - auto encBytes = trailingBytes(c); - // Are there enough bytes left to avoid accessing out-of-bounds memory? - if (idx + encBytes - 1 >= m_str.size()) { - hexEscapeChar(os, c); + auto encBytes = trailingBytes( c ); + // Are there enough bytes left to avoid accessing out-of-bounds + // memory? + if ( idx + encBytes - 1 >= m_str.size() ) { + write_to( idx ); + hexEscapeChar( os, c ); break; } // The header is valid, check data // The next encBytes bytes must together be a valid utf-8 - // This means: bitpattern 10XX XXXX and the extracted value is sane (ish) + // This means: bitpattern 10XX XXXX and the extracted value is sane + // (ish) bool valid = true; - uint32_t value = headerValue(c); - for (std::size_t n = 1; n < encBytes; ++n) { - unsigned char nc = static_cast(m_str[idx + n]); - valid &= ((nc & 0xC0) == 0x80); - value = (value << 6) | (nc & 0x3F); + uint32_t value = headerValue( c ); + for ( std::size_t n = 1; n < encBytes; ++n ) { + unsigned char nc = static_cast( m_str[idx + n] ); + valid &= ( ( nc & 0xC0 ) == 0x80 ); + value = ( value << 6 ) | ( nc & 0x3F ); } if ( // Wrong bit pattern of following bytes - (!valid) || + ( !valid ) || // Overlong encodings - (value < 0x80) || - (0x80 <= value && value < 0x800 && encBytes > 2) || - (0x800 < value && value < 0x10000 && encBytes > 3) || + ( value < 0x80 ) || + ( 0x80 <= value && value < 0x800 && encBytes > 2 ) || + ( 0x800 < value && value < 0x10000 && encBytes > 3 ) || // Encoded value out of range - (value >= 0x110000) - ) { - hexEscapeChar(os, c); + ( value >= 0x110000 ) ) { + write_to( idx ); + hexEscapeChar( os, c ); break; } // If we got here, this is in fact a valid(ish) utf-8 sequence - for (std::size_t n = 0; n < encBytes; ++n) { - os << m_str[idx + n]; - } idx += encBytes - 1; break; } } + + write_to( m_str.size() ); } std::ostream& operator << ( std::ostream& os, XmlEncode const& xmlEncode ) { @@ -8877,7 +9115,8 @@ class AssertionPrinter { << m_config->testSpec() << '\n'; } - m_stream << "RNG seed: " << getSeed() << '\n'; + m_stream << "RNG seed: " << getSeed() << '\n' + << std::flush; } void CompactReporter::assertionEnded( AssertionStats const& _assertionStats ) { @@ -9298,7 +9537,10 @@ ConsoleReporter::ConsoleReporter(ReporterConfig&& config): { "est run time high mean high std dev", 14, Justification::Right } }; } - }())) {} + }())) { + m_preferences.shouldReportAllAssertionStarts = false; +} + ConsoleReporter::~ConsoleReporter() = default; std::string ConsoleReporter::getDescription() { @@ -9313,8 +9555,6 @@ void ConsoleReporter::reportInvalidTestSpec( StringRef arg ) { m_stream << "Invalid Filter: " << arg << '\n'; } -void ConsoleReporter::assertionStarting(AssertionInfo const&) {} - void ConsoleReporter::assertionEnded(AssertionStats const& _assertionStats) { AssertionResult const& result = _assertionStats.assertionResult; @@ -9426,7 +9666,8 @@ void ConsoleReporter::testRunStarting(TestRunInfo const& _testRunInfo) { m_stream << m_colour->guardColour( Colour::BrightYellow ) << "Filters: " << m_config->testSpec() << '\n'; } - m_stream << "Randomness seeded to: " << getSeed() << '\n'; + m_stream << "Randomness seeded to: " << getSeed() << '\n' + << std::flush; } void ConsoleReporter::lazyPrint() { @@ -9754,6 +9995,7 @@ namespace Catch { #include #include +#include #include #include #include @@ -9917,9 +10159,29 @@ namespace Catch { out << "All available tags:\n"; } + // minimum whitespace to pad tag counts, possibly overwritten below + size_t maxTagCountLen = 2; + + // determine necessary padding for tag count column + if ( ! tags.empty() ) { + const auto maxTagCount = + std::max_element( tags.begin(), + tags.end(), + []( auto const& lhs, auto const& rhs ) { + return lhs.count < rhs.count; + } ) + ->count; + + // more padding necessary for 3+ digits + if (maxTagCount >= 100) { + auto numDigits = 1 + std::floor( std::log10( maxTagCount ) ); + maxTagCountLen = static_cast( numDigits ); + } + } + for ( auto const& tagCount : tags ) { ReusableStringStream rss; - rss << " " << std::setw( 2 ) << tagCount.count << " "; + rss << " " << std::setw( maxTagCountLen ) << tagCount.count << " "; auto str = rss.str(); auto wrapper = TextFlow::Column( tagCount.all() ) .initialIndent( 0 ) @@ -10117,6 +10379,8 @@ namespace Catch { // not, but for machine-parseable reporters I think the answer // should be yes. m_preferences.shouldReportAllAssertions = true; + // We only handle assertions when they end + m_preferences.shouldReportAllAssertionStarts = false; m_objectWriters.emplace( m_stream ); m_writers.emplace( Writer::Object ); @@ -10346,7 +10610,6 @@ namespace Catch { endObject(); } - void JsonReporter::assertionStarting( AssertionInfo const& /*assertionInfo*/ ) {} void JsonReporter::assertionEnded( AssertionStats const& assertionStats ) { // TODO: There is lot of different things to handle here, but // we can fill it in later, after we show that the basic @@ -10513,6 +10776,7 @@ namespace Catch { { m_preferences.shouldRedirectStdOut = true; m_preferences.shouldReportAllAssertions = false; + m_preferences.shouldReportAllAssertionStarts = false; m_shouldStoreSuccesfulAssertions = false; } @@ -10743,6 +11007,8 @@ namespace Catch { reporterish.getPreferences().shouldRedirectStdOut; m_preferences.shouldReportAllAssertions |= reporterish.getPreferences().shouldReportAllAssertions; + m_preferences.shouldReportAllAssertionStarts |= + reporterish.getPreferences().shouldReportAllAssertionStarts; } void MultiReporter::addListener( IEventListenerPtr&& listener ) { @@ -11300,7 +11566,8 @@ namespace Catch { if ( m_config->testSpec().hasFilters() ) { m_stream << "# filters: " << m_config->testSpec() << '\n'; } - m_stream << "# rng-seed: " << m_config->rngSeed() << '\n'; + m_stream << "# rng-seed: " << m_config->rngSeed() << '\n' + << std::flush; } void TAPReporter::noMatchingTestCases( StringRef unmatchedSpec ) { @@ -11514,6 +11781,7 @@ namespace Catch { { m_preferences.shouldRedirectStdOut = true; m_preferences.shouldReportAllAssertions = true; + m_preferences.shouldReportAllAssertionStarts = false; } XmlReporter::~XmlReporter() = default; @@ -11570,8 +11838,6 @@ namespace Catch { } } - void XmlReporter::assertionStarting( AssertionInfo const& ) { } - void XmlReporter::assertionEnded( AssertionStats const& assertionStats ) { AssertionResult const& result = assertionStats.assertionResult; diff --git a/extras/catch_amalgamated.hpp b/extras/catch_amalgamated.hpp index 7703e95..d5eb1bb 100644 --- a/extras/catch_amalgamated.hpp +++ b/extras/catch_amalgamated.hpp @@ -6,8 +6,8 @@ // SPDX-License-Identifier: BSL-1.0 -// Catch v3.8.1 -// Generated: 2025-04-08 12:33:19.851017 +// Catch v3.11.0 +// Generated: 2025-09-30 10:49:11.225746 // ---------------------------------------------------------- // This file is an amalgamation of multiple different files. // You probably shouldn't edit it directly. @@ -101,6 +101,9 @@ #elif defined(linux) || defined(__linux) || defined(__linux__) # define CATCH_PLATFORM_LINUX +#elif defined(__QNX__) +# define CATCH_PLATFORM_QNX + #elif defined(WIN32) || defined(__WIN32__) || defined(_WIN32) || defined(_MSC_VER) || defined(__MINGW32__) # define CATCH_PLATFORM_WINDOWS @@ -308,13 +311,17 @@ # endif // Universal Windows platform does not support SEH -// Or console colours (or console at all...) -# if defined(CATCH_PLATFORM_WINDOWS_UWP) -# define CATCH_INTERNAL_CONFIG_NO_COLOUR_WIN32 -# else +# if !defined(CATCH_PLATFORM_WINDOWS_UWP) # define CATCH_INTERNAL_CONFIG_WINDOWS_SEH # endif +// Only some Windows platform families support the console +# if defined(WINAPI_FAMILY_PARTITION) +# if !WINAPI_FAMILY_PARTITION(WINAPI_PARTITION_APP | WINAPI_PARTITION_SYSTEM) +# define CATCH_INTERNAL_CONFIG_NO_COLOUR_WIN32 +# endif +# endif + // MSVC traditional preprocessor needs some workaround for __VA_ARGS__ // _MSVC_TRADITIONAL == 0 means new conformant preprocessor // _MSVC_TRADITIONAL == 1 means old traditional non-conformant preprocessor @@ -564,11 +571,9 @@ namespace Catch { IConfig const* m_config = nullptr; IResultCapture* m_resultCapture = nullptr; - CATCH_EXPORT static Context* currentContext; + CATCH_EXPORT static Context currentContext; friend Context& getCurrentMutableContext(); friend Context const& getCurrentContext(); - static void createContext(); - friend void cleanUpContext(); public: constexpr IResultCapture* getResultCapture() const { @@ -579,21 +584,14 @@ namespace Catch { m_resultCapture = resultCapture; } constexpr void setConfig( IConfig const* config ) { m_config = config; } - }; Context& getCurrentMutableContext(); inline Context const& getCurrentContext() { - // We duplicate the logic from `getCurrentMutableContext` here, - // to avoid paying the call overhead in debug mode. - if ( !Context::currentContext ) { Context::createContext(); } - // NOLINTNEXTLINE(clang-analyzer-core.uninitialized.UndefReturn) - return *Context::currentContext; + return Context::currentContext; } - void cleanUpContext(); - class SimplePcg32; SimplePcg32& sharedRng(); } @@ -958,7 +956,7 @@ namespace Detail { } explicit operator bool() const { - return m_ptr; + return m_ptr != nullptr; } friend void swap(unique_ptr& lhs, unique_ptr& rhs) { @@ -1069,10 +1067,9 @@ namespace Catch { virtual void benchmarkEnded( BenchmarkStats<> const& stats ) = 0; virtual void benchmarkFailed( StringRef error ) = 0; - virtual void pushScopedMessage( MessageInfo const& message ) = 0; - virtual void popScopedMessage( MessageInfo const& message ) = 0; - - virtual void emplaceUnscopedMessage( MessageBuilder&& builder ) = 0; + static void pushScopedMessage( MessageInfo&& message ); + static void popScopedMessage( unsigned int messageId ); + static void emplaceUnscopedMessage( MessageBuilder&& builder ); virtual void handleFatalErrorCondition( StringRef message ) = 0; @@ -1100,9 +1097,7 @@ namespace Catch { AssertionReaction &reaction ) = 0; - virtual bool lastAssertionPassed() = 0; - virtual void assertionPassed() = 0; // Deprecated, do not use: virtual std::string getCurrentTestName() const = 0; @@ -1110,7 +1105,18 @@ namespace Catch { virtual void exceptionEarlyReported() = 0; }; - IResultCapture& getResultCapture(); + namespace Detail { + [[noreturn]] + void missingCaptureInstance(); + } + inline IResultCapture& getResultCapture() { + if (auto* capture = getCurrentContext().getResultCapture()) { + return *capture; + } else { + Detail::missingCaptureInstance(); + } + } + } #endif // CATCH_INTERFACES_CAPTURE_HPP_INCLUDED @@ -1129,6 +1135,7 @@ namespace Catch { //! Deriving classes become noncopyable and nonmovable class NonCopyable { + public: NonCopyable( NonCopyable const& ) = delete; NonCopyable( NonCopyable&& ) = delete; NonCopyable& operator=( NonCopyable const& ) = delete; @@ -1144,7 +1151,6 @@ namespace Catch { #endif // CATCH_NONCOPYABLE_HPP_INCLUDED #include -#include #include #include @@ -1599,8 +1605,7 @@ namespace Catch { namespace Benchmark { namespace Detail { template - struct is_related - : std::is_same, std::decay_t> {}; + static constexpr bool is_related_v = std::is_same, std::decay_t>::value; /// We need to reinvent std::function because every piece of code that might add overhead /// in a measurement context needs to have consistent performance characteristics so that we @@ -1641,7 +1646,7 @@ namespace Catch { BenchmarkFunction(); template ::value, int> = 0> + std::enable_if_t, int> = 0> BenchmarkFunction(Fun&& fun) : f(new model>(CATCH_FORWARD(fun))) {} @@ -1767,8 +1772,6 @@ namespace Catch { #define CATCH_TIMING_HPP_INCLUDED -#include - namespace Catch { namespace Benchmark { template @@ -2156,9 +2159,7 @@ namespace Catch { auto analysis = Detail::analyse(*cfg, samples.data(), samples.data() + samples.size()); BenchmarkStats<> stats{ CATCH_MOVE(info), CATCH_MOVE(analysis.samples), analysis.mean, analysis.standard_deviation, analysis.outliers, analysis.outlier_variance }; getResultCapture().benchmarkEnded(stats); - } CATCH_CATCH_ANON (TestFailureException const&) { - getResultCapture().benchmarkFailed("Benchmark failed due to failed assertion"_sr); - } CATCH_CATCH_ALL{ + } CATCH_CATCH_ALL { getResultCapture().benchmarkFailed(translateActiveException()); // We let the exception go further up so that the // test case is marked as failed. @@ -2167,7 +2168,7 @@ namespace Catch { } // sets lambda to be used in fun *and* executes benchmark! - template ::value, int> = 0> + template , int> = 0> Benchmark & operator=(Fun func) { auto const* cfg = getCurrentContext().getConfig(); if (!cfg->skipBenchmarks()) { @@ -2496,18 +2497,14 @@ namespace Catch { return rawMemoryToString( &object, sizeof(object) ); } - template - class IsStreamInsertable { - template - static auto test(int) - -> decltype(std::declval() << std::declval(), std::true_type()); - - template - static auto test(...)->std::false_type; + template + static constexpr bool IsStreamInsertable_v = false; - public: - static const bool value = decltype(test(0))::value; - }; + template + static constexpr bool IsStreamInsertable_v< + T, + decltype( void( std::declval() << std::declval() ) )> = + true; template std::string convertUnknownEnumToString( E e ); @@ -2552,7 +2549,7 @@ namespace Catch { struct StringMaker { template static - std::enable_if_t<::Catch::Detail::IsStreamInsertable::value, std::string> + std::enable_if_t<::Catch::Detail::IsStreamInsertable_v, std::string> convert(const Fake& value) { ReusableStringStream rss; // NB: call using the function-like syntax to avoid ambiguity with @@ -2563,7 +2560,7 @@ namespace Catch { template static - std::enable_if_t::value, std::string> + std::enable_if_t, std::string> convert( const Fake& value ) { #if !defined(CATCH_CONFIG_FALLBACK_STRINGIFIER) return Detail::convertUnstreamable(value); @@ -2575,11 +2572,17 @@ namespace Catch { namespace Detail { + std::string makeExceptionHappenedString(); + // This function dispatches all stringification requests inside of Catch. // Should be preferably called fully qualified, like ::Catch::Detail::stringify template - std::string stringify(const T& e) { - return ::Catch::StringMaker>>::convert(e); + std::string stringify( const T& e ) { + CATCH_TRY { + return ::Catch::StringMaker< + std::remove_cv_t>>::convert( e ); + } + CATCH_CATCH_ALL { return makeExceptionHappenedString(); } } template @@ -2955,7 +2958,7 @@ namespace Catch { } template - struct StringMaker::value && !::Catch::Detail::IsStreamInsertable::value>> { + struct StringMaker::value && !::Catch::Detail::IsStreamInsertable_v>> { static std::string convert( R const& range ) { return rangeToString( range ); } @@ -3062,17 +3065,19 @@ struct ratio_string { template struct StringMaker> { static std::string convert(std::chrono::time_point const& time_point) { - auto converted = std::chrono::system_clock::to_time_t(time_point); + const auto systemish = std::chrono::time_point_cast< + std::chrono::system_clock::duration>( time_point ); + const auto as_time_t = std::chrono::system_clock::to_time_t( systemish ); #ifdef _MSC_VER std::tm timeInfo = {}; - const auto err = gmtime_s(&timeInfo, &converted); + const auto err = gmtime_s( &timeInfo, &as_time_t ); if ( err ) { return "gmtime from provided timepoint has failed. This " "happens e.g. with pre-1970 dates using Microsoft libc"; } #else - std::tm* timeInfo = std::gmtime(&converted); + std::tm* timeInfo = std::gmtime( &as_time_t ); #endif auto const timeStampSize = sizeof("2017-01-16T17:06:45Z"); @@ -3792,7 +3797,7 @@ namespace Catch { WarnAbout::What warnings = WarnAbout::Nothing; ShowDurations showDurations = ShowDurations::DefaultForReporter; double minDuration = -1; - TestRunOrder runOrder = TestRunOrder::Declared; + TestRunOrder runOrder = TestRunOrder::Randomized; ColourMode defaultColourMode = ColourMode::PlatformDefault; WaitForKeypress::When waitForKeypress = WaitForKeypress::Never; @@ -3803,6 +3808,8 @@ namespace Catch { std::vector testsOrTags; std::vector sectionsToRun; + + std::string prematureExitGuardFilePath; }; @@ -3830,6 +3837,8 @@ namespace Catch { bool showHelp() const; + std::string const& getExitGuardFilePath() const; + // IConfig interface bool allowThrows() const override; StringRef name() const override; @@ -3938,6 +3947,19 @@ namespace Catch { #define CATCH_MESSAGE_INFO_HPP_INCLUDED + +#ifndef CATCH_DEPRECATION_MACRO_HPP_INCLUDED +#define CATCH_DEPRECATION_MACRO_HPP_INCLUDED + + +#if !defined( CATCH_CONFIG_NO_DEPRECATION_ANNOTATIONS ) +# define DEPRECATED( msg ) [[deprecated( msg )]] +#else +# define DEPRECATED( msg ) +#endif + +#endif // CATCH_DEPRECATION_MACRO_HPP_INCLUDED + #include namespace Catch { @@ -3951,16 +3973,19 @@ namespace Catch { std::string message; SourceLineInfo lineInfo; ResultWas::OfType type; + // The "ID" of the message, used to know when to remove it from reporter context. unsigned int sequence; + DEPRECATED( "Explicitly use the 'sequence' member instead" ) bool operator == (MessageInfo const& other) const { return sequence == other.sequence; } + DEPRECATED( "Explicitly use the 'sequence' member instead" ) bool operator < (MessageInfo const& other) const { return sequence < other.sequence; } private: - static unsigned int globalCount; + static thread_local unsigned int globalCount; }; } // end namespace Catch @@ -4008,13 +4033,12 @@ namespace Catch { ScopedMessage( ScopedMessage&& old ) noexcept; ~ScopedMessage(); - MessageInfo m_info; + unsigned int m_messageId; bool m_moved = false; }; class Capturer { std::vector m_messages; - IResultCapture& m_resultCapture; size_t m_captured = 0; public: Capturer( StringRef macroName, SourceLineInfo const& lineInfo, ResultWas::OfType resultType, StringRef names ); @@ -4062,7 +4086,7 @@ namespace Catch { /////////////////////////////////////////////////////////////////////////////// #define INTERNAL_CATCH_UNSCOPED_INFO( macroName, log ) \ - Catch::getResultCapture().emplaceUnscopedMessage( Catch::MessageBuilder( macroName##_catch_sr, CATCH_INTERNAL_LINEINFO, Catch::ResultWas::Info ) << log ) + Catch::IResultCapture::emplaceUnscopedMessage( Catch::MessageBuilder( macroName##_catch_sr, CATCH_INTERNAL_LINEINFO, Catch::ResultWas::Info ) << log ) #if defined(CATCH_CONFIG_PREFIX_MESSAGES) && !defined(CATCH_CONFIG_DISABLE) @@ -4237,15 +4261,13 @@ namespace Catch { }; template - struct is_unary_function : std::false_type {}; + static constexpr bool is_unary_function_v = false; template - struct is_unary_function< + static constexpr bool is_unary_function_v< F, - Catch::Detail::void_t()( fake_arg() ) ) - > - > : std::true_type {}; + Catch::Detail::void_t()( + fake_arg() ) )>> = true; // Traits for extracting arg and return type of lambdas (for single // argument lambdas) @@ -4678,14 +4700,14 @@ namespace Catch { template ::value>> + !Detail::is_unary_function_v>> ParserRefImpl( T& ref, StringRef hint ): m_ref( std::make_shared>( ref ) ), m_hint( hint ) {} template ::value>> + Detail::is_unary_function_v>> ParserRefImpl( LambdaT const& ref, StringRef hint ): m_ref( std::make_shared>( ref ) ), m_hint( hint ) {} @@ -4752,7 +4774,7 @@ namespace Catch { template ::value>> + Detail::is_unary_function_v>> Opt( LambdaT const& ref, StringRef hint ): ParserRefImpl( ref, hint ) {} @@ -4762,7 +4784,7 @@ namespace Catch { template ::value>> + !Detail::is_unary_function_v>> Opt( T& ref, StringRef hint ): ParserRefImpl( ref, hint ) {} @@ -4932,6 +4954,14 @@ namespace Catch { namespace Catch { + // TODO: Use C++17 `inline` variables + constexpr int UnspecifiedErrorExitCode = 1; + constexpr int NoTestsRunExitCode = 2; + constexpr int UnmatchedTestSpecExitCode = 3; + constexpr int AllTestsSkippedExitCode = 4; + constexpr int InvalidTestSpecExitCode = 5; + constexpr int TestFailureExitCode = 42; + class Session : Detail::NonCopyable { public: @@ -5016,7 +5046,7 @@ namespace Catch { #define CATCH_REGISTER_TAG_ALIAS( alias, spec ) \ CATCH_INTERNAL_START_WARNINGS_SUPPRESSION \ CATCH_INTERNAL_SUPPRESS_GLOBALS_WARNINGS \ - namespace{ Catch::RegistrarForTagAliases INTERNAL_CATCH_UNIQUE_NAME( AutoRegisterTagAlias )( alias, spec, CATCH_INTERNAL_LINEINFO ); } \ + namespace{ const Catch::RegistrarForTagAliases INTERNAL_CATCH_UNIQUE_NAME( AutoRegisterTagAlias )( alias, spec, CATCH_INTERNAL_LINEINFO ); } \ CATCH_INTERNAL_STOP_WARNINGS_SUPPRESSION #endif // CATCH_TAG_ALIAS_AUTOREGISTRAR_HPP_INCLUDED @@ -5193,7 +5223,7 @@ namespace Detail { * when the compiler handles `ExprLhs == b`, it also tries to resolve * the overload set for `b == ExprLhs`. * - * To accomodate these use cases, decomposer ended up rather complex. + * To accommodate these use cases, decomposer ended up rather complex. * * 1) These types are handled by adding SFINAE overloads to our comparison * operators, checking whether `T == U` are comparable with the given @@ -5226,10 +5256,11 @@ namespace Detail { * * 3) If a type has no linkage, we also cannot capture it by reference. * The solution is once again to capture them by value. We handle - * the common cases by using `std::is_arithmetic` as the default - * for `Catch::capture_by_value`, but that is only a some-effort - * heuristic. But as with 2), users can specialize `capture_by_value` - * for their own types as needed. + * the common cases by using `std::is_arithmetic` and `std::is_enum` + * as the default for `Catch::capture_by_value`, but that is only a + * some-effort heuristic. These combine to capture all possible bitfield + * bases, and also some trait-like types. As with 2), users can + * specialize `capture_by_value` for their own types as needed. * * 4) To support C++20 and make the SFINAE on our decomposing operators * work, the SFINAE has to happen in return type, rather than in @@ -5281,13 +5312,22 @@ namespace Catch { using RemoveCVRef_t = std::remove_cv_t>; } - // Note: There is nothing that stops us from extending this, - // e.g. to `std::is_scalar`, but the more encompassing - // traits are usually also more expensive. For now we - // keep this as it used to be and it can be changed later. + // Note: This is about as much as we can currently reasonably support. + // In an ideal world, we could capture by value small trivially + // copyable types, but the actual `std::is_trivially_copyable` + // trait is a huge mess with standard-violating results on + // GCC and Clang, which are unlikely to be fixed soon due to ABI + // concerns. + // `std::is_scalar` also causes issues due to the `is_pointer` + // component, which causes ambiguity issues with (references-to) + // function pointer. If those are resolved, we still need to + // disambiguate the overload set for arrays, through explicit + // overload for references to sized arrays. template struct capture_by_value - : std::integral_constant{}> {}; + : std::integral_constant::value || + std::is_enum::value> {}; #if defined( CATCH_CONFIG_CPP20_COMPARE_OVERLOADS ) template <> @@ -5311,7 +5351,7 @@ namespace Catch { public: constexpr auto isBinaryExpression() const -> bool { return m_isBinaryExpression; } constexpr auto getResult() const -> bool { return m_result; } - //! This function **has** to be overriden by the derived class. + //! This function **has** to be overridden by the derived class. virtual void streamReconstructedExpression( std::ostream& os ) const; constexpr ITransientExpression( bool isBinaryExpression, bool result ) @@ -5912,7 +5952,7 @@ namespace Catch { #else // These section definitions imply that at most one section at one level -// will be intered (because only one section's __LINE__ can be equal to +// will be entered (because only one section's __LINE__ can be equal to // the dummy `catchInternalSectionHint` variable from `TEST_CASE`). namespace Catch { @@ -6195,6 +6235,57 @@ static int catchInternalSectionHint = 0; #endif // CATCH_TEST_REGISTRY_HPP_INCLUDED +#ifndef CATCH_UNREACHABLE_HPP_INCLUDED +#define CATCH_UNREACHABLE_HPP_INCLUDED + +/**\file + * Polyfill `std::unreachable` + * + * We need something like `std::unreachable` to tell the compiler that + * some macros, e.g. `FAIL` or `SKIP`, do not continue execution in normal + * manner, and should handle it as such, e.g. not warn if there is no return + * from non-void function after a `FAIL` or `SKIP`. + */ + +#include + +#if defined( __cpp_lib_unreachable ) && __cpp_lib_unreachable > 202202L +# include +namespace Catch { + namespace Detail { + using Unreachable = std::unreachable; + } +} // namespace Catch + +#else // vv If we do not have std::unreachable, we implement something similar + +namespace Catch { + namespace Detail { + + [[noreturn]] + inline void Unreachable() noexcept { +# if defined( NDEBUG ) +# if defined( _MSC_VER ) && !defined( __clang__ ) + __assume( false ); +# elif defined( __GNUC__ ) + __builtin_unreachable(); +# else // vv platform without known optimization hint + std::terminate(); +# endif +# else // ^^ NDEBUG + // For non-release builds, we prefer termination on bug over UB + std::terminate(); +# endif // + } + + } // namespace Detail +} // end namespace Catch + +#endif + +#endif // CATCH_UNREACHABLE_HPP_INCLUDED + + // All of our user-facing macros support configuration toggle, that // forces them to be defined prefixed with CATCH_. We also like to // support another toggle that can minimize (disable) their implementation. @@ -6226,10 +6317,16 @@ static int catchInternalSectionHint = 0; #define CATCH_REGISTER_TEST_CASE( Function, ... ) INTERNAL_CATCH_REGISTER_TESTCASE( Function, __VA_ARGS__ ) #define CATCH_SECTION( ... ) INTERNAL_CATCH_SECTION( __VA_ARGS__ ) #define CATCH_DYNAMIC_SECTION( ... ) INTERNAL_CATCH_DYNAMIC_SECTION( __VA_ARGS__ ) - #define CATCH_FAIL( ... ) INTERNAL_CATCH_MSG( "CATCH_FAIL", Catch::ResultWas::ExplicitFailure, Catch::ResultDisposition::Normal, __VA_ARGS__ ) + #define CATCH_FAIL( ... ) do { \ + INTERNAL_CATCH_MSG("CATCH_FAIL", Catch::ResultWas::ExplicitFailure, Catch::ResultDisposition::Normal, __VA_ARGS__ ); \ + Catch::Detail::Unreachable(); \ + } while ( false ) #define CATCH_FAIL_CHECK( ... ) INTERNAL_CATCH_MSG( "CATCH_FAIL_CHECK", Catch::ResultWas::ExplicitFailure, Catch::ResultDisposition::ContinueOnFailure, __VA_ARGS__ ) #define CATCH_SUCCEED( ... ) INTERNAL_CATCH_MSG( "CATCH_SUCCEED", Catch::ResultWas::Ok, Catch::ResultDisposition::ContinueOnFailure, __VA_ARGS__ ) - #define CATCH_SKIP( ... ) INTERNAL_CATCH_MSG( "SKIP", Catch::ResultWas::ExplicitSkip, Catch::ResultDisposition::Normal, __VA_ARGS__ ) + #define CATCH_SKIP( ... ) do { \ + INTERNAL_CATCH_MSG( "CATCH_SKIP", Catch::ResultWas::ExplicitSkip, Catch::ResultDisposition::Normal, __VA_ARGS__ ); \ + Catch::Detail::Unreachable(); \ + } while (false) #if !defined(CATCH_CONFIG_RUNTIME_STATIC_REQUIRE) @@ -6327,10 +6424,16 @@ static int catchInternalSectionHint = 0; #define REGISTER_TEST_CASE( Function, ... ) INTERNAL_CATCH_REGISTER_TESTCASE( Function, __VA_ARGS__ ) #define SECTION( ... ) INTERNAL_CATCH_SECTION( __VA_ARGS__ ) #define DYNAMIC_SECTION( ... ) INTERNAL_CATCH_DYNAMIC_SECTION( __VA_ARGS__ ) - #define FAIL( ... ) INTERNAL_CATCH_MSG( "FAIL", Catch::ResultWas::ExplicitFailure, Catch::ResultDisposition::Normal, __VA_ARGS__ ) + #define FAIL( ... ) do { \ + INTERNAL_CATCH_MSG( "FAIL", Catch::ResultWas::ExplicitFailure, Catch::ResultDisposition::Normal, __VA_ARGS__ ); \ + Catch::Detail::Unreachable(); \ + } while (false) #define FAIL_CHECK( ... ) INTERNAL_CATCH_MSG( "FAIL_CHECK", Catch::ResultWas::ExplicitFailure, Catch::ResultDisposition::ContinueOnFailure, __VA_ARGS__ ) #define SUCCEED( ... ) INTERNAL_CATCH_MSG( "SUCCEED", Catch::ResultWas::Ok, Catch::ResultDisposition::ContinueOnFailure, __VA_ARGS__ ) - #define SKIP( ... ) INTERNAL_CATCH_MSG( "SKIP", Catch::ResultWas::ExplicitSkip, Catch::ResultDisposition::Normal, __VA_ARGS__ ) + #define SKIP( ... ) do { \ + INTERNAL_CATCH_MSG( "SKIP", Catch::ResultWas::ExplicitSkip, Catch::ResultDisposition::Normal, __VA_ARGS__ ); \ + Catch::Detail::Unreachable(); \ + } while (false) #if !defined(CATCH_CONFIG_RUNTIME_STATIC_REQUIRE) @@ -6424,6 +6527,15 @@ static int catchInternalSectionHint = 0; #endif +namespace Catch { + namespace Detail { + template + struct priority_tag : priority_tag {}; + template <> + struct priority_tag<0> {}; + } +} + #define CATCH_RECURSION_LEVEL0(...) __VA_ARGS__ #define CATCH_RECURSION_LEVEL1(...) CATCH_RECURSION_LEVEL0(CATCH_RECURSION_LEVEL0(CATCH_RECURSION_LEVEL0(__VA_ARGS__))) #define CATCH_RECURSION_LEVEL2(...) CATCH_RECURSION_LEVEL1(CATCH_RECURSION_LEVEL1(CATCH_RECURSION_LEVEL1(__VA_ARGS__))) @@ -6483,10 +6595,10 @@ static int catchInternalSectionHint = 0; #define INTERNAL_CATCH_MAKE_NAMESPACE(name) INTERNAL_CATCH_MAKE_NAMESPACE2(name) #ifndef CATCH_CONFIG_TRADITIONAL_MSVC_PREPROCESSOR -#define INTERNAL_CATCH_MAKE_TYPE_LIST2(...) decltype(get_wrapper()) +#define INTERNAL_CATCH_MAKE_TYPE_LIST2(...) decltype(get_wrapper(Catch::Detail::priority_tag<1>{})) #define INTERNAL_CATCH_MAKE_TYPE_LIST(...) INTERNAL_CATCH_MAKE_TYPE_LIST2(INTERNAL_CATCH_REMOVE_PARENS(__VA_ARGS__)) #else -#define INTERNAL_CATCH_MAKE_TYPE_LIST2(...) INTERNAL_CATCH_EXPAND_VARGS(decltype(get_wrapper())) +#define INTERNAL_CATCH_MAKE_TYPE_LIST2(...) INTERNAL_CATCH_EXPAND_VARGS(decltype(get_wrapper(Catch::Detail::priority_tag<1>{}))) #define INTERNAL_CATCH_MAKE_TYPE_LIST(...) INTERNAL_CATCH_EXPAND_VARGS(INTERNAL_CATCH_MAKE_TYPE_LIST2(INTERNAL_CATCH_REMOVE_PARENS(__VA_ARGS__))) #endif @@ -6509,11 +6621,11 @@ static int catchInternalSectionHint = 0; #define INTERNAL_CATCH_TYPE_GEN\ template struct TypeList {};\ - template\ - constexpr auto get_wrapper() noexcept -> TypeList { return {}; }\ + template\ + constexpr auto get_wrapper(Catch::Detail::priority_tag<1>) noexcept -> TypeList { return {}; }\ template class...> struct TemplateTypeList{};\ template class...Cs>\ - constexpr auto get_wrapper() noexcept -> TemplateTypeList { return {}; }\ + constexpr auto get_wrapper(Catch::Detail::priority_tag<1>) noexcept -> TemplateTypeList { return {}; }\ template\ struct append;\ template\ @@ -6543,10 +6655,10 @@ static int catchInternalSectionHint = 0; #define INTERNAL_CATCH_NTTP_1(signature, ...)\ template struct Nttp{};\ template\ - constexpr auto get_wrapper() noexcept -> Nttp<__VA_ARGS__> { return {}; } \ + constexpr auto get_wrapper(Catch::Detail::priority_tag<0>) noexcept -> Nttp<__VA_ARGS__> { return {}; } \ template class...> struct NttpTemplateTypeList{};\ template class...Cs>\ - constexpr auto get_wrapper() noexcept -> NttpTemplateTypeList { return {}; } \ + constexpr auto get_wrapper(Catch::Detail::priority_tag<0>) noexcept -> NttpTemplateTypeList { return {}; } \ \ template< template class Container, template class List, INTERNAL_CATCH_REMOVE_PARENS(signature)>\ struct rewrap, List<__VA_ARGS__>> { using type = TypeList>; };\ @@ -6571,13 +6683,14 @@ static int catchInternalSectionHint = 0; template\ static void TestName() -#define INTERNAL_CATCH_NTTP_REGISTER0(TestFunc, signature)\ - template\ - void reg_test(TypeList, Catch::NameAndTags nameAndTags)\ +#define INTERNAL_CATCH_TYPES_REGISTER(TestFunc)\ + template\ + void reg_test(TypeList, Catch::NameAndTags nameAndTags)\ {\ - Catch::AutoReg( Catch::makeTestInvoker(&TestFunc), CATCH_INTERNAL_LINEINFO, Catch::StringRef(), nameAndTags);\ + Catch::AutoReg( Catch::makeTestInvoker(&TestFunc), CATCH_INTERNAL_LINEINFO, Catch::StringRef(), nameAndTags);\ } +#define INTERNAL_CATCH_NTTP_REGISTER0(TestFunc, signature, ...) #define INTERNAL_CATCH_NTTP_REGISTER(TestFunc, signature, ...)\ template\ void reg_test(Nttp<__VA_ARGS__>, Catch::NameAndTags nameAndTags)\ @@ -6626,7 +6739,7 @@ static int catchInternalSectionHint = 0; #define INTERNAL_CATCH_DEFINE_SIG_TEST_METHOD(TestName, ...) INTERNAL_CATCH_VA_NARGS_IMPL( "dummy", __VA_ARGS__, INTERNAL_CATCH_DEFINE_SIG_TEST_METHOD_X,INTERNAL_CATCH_DEFINE_SIG_TEST_METHOD_X, INTERNAL_CATCH_DEFINE_SIG_TEST_METHOD_X, INTERNAL_CATCH_DEFINE_SIG_TEST_METHOD_X, INTERNAL_CATCH_DEFINE_SIG_TEST_METHOD_X, INTERNAL_CATCH_DEFINE_SIG_TEST_METHOD_X, INTERNAL_CATCH_DEFINE_SIG_TEST_METHOD_X,INTERNAL_CATCH_DEFINE_SIG_TEST_METHOD_X,INTERNAL_CATCH_DEFINE_SIG_TEST_METHOD_X, INTERNAL_CATCH_DEFINE_SIG_TEST_METHOD1, INTERNAL_CATCH_DEFINE_SIG_TEST_METHOD0)(TestName, __VA_ARGS__) #define INTERNAL_CATCH_DECLARE_SIG_TEST_METHOD(TestName, ClassName, ...) INTERNAL_CATCH_VA_NARGS_IMPL( "dummy", __VA_ARGS__, INTERNAL_CATCH_DECLARE_SIG_TEST_METHOD_X,INTERNAL_CATCH_DECLARE_SIG_TEST_METHOD_X, INTERNAL_CATCH_DECLARE_SIG_TEST_METHOD_X, INTERNAL_CATCH_DECLARE_SIG_TEST_METHOD_X, INTERNAL_CATCH_DECLARE_SIG_TEST_METHOD_X, INTERNAL_CATCH_DECLARE_SIG_TEST_METHOD_X, INTERNAL_CATCH_DECLARE_SIG_TEST_METHOD_X,INTERNAL_CATCH_DECLARE_SIG_TEST_METHOD_X,INTERNAL_CATCH_DECLARE_SIG_TEST_METHOD_X, INTERNAL_CATCH_DECLARE_SIG_TEST_METHOD1, INTERNAL_CATCH_DECLARE_SIG_TEST_METHOD0)(TestName, ClassName, __VA_ARGS__) #define INTERNAL_CATCH_NTTP_REG_METHOD_GEN(TestName, ...) INTERNAL_CATCH_VA_NARGS_IMPL( "dummy", __VA_ARGS__, INTERNAL_CATCH_NTTP_REGISTER_METHOD, INTERNAL_CATCH_NTTP_REGISTER_METHOD, INTERNAL_CATCH_NTTP_REGISTER_METHOD, INTERNAL_CATCH_NTTP_REGISTER_METHOD, INTERNAL_CATCH_NTTP_REGISTER_METHOD, INTERNAL_CATCH_NTTP_REGISTER_METHOD, INTERNAL_CATCH_NTTP_REGISTER_METHOD, INTERNAL_CATCH_NTTP_REGISTER_METHOD, INTERNAL_CATCH_NTTP_REGISTER_METHOD, INTERNAL_CATCH_NTTP_REGISTER_METHOD0, INTERNAL_CATCH_NTTP_REGISTER_METHOD0)(TestName, __VA_ARGS__) -#define INTERNAL_CATCH_NTTP_REG_GEN(TestFunc, ...) INTERNAL_CATCH_VA_NARGS_IMPL( "dummy", __VA_ARGS__, INTERNAL_CATCH_NTTP_REGISTER, INTERNAL_CATCH_NTTP_REGISTER, INTERNAL_CATCH_NTTP_REGISTER, INTERNAL_CATCH_NTTP_REGISTER, INTERNAL_CATCH_NTTP_REGISTER, INTERNAL_CATCH_NTTP_REGISTER, INTERNAL_CATCH_NTTP_REGISTER, INTERNAL_CATCH_NTTP_REGISTER, INTERNAL_CATCH_NTTP_REGISTER, INTERNAL_CATCH_NTTP_REGISTER0, INTERNAL_CATCH_NTTP_REGISTER0)(TestFunc, __VA_ARGS__) +#define INTERNAL_CATCH_NTTP_REG_GEN(TestFunc, ...) INTERNAL_CATCH_TYPES_REGISTER(TestFunc) INTERNAL_CATCH_VA_NARGS_IMPL( "dummy", __VA_ARGS__, INTERNAL_CATCH_NTTP_REGISTER, INTERNAL_CATCH_NTTP_REGISTER, INTERNAL_CATCH_NTTP_REGISTER, INTERNAL_CATCH_NTTP_REGISTER, INTERNAL_CATCH_NTTP_REGISTER, INTERNAL_CATCH_NTTP_REGISTER, INTERNAL_CATCH_NTTP_REGISTER, INTERNAL_CATCH_NTTP_REGISTER, INTERNAL_CATCH_NTTP_REGISTER, INTERNAL_CATCH_NTTP_REGISTER0, INTERNAL_CATCH_NTTP_REGISTER0)(TestFunc, __VA_ARGS__) #define INTERNAL_CATCH_DEFINE_SIG_TEST(TestName, ...) INTERNAL_CATCH_VA_NARGS_IMPL( "dummy", __VA_ARGS__, INTERNAL_CATCH_DEFINE_SIG_TEST_X, INTERNAL_CATCH_DEFINE_SIG_TEST_X, INTERNAL_CATCH_DEFINE_SIG_TEST_X, INTERNAL_CATCH_DEFINE_SIG_TEST_X, INTERNAL_CATCH_DEFINE_SIG_TEST_X, INTERNAL_CATCH_DEFINE_SIG_TEST_X, INTERNAL_CATCH_DEFINE_SIG_TEST_X, INTERNAL_CATCH_DEFINE_SIG_TEST_X,INTERNAL_CATCH_DEFINE_SIG_TEST_X,INTERNAL_CATCH_DEFINE_SIG_TEST1, INTERNAL_CATCH_DEFINE_SIG_TEST0)(TestName, __VA_ARGS__) #define INTERNAL_CATCH_DECLARE_SIG_TEST(TestName, ...) INTERNAL_CATCH_VA_NARGS_IMPL( "dummy", __VA_ARGS__, INTERNAL_CATCH_DECLARE_SIG_TEST_X,INTERNAL_CATCH_DECLARE_SIG_TEST_X, INTERNAL_CATCH_DECLARE_SIG_TEST_X, INTERNAL_CATCH_DECLARE_SIG_TEST_X, INTERNAL_CATCH_DECLARE_SIG_TEST_X, INTERNAL_CATCH_DECLARE_SIG_TEST_X, INTERNAL_CATCH_DEFINE_SIG_TEST_X,INTERNAL_CATCH_DECLARE_SIG_TEST_X,INTERNAL_CATCH_DECLARE_SIG_TEST_X, INTERNAL_CATCH_DECLARE_SIG_TEST1, INTERNAL_CATCH_DECLARE_SIG_TEST0)(TestName, __VA_ARGS__) #define INTERNAL_CATCH_REMOVE_PARENS_GEN(...) INTERNAL_CATCH_VA_NARGS_IMPL(__VA_ARGS__, INTERNAL_CATCH_REMOVE_PARENS_11_ARG,INTERNAL_CATCH_REMOVE_PARENS_10_ARG,INTERNAL_CATCH_REMOVE_PARENS_9_ARG,INTERNAL_CATCH_REMOVE_PARENS_8_ARG,INTERNAL_CATCH_REMOVE_PARENS_7_ARG,INTERNAL_CATCH_REMOVE_PARENS_6_ARG,INTERNAL_CATCH_REMOVE_PARENS_5_ARG,INTERNAL_CATCH_REMOVE_PARENS_4_ARG,INTERNAL_CATCH_REMOVE_PARENS_3_ARG,INTERNAL_CATCH_REMOVE_PARENS_2_ARG,INTERNAL_CATCH_REMOVE_PARENS_1_ARG)(__VA_ARGS__) @@ -6636,7 +6749,7 @@ static int catchInternalSectionHint = 0; #define INTERNAL_CATCH_DEFINE_SIG_TEST_METHOD(TestName, ...) INTERNAL_CATCH_EXPAND_VARGS(INTERNAL_CATCH_VA_NARGS_IMPL( "dummy", __VA_ARGS__, INTERNAL_CATCH_DEFINE_SIG_TEST_METHOD_X,INTERNAL_CATCH_DEFINE_SIG_TEST_METHOD_X, INTERNAL_CATCH_DEFINE_SIG_TEST_METHOD_X, INTERNAL_CATCH_DEFINE_SIG_TEST_METHOD_X, INTERNAL_CATCH_DEFINE_SIG_TEST_METHOD_X, INTERNAL_CATCH_DEFINE_SIG_TEST_METHOD_X, INTERNAL_CATCH_DEFINE_SIG_TEST_METHOD_X,INTERNAL_CATCH_DEFINE_SIG_TEST_METHOD_X,INTERNAL_CATCH_DEFINE_SIG_TEST_METHOD_X, INTERNAL_CATCH_DEFINE_SIG_TEST_METHOD1, INTERNAL_CATCH_DEFINE_SIG_TEST_METHOD0)(TestName, __VA_ARGS__)) #define INTERNAL_CATCH_DECLARE_SIG_TEST_METHOD(TestName, ClassName, ...) INTERNAL_CATCH_EXPAND_VARGS(INTERNAL_CATCH_VA_NARGS_IMPL( "dummy", __VA_ARGS__, INTERNAL_CATCH_DECLARE_SIG_TEST_METHOD_X,INTERNAL_CATCH_DECLARE_SIG_TEST_METHOD_X, INTERNAL_CATCH_DECLARE_SIG_TEST_METHOD_X, INTERNAL_CATCH_DECLARE_SIG_TEST_METHOD_X, INTERNAL_CATCH_DECLARE_SIG_TEST_METHOD_X, INTERNAL_CATCH_DECLARE_SIG_TEST_METHOD_X, INTERNAL_CATCH_DECLARE_SIG_TEST_METHOD_X,INTERNAL_CATCH_DECLARE_SIG_TEST_METHOD_X,INTERNAL_CATCH_DECLARE_SIG_TEST_METHOD_X, INTERNAL_CATCH_DECLARE_SIG_TEST_METHOD1, INTERNAL_CATCH_DECLARE_SIG_TEST_METHOD0)(TestName, ClassName, __VA_ARGS__)) #define INTERNAL_CATCH_NTTP_REG_METHOD_GEN(TestName, ...) INTERNAL_CATCH_EXPAND_VARGS(INTERNAL_CATCH_VA_NARGS_IMPL( "dummy", __VA_ARGS__, INTERNAL_CATCH_NTTP_REGISTER_METHOD, INTERNAL_CATCH_NTTP_REGISTER_METHOD, INTERNAL_CATCH_NTTP_REGISTER_METHOD, INTERNAL_CATCH_NTTP_REGISTER_METHOD, INTERNAL_CATCH_NTTP_REGISTER_METHOD, INTERNAL_CATCH_NTTP_REGISTER_METHOD, INTERNAL_CATCH_NTTP_REGISTER_METHOD, INTERNAL_CATCH_NTTP_REGISTER_METHOD, INTERNAL_CATCH_NTTP_REGISTER_METHOD, INTERNAL_CATCH_NTTP_REGISTER_METHOD0, INTERNAL_CATCH_NTTP_REGISTER_METHOD0)(TestName, __VA_ARGS__)) -#define INTERNAL_CATCH_NTTP_REG_GEN(TestFunc, ...) INTERNAL_CATCH_EXPAND_VARGS(INTERNAL_CATCH_VA_NARGS_IMPL( "dummy", __VA_ARGS__, INTERNAL_CATCH_NTTP_REGISTER, INTERNAL_CATCH_NTTP_REGISTER, INTERNAL_CATCH_NTTP_REGISTER, INTERNAL_CATCH_NTTP_REGISTER, INTERNAL_CATCH_NTTP_REGISTER, INTERNAL_CATCH_NTTP_REGISTER, INTERNAL_CATCH_NTTP_REGISTER, INTERNAL_CATCH_NTTP_REGISTER, INTERNAL_CATCH_NTTP_REGISTER, INTERNAL_CATCH_NTTP_REGISTER0, INTERNAL_CATCH_NTTP_REGISTER0)(TestFunc, __VA_ARGS__)) +#define INTERNAL_CATCH_NTTP_REG_GEN(TestFunc, ...) INTERNAL_CATCH_TYPES_REGISTER(TestFunc) INTERNAL_CATCH_EXPAND_VARGS(INTERNAL_CATCH_VA_NARGS_IMPL( "dummy", __VA_ARGS__, INTERNAL_CATCH_NTTP_REGISTER, INTERNAL_CATCH_NTTP_REGISTER, INTERNAL_CATCH_NTTP_REGISTER, INTERNAL_CATCH_NTTP_REGISTER, INTERNAL_CATCH_NTTP_REGISTER, INTERNAL_CATCH_NTTP_REGISTER, INTERNAL_CATCH_NTTP_REGISTER, INTERNAL_CATCH_NTTP_REGISTER, INTERNAL_CATCH_NTTP_REGISTER, INTERNAL_CATCH_NTTP_REGISTER0, INTERNAL_CATCH_NTTP_REGISTER0)(TestFunc, __VA_ARGS__)) #define INTERNAL_CATCH_DEFINE_SIG_TEST(TestName, ...) INTERNAL_CATCH_EXPAND_VARGS(INTERNAL_CATCH_VA_NARGS_IMPL( "dummy", __VA_ARGS__, INTERNAL_CATCH_DEFINE_SIG_TEST_X, INTERNAL_CATCH_DEFINE_SIG_TEST_X, INTERNAL_CATCH_DEFINE_SIG_TEST_X, INTERNAL_CATCH_DEFINE_SIG_TEST_X, INTERNAL_CATCH_DEFINE_SIG_TEST_X, INTERNAL_CATCH_DEFINE_SIG_TEST_X, INTERNAL_CATCH_DEFINE_SIG_TEST_X, INTERNAL_CATCH_DEFINE_SIG_TEST_X,INTERNAL_CATCH_DEFINE_SIG_TEST_X,INTERNAL_CATCH_DEFINE_SIG_TEST1, INTERNAL_CATCH_DEFINE_SIG_TEST0)(TestName, __VA_ARGS__)) #define INTERNAL_CATCH_DECLARE_SIG_TEST(TestName, ...) INTERNAL_CATCH_EXPAND_VARGS(INTERNAL_CATCH_VA_NARGS_IMPL( "dummy", __VA_ARGS__, INTERNAL_CATCH_DECLARE_SIG_TEST_X,INTERNAL_CATCH_DECLARE_SIG_TEST_X, INTERNAL_CATCH_DECLARE_SIG_TEST_X, INTERNAL_CATCH_DECLARE_SIG_TEST_X, INTERNAL_CATCH_DECLARE_SIG_TEST_X, INTERNAL_CATCH_DECLARE_SIG_TEST_X, INTERNAL_CATCH_DEFINE_SIG_TEST_X,INTERNAL_CATCH_DECLARE_SIG_TEST_X,INTERNAL_CATCH_DECLARE_SIG_TEST_X, INTERNAL_CATCH_DECLARE_SIG_TEST1, INTERNAL_CATCH_DECLARE_SIG_TEST0)(TestName, __VA_ARGS__)) #define INTERNAL_CATCH_REMOVE_PARENS_GEN(...) INTERNAL_CATCH_EXPAND_VARGS(INTERNAL_CATCH_VA_NARGS_IMPL(__VA_ARGS__, INTERNAL_CATCH_REMOVE_PARENS_11_ARG,INTERNAL_CATCH_REMOVE_PARENS_10_ARG,INTERNAL_CATCH_REMOVE_PARENS_9_ARG,INTERNAL_CATCH_REMOVE_PARENS_8_ARG,INTERNAL_CATCH_REMOVE_PARENS_7_ARG,INTERNAL_CATCH_REMOVE_PARENS_6_ARG,INTERNAL_CATCH_REMOVE_PARENS_5_ARG,INTERNAL_CATCH_REMOVE_PARENS_4_ARG,INTERNAL_CATCH_REMOVE_PARENS_3_ARG,INTERNAL_CATCH_REMOVE_PARENS_2_ARG,INTERNAL_CATCH_REMOVE_PARENS_1_ARG)(__VA_ARGS__)) @@ -6765,11 +6878,11 @@ static int catchInternalSectionHint = 0; constexpr char const* tmpl_types[] = {CATCH_REC_LIST(INTERNAL_CATCH_STRINGIZE_WITHOUT_PARENS, INTERNAL_CATCH_REMOVE_PARENS(TmplTypes))};\ constexpr char const* types_list[] = {CATCH_REC_LIST(INTERNAL_CATCH_STRINGIZE_WITHOUT_PARENS, INTERNAL_CATCH_REMOVE_PARENS(TypesList))};\ constexpr auto num_types = sizeof(types_list) / sizeof(types_list[0]);\ - (void)expander{(Catch::AutoReg( Catch::makeTestInvoker( &TestFuncName ), CATCH_INTERNAL_LINEINFO, Catch::StringRef(), Catch::NameAndTags{ Name " - " + std::string(tmpl_types[index / num_types]) + '<' + std::string(types_list[index % num_types]) + '>', Tags } ), index++)... };/* NOLINT */\ + (void)expander{(Catch::AutoReg( Catch::makeTestInvoker( &TestFuncName ), CATCH_INTERNAL_LINEINFO, Catch::StringRef(), Catch::NameAndTags{ Name " - " + std::string(tmpl_types[index / num_types]) + '<' + types_list[index % num_types] + '>', Tags } ), index++)... };/* NOLINT */\ } \ }; \ - static int INTERNAL_CATCH_UNIQUE_NAME( globalRegistrar ) = [](){ \ - using TestInit = typename create()), TypeList>::type; \ + static const int INTERNAL_CATCH_UNIQUE_NAME( globalRegistrar ) = [](){ \ + using TestInit = typename create(Catch::Detail::priority_tag<1>{})), TypeList>::type; \ TestInit t; \ t.reg_tests(); \ return 0; \ @@ -6814,7 +6927,7 @@ static int catchInternalSectionHint = 0; (void)expander{(Catch::AutoReg( Catch::makeTestInvoker( &TestFunc ), CATCH_INTERNAL_LINEINFO, Catch::StringRef(), Catch::NameAndTags{ Name " - " INTERNAL_CATCH_STRINGIZE(TmplList) " - " + std::to_string(index), Tags } ), index++)... };/* NOLINT */\ } \ };\ - static int INTERNAL_CATCH_UNIQUE_NAME( globalRegistrar ) = [](){ \ + static const int INTERNAL_CATCH_UNIQUE_NAME( globalRegistrar ) = [](){ \ using TestInit = typename convert::type; \ TestInit t; \ t.reg_tests(); \ @@ -6850,7 +6963,7 @@ static int catchInternalSectionHint = 0; (void)expander{(reg_test(Types{}, #ClassName, Catch::NameAndTags{ Name " - " + std::string(tmpl_types[index]), Tags } ), index++)... };/* NOLINT */ \ }\ };\ - static int INTERNAL_CATCH_UNIQUE_NAME( globalRegistrar ) = [](){\ + static const int INTERNAL_CATCH_UNIQUE_NAME( globalRegistrar ) = [](){\ TestNameClass();\ return 0;\ }();\ @@ -6897,11 +7010,11 @@ static int catchInternalSectionHint = 0; constexpr char const* tmpl_types[] = {CATCH_REC_LIST(INTERNAL_CATCH_STRINGIZE_WITHOUT_PARENS, INTERNAL_CATCH_REMOVE_PARENS(TmplTypes))};\ constexpr char const* types_list[] = {CATCH_REC_LIST(INTERNAL_CATCH_STRINGIZE_WITHOUT_PARENS, INTERNAL_CATCH_REMOVE_PARENS(TypesList))};\ constexpr auto num_types = sizeof(types_list) / sizeof(types_list[0]);\ - (void)expander{(Catch::AutoReg( Catch::makeTestInvoker( &TestName::test ), CATCH_INTERNAL_LINEINFO, #ClassName, Catch::NameAndTags{ Name " - " + std::string(tmpl_types[index / num_types]) + '<' + std::string(types_list[index % num_types]) + '>', Tags } ), index++)... };/* NOLINT */ \ + (void)expander{(Catch::AutoReg( Catch::makeTestInvoker( &TestName::test ), CATCH_INTERNAL_LINEINFO, #ClassName, Catch::NameAndTags{ Name " - " + std::string(tmpl_types[index / num_types]) + '<' + types_list[index % num_types] + '>', Tags } ), index++)... };/* NOLINT */ \ }\ };\ - static int INTERNAL_CATCH_UNIQUE_NAME( globalRegistrar ) = [](){\ - using TestInit = typename create()), TypeList>::type;\ + static const int INTERNAL_CATCH_UNIQUE_NAME( globalRegistrar ) = [](){\ + using TestInit = typename create(Catch::Detail::priority_tag<1>{})), TypeList>::type;\ TestInit t;\ t.reg_tests();\ return 0;\ @@ -6949,7 +7062,7 @@ static int catchInternalSectionHint = 0; (void)expander{(Catch::AutoReg( Catch::makeTestInvoker( &TestName::test ), CATCH_INTERNAL_LINEINFO, #ClassName##_catch_sr, Catch::NameAndTags{ Name " - " INTERNAL_CATCH_STRINGIZE(TmplList) " - " + std::to_string(index), Tags } ), index++)... };/* NOLINT */ \ }\ };\ - static int INTERNAL_CATCH_UNIQUE_NAME( globalRegistrar ) = [](){\ + static const int INTERNAL_CATCH_UNIQUE_NAME( globalRegistrar ) = [](){\ using TestInit = typename convert::type;\ TestInit t;\ t.reg_tests();\ @@ -7201,6 +7314,22 @@ namespace Catch { #endif // CATCH_TEST_CASE_INFO_HPP_INCLUDED +#ifndef CATCH_TEST_RUN_INFO_HPP_INCLUDED +#define CATCH_TEST_RUN_INFO_HPP_INCLUDED + + +namespace Catch { + + struct TestRunInfo { + constexpr TestRunInfo(StringRef _name) : name(_name) {} + StringRef name; + }; + +} // end namespace Catch + +#endif // CATCH_TEST_RUN_INFO_HPP_INCLUDED + + #ifndef CATCH_TRANSLATE_EXCEPTION_HPP_INCLUDED #define CATCH_TRANSLATE_EXCEPTION_HPP_INCLUDED @@ -7288,7 +7417,7 @@ namespace Catch { static std::string translatorName( signature ); \ CATCH_INTERNAL_START_WARNINGS_SUPPRESSION \ CATCH_INTERNAL_SUPPRESS_GLOBALS_WARNINGS \ - namespace{ Catch::ExceptionTranslatorRegistrar INTERNAL_CATCH_UNIQUE_NAME( catch_internal_ExceptionRegistrar )( &translatorName ); } \ + namespace{ const Catch::ExceptionTranslatorRegistrar INTERNAL_CATCH_UNIQUE_NAME( catch_internal_ExceptionRegistrar )( &translatorName ); } \ CATCH_INTERNAL_STOP_WARNINGS_SUPPRESSION \ static std::string translatorName( signature ) @@ -7349,8 +7478,8 @@ namespace Catch { #define CATCH_VERSION_MACROS_HPP_INCLUDED #define CATCH_VERSION_MAJOR 3 -#define CATCH_VERSION_MINOR 8 -#define CATCH_VERSION_PATCH 1 +#define CATCH_VERSION_MINOR 11 +#define CATCH_VERSION_PATCH 0 #endif // CATCH_VERSION_MACROS_HPP_INCLUDED @@ -7391,7 +7520,7 @@ namespace Catch { m_msg(msg) {} - const char* what() const noexcept override final; + const char* what() const noexcept final; }; } // end namespace Catch @@ -7765,8 +7894,9 @@ namespace Generators { class FilterGenerator final : public IGenerator { GeneratorWrapper m_generator; Predicate m_predicate; + static_assert(!std::is_reference::value, "This would most likely result in a dangling reference"); public: - template + template FilterGenerator(P&& pred, GeneratorWrapper&& generator): m_generator(CATCH_MOVE(generator)), m_predicate(CATCH_FORWARD(pred)) @@ -7798,7 +7928,7 @@ namespace Generators { template GeneratorWrapper filter(Predicate&& pred, GeneratorWrapper&& generator) { - return GeneratorWrapper(Catch::Detail::make_unique>(CATCH_FORWARD(pred), CATCH_MOVE(generator))); + return GeneratorWrapper(Catch::Detail::make_unique::type>>(CATCH_FORWARD(pred), CATCH_MOVE(generator))); } template @@ -8778,26 +8908,9 @@ auto from_range(Container const& cnt) { #define CATCH_INTERFACES_REPORTER_HPP_INCLUDED - -#ifndef CATCH_TEST_RUN_INFO_HPP_INCLUDED -#define CATCH_TEST_RUN_INFO_HPP_INCLUDED - - -namespace Catch { - - struct TestRunInfo { - constexpr TestRunInfo(StringRef _name) : name(_name) {} - StringRef name; - }; - -} // end namespace Catch - -#endif // CATCH_TEST_RUN_INFO_HPP_INCLUDED - #include #include #include -#include namespace Catch { @@ -8893,6 +9006,11 @@ namespace Catch { //! Catch2 should call `Reporter::assertionEnded` even for passing //! assertions bool shouldReportAllAssertions = false; + //! Catch2 should call `Reporter::assertionStarting` for all assertions + // Defaults to true for backwards compatibility, but none of our current + // reporters actually want this, and it enables a fast path in assertion + // handling. + bool shouldReportAllAssertionStarts = true; }; /** @@ -9415,6 +9533,17 @@ namespace Catch { bool isDebuggerActive(); } +#if !defined( CATCH_TRAP ) && defined( __clang__ ) && defined( __has_builtin ) +# if __has_builtin( __builtin_debugtrap ) +# define CATCH_TRAP() __builtin_debugtrap() +# endif +#endif + +#if !defined( CATCH_TRAP ) && defined( _MSC_VER ) +# define CATCH_TRAP() __debugbreak() +#endif + +#if !defined(CATCH_TRAP) // If we couldn't use compiler-specific impl from above, we get into platform-specific options #ifdef CATCH_PLATFORM_MAC #if defined(__i386__) || defined(__x86_64__) @@ -9439,7 +9568,7 @@ namespace Catch { #define CATCH_TRAP() __asm__(".inst 0xde01") #endif -#elif defined(CATCH_PLATFORM_LINUX) +#elif defined(CATCH_PLATFORM_LINUX) || defined(CATCH_PLATFORM_QNX) // If we can use inline assembler, do it because this allows us to break // directly at the location of the failing check instead of breaking inside // raise() called from it, i.e. one stack frame below. @@ -9450,15 +9579,15 @@ namespace Catch { #define CATCH_TRAP() raise(SIGTRAP) #endif -#elif defined(_MSC_VER) - #define CATCH_TRAP() __debugbreak() #elif defined(__MINGW32__) extern "C" __declspec(dllimport) void __stdcall DebugBreak(); #define CATCH_TRAP() DebugBreak() #endif +#endif // ^^ CATCH_TRAP is not defined yet, so we define it -#ifndef CATCH_BREAK_INTO_DEBUGGER - #ifdef CATCH_TRAP + +#if !defined(CATCH_BREAK_INTO_DEBUGGER) + #if defined(CATCH_TRAP) #define CATCH_BREAK_INTO_DEBUGGER() []{ if( Catch::isDebuggerActive() ) { CATCH_TRAP(); } }() #else #define CATCH_BREAK_INTO_DEBUGGER() []{}() @@ -9472,7 +9601,7 @@ namespace Catch { #define CATCH_ENFORCE_HPP_INCLUDED -#include +#include // for `std::exception` in no-exception configuration namespace Catch { #if !defined(CATCH_CONFIG_DISABLE_EXCEPTIONS) @@ -9568,7 +9697,6 @@ namespace Catch { #define CATCH_EXCEPTION_TRANSLATOR_REGISTRY_HPP_INCLUDED -#include #include namespace Catch { @@ -9768,8 +9896,8 @@ namespace Detail { #ifndef CATCH_IS_PERMUTATION_HPP_INCLUDED #define CATCH_IS_PERMUTATION_HPP_INCLUDED -#include #include +#include namespace Catch { namespace Detail { @@ -9906,8 +10034,6 @@ namespace Catch { #include -#include -#include #include namespace Catch { @@ -10491,6 +10617,48 @@ using TestCaseTracking::SectionTracker; #endif // CATCH_TEST_CASE_TRACKER_HPP_INCLUDED + +#ifndef CATCH_THREAD_SUPPORT_HPP_INCLUDED +#define CATCH_THREAD_SUPPORT_HPP_INCLUDED + + +#if defined( CATCH_CONFIG_EXPERIMENTAL_THREAD_SAFE_ASSERTIONS ) +# include +# include +#endif + + +namespace Catch { + namespace Detail { +#if defined( CATCH_CONFIG_EXPERIMENTAL_THREAD_SAFE_ASSERTIONS ) + using Mutex = std::mutex; + using LockGuard = std::lock_guard; + struct AtomicCounts { + std::atomic passed = 0; + std::atomic failed = 0; + std::atomic failedButOk = 0; + std::atomic skipped = 0; + }; +#else // ^^ Use actual mutex, lock and atomics + // vv Dummy implementations for single-thread performance + + struct Mutex { + void lock() {} + void unlock() {} + }; + + struct LockGuard { + LockGuard( Mutex ) {} + }; + + using AtomicCounts = Counts; +#endif + + } // namespace Detail +} // namespace Catch + +#endif // CATCH_THREAD_SUPPORT_HPP_INCLUDED + #include namespace Catch { @@ -10563,11 +10731,6 @@ namespace Catch { void benchmarkEnded( BenchmarkStats<> const& stats ) override; void benchmarkFailed( StringRef error ) override; - void pushScopedMessage( MessageInfo const& message ) override; - void popScopedMessage( MessageInfo const& message ) override; - - void emplaceUnscopedMessage( MessageBuilder&& builder ) override; - std::string getCurrentTestName() const override; const AssertionResult* getLastResult() const override; @@ -10578,18 +10741,18 @@ namespace Catch { bool lastAssertionPassed() override; - void assertionPassed() override; - public: // !TBD We need to do this another way! bool aborting() const; private: + void assertionPassedFastPath( SourceLineInfo lineInfo ); + // Update the non-thread-safe m_totals from the atomic assertion counts. + void updateTotalsFromAtomics(); void runCurrentTest(); void invokeActiveTestCase(); - void resetAssertionInfo(); bool testForMissingAssertions( Counts& assertions ); void assertionEnded( AssertionResult&& result ); @@ -10599,31 +10762,39 @@ namespace Catch { ITransientExpression const *expr, bool negated ); - void populateReaction( AssertionReaction& reaction ); + void populateReaction( AssertionReaction& reaction, bool has_normal_disposition ); + + // Creates dummy info for unexpected exceptions/fatal errors, + // where we do not have the access to one, but we still need + // to send one to the reporters. + AssertionInfo makeDummyAssertionInfo(); private: void handleUnfinishedSections(); - + mutable Detail::Mutex m_assertionMutex; TestRunInfo m_runInfo; TestCaseHandle const* m_activeTestCase = nullptr; ITracker* m_testCaseTracker = nullptr; Optional m_lastResult; - IConfig const* m_config; Totals m_totals; + Detail::AtomicCounts m_atomicAssertionCount; IEventListenerPtr m_reporter; - std::vector m_messages; - std::vector m_messageScopes; /* Keeps owners of so-called unscoped messages. */ - AssertionInfo m_lastAssertionInfo; std::vector m_unfinishedSections; std::vector m_activeSections; TrackerContext m_trackerContext; Detail::unique_ptr m_outputRedirect; FatalConditionHandler m_fatalConditionhandler; - bool m_lastAssertionPassed = false; + // Caches m_config->abortAfter() to avoid vptr calls/allow inlining + size_t m_abortAfterXFailedAssertions; bool m_shouldReportUnexpected = true; + // Caches whether `assertionStarting` events should be sent to the reporter. + bool m_reportAssertionStarting; + // Caches whether `assertionEnded` events for successful assertions should be sent to the reporter bool m_includeSuccessfulResults; + // Caches m_config->shouldDebugBreak() to avoid vptr calls/allow inlining + bool m_shouldDebugBreak; }; void seedRng(IConfig const& config); @@ -10637,7 +10808,6 @@ namespace Catch { #define CATCH_SHARDING_HPP_INCLUDED #include -#include #include namespace Catch { @@ -11860,19 +12030,19 @@ namespace Matchers { } template - using is_generic_matcher = std::is_base_of< + static constexpr bool is_generic_matcher_v = std::is_base_of< Catch::Matchers::MatcherGenericBase, std::remove_cv_t> - >; + >::value; template - using are_generic_matchers = Catch::Detail::conjunction...>; + static constexpr bool are_generic_matchers_v = Catch::Detail::conjunction>...>::value; template - using is_matcher = std::is_base_of< + static constexpr bool is_matcher_v = std::is_base_of< Catch::Matchers::MatcherUntypedBase, std::remove_cv_t> - >; + >::value; template @@ -11945,7 +12115,7 @@ namespace Matchers { //! Avoids type nesting for `GenericAllOf && some matcher` case template - friend std::enable_if_t::value, + friend std::enable_if_t, MatchAllOfGeneric> operator && ( MatchAllOfGeneric&& lhs, MatcherRHS const& rhs) { @@ -11954,7 +12124,7 @@ namespace Matchers { //! Avoids type nesting for `some matcher && GenericAllOf` case template - friend std::enable_if_t::value, + friend std::enable_if_t, MatchAllOfGeneric> operator && ( MatcherLHS const& lhs, MatchAllOfGeneric&& rhs) { @@ -11999,7 +12169,7 @@ namespace Matchers { //! Avoids type nesting for `GenericAnyOf || some matcher` case template - friend std::enable_if_t::value, + friend std::enable_if_t, MatchAnyOfGeneric> operator || ( MatchAnyOfGeneric&& lhs, MatcherRHS const& rhs) { @@ -12008,7 +12178,7 @@ namespace Matchers { //! Avoids type nesting for `some matcher || GenericAnyOf` case template - friend std::enable_if_t::value, + friend std::enable_if_t, MatchAnyOfGeneric> operator || ( MatcherLHS const& lhs, MatchAnyOfGeneric&& rhs) { @@ -12048,20 +12218,20 @@ namespace Matchers { // compose only generic matchers template - std::enable_if_t::value, Detail::MatchAllOfGeneric> + std::enable_if_t, Detail::MatchAllOfGeneric> operator && (MatcherLHS const& lhs, MatcherRHS const& rhs) { return { lhs, rhs }; } template - std::enable_if_t::value, Detail::MatchAnyOfGeneric> + std::enable_if_t, Detail::MatchAnyOfGeneric> operator || (MatcherLHS const& lhs, MatcherRHS const& rhs) { return { lhs, rhs }; } //! Wrap provided generic matcher in generic negator template - std::enable_if_t::value, Detail::MatchNotOfGeneric> + std::enable_if_t, Detail::MatchNotOfGeneric> operator ! (MatcherT const& matcher) { return Detail::MatchNotOfGeneric{matcher}; } @@ -12069,25 +12239,25 @@ namespace Matchers { // compose mixed generic and non-generic matchers template - std::enable_if_t::value, Detail::MatchAllOfGeneric>> + std::enable_if_t, Detail::MatchAllOfGeneric>> operator && (MatcherLHS const& lhs, MatcherBase const& rhs) { return { lhs, rhs }; } template - std::enable_if_t::value, Detail::MatchAllOfGeneric, MatcherRHS>> + std::enable_if_t, Detail::MatchAllOfGeneric, MatcherRHS>> operator && (MatcherBase const& lhs, MatcherRHS const& rhs) { return { lhs, rhs }; } template - std::enable_if_t::value, Detail::MatchAnyOfGeneric>> + std::enable_if_t, Detail::MatchAnyOfGeneric>> operator || (MatcherLHS const& lhs, MatcherBase const& rhs) { return { lhs, rhs }; } template - std::enable_if_t::value, Detail::MatchAnyOfGeneric, MatcherRHS>> + std::enable_if_t, Detail::MatchAnyOfGeneric, MatcherRHS>> operator || (MatcherBase const& lhs, MatcherRHS const& rhs) { return { lhs, rhs }; } @@ -12164,7 +12334,7 @@ namespace Catch { //! Creates a matcher that accepts ranges/containers with specific size HasSizeMatcher SizeIs(std::size_t sz); template - std::enable_if_t::value, + std::enable_if_t, SizeMatchesMatcher> SizeIs(Matcher&& m) { return SizeMatchesMatcher{CATCH_FORWARD(m)}; } @@ -12179,8 +12349,8 @@ namespace Catch { #define CATCH_MATCHERS_CONTAINS_HPP_INCLUDED -#include #include +#include namespace Catch { namespace Matchers { @@ -12242,14 +12412,14 @@ namespace Catch { * Uses `std::equal_to` to do the comparison */ template - std::enable_if_t::value, + std::enable_if_t, ContainsElementMatcher>> Contains(T&& elem) { return { CATCH_FORWARD(elem), std::equal_to<>{} }; } //! Creates a matcher that checks whether a range contains element matching a matcher template - std::enable_if_t::value, + std::enable_if_t, ContainsMatcherMatcher> Contains(Matcher&& matcher) { return { CATCH_FORWARD(matcher) }; } @@ -12626,8 +12796,7 @@ namespace Catch { #define CATCH_MATCHERS_RANGE_EQUALS_HPP_INCLUDED -#include -#include +#include namespace Catch { namespace Matchers { @@ -13211,9 +13380,11 @@ namespace Catch { public: // GCC5 compat: we cannot use inherited constructor, because it // doesn't implement backport of P0136 - AutomakeReporter(ReporterConfig&& _config): - StreamingReporterBase(CATCH_MOVE(_config)) - {} + AutomakeReporter( ReporterConfig&& _config ): + StreamingReporterBase( CATCH_MOVE( _config ) ) { + m_preferences.shouldReportAllAssertionStarts = false; + } + ~AutomakeReporter() override; static std::string getDescription() { @@ -13240,7 +13411,10 @@ namespace Catch { class CompactReporter final : public StreamingReporterBase { public: - using StreamingReporterBase::StreamingReporterBase; + CompactReporter( ReporterConfig&& _config ): + StreamingReporterBase( CATCH_MOVE( _config ) ) { + m_preferences.shouldReportAllAssertionStarts = false; + } ~CompactReporter() override; @@ -13282,8 +13456,6 @@ namespace Catch { void noMatchingTestCases( StringRef unmatchedSpec ) override; void reportInvalidTestSpec( StringRef arg ) override; - void assertionStarting(AssertionInfo const&) override; - void assertionEnded(AssertionStats const& _assertionStats) override; void sectionStarting(SectionInfo const& _sectionInfo) override; @@ -13637,7 +13809,6 @@ namespace Catch { void sectionStarting( SectionInfo const& sectionInfo ) override; void sectionEnded( SectionStats const& sectionStats ) override; - void assertionStarting( AssertionInfo const& assertionInfo ) override; void assertionEnded( AssertionStats const& assertionStats ) override; //void testRunEndedCumulative() override; @@ -13764,6 +13935,11 @@ namespace Catch { void updatePreferences(IEventListener const& reporterish); public: + MultiReporter( IConfig const* config ): + IEventListener( config ) { + m_preferences.shouldReportAllAssertionStarts = false; + } + using IEventListener::IEventListener; void addListener( IEventListenerPtr&& listener ); @@ -13899,22 +14075,24 @@ namespace Catch { #if !defined(CATCH_CONFIG_DISABLE) -# define CATCH_REGISTER_REPORTER( name, reporterType ) \ - CATCH_INTERNAL_START_WARNINGS_SUPPRESSION \ - CATCH_INTERNAL_SUPPRESS_GLOBALS_WARNINGS \ - namespace { \ - Catch::ReporterRegistrar INTERNAL_CATCH_UNIQUE_NAME( \ - catch_internal_RegistrarFor )( name ); \ - } \ +# define CATCH_REGISTER_REPORTER( name, reporterType ) \ + CATCH_INTERNAL_START_WARNINGS_SUPPRESSION \ + CATCH_INTERNAL_SUPPRESS_GLOBALS_WARNINGS \ + namespace { \ + const Catch::ReporterRegistrar \ + INTERNAL_CATCH_UNIQUE_NAME( catch_internal_RegistrarFor )( \ + name ); \ + } \ CATCH_INTERNAL_STOP_WARNINGS_SUPPRESSION -# define CATCH_REGISTER_LISTENER( listenerType ) \ - CATCH_INTERNAL_START_WARNINGS_SUPPRESSION \ - CATCH_INTERNAL_SUPPRESS_GLOBALS_WARNINGS \ - namespace { \ - Catch::ListenerRegistrar INTERNAL_CATCH_UNIQUE_NAME( \ - catch_internal_RegistrarFor )( #listenerType##_catch_sr ); \ - } \ +# define CATCH_REGISTER_LISTENER( listenerType ) \ + CATCH_INTERNAL_START_WARNINGS_SUPPRESSION \ + CATCH_INTERNAL_SUPPRESS_GLOBALS_WARNINGS \ + namespace { \ + const Catch::ListenerRegistrar \ + INTERNAL_CATCH_UNIQUE_NAME( catch_internal_RegistrarFor )( \ + #listenerType##_catch_sr ); \ + } \ CATCH_INTERNAL_STOP_WARNINGS_SUPPRESSION #else // CATCH_CONFIG_DISABLE @@ -13941,6 +14119,7 @@ namespace Catch { , xml(m_stream) { m_preferences.shouldRedirectStdOut = true; m_preferences.shouldReportAllAssertions = false; + m_preferences.shouldReportAllAssertionStarts = false; m_shouldStoreSuccesfulAssertions = false; } @@ -13989,6 +14168,7 @@ namespace Catch { TAPReporter( ReporterConfig&& config ): StreamingReporterBase( CATCH_MOVE(config) ) { m_preferences.shouldReportAllAssertions = true; + m_preferences.shouldReportAllAssertionStarts = false; } static std::string getDescription() { @@ -14032,6 +14212,7 @@ namespace Catch { : StreamingReporterBase( CATCH_MOVE(_config) ) { m_preferences.shouldRedirectStdOut = true; + m_preferences.shouldReportAllAssertionStarts = false; } ~TeamCityReporter() override; @@ -14099,8 +14280,6 @@ namespace Catch { void sectionStarting(SectionInfo const& sectionInfo) override; - void assertionStarting(AssertionInfo const&) override; - void assertionEnded(AssertionStats const& assertionStats) override; void sectionEnded(SectionStats const& sectionStats) override; diff --git a/fuzzing/CMakeLists.txt b/fuzzing/CMakeLists.txt index daba61d..8370fe3 100644 --- a/fuzzing/CMakeLists.txt +++ b/fuzzing/CMakeLists.txt @@ -15,6 +15,6 @@ target_compile_features(fuzzhelper PUBLIC cxx_std_17) target_link_options(fuzzhelper PUBLIC "-fsanitize=fuzzer") foreach(fuzzer TestSpecParser XmlWriter textflow) -add_executable(fuzz_${fuzzer} fuzz_${fuzzer}.cpp) -target_link_libraries(fuzz_${fuzzer} PRIVATE fuzzhelper) + add_executable(fuzz_${fuzzer} fuzz_${fuzzer}.cpp) + target_link_libraries(fuzz_${fuzzer} PRIVATE fuzzhelper) endforeach() diff --git a/fuzzing/NullOStream.h b/fuzzing/NullOStream.h index abbec09..390ac86 100644 --- a/fuzzing/NullOStream.h +++ b/fuzzing/NullOStream.h @@ -16,7 +16,7 @@ class NullStreambuf : public std::streambuf { char dummyBuffer[64]; protected: - virtual int overflow(int c) override final; + virtual int overflow(int c) final; }; class NullOStream final : private NullStreambuf, public std::ostream { diff --git a/meson.build b/meson.build index 6346d44..16b3879 100644 --- a/meson.build +++ b/meson.build @@ -8,7 +8,7 @@ project( 'catch2', 'cpp', - version: '3.8.1', # CML version placeholder, don't delete + version: '3.11.0', # CML version placeholder, don't delete license: 'BSL-1.0', meson_version: '>=0.54.1', ) diff --git a/src/CMakeLists.txt b/src/CMakeLists.txt index c40de04..1e3af14 100644 --- a/src/CMakeLists.txt +++ b/src/CMakeLists.txt @@ -40,7 +40,6 @@ set(BENCHMARK_SOURCES ) set(BENCHMARK_FILES ${BENCHMARK_HEADERS} ${BENCHMARK_SOURCES}) - set(IMPL_HEADERS "${PROJECT_BINARY_DIR}/generated-includes/catch2/catch_user_config.hpp" ${SOURCES_DIR}/catch_user_config.hpp.in @@ -59,6 +58,7 @@ set(IMPL_HEADERS ${SOURCES_DIR}/catch_template_test_macros.hpp ${SOURCES_DIR}/catch_test_case_info.hpp ${SOURCES_DIR}/catch_test_macros.hpp + ${SOURCES_DIR}/catch_test_run_info.hpp ${SOURCES_DIR}/catch_test_spec.hpp ${SOURCES_DIR}/catch_timer.hpp ${SOURCES_DIR}/catch_tostring.hpp @@ -85,6 +85,7 @@ set(IMPL_HEADERS ${SOURCES_DIR}/internal/catch_debug_console.hpp ${SOURCES_DIR}/internal/catch_debugger.hpp ${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_errno_guard.hpp @@ -136,15 +137,16 @@ set(IMPL_HEADERS ${SOURCES_DIR}/internal/catch_test_failure_exception.hpp ${SOURCES_DIR}/internal/catch_test_macro_impl.hpp ${SOURCES_DIR}/internal/catch_test_registry.hpp - ${SOURCES_DIR}/internal/catch_test_run_info.hpp ${SOURCES_DIR}/internal/catch_test_spec_parser.hpp ${SOURCES_DIR}/internal/catch_textflow.hpp + ${SOURCES_DIR}/internal/catch_thread_support.hpp ${SOURCES_DIR}/internal/catch_to_string.hpp ${SOURCES_DIR}/internal/catch_uncaught_exceptions.hpp ${SOURCES_DIR}/internal/catch_uniform_floating_point_distribution.hpp ${SOURCES_DIR}/internal/catch_uniform_integer_distribution.hpp ${SOURCES_DIR}/internal/catch_unique_name.hpp ${SOURCES_DIR}/internal/catch_unique_ptr.hpp + ${SOURCES_DIR}/internal/catch_unreachable.hpp ${SOURCES_DIR}/internal/catch_void_type.hpp ${SOURCES_DIR}/internal/catch_wildcard_pattern.hpp ${SOURCES_DIR}/internal/catch_windows_h_proxy.hpp @@ -348,25 +350,23 @@ source_group("generated headers" ) add_library(Catch2 ${ALL_FILES}) -if (CATCH_ENABLE_REPRODUCIBLE_BUILD) - add_build_reproducibility_settings(Catch2) +if(CATCH_ENABLE_REPRODUCIBLE_BUILD) + add_build_reproducibility_settings(Catch2) endif() add_library(Catch2::Catch2 ALIAS Catch2) -if (ANDROID) - target_link_libraries(Catch2 PRIVATE log) +if(ANDROID) + target_link_libraries(Catch2 PRIVATE log) endif() set_target_properties(Catch2 PROPERTIES - DEBUG_POSTFIX "d" - VERSION ${PROJECT_VERSION} - SOVERSION ${PROJECT_VERSION}) + DEBUG_POSTFIX "d" + VERSION ${PROJECT_VERSION} + SOVERSION ${PROJECT_VERSION} +) # require C++14 -target_compile_features(Catch2 - PUBLIC - cxx_std_14 -) +target_compile_features(Catch2 PUBLIC cxx_std_14) configure_file( "${SOURCES_DIR}/catch_user_config.hpp.in" @@ -382,105 +382,97 @@ target_include_directories(Catch2 add_library(Catch2WithMain - ${SOURCES_DIR}/internal/catch_main.cpp + ${SOURCES_DIR}/internal/catch_main.cpp ) -if (CATCH_ENABLE_REPRODUCIBLE_BUILD) - add_build_reproducibility_settings(Catch2WithMain) +if(CATCH_ENABLE_REPRODUCIBLE_BUILD) + add_build_reproducibility_settings(Catch2WithMain) endif() add_library(Catch2::Catch2WithMain ALIAS Catch2WithMain) target_link_libraries(Catch2WithMain PUBLIC Catch2) set_target_properties(Catch2WithMain PROPERTIES OUTPUT_NAME "Catch2Main" - VERSION ${PROJECT_VERSION} - SOVERSION ${PROJECT_VERSION} + VERSION ${PROJECT_VERSION} + SOVERSION ${PROJECT_VERSION} ) set_target_properties(Catch2WithMain PROPERTIES DEBUG_POSTFIX "d") -if (NOT_SUBPROJECT) - # create and install an export set for catch target as Catch2::Catch - install( - TARGETS - Catch2 - Catch2WithMain - EXPORT - Catch2Targets - LIBRARY DESTINATION - ${CMAKE_INSTALL_LIBDIR} - ARCHIVE DESTINATION - ${CMAKE_INSTALL_LIBDIR} - RUNTIME DESTINATION - ${CMAKE_INSTALL_BINDIR} - ) - +if(NOT_SUBPROJECT) + # create and install an export set for catch target as Catch2::Catch + install( + TARGETS + Catch2 + Catch2WithMain + EXPORT + Catch2Targets + LIBRARY DESTINATION + ${CMAKE_INSTALL_LIBDIR} + ARCHIVE DESTINATION + ${CMAKE_INSTALL_LIBDIR} + RUNTIME DESTINATION + ${CMAKE_INSTALL_BINDIR} + ) - install( - EXPORT - Catch2Targets - NAMESPACE - Catch2:: - DESTINATION - ${CATCH_CMAKE_CONFIG_DESTINATION} - ) - # Install the headers - install( - DIRECTORY - "${SOURCES_DIR}" - "${PROJECT_BINARY_DIR}/generated-includes/catch2" # Also install the generated header - DESTINATION - "${CMAKE_INSTALL_INCLUDEDIR}" - FILES_MATCHING - PATTERN "*.hpp" - ) + install( + EXPORT + Catch2Targets + NAMESPACE + Catch2:: + DESTINATION + ${CATCH_CMAKE_CONFIG_DESTINATION} + ) + # Install the headers + install( + DIRECTORY + "${SOURCES_DIR}" + "${PROJECT_BINARY_DIR}/generated-includes/catch2" # Also install the generated header + DESTINATION + "${CMAKE_INSTALL_INCLUDEDIR}" + FILES_MATCHING + PATTERN "*.hpp" + ) endif() # Some tests require a full recompilation of Catch2 lib with different # compilation flags. They can link against this target to recompile all # the sources into the binary. -if (CATCH_BUILD_EXAMPLES OR CATCH_BUILD_EXTRA_TESTS) - add_library(Catch2_buildall_interface INTERFACE) - target_sources(Catch2_buildall_interface INTERFACE - ${ALL_FILES} - # Also include main entry point - ${SOURCES_DIR}/internal/catch_main.cpp - ) - target_include_directories(Catch2_buildall_interface - INTERFACE - $ - $ - $ - ) - target_compile_definitions(Catch2_buildall_interface - INTERFACE - CATCH_CONFIG_STATIC - ) - target_compile_features(Catch2_buildall_interface - INTERFACE - cxx_std_14 - ) +if(CATCH_BUILD_EXAMPLES OR CATCH_BUILD_EXTRA_TESTS) + add_library(Catch2_buildall_interface INTERFACE) + target_sources(Catch2_buildall_interface INTERFACE + ${ALL_FILES} + # Also include main entry point + ${SOURCES_DIR}/internal/catch_main.cpp + ) + target_include_directories(Catch2_buildall_interface + INTERFACE + $ + $ + $ + ) + target_compile_definitions(Catch2_buildall_interface INTERFACE CATCH_CONFIG_STATIC) + target_compile_features(Catch2_buildall_interface INTERFACE cxx_std_14) endif() -list(APPEND CATCH_WARNING_TARGETS Catch2 Catch2WithMain) -set(CATCH_WARNING_TARGETS ${CATCH_WARNING_TARGETS} PARENT_SCOPE) - +list(APPEND CATCH_IMPL_TARGETS Catch2 Catch2WithMain) +set(CATCH_IMPL_TARGETS ${CATCH_IMPL_TARGETS} PARENT_SCOPE) # We still do not support building dynamic library with hidden visibility # so we want to check & warn users if they do this. However, we won't abort # the configuration step so that we don't have to also provide an override. -if (BUILD_SHARED_LIBS) +if(BUILD_SHARED_LIBS) + set_target_properties(Catch2 Catch2WithMain + PROPERTIES + WINDOWS_EXPORT_ALL_SYMBOLS ON + ) + + get_target_property(_VisPreset Catch2 CXX_VISIBILITY_PRESET) + if(NOT MSVC AND _VisPreset STREQUAL "hidden") set_target_properties(Catch2 Catch2WithMain PROPERTIES - WINDOWS_EXPORT_ALL_SYMBOLS ON + CXX_VISIBILITY_PRESET "default" + VISIBILITY_INLINES_HIDDEN OFF ) - - get_target_property(_VisPreset Catch2 CXX_VISIBILITY_PRESET) - if (NOT MSVC AND _VisPreset STREQUAL "hidden") - set_target_properties(Catch2 Catch2WithMain - PROPERTIES - CXX_VISIBILITY_PRESET "default" - VISIBILITY_INLINES_HIDDEN OFF - ) - message(WARNING "Setting Catch2's visibility to default." - " Hidden visibility is not supported.") - endif() + message(WARNING "Setting Catch2's visibility to default." + " Hidden visibility is not supported.") + endif() endif() diff --git a/src/catch2/benchmark/catch_benchmark.hpp b/src/catch2/benchmark/catch_benchmark.hpp index d0f88cf..ce68aee 100644 --- a/src/catch2/benchmark/catch_benchmark.hpp +++ b/src/catch2/benchmark/catch_benchmark.hpp @@ -86,9 +86,7 @@ namespace Catch { auto analysis = Detail::analyse(*cfg, samples.data(), samples.data() + samples.size()); BenchmarkStats<> stats{ CATCH_MOVE(info), CATCH_MOVE(analysis.samples), analysis.mean, analysis.standard_deviation, analysis.outliers, analysis.outlier_variance }; getResultCapture().benchmarkEnded(stats); - } CATCH_CATCH_ANON (TestFailureException const&) { - getResultCapture().benchmarkFailed("Benchmark failed due to failed assertion"_sr); - } CATCH_CATCH_ALL{ + } CATCH_CATCH_ALL { getResultCapture().benchmarkFailed(translateActiveException()); // We let the exception go further up so that the // test case is marked as failed. @@ -97,7 +95,7 @@ namespace Catch { } // sets lambda to be used in fun *and* executes benchmark! - template ::value, int> = 0> + template , int> = 0> Benchmark & operator=(Fun func) { auto const* cfg = getCurrentContext().getConfig(); if (!cfg->skipBenchmarks()) { diff --git a/src/catch2/benchmark/detail/catch_benchmark_function.hpp b/src/catch2/benchmark/detail/catch_benchmark_function.hpp index a03cb11..90b07b7 100644 --- a/src/catch2/benchmark/detail/catch_benchmark_function.hpp +++ b/src/catch2/benchmark/detail/catch_benchmark_function.hpp @@ -21,8 +21,7 @@ namespace Catch { namespace Benchmark { namespace Detail { template - struct is_related - : std::is_same, std::decay_t> {}; + static constexpr bool is_related_v = std::is_same, std::decay_t>::value; /// We need to reinvent std::function because every piece of code that might add overhead /// in a measurement context needs to have consistent performance characteristics so that we @@ -63,7 +62,7 @@ namespace Catch { BenchmarkFunction(); template ::value, int> = 0> + std::enable_if_t, int> = 0> BenchmarkFunction(Fun&& fun) : f(new model>(CATCH_FORWARD(fun))) {} diff --git a/src/catch2/benchmark/detail/catch_estimate_clock.hpp b/src/catch2/benchmark/detail/catch_estimate_clock.hpp index 6da24ce..576fdb6 100644 --- a/src/catch2/benchmark/detail/catch_estimate_clock.hpp +++ b/src/catch2/benchmark/detail/catch_estimate_clock.hpp @@ -15,7 +15,6 @@ #include #include #include -#include #include #include diff --git a/src/catch2/benchmark/detail/catch_timing.hpp b/src/catch2/benchmark/detail/catch_timing.hpp index da56719..7dfe822 100644 --- a/src/catch2/benchmark/detail/catch_timing.hpp +++ b/src/catch2/benchmark/detail/catch_timing.hpp @@ -13,8 +13,6 @@ #include #include -#include - namespace Catch { namespace Benchmark { template diff --git a/src/catch2/catch_all.hpp b/src/catch2/catch_all.hpp index 5715995..2417d85 100644 --- a/src/catch2/catch_all.hpp +++ b/src/catch2/catch_all.hpp @@ -37,6 +37,7 @@ #include #include #include +#include #include #include #include @@ -65,6 +66,7 @@ #include #include #include +#include #include #include #include @@ -117,15 +119,16 @@ #include #include #include -#include #include #include +#include #include #include #include #include #include #include +#include #include #include #include diff --git a/src/catch2/catch_config.cpp b/src/catch2/catch_config.cpp index 352c1f4..fef03d7 100644 --- a/src/catch2/catch_config.cpp +++ b/src/catch2/catch_config.cpp @@ -119,6 +119,8 @@ namespace Catch { m_data.reporterSpecifications.push_back( std::move( *parsed ) ); } + // Reading bazel env vars can change some parts of the config data, + // so we have to process the bazel env before acting on the config. if ( enableBazelEnvSupport() ) { readBazelEnvVars(); } @@ -183,6 +185,8 @@ namespace Catch { bool Config::showHelp() const { return m_data.showHelp; } + std::string const& Config::getExitGuardFilePath() const { return m_data.prematureExitGuardFilePath; } + // IConfig interface bool Config::allowThrows() const { return !m_data.noThrow; } StringRef Config::name() const { return m_data.name.empty() ? m_data.processName : m_data.name; } @@ -244,6 +248,26 @@ namespace Catch { m_data.shardCount = bazelShardOptions->shardCount; } } + + const auto bazelExitGuardFile = Detail::getEnv( "TEST_PREMATURE_EXIT_FILE" ); + if (bazelExitGuardFile) { + m_data.prematureExitGuardFilePath = bazelExitGuardFile; + } + + const auto bazelRandomSeed = Detail::getEnv( "TEST_RANDOM_SEED" ); + if ( bazelRandomSeed ) { + auto parsedSeed = parseUInt( bazelRandomSeed, 0 ); + if ( !parsedSeed ) { + // Currently we handle issues with parsing other Bazel Env + // options by warning and ignoring the issue. So we do the + // same for random seed option. + Catch::cerr() + << "Warning: could not parse 'TEST_RANDOM_SEED' ('" + << bazelRandomSeed << "') as proper seed.\n"; + } else { + m_data.rngSeed = *parsedSeed; + } + } } } // end namespace Catch diff --git a/src/catch2/catch_config.hpp b/src/catch2/catch_config.hpp index 17e983e..cdf286a 100644 --- a/src/catch2/catch_config.hpp +++ b/src/catch2/catch_config.hpp @@ -76,7 +76,7 @@ namespace Catch { WarnAbout::What warnings = WarnAbout::Nothing; ShowDurations showDurations = ShowDurations::DefaultForReporter; double minDuration = -1; - TestRunOrder runOrder = TestRunOrder::Declared; + TestRunOrder runOrder = TestRunOrder::Randomized; ColourMode defaultColourMode = ColourMode::PlatformDefault; WaitForKeypress::When waitForKeypress = WaitForKeypress::Never; @@ -87,6 +87,8 @@ namespace Catch { std::vector testsOrTags; std::vector sectionsToRun; + + std::string prematureExitGuardFilePath; }; @@ -114,6 +116,8 @@ namespace Catch { bool showHelp() const; + std::string const& getExitGuardFilePath() const; + // IConfig interface bool allowThrows() const override; StringRef name() const override; diff --git a/src/catch2/catch_message.cpp b/src/catch2/catch_message.cpp index 7b09ab8..dc7d567 100644 --- a/src/catch2/catch_message.cpp +++ b/src/catch2/catch_message.cpp @@ -7,7 +7,6 @@ // SPDX-License-Identifier: BSL-1.0 #include #include -#include #include #include @@ -20,28 +19,26 @@ namespace Catch { ScopedMessage::ScopedMessage( MessageBuilder&& builder ): - m_info( CATCH_MOVE(builder.m_info) ) { - m_info.message = builder.m_stream.str(); - getResultCapture().pushScopedMessage( m_info ); + m_messageId( builder.m_info.sequence ) { + MessageInfo info( CATCH_MOVE( builder.m_info ) ); + info.message = builder.m_stream.str(); + IResultCapture::pushScopedMessage( CATCH_MOVE( info ) ); } ScopedMessage::ScopedMessage( ScopedMessage&& old ) noexcept: - m_info( CATCH_MOVE( old.m_info ) ) { + m_messageId( old.m_messageId ) { old.m_moved = true; } ScopedMessage::~ScopedMessage() { - if ( !uncaught_exceptions() && !m_moved ){ - getResultCapture().popScopedMessage(m_info); - } + if ( !m_moved ) { IResultCapture::popScopedMessage( m_messageId ); } } Capturer::Capturer( StringRef macroName, SourceLineInfo const& lineInfo, ResultWas::OfType resultType, - StringRef names ): - m_resultCapture( getResultCapture() ) { + StringRef names ) { auto trimmed = [&] (size_t start, size_t end) { while (names[start] == ',' || isspace(static_cast(names[start]))) { ++start; @@ -87,8 +84,8 @@ namespace Catch { case ',': if (start != pos && openings.empty()) { m_messages.emplace_back(macroName, lineInfo, resultType); - m_messages.back().message = static_cast(trimmed(start, pos)); - m_messages.back().message += " := "; + m_messages.back().message += trimmed(start, pos); + m_messages.back().message += " := "_sr; start = pos; } break; @@ -97,21 +94,20 @@ namespace Catch { } assert(openings.empty() && "Mismatched openings"); m_messages.emplace_back(macroName, lineInfo, resultType); - m_messages.back().message = static_cast(trimmed(start, names.size() - 1)); - m_messages.back().message += " := "; + m_messages.back().message += trimmed(start, names.size() - 1); + m_messages.back().message += " := "_sr; } Capturer::~Capturer() { - if ( !uncaught_exceptions() ){ - assert( m_captured == m_messages.size() ); - for( size_t i = 0; i < m_captured; ++i ) - m_resultCapture.popScopedMessage( m_messages[i] ); + assert( m_captured == m_messages.size() ); + for (auto const& message : m_messages) { + IResultCapture::popScopedMessage( message.sequence ); } } void Capturer::captureValue( size_t index, std::string const& value ) { assert( index < m_messages.size() ); m_messages[index].message += value; - m_resultCapture.pushScopedMessage( m_messages[index] ); + IResultCapture::pushScopedMessage( CATCH_MOVE( m_messages[index] ) ); m_captured++; } diff --git a/src/catch2/catch_message.hpp b/src/catch2/catch_message.hpp index 0a73834..44b0747 100644 --- a/src/catch2/catch_message.hpp +++ b/src/catch2/catch_message.hpp @@ -57,13 +57,12 @@ namespace Catch { ScopedMessage( ScopedMessage&& old ) noexcept; ~ScopedMessage(); - MessageInfo m_info; + unsigned int m_messageId; bool m_moved = false; }; class Capturer { std::vector m_messages; - IResultCapture& m_resultCapture; size_t m_captured = 0; public: Capturer( StringRef macroName, SourceLineInfo const& lineInfo, ResultWas::OfType resultType, StringRef names ); @@ -111,7 +110,7 @@ namespace Catch { /////////////////////////////////////////////////////////////////////////////// #define INTERNAL_CATCH_UNSCOPED_INFO( macroName, log ) \ - Catch::getResultCapture().emplaceUnscopedMessage( Catch::MessageBuilder( macroName##_catch_sr, CATCH_INTERNAL_LINEINFO, Catch::ResultWas::Info ) << log ) + Catch::IResultCapture::emplaceUnscopedMessage( Catch::MessageBuilder( macroName##_catch_sr, CATCH_INTERNAL_LINEINFO, Catch::ResultWas::Info ) << log ) #if defined(CATCH_CONFIG_PREFIX_MESSAGES) && !defined(CATCH_CONFIG_DISABLE) diff --git a/src/catch2/catch_registry_hub.cpp b/src/catch2/catch_registry_hub.cpp index 3a59467..925468f 100644 --- a/src/catch2/catch_registry_hub.cpp +++ b/src/catch2/catch_registry_hub.cpp @@ -96,7 +96,6 @@ namespace Catch { } void cleanUp() { cleanupSingletons(); - cleanUpContext(); } std::string translateActiveException() { return getRegistryHub().getExceptionTranslatorRegistry().translateActiveException(); diff --git a/src/catch2/catch_session.cpp b/src/catch2/catch_session.cpp index a4a410c..042b7f4 100644 --- a/src/catch2/catch_session.cpp +++ b/src/catch2/catch_session.cpp @@ -25,8 +25,9 @@ #include #include -#include #include +#include +#include #include #include #include @@ -34,14 +35,6 @@ namespace Catch { namespace { - static constexpr int TestFailureExitCode = 42; - static constexpr int UnspecifiedErrorExitCode = 1; - static constexpr int AllTestsSkippedExitCode = 4; - static constexpr int NoTestsRunExitCode = 2; - static constexpr int UnmatchedTestSpecExitCode = 3; - static constexpr int InvalidTestSpecExitCode = 5; - - IEventListenerPtr createReporter(std::string const& reporterName, ReporterConfig&& config) { auto reporter = Catch::getRegistryHub().getReporterRegistry().create(reporterName, CATCH_MOVE(config)); CATCH_ENFORCE(reporter, "No reporter registered with name: '" << reporterName << '\''); @@ -149,6 +142,50 @@ namespace Catch { } } + // Creates empty file at path. The path must be writable, we do not + // try to create directories in path because that's hard in C++14. + void setUpGuardFile( std::string const& guardFilePath ) { + if ( !guardFilePath.empty() ) { +#if defined( _MSC_VER ) + std::FILE* file = nullptr; + if ( fopen_s( &file, guardFilePath.c_str(), "w" ) ) { + char msgBuffer[100]; + const auto err = errno; + std::string errMsg; + if ( !strerror_s( msgBuffer, err ) ) { + errMsg = msgBuffer; + } else { + errMsg = "Could not translate errno to a string"; + } + +#else + std::FILE* file = std::fopen( guardFilePath.c_str(), "w" ); + if ( !file ) { + const auto err = errno; + const char* errMsg = std::strerror( err ); +#endif + + CATCH_RUNTIME_ERROR( "Could not open the exit guard file '" + << guardFilePath << "' because '" + << errMsg << "' (" << err << ')' ); + } + const int ret = std::fclose( file ); + CATCH_ENFORCE( + ret == 0, + "Error when closing the exit guard file: " << ret ); + } + } + + // Removes file at path. Assuming we created it in setUpGuardFile. + void tearDownGuardFile( std::string const& guardFilePath ) { + if ( !guardFilePath.empty() ) { + const int ret = std::remove( guardFilePath.c_str() ); + CATCH_ENFORCE( + ret == 0, + "Error when removing the exit guard file: " << ret ); + } + } + } // anon namespace Session::Session() { @@ -267,6 +304,7 @@ namespace Catch { static_cast(std::getchar()); } int exitCode = runInternal(); + if( ( m_configData.waitForKeypress & WaitForKeypress::BeforeExit ) != 0 ) { Catch::cout() << "...waiting for enter/ return before exiting, with code: " << exitCode << '\n' << std::flush; static_cast(std::getchar()); @@ -307,6 +345,10 @@ namespace Catch { CATCH_TRY { config(); // Force config to be constructed + // We need to retrieve potential Bazel config with the full Config + // constructor, so we have to create the guard file after it is created. + setUpGuardFile( m_config->getExitGuardFilePath() ); + seedRng( *m_config ); if (m_configData.filenamesAsTags) { @@ -336,9 +378,12 @@ namespace Catch { TestGroup tests { CATCH_MOVE(reporter), m_config.get() }; auto const totals = tests.execute(); + // If we got here, running the tests finished normally-enough. + // They might've failed, but that would've been reported elsewhere. + tearDownGuardFile( m_config->getExitGuardFilePath() ); + if ( tests.hadUnmatchedTestSpecs() && m_config->warnAboutUnmatchedTestSpecs() ) { - // UnmatchedTestSpecExitCode return UnmatchedTestSpecExitCode; } diff --git a/src/catch2/catch_session.hpp b/src/catch2/catch_session.hpp index c1de6d5..23fc6f8 100644 --- a/src/catch2/catch_session.hpp +++ b/src/catch2/catch_session.hpp @@ -16,6 +16,14 @@ namespace Catch { + // TODO: Use C++17 `inline` variables + constexpr int UnspecifiedErrorExitCode = 1; + constexpr int NoTestsRunExitCode = 2; + constexpr int UnmatchedTestSpecExitCode = 3; + constexpr int AllTestsSkippedExitCode = 4; + constexpr int InvalidTestSpecExitCode = 5; + constexpr int TestFailureExitCode = 42; + class Session : Detail::NonCopyable { public: diff --git a/src/catch2/catch_tag_alias_autoregistrar.hpp b/src/catch2/catch_tag_alias_autoregistrar.hpp index 9f80f72..4fc183a 100644 --- a/src/catch2/catch_tag_alias_autoregistrar.hpp +++ b/src/catch2/catch_tag_alias_autoregistrar.hpp @@ -23,7 +23,7 @@ namespace Catch { #define CATCH_REGISTER_TAG_ALIAS( alias, spec ) \ CATCH_INTERNAL_START_WARNINGS_SUPPRESSION \ CATCH_INTERNAL_SUPPRESS_GLOBALS_WARNINGS \ - namespace{ Catch::RegistrarForTagAliases INTERNAL_CATCH_UNIQUE_NAME( AutoRegisterTagAlias )( alias, spec, CATCH_INTERNAL_LINEINFO ); } \ + namespace{ const Catch::RegistrarForTagAliases INTERNAL_CATCH_UNIQUE_NAME( AutoRegisterTagAlias )( alias, spec, CATCH_INTERNAL_LINEINFO ); } \ CATCH_INTERNAL_STOP_WARNINGS_SUPPRESSION #endif // CATCH_TAG_ALIAS_AUTOREGISTRAR_HPP_INCLUDED diff --git a/src/catch2/catch_test_macros.hpp b/src/catch2/catch_test_macros.hpp index 6ee2129..9770855 100644 --- a/src/catch2/catch_test_macros.hpp +++ b/src/catch2/catch_test_macros.hpp @@ -14,6 +14,7 @@ #include #include #include +#include // All of our user-facing macros support configuration toggle, that @@ -47,10 +48,16 @@ #define CATCH_REGISTER_TEST_CASE( Function, ... ) INTERNAL_CATCH_REGISTER_TESTCASE( Function, __VA_ARGS__ ) #define CATCH_SECTION( ... ) INTERNAL_CATCH_SECTION( __VA_ARGS__ ) #define CATCH_DYNAMIC_SECTION( ... ) INTERNAL_CATCH_DYNAMIC_SECTION( __VA_ARGS__ ) - #define CATCH_FAIL( ... ) INTERNAL_CATCH_MSG( "CATCH_FAIL", Catch::ResultWas::ExplicitFailure, Catch::ResultDisposition::Normal, __VA_ARGS__ ) + #define CATCH_FAIL( ... ) do { \ + INTERNAL_CATCH_MSG("CATCH_FAIL", Catch::ResultWas::ExplicitFailure, Catch::ResultDisposition::Normal, __VA_ARGS__ ); \ + Catch::Detail::Unreachable(); \ + } while ( false ) #define CATCH_FAIL_CHECK( ... ) INTERNAL_CATCH_MSG( "CATCH_FAIL_CHECK", Catch::ResultWas::ExplicitFailure, Catch::ResultDisposition::ContinueOnFailure, __VA_ARGS__ ) #define CATCH_SUCCEED( ... ) INTERNAL_CATCH_MSG( "CATCH_SUCCEED", Catch::ResultWas::Ok, Catch::ResultDisposition::ContinueOnFailure, __VA_ARGS__ ) - #define CATCH_SKIP( ... ) INTERNAL_CATCH_MSG( "SKIP", Catch::ResultWas::ExplicitSkip, Catch::ResultDisposition::Normal, __VA_ARGS__ ) + #define CATCH_SKIP( ... ) do { \ + INTERNAL_CATCH_MSG( "CATCH_SKIP", Catch::ResultWas::ExplicitSkip, Catch::ResultDisposition::Normal, __VA_ARGS__ ); \ + Catch::Detail::Unreachable(); \ + } while (false) #if !defined(CATCH_CONFIG_RUNTIME_STATIC_REQUIRE) @@ -148,10 +155,16 @@ #define REGISTER_TEST_CASE( Function, ... ) INTERNAL_CATCH_REGISTER_TESTCASE( Function, __VA_ARGS__ ) #define SECTION( ... ) INTERNAL_CATCH_SECTION( __VA_ARGS__ ) #define DYNAMIC_SECTION( ... ) INTERNAL_CATCH_DYNAMIC_SECTION( __VA_ARGS__ ) - #define FAIL( ... ) INTERNAL_CATCH_MSG( "FAIL", Catch::ResultWas::ExplicitFailure, Catch::ResultDisposition::Normal, __VA_ARGS__ ) + #define FAIL( ... ) do { \ + INTERNAL_CATCH_MSG( "FAIL", Catch::ResultWas::ExplicitFailure, Catch::ResultDisposition::Normal, __VA_ARGS__ ); \ + Catch::Detail::Unreachable(); \ + } while (false) #define FAIL_CHECK( ... ) INTERNAL_CATCH_MSG( "FAIL_CHECK", Catch::ResultWas::ExplicitFailure, Catch::ResultDisposition::ContinueOnFailure, __VA_ARGS__ ) #define SUCCEED( ... ) INTERNAL_CATCH_MSG( "SUCCEED", Catch::ResultWas::Ok, Catch::ResultDisposition::ContinueOnFailure, __VA_ARGS__ ) - #define SKIP( ... ) INTERNAL_CATCH_MSG( "SKIP", Catch::ResultWas::ExplicitSkip, Catch::ResultDisposition::Normal, __VA_ARGS__ ) + #define SKIP( ... ) do { \ + INTERNAL_CATCH_MSG( "SKIP", Catch::ResultWas::ExplicitSkip, Catch::ResultDisposition::Normal, __VA_ARGS__ ); \ + Catch::Detail::Unreachable(); \ + } while (false) #if !defined(CATCH_CONFIG_RUNTIME_STATIC_REQUIRE) diff --git a/src/catch2/internal/catch_test_run_info.hpp b/src/catch2/catch_test_run_info.hpp similarity index 100% rename from src/catch2/internal/catch_test_run_info.hpp rename to src/catch2/catch_test_run_info.hpp diff --git a/src/catch2/catch_tostring.cpp b/src/catch2/catch_tostring.cpp index 83327cf..cc68656 100644 --- a/src/catch2/catch_tostring.cpp +++ b/src/catch2/catch_tostring.cpp @@ -8,10 +8,10 @@ #include #include +#include #include #include -#include #include namespace Catch { @@ -61,35 +61,35 @@ namespace Detail { std::string ret; // This is enough for the "don't escape invisibles" case, and a good // lower bound on the "escape invisibles" case. - ret.reserve(string.size() + 2); + ret.reserve( string.size() + 2 ); - if (!escapeInvisibles) { + if ( !escapeInvisibles ) { ret += '"'; ret += string; ret += '"'; return ret; } + size_t last_start = 0; + auto write_to = [&]( size_t idx ) { + if ( last_start < idx ) { + ret += string.substr( last_start, idx - last_start ); + } + last_start = idx + 1; + }; + ret += '"'; - for (char c : string) { - switch (c) { - case '\r': - ret.append("\\r"); - break; - case '\n': - ret.append("\\n"); - break; - case '\t': - ret.append("\\t"); - break; - case '\f': - ret.append("\\f"); - break; - default: - ret.push_back(c); - break; + for ( size_t i = 0; i < string.size(); ++i ) { + const char c = string[i]; + if ( c == '\r' || c == '\n' || c == '\t' || c == '\f' ) { + write_to( i ); + if ( c == '\r' ) { ret.append( "\\r" ); } + if ( c == '\n' ) { ret.append( "\\n" ); } + if ( c == '\t' ) { ret.append( "\\t" ); } + if ( c == '\f' ) { ret.append( "\\f" ); } } } + write_to( string.size() ); ret += '"'; return ret; @@ -114,6 +114,13 @@ namespace Detail { rss << std::setw(2) << static_cast(bytes[i]); return rss.str(); } + + std::string makeExceptionHappenedString() { + return "{ stringification failed with an exception: \"" + + translateActiveException() + "\" }"; + + } + } // end Detail namespace diff --git a/src/catch2/catch_tostring.hpp b/src/catch2/catch_tostring.hpp index 5f84196..41f8941 100644 --- a/src/catch2/catch_tostring.hpp +++ b/src/catch2/catch_tostring.hpp @@ -64,18 +64,14 @@ namespace Catch { return rawMemoryToString( &object, sizeof(object) ); } - template - class IsStreamInsertable { - template - static auto test(int) - -> decltype(std::declval() << std::declval(), std::true_type()); - - template - static auto test(...)->std::false_type; + template + static constexpr bool IsStreamInsertable_v = false; - public: - static const bool value = decltype(test(0))::value; - }; + template + static constexpr bool IsStreamInsertable_v< + T, + decltype( void( std::declval() << std::declval() ) )> = + true; template std::string convertUnknownEnumToString( E e ); @@ -120,7 +116,7 @@ namespace Catch { struct StringMaker { template static - std::enable_if_t<::Catch::Detail::IsStreamInsertable::value, std::string> + std::enable_if_t<::Catch::Detail::IsStreamInsertable_v, std::string> convert(const Fake& value) { ReusableStringStream rss; // NB: call using the function-like syntax to avoid ambiguity with @@ -131,7 +127,7 @@ namespace Catch { template static - std::enable_if_t::value, std::string> + std::enable_if_t, std::string> convert( const Fake& value ) { #if !defined(CATCH_CONFIG_FALLBACK_STRINGIFIER) return Detail::convertUnstreamable(value); @@ -143,11 +139,17 @@ namespace Catch { namespace Detail { + std::string makeExceptionHappenedString(); + // This function dispatches all stringification requests inside of Catch. // Should be preferably called fully qualified, like ::Catch::Detail::stringify template - std::string stringify(const T& e) { - return ::Catch::StringMaker>>::convert(e); + std::string stringify( const T& e ) { + CATCH_TRY { + return ::Catch::StringMaker< + std::remove_cv_t>>::convert( e ); + } + CATCH_CATCH_ALL { return makeExceptionHappenedString(); } } template @@ -523,7 +525,7 @@ namespace Catch { } template - struct StringMaker::value && !::Catch::Detail::IsStreamInsertable::value>> { + struct StringMaker::value && !::Catch::Detail::IsStreamInsertable_v>> { static std::string convert( R const& range ) { return rangeToString( range ); } @@ -630,17 +632,19 @@ struct ratio_string { template struct StringMaker> { static std::string convert(std::chrono::time_point const& time_point) { - auto converted = std::chrono::system_clock::to_time_t(time_point); + const auto systemish = std::chrono::time_point_cast< + std::chrono::system_clock::duration>( time_point ); + const auto as_time_t = std::chrono::system_clock::to_time_t( systemish ); #ifdef _MSC_VER std::tm timeInfo = {}; - const auto err = gmtime_s(&timeInfo, &converted); + const auto err = gmtime_s( &timeInfo, &as_time_t ); if ( err ) { return "gmtime from provided timepoint has failed. This " "happens e.g. with pre-1970 dates using Microsoft libc"; } #else - std::tm* timeInfo = std::gmtime(&converted); + std::tm* timeInfo = std::gmtime( &as_time_t ); #endif auto const timeStampSize = sizeof("2017-01-16T17:06:45Z"); diff --git a/src/catch2/catch_translate_exception.hpp b/src/catch2/catch_translate_exception.hpp index 2bf8d36..4d3bac6 100644 --- a/src/catch2/catch_translate_exception.hpp +++ b/src/catch2/catch_translate_exception.hpp @@ -65,7 +65,7 @@ namespace Catch { static std::string translatorName( signature ); \ CATCH_INTERNAL_START_WARNINGS_SUPPRESSION \ CATCH_INTERNAL_SUPPRESS_GLOBALS_WARNINGS \ - namespace{ Catch::ExceptionTranslatorRegistrar INTERNAL_CATCH_UNIQUE_NAME( catch_internal_ExceptionRegistrar )( &translatorName ); } \ + namespace{ const Catch::ExceptionTranslatorRegistrar INTERNAL_CATCH_UNIQUE_NAME( catch_internal_ExceptionRegistrar )( &translatorName ); } \ CATCH_INTERNAL_STOP_WARNINGS_SUPPRESSION \ static std::string translatorName( signature ) diff --git a/src/catch2/catch_user_config.hpp.in b/src/catch2/catch_user_config.hpp.in index 3acda68..18e2ef1 100644 --- a/src/catch2/catch_user_config.hpp.in +++ b/src/catch2/catch_user_config.hpp.in @@ -187,6 +187,24 @@ #endif +#cmakedefine CATCH_CONFIG_DEPRECATION_ANNOTATIONS +#cmakedefine CATCH_CONFIG_NO_DEPRECATION_ANNOTATIONS + +#if defined( CATCH_CONFIG_DEPRECATION_ANNOTATIONS ) && \ + defined( CATCH_CONFIG_NO_DEPRECATION_ANNOTATIONS ) +# error Cannot force DEPRECATION_ANNOTATIONS to both ON and OFF +#endif + + +#cmakedefine CATCH_CONFIG_EXPERIMENTAL_THREAD_SAFE_ASSERTIONS +#cmakedefine CATCH_CONFIG_NO_EXPERIMENTAL_THREAD_SAFE_ASSERTIONS + +#if defined( CATCH_CONFIG_EXPERIMENTAL_THREAD_SAFE_ASSERTIONS ) && \ + defined( CATCH_CONFIG_NO_EXPERIMENTAL_THREAD_SAFE_ASSERTIONS ) +# error Cannot force EXPERIMENTAL_THREAD_SAFE_ASSERTIONS to both ON and OFF +#endif + + // ------ // Simple toggle defines // their value is never used and they cannot be overridden diff --git a/src/catch2/catch_version.cpp b/src/catch2/catch_version.cpp index 2c40ccc..39b0c0b 100644 --- a/src/catch2/catch_version.cpp +++ b/src/catch2/catch_version.cpp @@ -36,7 +36,7 @@ namespace Catch { } Version const& libraryVersion() { - static Version version( 3, 8, 1, "", 0 ); + static Version version( 3, 11, 0, "", 0 ); return version; } diff --git a/src/catch2/catch_version_macros.hpp b/src/catch2/catch_version_macros.hpp index 42655fa..630f5b0 100644 --- a/src/catch2/catch_version_macros.hpp +++ b/src/catch2/catch_version_macros.hpp @@ -9,7 +9,7 @@ #define CATCH_VERSION_MACROS_HPP_INCLUDED #define CATCH_VERSION_MAJOR 3 -#define CATCH_VERSION_MINOR 8 -#define CATCH_VERSION_PATCH 1 +#define CATCH_VERSION_MINOR 11 +#define CATCH_VERSION_PATCH 0 #endif // CATCH_VERSION_MACROS_HPP_INCLUDED diff --git a/src/catch2/generators/catch_generator_exception.hpp b/src/catch2/generators/catch_generator_exception.hpp index f353042..8dffcd2 100644 --- a/src/catch2/generators/catch_generator_exception.hpp +++ b/src/catch2/generators/catch_generator_exception.hpp @@ -23,7 +23,7 @@ namespace Catch { m_msg(msg) {} - const char* what() const noexcept override final; + const char* what() const noexcept final; }; } // end namespace Catch diff --git a/src/catch2/generators/catch_generators_adapters.hpp b/src/catch2/generators/catch_generators_adapters.hpp index d5fc1e1..f623bd2 100644 --- a/src/catch2/generators/catch_generators_adapters.hpp +++ b/src/catch2/generators/catch_generators_adapters.hpp @@ -58,8 +58,9 @@ namespace Generators { class FilterGenerator final : public IGenerator { GeneratorWrapper m_generator; Predicate m_predicate; + static_assert(!std::is_reference::value, "This would most likely result in a dangling reference"); public: - template + template FilterGenerator(P&& pred, GeneratorWrapper&& generator): m_generator(CATCH_MOVE(generator)), m_predicate(CATCH_FORWARD(pred)) @@ -91,7 +92,7 @@ namespace Generators { template GeneratorWrapper filter(Predicate&& pred, GeneratorWrapper&& generator) { - return GeneratorWrapper(Catch::Detail::make_unique>(CATCH_FORWARD(pred), CATCH_MOVE(generator))); + return GeneratorWrapper(Catch::Detail::make_unique::type>>(CATCH_FORWARD(pred), CATCH_MOVE(generator))); } template diff --git a/src/catch2/interfaces/catch_interfaces_capture.cpp b/src/catch2/interfaces/catch_interfaces_capture.cpp index 9b40ee5..492c5bd 100644 --- a/src/catch2/interfaces/catch_interfaces_capture.cpp +++ b/src/catch2/interfaces/catch_interfaces_capture.cpp @@ -7,7 +7,14 @@ // SPDX-License-Identifier: BSL-1.0 #include +#include namespace Catch { + namespace Detail { + void missingCaptureInstance() { + CATCH_INTERNAL_ERROR( "No result capture instance" ); + } + } // namespace Detail + IResultCapture::~IResultCapture() = default; -} +} // namespace Catch diff --git a/src/catch2/interfaces/catch_interfaces_capture.hpp b/src/catch2/interfaces/catch_interfaces_capture.hpp index 9e5431d..4e27a98 100644 --- a/src/catch2/interfaces/catch_interfaces_capture.hpp +++ b/src/catch2/interfaces/catch_interfaces_capture.hpp @@ -10,6 +10,7 @@ #include +#include #include #include #include @@ -62,10 +63,9 @@ namespace Catch { virtual void benchmarkEnded( BenchmarkStats<> const& stats ) = 0; virtual void benchmarkFailed( StringRef error ) = 0; - virtual void pushScopedMessage( MessageInfo const& message ) = 0; - virtual void popScopedMessage( MessageInfo const& message ) = 0; - - virtual void emplaceUnscopedMessage( MessageBuilder&& builder ) = 0; + static void pushScopedMessage( MessageInfo&& message ); + static void popScopedMessage( unsigned int messageId ); + static void emplaceUnscopedMessage( MessageBuilder&& builder ); virtual void handleFatalErrorCondition( StringRef message ) = 0; @@ -93,9 +93,7 @@ namespace Catch { AssertionReaction &reaction ) = 0; - virtual bool lastAssertionPassed() = 0; - virtual void assertionPassed() = 0; // Deprecated, do not use: virtual std::string getCurrentTestName() const = 0; @@ -103,7 +101,18 @@ namespace Catch { virtual void exceptionEarlyReported() = 0; }; - IResultCapture& getResultCapture(); + namespace Detail { + [[noreturn]] + void missingCaptureInstance(); + } + inline IResultCapture& getResultCapture() { + if (auto* capture = getCurrentContext().getResultCapture()) { + return *capture; + } else { + Detail::missingCaptureInstance(); + } + } + } #endif // CATCH_INTERFACES_CAPTURE_HPP_INCLUDED diff --git a/src/catch2/interfaces/catch_interfaces_config.hpp b/src/catch2/interfaces/catch_interfaces_config.hpp index eb05480..82a298d 100644 --- a/src/catch2/interfaces/catch_interfaces_config.hpp +++ b/src/catch2/interfaces/catch_interfaces_config.hpp @@ -12,7 +12,6 @@ #include #include -#include #include #include diff --git a/src/catch2/interfaces/catch_interfaces_reporter.hpp b/src/catch2/interfaces/catch_interfaces_reporter.hpp index a052c5d..c721788 100644 --- a/src/catch2/interfaces/catch_interfaces_reporter.hpp +++ b/src/catch2/interfaces/catch_interfaces_reporter.hpp @@ -9,18 +9,17 @@ #define CATCH_INTERFACES_REPORTER_HPP_INCLUDED #include +#include #include #include #include #include -#include #include #include #include #include #include -#include namespace Catch { @@ -116,6 +115,11 @@ namespace Catch { //! Catch2 should call `Reporter::assertionEnded` even for passing //! assertions bool shouldReportAllAssertions = false; + //! Catch2 should call `Reporter::assertionStarting` for all assertions + // Defaults to true for backwards compatibility, but none of our current + // reporters actually want this, and it enables a fast path in assertion + // handling. + bool shouldReportAllAssertionStarts = true; }; /** diff --git a/src/catch2/internal/catch_clara.hpp b/src/catch2/internal/catch_clara.hpp index d869593..85874a1 100644 --- a/src/catch2/internal/catch_clara.hpp +++ b/src/catch2/internal/catch_clara.hpp @@ -66,15 +66,13 @@ namespace Catch { }; template - struct is_unary_function : std::false_type {}; + static constexpr bool is_unary_function_v = false; template - struct is_unary_function< + static constexpr bool is_unary_function_v< F, - Catch::Detail::void_t()( fake_arg() ) ) - > - > : std::true_type {}; + Catch::Detail::void_t()( + fake_arg() ) )>> = true; // Traits for extracting arg and return type of lambdas (for single // argument lambdas) @@ -507,14 +505,14 @@ namespace Catch { template ::value>> + !Detail::is_unary_function_v>> ParserRefImpl( T& ref, StringRef hint ): m_ref( std::make_shared>( ref ) ), m_hint( hint ) {} template ::value>> + Detail::is_unary_function_v>> ParserRefImpl( LambdaT const& ref, StringRef hint ): m_ref( std::make_shared>( ref ) ), m_hint( hint ) {} @@ -581,7 +579,7 @@ namespace Catch { template ::value>> + Detail::is_unary_function_v>> Opt( LambdaT const& ref, StringRef hint ): ParserRefImpl( ref, hint ) {} @@ -591,7 +589,7 @@ namespace Catch { template ::value>> + !Detail::is_unary_function_v>> Opt( T& ref, StringRef hint ): ParserRefImpl( ref, hint ) {} diff --git a/src/catch2/internal/catch_commandline.cpp b/src/catch2/internal/catch_commandline.cpp index 212f177..db90350 100644 --- a/src/catch2/internal/catch_commandline.cpp +++ b/src/catch2/internal/catch_commandline.cpp @@ -305,6 +305,9 @@ namespace Catch { | Opt( config.allowZeroTests ) ["--allow-running-no-tests"] ( "Treat 'No tests run' as a success" ) + | Opt( config.prematureExitGuardFilePath, "path" ) + ["--premature-exit-guard-file"] + ( "create a file before running tests and delete it during clean exit" ) | Arg( config.testsOrTags, "test name|pattern|tags" ) ( "which test or tests to use" ); diff --git a/src/catch2/internal/catch_compiler_capabilities.hpp b/src/catch2/internal/catch_compiler_capabilities.hpp index 4cf1d65..983fb63 100644 --- a/src/catch2/internal/catch_compiler_capabilities.hpp +++ b/src/catch2/internal/catch_compiler_capabilities.hpp @@ -220,13 +220,17 @@ # endif // Universal Windows platform does not support SEH -// Or console colours (or console at all...) -# if defined(CATCH_PLATFORM_WINDOWS_UWP) -# define CATCH_INTERNAL_CONFIG_NO_COLOUR_WIN32 -# else +# if !defined(CATCH_PLATFORM_WINDOWS_UWP) # define CATCH_INTERNAL_CONFIG_WINDOWS_SEH # endif +// Only some Windows platform families support the console +# if defined(WINAPI_FAMILY_PARTITION) +# if !WINAPI_FAMILY_PARTITION(WINAPI_PARTITION_APP | WINAPI_PARTITION_SYSTEM) +# define CATCH_INTERNAL_CONFIG_NO_COLOUR_WIN32 +# endif +# endif + // MSVC traditional preprocessor needs some workaround for __VA_ARGS__ // _MSVC_TRADITIONAL == 0 means new conformant preprocessor // _MSVC_TRADITIONAL == 1 means old traditional non-conformant preprocessor diff --git a/src/catch2/internal/catch_console_colour.cpp b/src/catch2/internal/catch_console_colour.cpp index 142de9e..948c4cb 100644 --- a/src/catch2/internal/catch_console_colour.cpp +++ b/src/catch2/internal/catch_console_colour.cpp @@ -161,7 +161,11 @@ namespace { #endif // Windows/ ANSI/ None -#if defined( CATCH_PLATFORM_LINUX ) || defined( CATCH_PLATFORM_MAC ) || defined( __GLIBC__ ) +#if defined( CATCH_PLATFORM_LINUX ) \ + || defined( CATCH_PLATFORM_MAC ) \ + || defined( __GLIBC__ ) \ + || defined( __FreeBSD__ ) \ + || defined( CATCH_PLATFORM_QNX ) # define CATCH_INTERNAL_HAS_ISATTY # include #endif diff --git a/src/catch2/internal/catch_context.cpp b/src/catch2/internal/catch_context.cpp index 8acf1ed..5389de3 100644 --- a/src/catch2/internal/catch_context.cpp +++ b/src/catch2/internal/catch_context.cpp @@ -6,25 +6,14 @@ // SPDX-License-Identifier: BSL-1.0 #include -#include #include namespace Catch { - Context* Context::currentContext = nullptr; - - void cleanUpContext() { - delete Context::currentContext; - Context::currentContext = nullptr; - } - void Context::createContext() { - currentContext = new Context(); - } + Context Context::currentContext; Context& getCurrentMutableContext() { - if ( !Context::currentContext ) { Context::createContext(); } - // NOLINTNEXTLINE(clang-analyzer-core.uninitialized.UndefReturn) - return *Context::currentContext; + return Context::currentContext; } SimplePcg32& sharedRng() { diff --git a/src/catch2/internal/catch_context.hpp b/src/catch2/internal/catch_context.hpp index 4d8a5da..0b89632 100644 --- a/src/catch2/internal/catch_context.hpp +++ b/src/catch2/internal/catch_context.hpp @@ -19,11 +19,9 @@ namespace Catch { IConfig const* m_config = nullptr; IResultCapture* m_resultCapture = nullptr; - CATCH_EXPORT static Context* currentContext; + CATCH_EXPORT static Context currentContext; friend Context& getCurrentMutableContext(); friend Context const& getCurrentContext(); - static void createContext(); - friend void cleanUpContext(); public: constexpr IResultCapture* getResultCapture() const { @@ -34,21 +32,14 @@ namespace Catch { m_resultCapture = resultCapture; } constexpr void setConfig( IConfig const* config ) { m_config = config; } - }; Context& getCurrentMutableContext(); inline Context const& getCurrentContext() { - // We duplicate the logic from `getCurrentMutableContext` here, - // to avoid paying the call overhead in debug mode. - if ( !Context::currentContext ) { Context::createContext(); } - // NOLINTNEXTLINE(clang-analyzer-core.uninitialized.UndefReturn) - return *Context::currentContext; + return Context::currentContext; } - void cleanUpContext(); - class SimplePcg32; SimplePcg32& sharedRng(); } diff --git a/src/catch2/internal/catch_debugger.cpp b/src/catch2/internal/catch_debugger.cpp index bd3be17..da22c7f 100644 --- a/src/catch2/internal/catch_debugger.cpp +++ b/src/catch2/internal/catch_debugger.cpp @@ -69,7 +69,7 @@ #endif } // namespace Catch -#elif defined(CATCH_PLATFORM_LINUX) +#elif defined(CATCH_PLATFORM_LINUX) || defined(CATCH_PLATFORM_QNX) #include #include diff --git a/src/catch2/internal/catch_debugger.hpp b/src/catch2/internal/catch_debugger.hpp index 25c5a26..997f440 100644 --- a/src/catch2/internal/catch_debugger.hpp +++ b/src/catch2/internal/catch_debugger.hpp @@ -14,6 +14,17 @@ namespace Catch { bool isDebuggerActive(); } +#if !defined( CATCH_TRAP ) && defined( __clang__ ) && defined( __has_builtin ) +# if __has_builtin( __builtin_debugtrap ) +# define CATCH_TRAP() __builtin_debugtrap() +# endif +#endif + +#if !defined( CATCH_TRAP ) && defined( _MSC_VER ) +# define CATCH_TRAP() __debugbreak() +#endif + +#if !defined(CATCH_TRAP) // If we couldn't use compiler-specific impl from above, we get into platform-specific options #ifdef CATCH_PLATFORM_MAC #if defined(__i386__) || defined(__x86_64__) @@ -38,7 +49,7 @@ namespace Catch { #define CATCH_TRAP() __asm__(".inst 0xde01") #endif -#elif defined(CATCH_PLATFORM_LINUX) +#elif defined(CATCH_PLATFORM_LINUX) || defined(CATCH_PLATFORM_QNX) // If we can use inline assembler, do it because this allows us to break // directly at the location of the failing check instead of breaking inside // raise() called from it, i.e. one stack frame below. @@ -49,15 +60,15 @@ namespace Catch { #define CATCH_TRAP() raise(SIGTRAP) #endif -#elif defined(_MSC_VER) - #define CATCH_TRAP() __debugbreak() #elif defined(__MINGW32__) extern "C" __declspec(dllimport) void __stdcall DebugBreak(); #define CATCH_TRAP() DebugBreak() #endif +#endif // ^^ CATCH_TRAP is not defined yet, so we define it + -#ifndef CATCH_BREAK_INTO_DEBUGGER - #ifdef CATCH_TRAP +#if !defined(CATCH_BREAK_INTO_DEBUGGER) + #if defined(CATCH_TRAP) #define CATCH_BREAK_INTO_DEBUGGER() []{ if( Catch::isDebuggerActive() ) { CATCH_TRAP(); } }() #else #define CATCH_BREAK_INTO_DEBUGGER() []{}() diff --git a/src/catch2/internal/catch_decomposer.hpp b/src/catch2/internal/catch_decomposer.hpp index adce89f..fcc017d 100644 --- a/src/catch2/internal/catch_decomposer.hpp +++ b/src/catch2/internal/catch_decomposer.hpp @@ -45,7 +45,7 @@ * when the compiler handles `ExprLhs == b`, it also tries to resolve * the overload set for `b == ExprLhs`. * - * To accomodate these use cases, decomposer ended up rather complex. + * To accommodate these use cases, decomposer ended up rather complex. * * 1) These types are handled by adding SFINAE overloads to our comparison * operators, checking whether `T == U` are comparable with the given @@ -78,10 +78,11 @@ * * 3) If a type has no linkage, we also cannot capture it by reference. * The solution is once again to capture them by value. We handle - * the common cases by using `std::is_arithmetic` as the default - * for `Catch::capture_by_value`, but that is only a some-effort - * heuristic. But as with 2), users can specialize `capture_by_value` - * for their own types as needed. + * the common cases by using `std::is_arithmetic` and `std::is_enum` + * as the default for `Catch::capture_by_value`, but that is only a + * some-effort heuristic. These combine to capture all possible bitfield + * bases, and also some trait-like types. As with 2), users can + * specialize `capture_by_value` for their own types as needed. * * 4) To support C++20 and make the SFINAE on our decomposing operators * work, the SFINAE has to happen in return type, rather than in @@ -133,13 +134,22 @@ namespace Catch { using RemoveCVRef_t = std::remove_cv_t>; } - // Note: There is nothing that stops us from extending this, - // e.g. to `std::is_scalar`, but the more encompassing - // traits are usually also more expensive. For now we - // keep this as it used to be and it can be changed later. + // Note: This is about as much as we can currently reasonably support. + // In an ideal world, we could capture by value small trivially + // copyable types, but the actual `std::is_trivially_copyable` + // trait is a huge mess with standard-violating results on + // GCC and Clang, which are unlikely to be fixed soon due to ABI + // concerns. + // `std::is_scalar` also causes issues due to the `is_pointer` + // component, which causes ambiguity issues with (references-to) + // function pointer. If those are resolved, we still need to + // disambiguate the overload set for arrays, through explicit + // overload for references to sized arrays. template struct capture_by_value - : std::integral_constant{}> {}; + : std::integral_constant::value || + std::is_enum::value> {}; #if defined( CATCH_CONFIG_CPP20_COMPARE_OVERLOADS ) template <> @@ -163,7 +173,7 @@ namespace Catch { public: constexpr auto isBinaryExpression() const -> bool { return m_isBinaryExpression; } constexpr auto getResult() const -> bool { return m_result; } - //! This function **has** to be overriden by the derived class. + //! This function **has** to be overridden by the derived class. virtual void streamReconstructedExpression( std::ostream& os ) const; constexpr ITransientExpression( bool isBinaryExpression, bool result ) diff --git a/src/catch2/internal/catch_deprecation_macro.hpp b/src/catch2/internal/catch_deprecation_macro.hpp new file mode 100644 index 0000000..7834882 --- /dev/null +++ b/src/catch2/internal/catch_deprecation_macro.hpp @@ -0,0 +1,19 @@ + +// 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_DEPRECATION_MACRO_HPP_INCLUDED +#define CATCH_DEPRECATION_MACRO_HPP_INCLUDED + +#include + +#if !defined( CATCH_CONFIG_NO_DEPRECATION_ANNOTATIONS ) +# define DEPRECATED( msg ) [[deprecated( msg )]] +#else +# define DEPRECATED( msg ) +#endif + +#endif // CATCH_DEPRECATION_MACRO_HPP_INCLUDED diff --git a/src/catch2/internal/catch_enforce.hpp b/src/catch2/internal/catch_enforce.hpp index 076cea3..171ea12 100644 --- a/src/catch2/internal/catch_enforce.hpp +++ b/src/catch2/internal/catch_enforce.hpp @@ -12,7 +12,7 @@ #include #include -#include +#include // for `std::exception` in no-exception configuration namespace Catch { #if !defined(CATCH_CONFIG_DISABLE_EXCEPTIONS) diff --git a/src/catch2/internal/catch_exception_translator_registry.cpp b/src/catch2/internal/catch_exception_translator_registry.cpp index 1eb6114..8867d78 100644 --- a/src/catch2/internal/catch_exception_translator_registry.cpp +++ b/src/catch2/internal/catch_exception_translator_registry.cpp @@ -59,10 +59,10 @@ namespace Catch { // To avoid having to handle TFE explicitly everywhere, we just // rethrow it so that it goes back up the caller. catch( TestFailureException& ) { - std::rethrow_exception(std::current_exception()); + return "{ nested assertion failed }"; } catch( TestSkipException& ) { - std::rethrow_exception(std::current_exception()); + return "{ nested SKIP() called }"; } catch( std::exception const& ex ) { return ex.what(); diff --git a/src/catch2/internal/catch_exception_translator_registry.hpp b/src/catch2/internal/catch_exception_translator_registry.hpp index 3123e93..32917d4 100644 --- a/src/catch2/internal/catch_exception_translator_registry.hpp +++ b/src/catch2/internal/catch_exception_translator_registry.hpp @@ -11,7 +11,6 @@ #include #include -#include #include namespace Catch { diff --git a/src/catch2/internal/catch_fatal_condition_handler.cpp b/src/catch2/internal/catch_fatal_condition_handler.cpp index 9ef5b21..3dfa138 100644 --- a/src/catch2/internal/catch_fatal_condition_handler.cpp +++ b/src/catch2/internal/catch_fatal_condition_handler.cpp @@ -79,30 +79,34 @@ namespace Catch { // There is no 1-1 mapping between signals and windows exceptions. // Windows can easily distinguish between SO and SigSegV, // but SigInt, SigTerm, etc are handled differently. - static SignalDefs signalDefs[] = { + static constexpr SignalDefs signalDefs[] = { { EXCEPTION_ILLEGAL_INSTRUCTION, "SIGILL - Illegal instruction signal" }, { EXCEPTION_STACK_OVERFLOW, "SIGSEGV - Stack overflow" }, { EXCEPTION_ACCESS_VIOLATION, "SIGSEGV - Segmentation violation signal" }, { EXCEPTION_INT_DIVIDE_BY_ZERO, "Divide by zero error" }, }; + // Since we do not support multiple instantiations, we put these + // into global variables and rely on cleaning them up in outlined + // constructors/destructors + static LPTOP_LEVEL_EXCEPTION_FILTER previousTopLevelExceptionFilter = nullptr; + + static LONG CALLBACK topLevelExceptionFilter(PEXCEPTION_POINTERS ExceptionInfo) { for (auto const& def : signalDefs) { if (ExceptionInfo->ExceptionRecord->ExceptionCode == def.id) { reportFatal(def.name); } } - // If its not an exception we care about, pass it along. + // If a filter was previously registered, invoke it + if (previousTopLevelExceptionFilter) { + return previousTopLevelExceptionFilter(ExceptionInfo); + } + // Otherwise, pass along all exceptions. // This stops us from eating debugger breaks etc. return EXCEPTION_CONTINUE_SEARCH; } - // Since we do not support multiple instantiations, we put these - // into global variables and rely on cleaning them up in outlined - // constructors/destructors - static LPTOP_LEVEL_EXCEPTION_FILTER previousTopLevelExceptionFilter = nullptr; - - // For MSVC, we reserve part of the stack memory for handling // memory overflow structured exception. FatalConditionHandler::FatalConditionHandler() { @@ -150,7 +154,7 @@ namespace Catch { const char* name; }; - static SignalDefs signalDefs[] = { + static constexpr SignalDefs signalDefs[] = { { SIGINT, "SIGINT - Terminal interrupt signal" }, { SIGILL, "SIGILL - Illegal instruction signal" }, { SIGFPE, "SIGFPE - Floating point error signal" }, diff --git a/src/catch2/internal/catch_is_permutation.hpp b/src/catch2/internal/catch_is_permutation.hpp index c77a6d3..ba7082d 100644 --- a/src/catch2/internal/catch_is_permutation.hpp +++ b/src/catch2/internal/catch_is_permutation.hpp @@ -8,8 +8,8 @@ #ifndef CATCH_IS_PERMUTATION_HPP_INCLUDED #define CATCH_IS_PERMUTATION_HPP_INCLUDED -#include #include +#include namespace Catch { namespace Detail { diff --git a/src/catch2/internal/catch_istream.cpp b/src/catch2/internal/catch_istream.cpp index 2867ce7..717da0e 100644 --- a/src/catch2/internal/catch_istream.cpp +++ b/src/catch2/internal/catch_istream.cpp @@ -14,8 +14,6 @@ #include #include -#include -#include namespace Catch { diff --git a/src/catch2/internal/catch_istream.hpp b/src/catch2/internal/catch_istream.hpp index e6b9a2d..1247be3 100644 --- a/src/catch2/internal/catch_istream.hpp +++ b/src/catch2/internal/catch_istream.hpp @@ -12,8 +12,6 @@ #include #include -#include -#include #include namespace Catch { diff --git a/src/catch2/internal/catch_jsonwriter.cpp b/src/catch2/internal/catch_jsonwriter.cpp index 1a96e34..6915c9c 100644 --- a/src/catch2/internal/catch_jsonwriter.cpp +++ b/src/catch2/internal/catch_jsonwriter.cpp @@ -7,8 +7,36 @@ // SPDX-License-Identifier: BSL-1.0 #include #include +#include namespace Catch { + + namespace { + static bool needsEscape( char c ) { + return c == '"' || c == '\\' || c == '\b' || c == '\f' || + c == '\n' || c == '\r' || c == '\t'; + } + + static Catch::StringRef makeEscapeStringRef( char c ) { + if ( c == '"' ) { + return "\\\""_sr; + } else if ( c == '\\' ) { + return "\\\\"_sr; + } else if ( c == '\b' ) { + return "\\b"_sr; + } else if ( c == '\f' ) { + return "\\f"_sr; + } else if ( c == '\n' ) { + return "\\n"_sr; + } else if ( c == '\r' ) { + return "\\r"_sr; + } else if ( c == '\t' ) { + return "\\t"_sr; + } + Catch::Detail::Unreachable(); + } + } // namespace + void JsonUtils::indent( std::ostream& os, std::uint64_t level ) { for ( std::uint64_t i = 0; i < level; ++i ) { os << " "; @@ -118,30 +146,19 @@ namespace Catch { void JsonValueWriter::writeImpl( Catch::StringRef value, bool quote ) { if ( quote ) { m_os << '"'; } - for (char c : value) { - // Escape list taken from https://www.json.org/json-en.html, - // string definition. - // Note that while forward slash _can_ be escaped, it does - // not have to be, if JSON is not further embedded somewhere - // where forward slash is meaningful. - if ( c == '"' ) { - m_os << "\\\""; - } else if ( c == '\\' ) { - m_os << "\\\\"; - } else if ( c == '\b' ) { - m_os << "\\b"; - } else if ( c == '\f' ) { - m_os << "\\f"; - } else if ( c == '\n' ) { - m_os << "\\n"; - } else if ( c == '\r' ) { - m_os << "\\r"; - } else if ( c == '\t' ) { - m_os << "\\t"; - } else { - m_os << c; + size_t current_start = 0; + for ( size_t i = 0; i < value.size(); ++i ) { + if ( needsEscape( value[i] ) ) { + if ( current_start < i ) { + m_os << value.substr( current_start, i - current_start ); + } + m_os << makeEscapeStringRef( value[i] ); + current_start = i + 1; } } + if ( current_start < value.size() ) { + m_os << value.substr( current_start, value.size() - current_start ); + } if ( quote ) { m_os << '"'; } } diff --git a/src/catch2/internal/catch_main.cpp b/src/catch2/internal/catch_main.cpp index 503b540..adabdb5 100644 --- a/src/catch2/internal/catch_main.cpp +++ b/src/catch2/internal/catch_main.cpp @@ -14,7 +14,7 @@ namespace Catch { CATCH_INTERNAL_START_WARNINGS_SUPPRESSION CATCH_INTERNAL_SUPPRESS_GLOBALS_WARNINGS - static LeakDetector leakDetector; + static const LeakDetector leakDetector; CATCH_INTERNAL_STOP_WARNINGS_SUPPRESSION } diff --git a/src/catch2/internal/catch_message_info.cpp b/src/catch2/internal/catch_message_info.cpp index e0e9dc7..bc887ab 100644 --- a/src/catch2/internal/catch_message_info.cpp +++ b/src/catch2/internal/catch_message_info.cpp @@ -10,16 +10,17 @@ namespace Catch { - MessageInfo::MessageInfo( StringRef _macroName, - SourceLineInfo const& _lineInfo, - ResultWas::OfType _type ) + MessageInfo::MessageInfo( StringRef _macroName, + SourceLineInfo const& _lineInfo, + ResultWas::OfType _type ) : macroName( _macroName ), lineInfo( _lineInfo ), type( _type ), sequence( ++globalCount ) {} - // This may need protecting if threading support is added - unsigned int MessageInfo::globalCount = 0; + // Messages are owned by their individual threads, so the counter should be thread-local as well. + // Alternative consideration: atomic, so threads don't share IDs and things are easier to debug. + thread_local unsigned int MessageInfo::globalCount = 0; } // end namespace Catch diff --git a/src/catch2/internal/catch_message_info.hpp b/src/catch2/internal/catch_message_info.hpp index 1ef43fd..8c18186 100644 --- a/src/catch2/internal/catch_message_info.hpp +++ b/src/catch2/internal/catch_message_info.hpp @@ -8,6 +8,7 @@ #ifndef CATCH_MESSAGE_INFO_HPP_INCLUDED #define CATCH_MESSAGE_INFO_HPP_INCLUDED +#include #include #include #include @@ -25,16 +26,19 @@ namespace Catch { std::string message; SourceLineInfo lineInfo; ResultWas::OfType type; + // The "ID" of the message, used to know when to remove it from reporter context. unsigned int sequence; + DEPRECATED( "Explicitly use the 'sequence' member instead" ) bool operator == (MessageInfo const& other) const { return sequence == other.sequence; } + DEPRECATED( "Explicitly use the 'sequence' member instead" ) bool operator < (MessageInfo const& other) const { return sequence < other.sequence; } private: - static unsigned int globalCount; + static thread_local unsigned int globalCount; }; } // end namespace Catch diff --git a/src/catch2/internal/catch_noncopyable.hpp b/src/catch2/internal/catch_noncopyable.hpp index eb0823e..f574658 100644 --- a/src/catch2/internal/catch_noncopyable.hpp +++ b/src/catch2/internal/catch_noncopyable.hpp @@ -13,6 +13,7 @@ namespace Catch { //! Deriving classes become noncopyable and nonmovable class NonCopyable { + public: NonCopyable( NonCopyable const& ) = delete; NonCopyable( NonCopyable&& ) = delete; NonCopyable& operator=( NonCopyable const& ) = delete; diff --git a/src/catch2/internal/catch_platform.hpp b/src/catch2/internal/catch_platform.hpp index b653a58..ba4717f 100644 --- a/src/catch2/internal/catch_platform.hpp +++ b/src/catch2/internal/catch_platform.hpp @@ -25,6 +25,9 @@ #elif defined(linux) || defined(__linux) || defined(__linux__) # define CATCH_PLATFORM_LINUX +#elif defined(__QNX__) +# define CATCH_PLATFORM_QNX + #elif defined(WIN32) || defined(__WIN32__) || defined(_WIN32) || defined(_MSC_VER) || defined(__MINGW32__) # define CATCH_PLATFORM_WINDOWS diff --git a/src/catch2/internal/catch_preprocessor.hpp b/src/catch2/internal/catch_preprocessor.hpp index 08e844d..1fc8fb8 100644 --- a/src/catch2/internal/catch_preprocessor.hpp +++ b/src/catch2/internal/catch_preprocessor.hpp @@ -16,6 +16,15 @@ #endif +namespace Catch { + namespace Detail { + template + struct priority_tag : priority_tag {}; + template <> + struct priority_tag<0> {}; + } +} + #define CATCH_RECURSION_LEVEL0(...) __VA_ARGS__ #define CATCH_RECURSION_LEVEL1(...) CATCH_RECURSION_LEVEL0(CATCH_RECURSION_LEVEL0(CATCH_RECURSION_LEVEL0(__VA_ARGS__))) #define CATCH_RECURSION_LEVEL2(...) CATCH_RECURSION_LEVEL1(CATCH_RECURSION_LEVEL1(CATCH_RECURSION_LEVEL1(__VA_ARGS__))) @@ -75,10 +84,10 @@ #define INTERNAL_CATCH_MAKE_NAMESPACE(name) INTERNAL_CATCH_MAKE_NAMESPACE2(name) #ifndef CATCH_CONFIG_TRADITIONAL_MSVC_PREPROCESSOR -#define INTERNAL_CATCH_MAKE_TYPE_LIST2(...) decltype(get_wrapper()) +#define INTERNAL_CATCH_MAKE_TYPE_LIST2(...) decltype(get_wrapper(Catch::Detail::priority_tag<1>{})) #define INTERNAL_CATCH_MAKE_TYPE_LIST(...) INTERNAL_CATCH_MAKE_TYPE_LIST2(INTERNAL_CATCH_REMOVE_PARENS(__VA_ARGS__)) #else -#define INTERNAL_CATCH_MAKE_TYPE_LIST2(...) INTERNAL_CATCH_EXPAND_VARGS(decltype(get_wrapper())) +#define INTERNAL_CATCH_MAKE_TYPE_LIST2(...) INTERNAL_CATCH_EXPAND_VARGS(decltype(get_wrapper(Catch::Detail::priority_tag<1>{}))) #define INTERNAL_CATCH_MAKE_TYPE_LIST(...) INTERNAL_CATCH_EXPAND_VARGS(INTERNAL_CATCH_MAKE_TYPE_LIST2(INTERNAL_CATCH_REMOVE_PARENS(__VA_ARGS__))) #endif @@ -101,11 +110,11 @@ #define INTERNAL_CATCH_TYPE_GEN\ template struct TypeList {};\ - template\ - constexpr auto get_wrapper() noexcept -> TypeList { return {}; }\ + template\ + constexpr auto get_wrapper(Catch::Detail::priority_tag<1>) noexcept -> TypeList { return {}; }\ template class...> struct TemplateTypeList{};\ template class...Cs>\ - constexpr auto get_wrapper() noexcept -> TemplateTypeList { return {}; }\ + constexpr auto get_wrapper(Catch::Detail::priority_tag<1>) noexcept -> TemplateTypeList { return {}; }\ template\ struct append;\ template\ @@ -135,10 +144,10 @@ #define INTERNAL_CATCH_NTTP_1(signature, ...)\ template struct Nttp{};\ template\ - constexpr auto get_wrapper() noexcept -> Nttp<__VA_ARGS__> { return {}; } \ + constexpr auto get_wrapper(Catch::Detail::priority_tag<0>) noexcept -> Nttp<__VA_ARGS__> { return {}; } \ template class...> struct NttpTemplateTypeList{};\ template class...Cs>\ - constexpr auto get_wrapper() noexcept -> NttpTemplateTypeList { return {}; } \ + constexpr auto get_wrapper(Catch::Detail::priority_tag<0>) noexcept -> NttpTemplateTypeList { return {}; } \ \ template< template class Container, template class List, INTERNAL_CATCH_REMOVE_PARENS(signature)>\ struct rewrap, List<__VA_ARGS__>> { using type = TypeList>; };\ @@ -163,13 +172,14 @@ template\ static void TestName() -#define INTERNAL_CATCH_NTTP_REGISTER0(TestFunc, signature)\ - template\ - void reg_test(TypeList, Catch::NameAndTags nameAndTags)\ +#define INTERNAL_CATCH_TYPES_REGISTER(TestFunc)\ + template\ + void reg_test(TypeList, Catch::NameAndTags nameAndTags)\ {\ - Catch::AutoReg( Catch::makeTestInvoker(&TestFunc), CATCH_INTERNAL_LINEINFO, Catch::StringRef(), nameAndTags);\ + Catch::AutoReg( Catch::makeTestInvoker(&TestFunc), CATCH_INTERNAL_LINEINFO, Catch::StringRef(), nameAndTags);\ } +#define INTERNAL_CATCH_NTTP_REGISTER0(TestFunc, signature, ...) #define INTERNAL_CATCH_NTTP_REGISTER(TestFunc, signature, ...)\ template\ void reg_test(Nttp<__VA_ARGS__>, Catch::NameAndTags nameAndTags)\ @@ -218,7 +228,7 @@ #define INTERNAL_CATCH_DEFINE_SIG_TEST_METHOD(TestName, ...) INTERNAL_CATCH_VA_NARGS_IMPL( "dummy", __VA_ARGS__, INTERNAL_CATCH_DEFINE_SIG_TEST_METHOD_X,INTERNAL_CATCH_DEFINE_SIG_TEST_METHOD_X, INTERNAL_CATCH_DEFINE_SIG_TEST_METHOD_X, INTERNAL_CATCH_DEFINE_SIG_TEST_METHOD_X, INTERNAL_CATCH_DEFINE_SIG_TEST_METHOD_X, INTERNAL_CATCH_DEFINE_SIG_TEST_METHOD_X, INTERNAL_CATCH_DEFINE_SIG_TEST_METHOD_X,INTERNAL_CATCH_DEFINE_SIG_TEST_METHOD_X,INTERNAL_CATCH_DEFINE_SIG_TEST_METHOD_X, INTERNAL_CATCH_DEFINE_SIG_TEST_METHOD1, INTERNAL_CATCH_DEFINE_SIG_TEST_METHOD0)(TestName, __VA_ARGS__) #define INTERNAL_CATCH_DECLARE_SIG_TEST_METHOD(TestName, ClassName, ...) INTERNAL_CATCH_VA_NARGS_IMPL( "dummy", __VA_ARGS__, INTERNAL_CATCH_DECLARE_SIG_TEST_METHOD_X,INTERNAL_CATCH_DECLARE_SIG_TEST_METHOD_X, INTERNAL_CATCH_DECLARE_SIG_TEST_METHOD_X, INTERNAL_CATCH_DECLARE_SIG_TEST_METHOD_X, INTERNAL_CATCH_DECLARE_SIG_TEST_METHOD_X, INTERNAL_CATCH_DECLARE_SIG_TEST_METHOD_X, INTERNAL_CATCH_DECLARE_SIG_TEST_METHOD_X,INTERNAL_CATCH_DECLARE_SIG_TEST_METHOD_X,INTERNAL_CATCH_DECLARE_SIG_TEST_METHOD_X, INTERNAL_CATCH_DECLARE_SIG_TEST_METHOD1, INTERNAL_CATCH_DECLARE_SIG_TEST_METHOD0)(TestName, ClassName, __VA_ARGS__) #define INTERNAL_CATCH_NTTP_REG_METHOD_GEN(TestName, ...) INTERNAL_CATCH_VA_NARGS_IMPL( "dummy", __VA_ARGS__, INTERNAL_CATCH_NTTP_REGISTER_METHOD, INTERNAL_CATCH_NTTP_REGISTER_METHOD, INTERNAL_CATCH_NTTP_REGISTER_METHOD, INTERNAL_CATCH_NTTP_REGISTER_METHOD, INTERNAL_CATCH_NTTP_REGISTER_METHOD, INTERNAL_CATCH_NTTP_REGISTER_METHOD, INTERNAL_CATCH_NTTP_REGISTER_METHOD, INTERNAL_CATCH_NTTP_REGISTER_METHOD, INTERNAL_CATCH_NTTP_REGISTER_METHOD, INTERNAL_CATCH_NTTP_REGISTER_METHOD0, INTERNAL_CATCH_NTTP_REGISTER_METHOD0)(TestName, __VA_ARGS__) -#define INTERNAL_CATCH_NTTP_REG_GEN(TestFunc, ...) INTERNAL_CATCH_VA_NARGS_IMPL( "dummy", __VA_ARGS__, INTERNAL_CATCH_NTTP_REGISTER, INTERNAL_CATCH_NTTP_REGISTER, INTERNAL_CATCH_NTTP_REGISTER, INTERNAL_CATCH_NTTP_REGISTER, INTERNAL_CATCH_NTTP_REGISTER, INTERNAL_CATCH_NTTP_REGISTER, INTERNAL_CATCH_NTTP_REGISTER, INTERNAL_CATCH_NTTP_REGISTER, INTERNAL_CATCH_NTTP_REGISTER, INTERNAL_CATCH_NTTP_REGISTER0, INTERNAL_CATCH_NTTP_REGISTER0)(TestFunc, __VA_ARGS__) +#define INTERNAL_CATCH_NTTP_REG_GEN(TestFunc, ...) INTERNAL_CATCH_TYPES_REGISTER(TestFunc) INTERNAL_CATCH_VA_NARGS_IMPL( "dummy", __VA_ARGS__, INTERNAL_CATCH_NTTP_REGISTER, INTERNAL_CATCH_NTTP_REGISTER, INTERNAL_CATCH_NTTP_REGISTER, INTERNAL_CATCH_NTTP_REGISTER, INTERNAL_CATCH_NTTP_REGISTER, INTERNAL_CATCH_NTTP_REGISTER, INTERNAL_CATCH_NTTP_REGISTER, INTERNAL_CATCH_NTTP_REGISTER, INTERNAL_CATCH_NTTP_REGISTER, INTERNAL_CATCH_NTTP_REGISTER0, INTERNAL_CATCH_NTTP_REGISTER0)(TestFunc, __VA_ARGS__) #define INTERNAL_CATCH_DEFINE_SIG_TEST(TestName, ...) INTERNAL_CATCH_VA_NARGS_IMPL( "dummy", __VA_ARGS__, INTERNAL_CATCH_DEFINE_SIG_TEST_X, INTERNAL_CATCH_DEFINE_SIG_TEST_X, INTERNAL_CATCH_DEFINE_SIG_TEST_X, INTERNAL_CATCH_DEFINE_SIG_TEST_X, INTERNAL_CATCH_DEFINE_SIG_TEST_X, INTERNAL_CATCH_DEFINE_SIG_TEST_X, INTERNAL_CATCH_DEFINE_SIG_TEST_X, INTERNAL_CATCH_DEFINE_SIG_TEST_X,INTERNAL_CATCH_DEFINE_SIG_TEST_X,INTERNAL_CATCH_DEFINE_SIG_TEST1, INTERNAL_CATCH_DEFINE_SIG_TEST0)(TestName, __VA_ARGS__) #define INTERNAL_CATCH_DECLARE_SIG_TEST(TestName, ...) INTERNAL_CATCH_VA_NARGS_IMPL( "dummy", __VA_ARGS__, INTERNAL_CATCH_DECLARE_SIG_TEST_X,INTERNAL_CATCH_DECLARE_SIG_TEST_X, INTERNAL_CATCH_DECLARE_SIG_TEST_X, INTERNAL_CATCH_DECLARE_SIG_TEST_X, INTERNAL_CATCH_DECLARE_SIG_TEST_X, INTERNAL_CATCH_DECLARE_SIG_TEST_X, INTERNAL_CATCH_DEFINE_SIG_TEST_X,INTERNAL_CATCH_DECLARE_SIG_TEST_X,INTERNAL_CATCH_DECLARE_SIG_TEST_X, INTERNAL_CATCH_DECLARE_SIG_TEST1, INTERNAL_CATCH_DECLARE_SIG_TEST0)(TestName, __VA_ARGS__) #define INTERNAL_CATCH_REMOVE_PARENS_GEN(...) INTERNAL_CATCH_VA_NARGS_IMPL(__VA_ARGS__, INTERNAL_CATCH_REMOVE_PARENS_11_ARG,INTERNAL_CATCH_REMOVE_PARENS_10_ARG,INTERNAL_CATCH_REMOVE_PARENS_9_ARG,INTERNAL_CATCH_REMOVE_PARENS_8_ARG,INTERNAL_CATCH_REMOVE_PARENS_7_ARG,INTERNAL_CATCH_REMOVE_PARENS_6_ARG,INTERNAL_CATCH_REMOVE_PARENS_5_ARG,INTERNAL_CATCH_REMOVE_PARENS_4_ARG,INTERNAL_CATCH_REMOVE_PARENS_3_ARG,INTERNAL_CATCH_REMOVE_PARENS_2_ARG,INTERNAL_CATCH_REMOVE_PARENS_1_ARG)(__VA_ARGS__) @@ -228,7 +238,7 @@ #define INTERNAL_CATCH_DEFINE_SIG_TEST_METHOD(TestName, ...) INTERNAL_CATCH_EXPAND_VARGS(INTERNAL_CATCH_VA_NARGS_IMPL( "dummy", __VA_ARGS__, INTERNAL_CATCH_DEFINE_SIG_TEST_METHOD_X,INTERNAL_CATCH_DEFINE_SIG_TEST_METHOD_X, INTERNAL_CATCH_DEFINE_SIG_TEST_METHOD_X, INTERNAL_CATCH_DEFINE_SIG_TEST_METHOD_X, INTERNAL_CATCH_DEFINE_SIG_TEST_METHOD_X, INTERNAL_CATCH_DEFINE_SIG_TEST_METHOD_X, INTERNAL_CATCH_DEFINE_SIG_TEST_METHOD_X,INTERNAL_CATCH_DEFINE_SIG_TEST_METHOD_X,INTERNAL_CATCH_DEFINE_SIG_TEST_METHOD_X, INTERNAL_CATCH_DEFINE_SIG_TEST_METHOD1, INTERNAL_CATCH_DEFINE_SIG_TEST_METHOD0)(TestName, __VA_ARGS__)) #define INTERNAL_CATCH_DECLARE_SIG_TEST_METHOD(TestName, ClassName, ...) INTERNAL_CATCH_EXPAND_VARGS(INTERNAL_CATCH_VA_NARGS_IMPL( "dummy", __VA_ARGS__, INTERNAL_CATCH_DECLARE_SIG_TEST_METHOD_X,INTERNAL_CATCH_DECLARE_SIG_TEST_METHOD_X, INTERNAL_CATCH_DECLARE_SIG_TEST_METHOD_X, INTERNAL_CATCH_DECLARE_SIG_TEST_METHOD_X, INTERNAL_CATCH_DECLARE_SIG_TEST_METHOD_X, INTERNAL_CATCH_DECLARE_SIG_TEST_METHOD_X, INTERNAL_CATCH_DECLARE_SIG_TEST_METHOD_X,INTERNAL_CATCH_DECLARE_SIG_TEST_METHOD_X,INTERNAL_CATCH_DECLARE_SIG_TEST_METHOD_X, INTERNAL_CATCH_DECLARE_SIG_TEST_METHOD1, INTERNAL_CATCH_DECLARE_SIG_TEST_METHOD0)(TestName, ClassName, __VA_ARGS__)) #define INTERNAL_CATCH_NTTP_REG_METHOD_GEN(TestName, ...) INTERNAL_CATCH_EXPAND_VARGS(INTERNAL_CATCH_VA_NARGS_IMPL( "dummy", __VA_ARGS__, INTERNAL_CATCH_NTTP_REGISTER_METHOD, INTERNAL_CATCH_NTTP_REGISTER_METHOD, INTERNAL_CATCH_NTTP_REGISTER_METHOD, INTERNAL_CATCH_NTTP_REGISTER_METHOD, INTERNAL_CATCH_NTTP_REGISTER_METHOD, INTERNAL_CATCH_NTTP_REGISTER_METHOD, INTERNAL_CATCH_NTTP_REGISTER_METHOD, INTERNAL_CATCH_NTTP_REGISTER_METHOD, INTERNAL_CATCH_NTTP_REGISTER_METHOD, INTERNAL_CATCH_NTTP_REGISTER_METHOD0, INTERNAL_CATCH_NTTP_REGISTER_METHOD0)(TestName, __VA_ARGS__)) -#define INTERNAL_CATCH_NTTP_REG_GEN(TestFunc, ...) INTERNAL_CATCH_EXPAND_VARGS(INTERNAL_CATCH_VA_NARGS_IMPL( "dummy", __VA_ARGS__, INTERNAL_CATCH_NTTP_REGISTER, INTERNAL_CATCH_NTTP_REGISTER, INTERNAL_CATCH_NTTP_REGISTER, INTERNAL_CATCH_NTTP_REGISTER, INTERNAL_CATCH_NTTP_REGISTER, INTERNAL_CATCH_NTTP_REGISTER, INTERNAL_CATCH_NTTP_REGISTER, INTERNAL_CATCH_NTTP_REGISTER, INTERNAL_CATCH_NTTP_REGISTER, INTERNAL_CATCH_NTTP_REGISTER0, INTERNAL_CATCH_NTTP_REGISTER0)(TestFunc, __VA_ARGS__)) +#define INTERNAL_CATCH_NTTP_REG_GEN(TestFunc, ...) INTERNAL_CATCH_TYPES_REGISTER(TestFunc) INTERNAL_CATCH_EXPAND_VARGS(INTERNAL_CATCH_VA_NARGS_IMPL( "dummy", __VA_ARGS__, INTERNAL_CATCH_NTTP_REGISTER, INTERNAL_CATCH_NTTP_REGISTER, INTERNAL_CATCH_NTTP_REGISTER, INTERNAL_CATCH_NTTP_REGISTER, INTERNAL_CATCH_NTTP_REGISTER, INTERNAL_CATCH_NTTP_REGISTER, INTERNAL_CATCH_NTTP_REGISTER, INTERNAL_CATCH_NTTP_REGISTER, INTERNAL_CATCH_NTTP_REGISTER, INTERNAL_CATCH_NTTP_REGISTER0, INTERNAL_CATCH_NTTP_REGISTER0)(TestFunc, __VA_ARGS__)) #define INTERNAL_CATCH_DEFINE_SIG_TEST(TestName, ...) INTERNAL_CATCH_EXPAND_VARGS(INTERNAL_CATCH_VA_NARGS_IMPL( "dummy", __VA_ARGS__, INTERNAL_CATCH_DEFINE_SIG_TEST_X, INTERNAL_CATCH_DEFINE_SIG_TEST_X, INTERNAL_CATCH_DEFINE_SIG_TEST_X, INTERNAL_CATCH_DEFINE_SIG_TEST_X, INTERNAL_CATCH_DEFINE_SIG_TEST_X, INTERNAL_CATCH_DEFINE_SIG_TEST_X, INTERNAL_CATCH_DEFINE_SIG_TEST_X, INTERNAL_CATCH_DEFINE_SIG_TEST_X,INTERNAL_CATCH_DEFINE_SIG_TEST_X,INTERNAL_CATCH_DEFINE_SIG_TEST1, INTERNAL_CATCH_DEFINE_SIG_TEST0)(TestName, __VA_ARGS__)) #define INTERNAL_CATCH_DECLARE_SIG_TEST(TestName, ...) INTERNAL_CATCH_EXPAND_VARGS(INTERNAL_CATCH_VA_NARGS_IMPL( "dummy", __VA_ARGS__, INTERNAL_CATCH_DECLARE_SIG_TEST_X,INTERNAL_CATCH_DECLARE_SIG_TEST_X, INTERNAL_CATCH_DECLARE_SIG_TEST_X, INTERNAL_CATCH_DECLARE_SIG_TEST_X, INTERNAL_CATCH_DECLARE_SIG_TEST_X, INTERNAL_CATCH_DECLARE_SIG_TEST_X, INTERNAL_CATCH_DEFINE_SIG_TEST_X,INTERNAL_CATCH_DECLARE_SIG_TEST_X,INTERNAL_CATCH_DECLARE_SIG_TEST_X, INTERNAL_CATCH_DECLARE_SIG_TEST1, INTERNAL_CATCH_DECLARE_SIG_TEST0)(TestName, __VA_ARGS__)) #define INTERNAL_CATCH_REMOVE_PARENS_GEN(...) INTERNAL_CATCH_EXPAND_VARGS(INTERNAL_CATCH_VA_NARGS_IMPL(__VA_ARGS__, INTERNAL_CATCH_REMOVE_PARENS_11_ARG,INTERNAL_CATCH_REMOVE_PARENS_10_ARG,INTERNAL_CATCH_REMOVE_PARENS_9_ARG,INTERNAL_CATCH_REMOVE_PARENS_8_ARG,INTERNAL_CATCH_REMOVE_PARENS_7_ARG,INTERNAL_CATCH_REMOVE_PARENS_6_ARG,INTERNAL_CATCH_REMOVE_PARENS_5_ARG,INTERNAL_CATCH_REMOVE_PARENS_4_ARG,INTERNAL_CATCH_REMOVE_PARENS_3_ARG,INTERNAL_CATCH_REMOVE_PARENS_2_ARG,INTERNAL_CATCH_REMOVE_PARENS_1_ARG)(__VA_ARGS__)) diff --git a/src/catch2/internal/catch_random_number_generator.cpp b/src/catch2/internal/catch_random_number_generator.cpp index c88cd8f..e46b5fd 100644 --- a/src/catch2/internal/catch_random_number_generator.cpp +++ b/src/catch2/internal/catch_random_number_generator.cpp @@ -7,6 +7,12 @@ // SPDX-License-Identifier: BSL-1.0 #include +#if defined( __clang__ ) +# define CATCH2_CLANG_NO_SANITIZE_INTEGER \ + __attribute__( ( no_sanitize( "unsigned-integer-overflow" ) ) ) +#else +# define CATCH2_CLANG_NO_SANITIZE_INTEGER +#endif namespace Catch { namespace { @@ -16,6 +22,7 @@ namespace { #pragma warning(disable:4146) // we negate uint32 during the rotate #endif // Safe rotr implementation thanks to John Regehr + CATCH2_CLANG_NO_SANITIZE_INTEGER uint32_t rotate_right(uint32_t val, uint32_t count) { const uint32_t mask = 31; count &= mask; @@ -49,6 +56,7 @@ namespace { } } + CATCH2_CLANG_NO_SANITIZE_INTEGER SimplePcg32::result_type SimplePcg32::operator()() { // prepare the output value const uint32_t xorshifted = static_cast(((m_state >> 18u) ^ m_state) >> 27u); diff --git a/src/catch2/internal/catch_reusable_string_stream.cpp b/src/catch2/internal/catch_reusable_string_stream.cpp index 33eafde..875da4d 100644 --- a/src/catch2/internal/catch_reusable_string_stream.cpp +++ b/src/catch2/internal/catch_reusable_string_stream.cpp @@ -7,10 +7,12 @@ // SPDX-License-Identifier: BSL-1.0 #include #include +#include #include #include #include +#include #include namespace Catch { @@ -20,34 +22,40 @@ namespace Catch { std::vector> m_streams; std::vector m_unused; std::ostringstream m_referenceStream; // Used for copy state/ flags from + Detail::Mutex m_mutex; - auto add() -> std::size_t { + auto add() -> std::pair { + Detail::LockGuard _( m_mutex ); if( m_unused.empty() ) { m_streams.push_back( Detail::make_unique() ); - return m_streams.size()-1; + return { m_streams.size()-1, m_streams.back().get() }; } else { auto index = m_unused.back(); m_unused.pop_back(); - return index; + return { index, m_streams[index].get() }; } } - void release( std::size_t index ) { - m_streams[index]->copyfmt( m_referenceStream ); // Restore initial flags and other state - m_unused.push_back(index); + void release( std::size_t index, std::ostream* originalPtr ) { + assert( originalPtr ); + originalPtr->copyfmt( m_referenceStream ); // Restore initial flags and other state + + Detail::LockGuard _( m_mutex ); + assert( originalPtr == m_streams[index].get() && "Mismatch between release index and stream ptr" ); + m_unused.push_back( index ); } }; - ReusableStringStream::ReusableStringStream() - : m_index( Singleton::getMutable().add() ), - m_oss( Singleton::getMutable().m_streams[m_index].get() ) - {} + ReusableStringStream::ReusableStringStream() { + std::tie( m_index, m_oss ) = + Singleton::getMutable().add(); + } ReusableStringStream::~ReusableStringStream() { static_cast( m_oss )->str(""); m_oss->clear(); - Singleton::getMutable().release( m_index ); + Singleton::getMutable().release( m_index, m_oss ); } std::string ReusableStringStream::str() const { diff --git a/src/catch2/internal/catch_run_context.cpp b/src/catch2/internal/catch_run_context.cpp index 2a102fb..0029bf5 100644 --- a/src/catch2/internal/catch_run_context.cpp +++ b/src/catch2/internal/catch_run_context.cpp @@ -165,23 +165,64 @@ namespace Catch { } // namespace } + namespace Detail { + // Assertions are owned by the thread that is executing them. + // This allows for lock-free progress in common cases where we + // do not need to send the assertion events to the reporter. + // This also implies that messages are owned by their respective + // threads, and should not be shared across different threads. + // + // This implies that various pieces of metadata referring to last + // assertion result/source location/message handling, etc + // should also be thread local. For now we just use naked globals + // below, in the future we will want to allocate piece of memory + // from heap, to avoid consuming too much thread-local storage. + + // This is used for the "if" part of CHECKED_IF/CHECKED_ELSE + static thread_local bool g_lastAssertionPassed = false; + + // This is the source location for last encountered macro. It is + // used to provide the users with more precise location of error + // when an unexpected exception/fatal error happens. + static thread_local SourceLineInfo g_lastKnownLineInfo("DummyLocation", static_cast(-1)); + + // Should we clear message scopes before sending off the messages to + // reporter? Set in `assertionPassedFastPath` to avoid doing the full + // clear there for performance reasons. + static thread_local bool g_clearMessageScopes = false; + + CATCH_INTERNAL_START_WARNINGS_SUPPRESSION + CATCH_INTERNAL_SUPPRESS_GLOBALS_WARNINGS + // Actual messages to be provided to the reporter + static thread_local std::vector g_messages; + + // Owners for the UNSCOPED_X information macro + static thread_local std::vector g_messageScopes; + CATCH_INTERNAL_STOP_WARNINGS_SUPPRESSION + + } // namespace Detail + RunContext::RunContext(IConfig const* _config, IEventListenerPtr&& reporter) : m_runInfo(_config->name()), m_config(_config), m_reporter(CATCH_MOVE(reporter)), - m_lastAssertionInfo{ StringRef(), SourceLineInfo("",0), StringRef(), ResultDisposition::Normal }, m_outputRedirect( makeOutputRedirect( m_reporter->getPreferences().shouldRedirectStdOut ) ), - m_includeSuccessfulResults( m_config->includeSuccessfulResults() || m_reporter->getPreferences().shouldReportAllAssertions ) + m_abortAfterXFailedAssertions( m_config->abortAfter() ), + m_reportAssertionStarting( m_reporter->getPreferences().shouldReportAllAssertionStarts ), + m_includeSuccessfulResults( m_config->includeSuccessfulResults() || m_reporter->getPreferences().shouldReportAllAssertions ), + m_shouldDebugBreak( m_config->shouldDebugBreak() ) { getCurrentMutableContext().setResultCapture( this ); m_reporter->testRunStarting(m_runInfo); } RunContext::~RunContext() { + updateTotalsFromAtomics(); m_reporter->testRunEnded(TestRunStats(m_runInfo, m_totals, aborting())); } Totals RunContext::runTest(TestCaseHandle const& testCase) { + updateTotalsFromAtomics(); const Totals prevTotals = m_totals; auto const& testInfo = testCase.getTestCaseInfo(); @@ -236,6 +277,7 @@ namespace Catch { m_reporter->testCasePartialStarting(testInfo, testRuns); + updateTotalsFromAtomics(); const auto beforeRunTotals = m_totals; runCurrentTest(); std::string oneRunCout = m_outputRedirect->getStdout(); @@ -244,6 +286,7 @@ namespace Catch { redirectedCout += oneRunCout; redirectedCerr += oneRunCerr; + updateTotalsFromAtomics(); const auto singleRunTotals = m_totals.delta(beforeRunTotals); auto statsForOneRun = TestCaseStats(testInfo, singleRunTotals, CATCH_MOVE(oneRunCout), CATCH_MOVE(oneRunCerr), aborting()); m_reporter->testCasePartialEnded(statsForOneRun, testRuns); @@ -273,47 +316,54 @@ namespace Catch { void RunContext::assertionEnded(AssertionResult&& result) { + Detail::g_lastKnownLineInfo = result.m_info.lineInfo; if (result.getResultType() == ResultWas::Ok) { - m_totals.assertions.passed++; - m_lastAssertionPassed = true; + m_atomicAssertionCount.passed++; + Detail::g_lastAssertionPassed = true; } else if (result.getResultType() == ResultWas::ExplicitSkip) { - m_totals.assertions.skipped++; - m_lastAssertionPassed = true; + m_atomicAssertionCount.skipped++; + Detail::g_lastAssertionPassed = true; } else if (!result.succeeded()) { - m_lastAssertionPassed = false; + Detail::g_lastAssertionPassed = false; if (result.isOk()) { } - else if( m_activeTestCase->getTestCaseInfo().okToFail() ) - m_totals.assertions.failedButOk++; + else if( m_activeTestCase->getTestCaseInfo().okToFail() ) // Read from a shared state established before the threads could start, this is fine + m_atomicAssertionCount.failedButOk++; else - m_totals.assertions.failed++; + m_atomicAssertionCount.failed++; } else { - m_lastAssertionPassed = true; + Detail::g_lastAssertionPassed = true; + } + + if ( Detail::g_clearMessageScopes ) { + Detail::g_messageScopes.clear(); + Detail::g_clearMessageScopes = false; } + // From here, we are touching shared state and need mutex. + Detail::LockGuard lock( m_assertionMutex ); { auto _ = scopedDeactivate( *m_outputRedirect ); - m_reporter->assertionEnded( AssertionStats( result, m_messages, m_totals ) ); + updateTotalsFromAtomics(); + m_reporter->assertionEnded( AssertionStats( result, Detail::g_messages, m_totals ) ); } if ( result.getResultType() != ResultWas::Warning ) { - m_messageScopes.clear(); + Detail::g_messageScopes.clear(); } // Reset working state. assertion info will be reset after // populateReaction is run if it is needed m_lastResult = CATCH_MOVE( result ); } - void RunContext::resetAssertionInfo() { - m_lastAssertionInfo.macroName = StringRef(); - m_lastAssertionInfo.capturedExpression = "{Unknown expression after the reported line}"_sr; - m_lastAssertionInfo.resultDisposition = ResultDisposition::Normal; - } void RunContext::notifyAssertionStarted( AssertionInfo const& info ) { - auto _ = scopedDeactivate( *m_outputRedirect ); - m_reporter->assertionStarting( info ); + if (m_reportAssertionStarting) { + Detail::LockGuard lock( m_assertionMutex ); + auto _ = scopedDeactivate( *m_outputRedirect ); + m_reporter->assertionStarting( info ); + } } bool RunContext::sectionStarted( StringRef sectionName, @@ -329,13 +379,14 @@ namespace Catch { m_activeSections.push_back(§ionTracker); SectionInfo sectionInfo( sectionLineInfo, static_cast(sectionName) ); - m_lastAssertionInfo.lineInfo = sectionInfo.lineInfo; + Detail::g_lastKnownLineInfo = sectionLineInfo; { auto _ = scopedDeactivate( *m_outputRedirect ); m_reporter->sectionStarting( sectionInfo ); } + updateTotalsFromAtomics(); assertions = m_totals.assertions; return true; @@ -343,12 +394,11 @@ namespace Catch { IGeneratorTracker* RunContext::acquireGeneratorTracker( StringRef generatorName, SourceLineInfo const& lineInfo ) { - using namespace Generators; - GeneratorTracker* tracker = GeneratorTracker::acquire( + auto* tracker = Generators::GeneratorTracker::acquire( m_trackerContext, TestCaseTracking::NameAndLocationRef( generatorName, lineInfo ) ); - m_lastAssertionInfo.lineInfo = lineInfo; + Detail::g_lastKnownLineInfo = lineInfo; return tracker; } @@ -361,7 +411,7 @@ namespace Catch { auto& currentTracker = m_trackerContext.currentTracker(); assert( currentTracker.nameAndLocation() != nameAndLoc && - "Trying to create tracker for a genreator that already has one" ); + "Trying to create tracker for a generator that already has one" ); auto newTracker = Catch::Detail::make_unique( CATCH_MOVE(nameAndLoc), m_trackerContext, ¤tTracker ); @@ -380,12 +430,13 @@ namespace Catch { return false; if (m_trackerContext.currentTracker().hasChildren()) return false; - m_totals.assertions.failed++; + m_atomicAssertionCount.failed++; assertions.failed++; return true; } void RunContext::sectionEnded(SectionEndInfo&& endInfo) { + updateTotalsFromAtomics(); Counts assertions = m_totals.assertions - endInfo.prevAssertions; bool missingAssertions = testForMissingAssertions(assertions); @@ -402,9 +453,6 @@ namespace Catch { endInfo.durationInSeconds, missingAssertions ) ); } - - m_messages.clear(); - m_messageScopes.clear(); } void RunContext::sectionEndedEarly(SectionEndInfo&& endInfo) { @@ -435,18 +483,6 @@ namespace Catch { m_reporter->benchmarkFailed( error ); } - void RunContext::pushScopedMessage(MessageInfo const & message) { - m_messages.push_back(message); - } - - void RunContext::popScopedMessage(MessageInfo const & message) { - m_messages.erase(std::remove(m_messages.begin(), m_messages.end(), message), m_messages.end()); - } - - void RunContext::emplaceUnscopedMessage( MessageBuilder&& builder ) { - m_messageScopes.emplace_back( CATCH_MOVE(builder) ); - } - std::string RunContext::getCurrentTestName() const { return m_activeTestCase ? m_activeTestCase->getTestCaseInfo().name @@ -454,6 +490,18 @@ namespace Catch { } const AssertionResult * RunContext::getLastResult() const { + // m_lastResult is updated inside the assertion slow-path, under + // a mutex, so the read needs to happen under mutex as well. + + // TBD: The last result only makes sense if it is a thread-local + // thing, because the answer is different per thread, like + // last line info, whether last assertion passed, and so on. + // + // However, the last result was also never updated in the + // assertion fast path, so it was always somewhat broken, + // and since IResultCapture::getLastResult is deprecated, + // we will leave it as is, until it is finally removed. + Detail::LockGuard _( m_assertionMutex ); return &(*m_lastResult); } @@ -462,28 +510,44 @@ namespace Catch { } void RunContext::handleFatalErrorCondition( StringRef message ) { - // TODO: scoped deactivate here? Just give up and do best effort? - // the deactivation can break things further, OTOH so can the - // capture - auto _ = scopedDeactivate( *m_outputRedirect ); + // We lock only when touching the reporters directly, to avoid + // deadlocks when we call into other functions that also want + // to lock the mutex before touching reporters. + // + // This does mean that we allow other threads to run while handling + // a fatal error, but this is all a best effort attempt anyway. + { + Detail::LockGuard lock( m_assertionMutex ); + // TODO: scoped deactivate here? Just give up and do best effort? + // the deactivation can break things further, OTOH so can the + // capture + auto _ = scopedDeactivate( *m_outputRedirect ); - // First notify reporter that bad things happened - m_reporter->fatalErrorEncountered( message ); + // First notify reporter that bad things happened + m_reporter->fatalErrorEncountered( message ); + } // Don't rebuild the result -- the stringification itself can cause more fatal errors // Instead, fake a result data. AssertionResultData tempResult( ResultWas::FatalErrorCondition, { false } ); tempResult.message = static_cast(message); - AssertionResult result(m_lastAssertionInfo, CATCH_MOVE(tempResult)); + AssertionResult result( makeDummyAssertionInfo(), + CATCH_MOVE( tempResult ) ); assertionEnded(CATCH_MOVE(result) ); - resetAssertionInfo(); + + + // At this point we touch sections/test cases from this thread + // to try and end them. Technically that is not supported when + // using multiple threads, but the worst thing that can happen + // is that the process aborts harder :-D + Detail::LockGuard lock( m_assertionMutex ); // Best effort cleanup for sections that have not been destructed yet // Since this is a fatal error, we have not had and won't have the opportunity to destruct them properly while (!m_activeSections.empty()) { - auto nl = m_activeSections.back()->nameAndLocation(); - SectionEndInfo endInfo{ SectionInfo(CATCH_MOVE(nl.location), CATCH_MOVE(nl.name)), {}, 0.0 }; + auto const& nl = m_activeSections.back()->nameAndLocation(); + SectionEndInfo endInfo{ SectionInfo(nl.location, nl.name), {}, 0.0 }; sectionEndedEarly(CATCH_MOVE(endInfo)); } handleUnfinishedSections(); @@ -508,32 +572,44 @@ namespace Catch { std::string(), false)); m_totals.testCases.failed++; + updateTotalsFromAtomics(); m_reporter->testRunEnded(TestRunStats(m_runInfo, m_totals, false)); } bool RunContext::lastAssertionPassed() { - return m_lastAssertionPassed; + return Detail::g_lastAssertionPassed; + } + + void RunContext::assertionPassedFastPath(SourceLineInfo lineInfo) { + // We want to save the line info for better experience with unexpected assertions + Detail::g_lastKnownLineInfo = lineInfo; + ++m_atomicAssertionCount.passed; + Detail::g_lastAssertionPassed = true; + Detail::g_clearMessageScopes = true; } - void RunContext::assertionPassed() { - m_lastAssertionPassed = true; - ++m_totals.assertions.passed; - resetAssertionInfo(); - m_messageScopes.clear(); + void RunContext::updateTotalsFromAtomics() { + m_totals.assertions = Counts{ + m_atomicAssertionCount.passed, + m_atomicAssertionCount.failed, + m_atomicAssertionCount.failedButOk, + m_atomicAssertionCount.skipped, + }; } bool RunContext::aborting() const { - return m_totals.assertions.failed >= static_cast(m_config->abortAfter()); + return m_atomicAssertionCount.failed >= m_abortAfterXFailedAssertions; } void RunContext::runCurrentTest() { auto const& testCaseInfo = m_activeTestCase->getTestCaseInfo(); SectionInfo testCaseSection(testCaseInfo.lineInfo, testCaseInfo.name); m_reporter->sectionStarting(testCaseSection); + updateTotalsFromAtomics(); Counts prevAssertions = m_totals.assertions; double duration = 0; m_shouldReportUnexpected = true; - m_lastAssertionInfo = { "TEST_CASE"_sr, testCaseInfo.lineInfo, StringRef(), ResultDisposition::Normal }; + Detail::g_lastKnownLineInfo = testCaseInfo.lineInfo; Timer timer; CATCH_TRY { @@ -550,18 +626,23 @@ namespace Catch { } CATCH_CATCH_ALL { // Under CATCH_CONFIG_FAST_COMPILE, unexpected exceptions under REQUIRE assertions // are reported without translation at the point of origin. - if( m_shouldReportUnexpected ) { + if ( m_shouldReportUnexpected ) { AssertionReaction dummyReaction; - handleUnexpectedInflightException( m_lastAssertionInfo, translateActiveException(), dummyReaction ); + handleUnexpectedInflightException( makeDummyAssertionInfo(), + translateActiveException(), + dummyReaction ); } } + updateTotalsFromAtomics(); Counts assertions = m_totals.assertions - prevAssertions; bool missingAssertions = testForMissingAssertions(assertions); m_testCaseTracker->close(); handleUnfinishedSections(); - m_messages.clear(); - m_messageScopes.clear(); + Detail::g_messageScopes.clear(); + // TBD: At this point, m_messages should be empty. Do we want to + // assert that this is true, or keep the defensive clear call? + Detail::g_messages.clear(); SectionStats testCaseSectionStats(CATCH_MOVE(testCaseSection), assertions, duration, missingAssertions); m_reporter->sectionEnded(testCaseSectionStats); @@ -602,7 +683,7 @@ namespace Catch { if( result ) { if (!m_includeSuccessfulResults) { - assertionPassed(); + assertionPassedFastPath(info.lineInfo); } else { reportExpr(info, ResultWas::Ok, &expr, negated); @@ -610,9 +691,9 @@ namespace Catch { } else { reportExpr(info, ResultWas::ExpressionFailed, &expr, negated ); - populateReaction( reaction ); + populateReaction( + reaction, info.resultDisposition & ResultDisposition::Normal ); } - resetAssertionInfo(); } void RunContext::reportExpr( AssertionInfo const &info, @@ -620,7 +701,7 @@ namespace Catch { ITransientExpression const *expr, bool negated ) { - m_lastAssertionInfo = info; + Detail::g_lastKnownLineInfo = info.lineInfo; AssertionResultData data( resultType, LazyExpression( negated ) ); AssertionResult assertionResult{ info, CATCH_MOVE( data ) }; @@ -635,24 +716,25 @@ namespace Catch { std::string&& message, AssertionReaction& reaction ) { - m_lastAssertionInfo = info; + Detail::g_lastKnownLineInfo = info.lineInfo; AssertionResultData data( resultType, LazyExpression( false ) ); data.message = CATCH_MOVE( message ); - AssertionResult assertionResult{ m_lastAssertionInfo, + AssertionResult assertionResult{ info, CATCH_MOVE( data ) }; const auto isOk = assertionResult.isOk(); assertionEnded( CATCH_MOVE(assertionResult) ); if ( !isOk ) { - populateReaction( reaction ); + populateReaction( + reaction, info.resultDisposition & ResultDisposition::Normal ); } else if ( resultType == ResultWas::ExplicitSkip ) { // TODO: Need to handle this explicitly, as ExplicitSkip is // considered "OK" reaction.shouldSkip = true; } - resetAssertionInfo(); } + void RunContext::handleUnexpectedExceptionNotThrown( AssertionInfo const& info, AssertionReaction& reaction @@ -665,55 +747,88 @@ namespace Catch { std::string&& message, AssertionReaction& reaction ) { - m_lastAssertionInfo = info; + Detail::g_lastKnownLineInfo = info.lineInfo; AssertionResultData data( ResultWas::ThrewException, LazyExpression( false ) ); data.message = CATCH_MOVE(message); AssertionResult assertionResult{ info, CATCH_MOVE(data) }; assertionEnded( CATCH_MOVE(assertionResult) ); - populateReaction( reaction ); - resetAssertionInfo(); + populateReaction( reaction, + info.resultDisposition & ResultDisposition::Normal ); + } + + void RunContext::populateReaction( AssertionReaction& reaction, + bool has_normal_disposition ) { + reaction.shouldDebugBreak = m_shouldDebugBreak; + reaction.shouldThrow = aborting() || has_normal_disposition; } - void RunContext::populateReaction( AssertionReaction& reaction ) { - reaction.shouldDebugBreak = m_config->shouldDebugBreak(); - reaction.shouldThrow = aborting() || (m_lastAssertionInfo.resultDisposition & ResultDisposition::Normal); + AssertionInfo RunContext::makeDummyAssertionInfo() { + const bool testCaseJustStarted = + Detail::g_lastKnownLineInfo == + m_activeTestCase->getTestCaseInfo().lineInfo; + + return AssertionInfo{ + testCaseJustStarted ? "TEST_CASE"_sr : StringRef(), + Detail::g_lastKnownLineInfo, + testCaseJustStarted ? StringRef() : "{Unknown expression after the reported line}"_sr, + ResultDisposition::Normal + }; } void RunContext::handleIncomplete( AssertionInfo const& info ) { using namespace std::string_literals; - m_lastAssertionInfo = info; + Detail::g_lastKnownLineInfo = info.lineInfo; AssertionResultData data( ResultWas::ThrewException, LazyExpression( false ) ); data.message = "Exception translation was disabled by CATCH_CONFIG_FAST_COMPILE"s; AssertionResult assertionResult{ info, CATCH_MOVE( data ) }; assertionEnded( CATCH_MOVE(assertionResult) ); - resetAssertionInfo(); } + void RunContext::handleNonExpr( AssertionInfo const &info, ResultWas::OfType resultType, AssertionReaction &reaction ) { - m_lastAssertionInfo = info; - AssertionResultData data( resultType, LazyExpression( false ) ); AssertionResult assertionResult{ info, CATCH_MOVE( data ) }; const auto isOk = assertionResult.isOk(); + if ( isOk && !m_includeSuccessfulResults ) { + assertionPassedFastPath( info.lineInfo ); + return; + } + assertionEnded( CATCH_MOVE(assertionResult) ); - if ( !isOk ) { populateReaction( reaction ); } - resetAssertionInfo(); + if ( !isOk ) { + populateReaction( + reaction, info.resultDisposition & ResultDisposition::Normal ); + } } + void IResultCapture::pushScopedMessage( MessageInfo&& message ) { + Detail::g_messages.push_back( CATCH_MOVE( message ) ); + } + + void IResultCapture::popScopedMessage( unsigned int messageId ) { + // Note: On average, it would probably be better to look for the message + // backwards. However, we do not expect to have to deal with more + // messages than low single digits, so the optimization is tiny, + // and we would have to hand-write the loop to avoid terrible + // codegen of reverse iterators in debug mode. + Detail::g_messages.erase( std::find_if( Detail::g_messages.begin(), + Detail::g_messages.end(), + [=]( MessageInfo const& msg ) { + return msg.sequence == + messageId; + } ) ); + } - IResultCapture& getResultCapture() { - if (auto* capture = getCurrentContext().getResultCapture()) - return *capture; - else - CATCH_INTERNAL_ERROR("No result capture instance"); + void IResultCapture::emplaceUnscopedMessage( MessageBuilder&& builder ) { + Detail::g_messageScopes.emplace_back( CATCH_MOVE( builder ) ); } void seedRng(IConfig const& config) { diff --git a/src/catch2/internal/catch_run_context.hpp b/src/catch2/internal/catch_run_context.hpp index c66fec0..dee851e 100644 --- a/src/catch2/internal/catch_run_context.hpp +++ b/src/catch2/internal/catch_run_context.hpp @@ -10,7 +10,7 @@ #include #include -#include +#include #include #include #include @@ -20,6 +20,7 @@ #include #include #include +#include #include @@ -93,11 +94,6 @@ namespace Catch { void benchmarkEnded( BenchmarkStats<> const& stats ) override; void benchmarkFailed( StringRef error ) override; - void pushScopedMessage( MessageInfo const& message ) override; - void popScopedMessage( MessageInfo const& message ) override; - - void emplaceUnscopedMessage( MessageBuilder&& builder ) override; - std::string getCurrentTestName() const override; const AssertionResult* getLastResult() const override; @@ -108,18 +104,18 @@ namespace Catch { bool lastAssertionPassed() override; - void assertionPassed() override; - public: // !TBD We need to do this another way! bool aborting() const; private: + void assertionPassedFastPath( SourceLineInfo lineInfo ); + // Update the non-thread-safe m_totals from the atomic assertion counts. + void updateTotalsFromAtomics(); void runCurrentTest(); void invokeActiveTestCase(); - void resetAssertionInfo(); bool testForMissingAssertions( Counts& assertions ); void assertionEnded( AssertionResult&& result ); @@ -129,31 +125,39 @@ namespace Catch { ITransientExpression const *expr, bool negated ); - void populateReaction( AssertionReaction& reaction ); + void populateReaction( AssertionReaction& reaction, bool has_normal_disposition ); + + // Creates dummy info for unexpected exceptions/fatal errors, + // where we do not have the access to one, but we still need + // to send one to the reporters. + AssertionInfo makeDummyAssertionInfo(); private: void handleUnfinishedSections(); - + mutable Detail::Mutex m_assertionMutex; TestRunInfo m_runInfo; TestCaseHandle const* m_activeTestCase = nullptr; ITracker* m_testCaseTracker = nullptr; Optional m_lastResult; - IConfig const* m_config; Totals m_totals; + Detail::AtomicCounts m_atomicAssertionCount; IEventListenerPtr m_reporter; - std::vector m_messages; - std::vector m_messageScopes; /* Keeps owners of so-called unscoped messages. */ - AssertionInfo m_lastAssertionInfo; std::vector m_unfinishedSections; std::vector m_activeSections; TrackerContext m_trackerContext; Detail::unique_ptr m_outputRedirect; FatalConditionHandler m_fatalConditionhandler; - bool m_lastAssertionPassed = false; + // Caches m_config->abortAfter() to avoid vptr calls/allow inlining + size_t m_abortAfterXFailedAssertions; bool m_shouldReportUnexpected = true; + // Caches whether `assertionStarting` events should be sent to the reporter. + bool m_reportAssertionStarting; + // Caches whether `assertionEnded` events for successful assertions should be sent to the reporter bool m_includeSuccessfulResults; + // Caches m_config->shouldDebugBreak() to avoid vptr calls/allow inlining + bool m_shouldDebugBreak; }; void seedRng(IConfig const& config); diff --git a/src/catch2/internal/catch_section.hpp b/src/catch2/internal/catch_section.hpp index e56c79f..b99a4c9 100644 --- a/src/catch2/internal/catch_section.hpp +++ b/src/catch2/internal/catch_section.hpp @@ -62,7 +62,7 @@ namespace Catch { #else // These section definitions imply that at most one section at one level -// will be intered (because only one section's __LINE__ can be equal to +// will be entered (because only one section's __LINE__ can be equal to // the dummy `catchInternalSectionHint` variable from `TEST_CASE`). namespace Catch { diff --git a/src/catch2/internal/catch_sharding.hpp b/src/catch2/internal/catch_sharding.hpp index 22561f4..4d7ee26 100644 --- a/src/catch2/internal/catch_sharding.hpp +++ b/src/catch2/internal/catch_sharding.hpp @@ -9,7 +9,6 @@ #define CATCH_SHARDING_HPP_INCLUDED #include -#include #include namespace Catch { diff --git a/src/catch2/internal/catch_string_manip.cpp b/src/catch2/internal/catch_string_manip.cpp index ce1abaa..6898378 100644 --- a/src/catch2/internal/catch_string_manip.cpp +++ b/src/catch2/internal/catch_string_manip.cpp @@ -74,7 +74,7 @@ namespace Catch { std::string origStr = CATCH_MOVE(str); str.clear(); // There is at least one replacement, so reserve with the best guess - // we can make without actually counting the number of occurences. + // we can make without actually counting the number of occurrences. str.reserve(origStr.size() - replaceThis.size() + withThis.size()); do { str.append(origStr, copyBegin, i-copyBegin ); diff --git a/src/catch2/internal/catch_stringref.cpp b/src/catch2/internal/catch_stringref.cpp index 232498e..5b59354 100644 --- a/src/catch2/internal/catch_stringref.cpp +++ b/src/catch2/internal/catch_stringref.cpp @@ -10,7 +10,6 @@ #include #include #include -#include namespace Catch { StringRef::StringRef( char const* rawChars ) noexcept diff --git a/src/catch2/internal/catch_template_test_registry.hpp b/src/catch2/internal/catch_template_test_registry.hpp index 0ea354b..7aec888 100644 --- a/src/catch2/internal/catch_template_test_registry.hpp +++ b/src/catch2/internal/catch_template_test_registry.hpp @@ -135,11 +135,11 @@ constexpr char const* tmpl_types[] = {CATCH_REC_LIST(INTERNAL_CATCH_STRINGIZE_WITHOUT_PARENS, INTERNAL_CATCH_REMOVE_PARENS(TmplTypes))};\ constexpr char const* types_list[] = {CATCH_REC_LIST(INTERNAL_CATCH_STRINGIZE_WITHOUT_PARENS, INTERNAL_CATCH_REMOVE_PARENS(TypesList))};\ constexpr auto num_types = sizeof(types_list) / sizeof(types_list[0]);\ - (void)expander{(Catch::AutoReg( Catch::makeTestInvoker( &TestFuncName ), CATCH_INTERNAL_LINEINFO, Catch::StringRef(), Catch::NameAndTags{ Name " - " + std::string(tmpl_types[index / num_types]) + '<' + std::string(types_list[index % num_types]) + '>', Tags } ), index++)... };/* NOLINT */\ + (void)expander{(Catch::AutoReg( Catch::makeTestInvoker( &TestFuncName ), CATCH_INTERNAL_LINEINFO, Catch::StringRef(), Catch::NameAndTags{ Name " - " + std::string(tmpl_types[index / num_types]) + '<' + types_list[index % num_types] + '>', Tags } ), index++)... };/* NOLINT */\ } \ }; \ - static int INTERNAL_CATCH_UNIQUE_NAME( globalRegistrar ) = [](){ \ - using TestInit = typename create()), TypeList>::type; \ + static const int INTERNAL_CATCH_UNIQUE_NAME( globalRegistrar ) = [](){ \ + using TestInit = typename create(Catch::Detail::priority_tag<1>{})), TypeList>::type; \ TestInit t; \ t.reg_tests(); \ return 0; \ @@ -184,7 +184,7 @@ (void)expander{(Catch::AutoReg( Catch::makeTestInvoker( &TestFunc ), CATCH_INTERNAL_LINEINFO, Catch::StringRef(), Catch::NameAndTags{ Name " - " INTERNAL_CATCH_STRINGIZE(TmplList) " - " + std::to_string(index), Tags } ), index++)... };/* NOLINT */\ } \ };\ - static int INTERNAL_CATCH_UNIQUE_NAME( globalRegistrar ) = [](){ \ + static const int INTERNAL_CATCH_UNIQUE_NAME( globalRegistrar ) = [](){ \ using TestInit = typename convert::type; \ TestInit t; \ t.reg_tests(); \ @@ -220,7 +220,7 @@ (void)expander{(reg_test(Types{}, #ClassName, Catch::NameAndTags{ Name " - " + std::string(tmpl_types[index]), Tags } ), index++)... };/* NOLINT */ \ }\ };\ - static int INTERNAL_CATCH_UNIQUE_NAME( globalRegistrar ) = [](){\ + static const int INTERNAL_CATCH_UNIQUE_NAME( globalRegistrar ) = [](){\ TestNameClass();\ return 0;\ }();\ @@ -267,11 +267,11 @@ constexpr char const* tmpl_types[] = {CATCH_REC_LIST(INTERNAL_CATCH_STRINGIZE_WITHOUT_PARENS, INTERNAL_CATCH_REMOVE_PARENS(TmplTypes))};\ constexpr char const* types_list[] = {CATCH_REC_LIST(INTERNAL_CATCH_STRINGIZE_WITHOUT_PARENS, INTERNAL_CATCH_REMOVE_PARENS(TypesList))};\ constexpr auto num_types = sizeof(types_list) / sizeof(types_list[0]);\ - (void)expander{(Catch::AutoReg( Catch::makeTestInvoker( &TestName::test ), CATCH_INTERNAL_LINEINFO, #ClassName, Catch::NameAndTags{ Name " - " + std::string(tmpl_types[index / num_types]) + '<' + std::string(types_list[index % num_types]) + '>', Tags } ), index++)... };/* NOLINT */ \ + (void)expander{(Catch::AutoReg( Catch::makeTestInvoker( &TestName::test ), CATCH_INTERNAL_LINEINFO, #ClassName, Catch::NameAndTags{ Name " - " + std::string(tmpl_types[index / num_types]) + '<' + types_list[index % num_types] + '>', Tags } ), index++)... };/* NOLINT */ \ }\ };\ - static int INTERNAL_CATCH_UNIQUE_NAME( globalRegistrar ) = [](){\ - using TestInit = typename create()), TypeList>::type;\ + static const int INTERNAL_CATCH_UNIQUE_NAME( globalRegistrar ) = [](){\ + using TestInit = typename create(Catch::Detail::priority_tag<1>{})), TypeList>::type;\ TestInit t;\ t.reg_tests();\ return 0;\ @@ -319,7 +319,7 @@ (void)expander{(Catch::AutoReg( Catch::makeTestInvoker( &TestName::test ), CATCH_INTERNAL_LINEINFO, #ClassName##_catch_sr, Catch::NameAndTags{ Name " - " INTERNAL_CATCH_STRINGIZE(TmplList) " - " + std::to_string(index), Tags } ), index++)... };/* NOLINT */ \ }\ };\ - static int INTERNAL_CATCH_UNIQUE_NAME( globalRegistrar ) = [](){\ + static const int INTERNAL_CATCH_UNIQUE_NAME( globalRegistrar ) = [](){\ using TestInit = typename convert::type;\ TestInit t;\ t.reg_tests();\ diff --git a/src/catch2/internal/catch_thread_support.hpp b/src/catch2/internal/catch_thread_support.hpp new file mode 100644 index 0000000..c15980b --- /dev/null +++ b/src/catch2/internal/catch_thread_support.hpp @@ -0,0 +1,49 @@ + +// 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_THREAD_SUPPORT_HPP_INCLUDED +#define CATCH_THREAD_SUPPORT_HPP_INCLUDED + +#include + +#if defined( CATCH_CONFIG_EXPERIMENTAL_THREAD_SAFE_ASSERTIONS ) +# include +# include +#endif + +#include + +namespace Catch { + namespace Detail { +#if defined( CATCH_CONFIG_EXPERIMENTAL_THREAD_SAFE_ASSERTIONS ) + using Mutex = std::mutex; + using LockGuard = std::lock_guard; + struct AtomicCounts { + std::atomic passed = 0; + std::atomic failed = 0; + std::atomic failedButOk = 0; + std::atomic skipped = 0; + }; +#else // ^^ Use actual mutex, lock and atomics + // vv Dummy implementations for single-thread performance + + struct Mutex { + void lock() {} + void unlock() {} + }; + + struct LockGuard { + LockGuard( Mutex ) {} + }; + + using AtomicCounts = Counts; +#endif + + } // namespace Detail +} // namespace Catch + +#endif // CATCH_THREAD_SUPPORT_HPP_INCLUDED diff --git a/src/catch2/internal/catch_unique_ptr.hpp b/src/catch2/internal/catch_unique_ptr.hpp index 49cbc78..8add2a8 100644 --- a/src/catch2/internal/catch_unique_ptr.hpp +++ b/src/catch2/internal/catch_unique_ptr.hpp @@ -92,7 +92,7 @@ namespace Detail { } explicit operator bool() const { - return m_ptr; + return m_ptr != nullptr; } friend void swap(unique_ptr& lhs, unique_ptr& rhs) { diff --git a/src/catch2/internal/catch_unreachable.hpp b/src/catch2/internal/catch_unreachable.hpp new file mode 100644 index 0000000..fc3f75b --- /dev/null +++ b/src/catch2/internal/catch_unreachable.hpp @@ -0,0 +1,56 @@ + +// 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_UNREACHABLE_HPP_INCLUDED +#define CATCH_UNREACHABLE_HPP_INCLUDED + +/**\file + * Polyfill `std::unreachable` + * + * We need something like `std::unreachable` to tell the compiler that + * some macros, e.g. `FAIL` or `SKIP`, do not continue execution in normal + * manner, and should handle it as such, e.g. not warn if there is no return + * from non-void function after a `FAIL` or `SKIP`. + */ + +#include + +#if defined( __cpp_lib_unreachable ) && __cpp_lib_unreachable > 202202L +# include +namespace Catch { + namespace Detail { + using Unreachable = std::unreachable; + } +} // namespace Catch + +#else // vv If we do not have std::unreachable, we implement something similar + +namespace Catch { + namespace Detail { + + [[noreturn]] + inline void Unreachable() noexcept { +# if defined( NDEBUG ) +# if defined( _MSC_VER ) && !defined( __clang__ ) + __assume( false ); +# elif defined( __GNUC__ ) + __builtin_unreachable(); +# else // vv platform without known optimization hint + std::terminate(); +# endif +# else // ^^ NDEBUG + // For non-release builds, we prefer termination on bug over UB + std::terminate(); +# endif // + } + + } // namespace Detail +} // end namespace Catch + +#endif + +#endif // CATCH_UNREACHABLE_HPP_INCLUDED diff --git a/src/catch2/internal/catch_xmlwriter.cpp b/src/catch2/internal/catch_xmlwriter.cpp index ccf63a5..67126be 100644 --- a/src/catch2/internal/catch_xmlwriter.cpp +++ b/src/catch2/internal/catch_xmlwriter.cpp @@ -47,7 +47,7 @@ namespace { void hexEscapeChar(std::ostream& os, unsigned char c) { std::ios_base::fmtflags f(os.flags()); - os << "\\x" + os << "\\x"_sr << std::uppercase << std::hex << std::setfill('0') << std::setw(2) << static_cast(c); os.flags(f); @@ -66,95 +66,111 @@ namespace { void XmlEncode::encodeTo( std::ostream& os ) const { // Apostrophe escaping not necessary if we always use " to write attributes // (see: http://www.w3.org/TR/xml/#syntax) - - for( std::size_t idx = 0; idx < m_str.size(); ++ idx ) { - unsigned char c = static_cast(m_str[idx]); - switch (c) { - case '<': os << "<"; break; - case '&': os << "&"; break; + size_t last_start = 0; + auto write_to = [&]( size_t idx ) { + if ( last_start < idx ) { + os << m_str.substr( last_start, idx - last_start ); + } + last_start = idx + 1; + }; + + for ( std::size_t idx = 0; idx < m_str.size(); ++idx ) { + unsigned char c = static_cast( m_str[idx] ); + switch ( c ) { + case '<': + write_to( idx ); + os << "<"_sr; + break; + case '&': + write_to( idx ); + os << "&"_sr; + break; case '>': // See: http://www.w3.org/TR/xml/#syntax - if (idx > 2 && m_str[idx - 1] == ']' && m_str[idx - 2] == ']') - os << ">"; - else - os << c; + if ( idx > 2 && m_str[idx - 1] == ']' && m_str[idx - 2] == ']' ) { + write_to( idx ); + os << ">"_sr; + } break; case '\"': - if (m_forWhat == ForAttributes) - os << """; - else - os << c; + if ( m_forWhat == ForAttributes ) { + write_to( idx ); + os << """_sr; + } break; default: // Check for control characters and invalid utf-8 // Escape control characters in standard ascii - // see http://stackoverflow.com/questions/404107/why-are-control-characters-illegal-in-xml-1-0 - if (c < 0x09 || (c > 0x0D && c < 0x20) || c == 0x7F) { - hexEscapeChar(os, c); + // see + // http://stackoverflow.com/questions/404107/why-are-control-characters-illegal-in-xml-1-0 + if ( c < 0x09 || ( c > 0x0D && c < 0x20 ) || c == 0x7F ) { + write_to( idx ); + hexEscapeChar( os, c ); break; } // Plain ASCII: Write it to stream - if (c < 0x7F) { - os << c; + if ( c < 0x7F ) { break; } // UTF-8 territory - // Check if the encoding is valid and if it is not, hex escape bytes. - // Important: We do not check the exact decoded values for validity, only the encoding format - // First check that this bytes is a valid lead byte: - // This means that it is not encoded as 1111 1XXX + // Check if the encoding is valid and if it is not, hex escape + // bytes. Important: We do not check the exact decoded values for + // validity, only the encoding format First check that this bytes is + // a valid lead byte: This means that it is not encoded as 1111 1XXX // Or as 10XX XXXX - if (c < 0xC0 || - c >= 0xF8) { - hexEscapeChar(os, c); + if ( c < 0xC0 || c >= 0xF8 ) { + write_to( idx ); + hexEscapeChar( os, c ); break; } - auto encBytes = trailingBytes(c); - // Are there enough bytes left to avoid accessing out-of-bounds memory? - if (idx + encBytes - 1 >= m_str.size()) { - hexEscapeChar(os, c); + auto encBytes = trailingBytes( c ); + // Are there enough bytes left to avoid accessing out-of-bounds + // memory? + if ( idx + encBytes - 1 >= m_str.size() ) { + write_to( idx ); + hexEscapeChar( os, c ); break; } // The header is valid, check data // The next encBytes bytes must together be a valid utf-8 - // This means: bitpattern 10XX XXXX and the extracted value is sane (ish) + // This means: bitpattern 10XX XXXX and the extracted value is sane + // (ish) bool valid = true; - uint32_t value = headerValue(c); - for (std::size_t n = 1; n < encBytes; ++n) { - unsigned char nc = static_cast(m_str[idx + n]); - valid &= ((nc & 0xC0) == 0x80); - value = (value << 6) | (nc & 0x3F); + uint32_t value = headerValue( c ); + for ( std::size_t n = 1; n < encBytes; ++n ) { + unsigned char nc = static_cast( m_str[idx + n] ); + valid &= ( ( nc & 0xC0 ) == 0x80 ); + value = ( value << 6 ) | ( nc & 0x3F ); } if ( // Wrong bit pattern of following bytes - (!valid) || + ( !valid ) || // Overlong encodings - (value < 0x80) || - (0x80 <= value && value < 0x800 && encBytes > 2) || - (0x800 < value && value < 0x10000 && encBytes > 3) || + ( value < 0x80 ) || + ( 0x80 <= value && value < 0x800 && encBytes > 2 ) || + ( 0x800 < value && value < 0x10000 && encBytes > 3 ) || // Encoded value out of range - (value >= 0x110000) - ) { - hexEscapeChar(os, c); + ( value >= 0x110000 ) ) { + write_to( idx ); + hexEscapeChar( os, c ); break; } // If we got here, this is in fact a valid(ish) utf-8 sequence - for (std::size_t n = 0; n < encBytes; ++n) { - os << m_str[idx + n]; - } idx += encBytes - 1; break; } } + + write_to( m_str.size() ); } std::ostream& operator << ( std::ostream& os, XmlEncode const& xmlEncode ) { diff --git a/src/catch2/matchers/catch_matchers_container_properties.hpp b/src/catch2/matchers/catch_matchers_container_properties.hpp index 5f3fc7f..33795fb 100644 --- a/src/catch2/matchers/catch_matchers_container_properties.hpp +++ b/src/catch2/matchers/catch_matchers_container_properties.hpp @@ -79,7 +79,7 @@ namespace Catch { //! Creates a matcher that accepts ranges/containers with specific size HasSizeMatcher SizeIs(std::size_t sz); template - std::enable_if_t::value, + std::enable_if_t, SizeMatchesMatcher> SizeIs(Matcher&& m) { return SizeMatchesMatcher{CATCH_FORWARD(m)}; } diff --git a/src/catch2/matchers/catch_matchers_contains.hpp b/src/catch2/matchers/catch_matchers_contains.hpp index 877d692..90cfe64 100644 --- a/src/catch2/matchers/catch_matchers_contains.hpp +++ b/src/catch2/matchers/catch_matchers_contains.hpp @@ -11,8 +11,8 @@ #include #include -#include #include +#include namespace Catch { namespace Matchers { @@ -74,14 +74,14 @@ namespace Catch { * Uses `std::equal_to` to do the comparison */ template - std::enable_if_t::value, + std::enable_if_t, ContainsElementMatcher>> Contains(T&& elem) { return { CATCH_FORWARD(elem), std::equal_to<>{} }; } //! Creates a matcher that checks whether a range contains element matching a matcher template - std::enable_if_t::value, + std::enable_if_t, ContainsMatcherMatcher> Contains(Matcher&& matcher) { return { CATCH_FORWARD(matcher) }; } diff --git a/src/catch2/matchers/catch_matchers_range_equals.hpp b/src/catch2/matchers/catch_matchers_range_equals.hpp index 8130f60..11a1f2f 100644 --- a/src/catch2/matchers/catch_matchers_range_equals.hpp +++ b/src/catch2/matchers/catch_matchers_range_equals.hpp @@ -11,8 +11,7 @@ #include #include -#include -#include +#include namespace Catch { namespace Matchers { diff --git a/src/catch2/matchers/catch_matchers_templated.hpp b/src/catch2/matchers/catch_matchers_templated.hpp index ba0661a..fc9fcb2 100644 --- a/src/catch2/matchers/catch_matchers_templated.hpp +++ b/src/catch2/matchers/catch_matchers_templated.hpp @@ -58,19 +58,19 @@ namespace Matchers { } template - using is_generic_matcher = std::is_base_of< + static constexpr bool is_generic_matcher_v = std::is_base_of< Catch::Matchers::MatcherGenericBase, std::remove_cv_t> - >; + >::value; template - using are_generic_matchers = Catch::Detail::conjunction...>; + static constexpr bool are_generic_matchers_v = Catch::Detail::conjunction>...>::value; template - using is_matcher = std::is_base_of< + static constexpr bool is_matcher_v = std::is_base_of< Catch::Matchers::MatcherUntypedBase, std::remove_cv_t> - >; + >::value; template @@ -143,7 +143,7 @@ namespace Matchers { //! Avoids type nesting for `GenericAllOf && some matcher` case template - friend std::enable_if_t::value, + friend std::enable_if_t, MatchAllOfGeneric> operator && ( MatchAllOfGeneric&& lhs, MatcherRHS const& rhs) { @@ -152,7 +152,7 @@ namespace Matchers { //! Avoids type nesting for `some matcher && GenericAllOf` case template - friend std::enable_if_t::value, + friend std::enable_if_t, MatchAllOfGeneric> operator && ( MatcherLHS const& lhs, MatchAllOfGeneric&& rhs) { @@ -197,7 +197,7 @@ namespace Matchers { //! Avoids type nesting for `GenericAnyOf || some matcher` case template - friend std::enable_if_t::value, + friend std::enable_if_t, MatchAnyOfGeneric> operator || ( MatchAnyOfGeneric&& lhs, MatcherRHS const& rhs) { @@ -206,7 +206,7 @@ namespace Matchers { //! Avoids type nesting for `some matcher || GenericAnyOf` case template - friend std::enable_if_t::value, + friend std::enable_if_t, MatchAnyOfGeneric> operator || ( MatcherLHS const& lhs, MatchAnyOfGeneric&& rhs) { @@ -246,20 +246,20 @@ namespace Matchers { // compose only generic matchers template - std::enable_if_t::value, Detail::MatchAllOfGeneric> + std::enable_if_t, Detail::MatchAllOfGeneric> operator && (MatcherLHS const& lhs, MatcherRHS const& rhs) { return { lhs, rhs }; } template - std::enable_if_t::value, Detail::MatchAnyOfGeneric> + std::enable_if_t, Detail::MatchAnyOfGeneric> operator || (MatcherLHS const& lhs, MatcherRHS const& rhs) { return { lhs, rhs }; } //! Wrap provided generic matcher in generic negator template - std::enable_if_t::value, Detail::MatchNotOfGeneric> + std::enable_if_t, Detail::MatchNotOfGeneric> operator ! (MatcherT const& matcher) { return Detail::MatchNotOfGeneric{matcher}; } @@ -267,25 +267,25 @@ namespace Matchers { // compose mixed generic and non-generic matchers template - std::enable_if_t::value, Detail::MatchAllOfGeneric>> + std::enable_if_t, Detail::MatchAllOfGeneric>> operator && (MatcherLHS const& lhs, MatcherBase const& rhs) { return { lhs, rhs }; } template - std::enable_if_t::value, Detail::MatchAllOfGeneric, MatcherRHS>> + std::enable_if_t, Detail::MatchAllOfGeneric, MatcherRHS>> operator && (MatcherBase const& lhs, MatcherRHS const& rhs) { return { lhs, rhs }; } template - std::enable_if_t::value, Detail::MatchAnyOfGeneric>> + std::enable_if_t, Detail::MatchAnyOfGeneric>> operator || (MatcherLHS const& lhs, MatcherBase const& rhs) { return { lhs, rhs }; } template - std::enable_if_t::value, Detail::MatchAnyOfGeneric, MatcherRHS>> + std::enable_if_t, Detail::MatchAnyOfGeneric, MatcherRHS>> operator || (MatcherBase const& lhs, MatcherRHS const& rhs) { return { lhs, rhs }; } diff --git a/src/catch2/meson.build b/src/catch2/meson.build index 2eb5399..60f7777 100644 --- a/src/catch2/meson.build +++ b/src/catch2/meson.build @@ -92,6 +92,7 @@ internal_headers = [ 'internal/catch_debug_console.hpp', 'internal/catch_debugger.hpp', 'internal/catch_decomposer.hpp', + 'internal/catch_deprecation_macro.hpp', 'internal/catch_enforce.hpp', 'internal/catch_enum_values_registry.hpp', 'internal/catch_errno_guard.hpp', @@ -144,15 +145,16 @@ internal_headers = [ 'internal/catch_test_failure_exception.hpp', 'internal/catch_test_macro_impl.hpp', 'internal/catch_test_registry.hpp', - 'internal/catch_test_run_info.hpp', 'internal/catch_test_spec_parser.hpp', 'internal/catch_textflow.hpp', + 'internal/catch_thread_support.hpp', 'internal/catch_to_string.hpp', 'internal/catch_uncaught_exceptions.hpp', 'internal/catch_uniform_floating_point_distribution.hpp', 'internal/catch_uniform_integer_distribution.hpp', 'internal/catch_unique_name.hpp', 'internal/catch_unique_ptr.hpp', + 'internal/catch_unreachable.hpp', 'internal/catch_void_type.hpp', 'internal/catch_wildcard_pattern.hpp', 'internal/catch_windows_h_proxy.hpp', @@ -185,6 +187,7 @@ internal_headers = [ 'catch_template_test_macros.hpp', 'catch_test_case_info.hpp', 'catch_test_macros.hpp', + 'catch_test_run_info.hpp', 'catch_test_spec.hpp', 'catch_timer.hpp', 'catch_tostring.hpp', diff --git a/src/catch2/reporters/catch_reporter_automake.hpp b/src/catch2/reporters/catch_reporter_automake.hpp index a639428..d920adf 100644 --- a/src/catch2/reporters/catch_reporter_automake.hpp +++ b/src/catch2/reporters/catch_reporter_automake.hpp @@ -19,9 +19,11 @@ namespace Catch { public: // GCC5 compat: we cannot use inherited constructor, because it // doesn't implement backport of P0136 - AutomakeReporter(ReporterConfig&& _config): - StreamingReporterBase(CATCH_MOVE(_config)) - {} + AutomakeReporter( ReporterConfig&& _config ): + StreamingReporterBase( CATCH_MOVE( _config ) ) { + m_preferences.shouldReportAllAssertionStarts = false; + } + ~AutomakeReporter() override; static std::string getDescription() { diff --git a/src/catch2/reporters/catch_reporter_compact.cpp b/src/catch2/reporters/catch_reporter_compact.cpp index 0f85594..6d2c865 100644 --- a/src/catch2/reporters/catch_reporter_compact.cpp +++ b/src/catch2/reporters/catch_reporter_compact.cpp @@ -215,7 +215,8 @@ class AssertionPrinter { << m_config->testSpec() << '\n'; } - m_stream << "RNG seed: " << getSeed() << '\n'; + m_stream << "RNG seed: " << getSeed() << '\n' + << std::flush; } void CompactReporter::assertionEnded( AssertionStats const& _assertionStats ) { diff --git a/src/catch2/reporters/catch_reporter_compact.hpp b/src/catch2/reporters/catch_reporter_compact.hpp index d95bbff..a6437c6 100644 --- a/src/catch2/reporters/catch_reporter_compact.hpp +++ b/src/catch2/reporters/catch_reporter_compact.hpp @@ -16,7 +16,10 @@ namespace Catch { class CompactReporter final : public StreamingReporterBase { public: - using StreamingReporterBase::StreamingReporterBase; + CompactReporter( ReporterConfig&& _config ): + StreamingReporterBase( CATCH_MOVE( _config ) ) { + m_preferences.shouldReportAllAssertionStarts = false; + } ~CompactReporter() override; diff --git a/src/catch2/reporters/catch_reporter_console.cpp b/src/catch2/reporters/catch_reporter_console.cpp index 9529f39..acb4ae8 100644 --- a/src/catch2/reporters/catch_reporter_console.cpp +++ b/src/catch2/reporters/catch_reporter_console.cpp @@ -401,7 +401,10 @@ ConsoleReporter::ConsoleReporter(ReporterConfig&& config): { "est run time high mean high std dev", 14, Justification::Right } }; } - }())) {} + }())) { + m_preferences.shouldReportAllAssertionStarts = false; +} + ConsoleReporter::~ConsoleReporter() = default; std::string ConsoleReporter::getDescription() { @@ -416,8 +419,6 @@ void ConsoleReporter::reportInvalidTestSpec( StringRef arg ) { m_stream << "Invalid Filter: " << arg << '\n'; } -void ConsoleReporter::assertionStarting(AssertionInfo const&) {} - void ConsoleReporter::assertionEnded(AssertionStats const& _assertionStats) { AssertionResult const& result = _assertionStats.assertionResult; @@ -529,7 +530,8 @@ void ConsoleReporter::testRunStarting(TestRunInfo const& _testRunInfo) { m_stream << m_colour->guardColour( Colour::BrightYellow ) << "Filters: " << m_config->testSpec() << '\n'; } - m_stream << "Randomness seeded to: " << getSeed() << '\n'; + m_stream << "Randomness seeded to: " << getSeed() << '\n' + << std::flush; } void ConsoleReporter::lazyPrint() { diff --git a/src/catch2/reporters/catch_reporter_console.hpp b/src/catch2/reporters/catch_reporter_console.hpp index 2437726..86ecf43 100644 --- a/src/catch2/reporters/catch_reporter_console.hpp +++ b/src/catch2/reporters/catch_reporter_console.hpp @@ -26,8 +26,6 @@ namespace Catch { void noMatchingTestCases( StringRef unmatchedSpec ) override; void reportInvalidTestSpec( StringRef arg ) override; - void assertionStarting(AssertionInfo const&) override; - void assertionEnded(AssertionStats const& _assertionStats) override; void sectionStarting(SectionInfo const& _sectionInfo) override; diff --git a/src/catch2/reporters/catch_reporter_helpers.cpp b/src/catch2/reporters/catch_reporter_helpers.cpp index ffb32ff..c710ac4 100644 --- a/src/catch2/reporters/catch_reporter_helpers.cpp +++ b/src/catch2/reporters/catch_reporter_helpers.cpp @@ -19,6 +19,7 @@ #include #include +#include #include #include #include @@ -182,9 +183,29 @@ namespace Catch { out << "All available tags:\n"; } + // minimum whitespace to pad tag counts, possibly overwritten below + size_t maxTagCountLen = 2; + + // determine necessary padding for tag count column + if ( ! tags.empty() ) { + const auto maxTagCount = + std::max_element( tags.begin(), + tags.end(), + []( auto const& lhs, auto const& rhs ) { + return lhs.count < rhs.count; + } ) + ->count; + + // more padding necessary for 3+ digits + if (maxTagCount >= 100) { + auto numDigits = 1 + std::floor( std::log10( maxTagCount ) ); + maxTagCountLen = static_cast( numDigits ); + } + } + for ( auto const& tagCount : tags ) { ReusableStringStream rss; - rss << " " << std::setw( 2 ) << tagCount.count << " "; + rss << " " << std::setw( maxTagCountLen ) << tagCount.count << " "; auto str = rss.str(); auto wrapper = TextFlow::Column( tagCount.all() ) .initialIndent( 0 ) diff --git a/src/catch2/reporters/catch_reporter_json.cpp b/src/catch2/reporters/catch_reporter_json.cpp index 6a8e655..4328d56 100644 --- a/src/catch2/reporters/catch_reporter_json.cpp +++ b/src/catch2/reporters/catch_reporter_json.cpp @@ -51,6 +51,8 @@ namespace Catch { // not, but for machine-parseable reporters I think the answer // should be yes. m_preferences.shouldReportAllAssertions = true; + // We only handle assertions when they end + m_preferences.shouldReportAllAssertionStarts = false; m_objectWriters.emplace( m_stream ); m_writers.emplace( Writer::Object ); @@ -280,7 +282,6 @@ namespace Catch { endObject(); } - void JsonReporter::assertionStarting( AssertionInfo const& /*assertionInfo*/ ) {} void JsonReporter::assertionEnded( AssertionStats const& assertionStats ) { // TODO: There is lot of different things to handle here, but // we can fill it in later, after we show that the basic diff --git a/src/catch2/reporters/catch_reporter_json.hpp b/src/catch2/reporters/catch_reporter_json.hpp index c938ca3..b28aaf8 100644 --- a/src/catch2/reporters/catch_reporter_json.hpp +++ b/src/catch2/reporters/catch_reporter_json.hpp @@ -39,7 +39,6 @@ namespace Catch { void sectionStarting( SectionInfo const& sectionInfo ) override; void sectionEnded( SectionStats const& sectionStats ) override; - void assertionStarting( AssertionInfo const& assertionInfo ) override; void assertionEnded( AssertionStats const& assertionStats ) override; //void testRunEndedCumulative() override; diff --git a/src/catch2/reporters/catch_reporter_junit.cpp b/src/catch2/reporters/catch_reporter_junit.cpp index 27bdfe2..df34f17 100644 --- a/src/catch2/reporters/catch_reporter_junit.cpp +++ b/src/catch2/reporters/catch_reporter_junit.cpp @@ -89,6 +89,7 @@ namespace Catch { { m_preferences.shouldRedirectStdOut = true; m_preferences.shouldReportAllAssertions = false; + m_preferences.shouldReportAllAssertionStarts = false; m_shouldStoreSuccesfulAssertions = false; } diff --git a/src/catch2/reporters/catch_reporter_multi.cpp b/src/catch2/reporters/catch_reporter_multi.cpp index 531902b..6bf9bcb 100644 --- a/src/catch2/reporters/catch_reporter_multi.cpp +++ b/src/catch2/reporters/catch_reporter_multi.cpp @@ -19,6 +19,8 @@ namespace Catch { reporterish.getPreferences().shouldRedirectStdOut; m_preferences.shouldReportAllAssertions |= reporterish.getPreferences().shouldReportAllAssertions; + m_preferences.shouldReportAllAssertionStarts |= + reporterish.getPreferences().shouldReportAllAssertionStarts; } void MultiReporter::addListener( IEventListenerPtr&& listener ) { diff --git a/src/catch2/reporters/catch_reporter_multi.hpp b/src/catch2/reporters/catch_reporter_multi.hpp index 6611383..d206663 100644 --- a/src/catch2/reporters/catch_reporter_multi.hpp +++ b/src/catch2/reporters/catch_reporter_multi.hpp @@ -29,6 +29,11 @@ namespace Catch { void updatePreferences(IEventListener const& reporterish); public: + MultiReporter( IConfig const* config ): + IEventListener( config ) { + m_preferences.shouldReportAllAssertionStarts = false; + } + using IEventListener::IEventListener; void addListener( IEventListenerPtr&& listener ); diff --git a/src/catch2/reporters/catch_reporter_registrars.hpp b/src/catch2/reporters/catch_reporter_registrars.hpp index a93963f..d971c45 100644 --- a/src/catch2/reporters/catch_reporter_registrars.hpp +++ b/src/catch2/reporters/catch_reporter_registrars.hpp @@ -103,22 +103,24 @@ namespace Catch { #if !defined(CATCH_CONFIG_DISABLE) -# define CATCH_REGISTER_REPORTER( name, reporterType ) \ - CATCH_INTERNAL_START_WARNINGS_SUPPRESSION \ - CATCH_INTERNAL_SUPPRESS_GLOBALS_WARNINGS \ - namespace { \ - Catch::ReporterRegistrar INTERNAL_CATCH_UNIQUE_NAME( \ - catch_internal_RegistrarFor )( name ); \ - } \ +# define CATCH_REGISTER_REPORTER( name, reporterType ) \ + CATCH_INTERNAL_START_WARNINGS_SUPPRESSION \ + CATCH_INTERNAL_SUPPRESS_GLOBALS_WARNINGS \ + namespace { \ + const Catch::ReporterRegistrar \ + INTERNAL_CATCH_UNIQUE_NAME( catch_internal_RegistrarFor )( \ + name ); \ + } \ CATCH_INTERNAL_STOP_WARNINGS_SUPPRESSION -# define CATCH_REGISTER_LISTENER( listenerType ) \ - CATCH_INTERNAL_START_WARNINGS_SUPPRESSION \ - CATCH_INTERNAL_SUPPRESS_GLOBALS_WARNINGS \ - namespace { \ - Catch::ListenerRegistrar INTERNAL_CATCH_UNIQUE_NAME( \ - catch_internal_RegistrarFor )( #listenerType##_catch_sr ); \ - } \ +# define CATCH_REGISTER_LISTENER( listenerType ) \ + CATCH_INTERNAL_START_WARNINGS_SUPPRESSION \ + CATCH_INTERNAL_SUPPRESS_GLOBALS_WARNINGS \ + namespace { \ + const Catch::ListenerRegistrar \ + INTERNAL_CATCH_UNIQUE_NAME( catch_internal_RegistrarFor )( \ + #listenerType##_catch_sr ); \ + } \ CATCH_INTERNAL_STOP_WARNINGS_SUPPRESSION #else // CATCH_CONFIG_DISABLE diff --git a/src/catch2/reporters/catch_reporter_sonarqube.hpp b/src/catch2/reporters/catch_reporter_sonarqube.hpp index 509f411..98803a2 100644 --- a/src/catch2/reporters/catch_reporter_sonarqube.hpp +++ b/src/catch2/reporters/catch_reporter_sonarqube.hpp @@ -22,6 +22,7 @@ namespace Catch { , xml(m_stream) { m_preferences.shouldRedirectStdOut = true; m_preferences.shouldReportAllAssertions = false; + m_preferences.shouldReportAllAssertionStarts = false; m_shouldStoreSuccesfulAssertions = false; } diff --git a/src/catch2/reporters/catch_reporter_tap.cpp b/src/catch2/reporters/catch_reporter_tap.cpp index 67d406f..d7e68bf 100644 --- a/src/catch2/reporters/catch_reporter_tap.cpp +++ b/src/catch2/reporters/catch_reporter_tap.cpp @@ -196,7 +196,8 @@ namespace Catch { if ( m_config->testSpec().hasFilters() ) { m_stream << "# filters: " << m_config->testSpec() << '\n'; } - m_stream << "# rng-seed: " << m_config->rngSeed() << '\n'; + m_stream << "# rng-seed: " << m_config->rngSeed() << '\n' + << std::flush; } void TAPReporter::noMatchingTestCases( StringRef unmatchedSpec ) { diff --git a/src/catch2/reporters/catch_reporter_tap.hpp b/src/catch2/reporters/catch_reporter_tap.hpp index e6889bb..4664928 100644 --- a/src/catch2/reporters/catch_reporter_tap.hpp +++ b/src/catch2/reporters/catch_reporter_tap.hpp @@ -18,6 +18,7 @@ namespace Catch { TAPReporter( ReporterConfig&& config ): StreamingReporterBase( CATCH_MOVE(config) ) { m_preferences.shouldReportAllAssertions = true; + m_preferences.shouldReportAllAssertionStarts = false; } static std::string getDescription() { diff --git a/src/catch2/reporters/catch_reporter_teamcity.hpp b/src/catch2/reporters/catch_reporter_teamcity.hpp index 662e989..4adb0e3 100644 --- a/src/catch2/reporters/catch_reporter_teamcity.hpp +++ b/src/catch2/reporters/catch_reporter_teamcity.hpp @@ -26,6 +26,7 @@ namespace Catch { : StreamingReporterBase( CATCH_MOVE(_config) ) { m_preferences.shouldRedirectStdOut = true; + m_preferences.shouldReportAllAssertionStarts = false; } ~TeamCityReporter() override; diff --git a/src/catch2/reporters/catch_reporter_xml.cpp b/src/catch2/reporters/catch_reporter_xml.cpp index 35a3028..8b7b5a7 100644 --- a/src/catch2/reporters/catch_reporter_xml.cpp +++ b/src/catch2/reporters/catch_reporter_xml.cpp @@ -30,6 +30,7 @@ namespace Catch { { m_preferences.shouldRedirectStdOut = true; m_preferences.shouldReportAllAssertions = true; + m_preferences.shouldReportAllAssertionStarts = false; } XmlReporter::~XmlReporter() = default; @@ -86,8 +87,6 @@ namespace Catch { } } - void XmlReporter::assertionStarting( AssertionInfo const& ) { } - void XmlReporter::assertionEnded( AssertionStats const& assertionStats ) { AssertionResult const& result = assertionStats.assertionResult; diff --git a/src/catch2/reporters/catch_reporter_xml.hpp b/src/catch2/reporters/catch_reporter_xml.hpp index bead7a8..66f95a8 100644 --- a/src/catch2/reporters/catch_reporter_xml.hpp +++ b/src/catch2/reporters/catch_reporter_xml.hpp @@ -35,8 +35,6 @@ namespace Catch { void sectionStarting(SectionInfo const& sectionInfo) override; - void assertionStarting(AssertionInfo const&) override; - void assertionEnded(AssertionStats const& assertionStats) override; void sectionEnded(SectionStats const& sectionStats) override; diff --git a/tests/BUILD.bazel b/tests/BUILD.bazel index 5f0362f..58c0520 100644 --- a/tests/BUILD.bazel +++ b/tests/BUILD.bazel @@ -21,6 +21,7 @@ cc_test( size = "small", srcs = [ "SelfTest/IntrospectiveTests/Algorithms.tests.cpp", + "SelfTest/IntrospectiveTests/AssertionHandler.tests.cpp", "SelfTest/IntrospectiveTests/Clara.tests.cpp", "SelfTest/IntrospectiveTests/CmdLine.tests.cpp", "SelfTest/IntrospectiveTests/CmdLineHelpers.tests.cpp", @@ -30,6 +31,7 @@ cc_test( "SelfTest/IntrospectiveTests/GeneratorsImpl.tests.cpp", "SelfTest/IntrospectiveTests/Integer.tests.cpp", "SelfTest/IntrospectiveTests/InternalBenchmark.tests.cpp", + "SelfTest/IntrospectiveTests/Json.tests.cpp", "SelfTest/IntrospectiveTests/Parse.tests.cpp", "SelfTest/IntrospectiveTests/PartTracker.tests.cpp", "SelfTest/IntrospectiveTests/RandomNumberGeneration.tests.cpp", @@ -63,6 +65,7 @@ cc_test( "SelfTest/UsageTests/MatchersRanges.tests.cpp", "SelfTest/UsageTests/Message.tests.cpp", "SelfTest/UsageTests/Misc.tests.cpp", + "SelfTest/UsageTests/Skip.tests.cpp", "SelfTest/UsageTests/ToStringByte.tests.cpp", "SelfTest/UsageTests/ToStringChrono.tests.cpp", "SelfTest/UsageTests/ToStringGeneral.tests.cpp", diff --git a/tests/CMakeLists.txt b/tests/CMakeLists.txt index 37a5977..651ac83 100644 --- a/tests/CMakeLists.txt +++ b/tests/CMakeLists.txt @@ -1,6 +1,6 @@ include(CatchMiscFunctions) -if (CATCH_BUILD_SURROGATES) +if(CATCH_BUILD_SURROGATES) message(STATUS "Configuring targets for surrogate TUs") # If the folder does not exist before we ask for output redirect to @@ -11,11 +11,9 @@ if (CATCH_BUILD_SURROGATES) # Returns the path to the generated file. function(createSurrogateFileTarget sourceHeader pathToFile) set(pathPrefix ${PROJECT_SOURCE_DIR}/src) - file(RELATIVE_PATH includePath ${pathPrefix} ${sourceHeader}) get_filename_component(basicFileName "${sourceHeader}" NAME_WE) - set(surrogateFilePath ${CMAKE_CURRENT_BINARY_DIR}/surrogates/surrogate_${basicFileName}.cpp) add_custom_command( @@ -41,10 +39,9 @@ if (CATCH_BUILD_SURROGATES) set(${OutArg} ${AllHeaders} PARENT_SCOPE) endfunction() - ExtractCatch2Headers(mainHeaders) - if (NOT mainHeaders) + if(NOT mainHeaders) message(FATAL_ERROR "No headers in the main target were detected. Something is broken.") endif() @@ -53,12 +50,10 @@ if (CATCH_BUILD_SURROGATES) list(APPEND surrogateFiles ${pathToGeneratedFile}) endforeach() - add_executable(Catch2SurrogateTarget ${surrogateFiles} ) target_link_libraries(Catch2SurrogateTarget PRIVATE Catch2WithMain) - endif() #### @@ -66,75 +61,72 @@ endif() # We need to disable property, but CMake doesn't support it # until 3.13 (not yet released) #### -if (MSVC) -configure_file(${CATCH_DIR}/tools/misc/SelfTest.vcxproj.user - ${CMAKE_BINARY_DIR}/tests - COPYONLY) +if(MSVC) + configure_file(${CATCH_DIR}/tools/misc/SelfTest.vcxproj.user ${CMAKE_BINARY_DIR}/tests COPYONLY) endif() #Temporary workaround - # define the sources of the self test # Please keep these ordered alphabetically set(TEST_SOURCES - ${SELF_TEST_DIR}/TestRegistrations.cpp - ${SELF_TEST_DIR}/IntrospectiveTests/Algorithms.tests.cpp - ${SELF_TEST_DIR}/IntrospectiveTests/AssertionHandler.tests.cpp - ${SELF_TEST_DIR}/IntrospectiveTests/Clara.tests.cpp - ${SELF_TEST_DIR}/IntrospectiveTests/CmdLine.tests.cpp - ${SELF_TEST_DIR}/IntrospectiveTests/CmdLineHelpers.tests.cpp - ${SELF_TEST_DIR}/IntrospectiveTests/ColourImpl.tests.cpp - ${SELF_TEST_DIR}/IntrospectiveTests/Details.tests.cpp - ${SELF_TEST_DIR}/IntrospectiveTests/FloatingPoint.tests.cpp - ${SELF_TEST_DIR}/IntrospectiveTests/GeneratorsImpl.tests.cpp - ${SELF_TEST_DIR}/IntrospectiveTests/Integer.tests.cpp - ${SELF_TEST_DIR}/IntrospectiveTests/InternalBenchmark.tests.cpp - ${SELF_TEST_DIR}/IntrospectiveTests/Json.tests.cpp - ${SELF_TEST_DIR}/IntrospectiveTests/Parse.tests.cpp - ${SELF_TEST_DIR}/IntrospectiveTests/PartTracker.tests.cpp - ${SELF_TEST_DIR}/IntrospectiveTests/RandomNumberGeneration.tests.cpp - ${SELF_TEST_DIR}/IntrospectiveTests/Reporters.tests.cpp - ${SELF_TEST_DIR}/IntrospectiveTests/Tag.tests.cpp - ${SELF_TEST_DIR}/IntrospectiveTests/TestCaseInfoHasher.tests.cpp - ${SELF_TEST_DIR}/IntrospectiveTests/TestSpec.tests.cpp - ${SELF_TEST_DIR}/IntrospectiveTests/TestSpecParser.tests.cpp - ${SELF_TEST_DIR}/IntrospectiveTests/TextFlow.tests.cpp - ${SELF_TEST_DIR}/IntrospectiveTests/Sharding.tests.cpp - ${SELF_TEST_DIR}/IntrospectiveTests/Stream.tests.cpp - ${SELF_TEST_DIR}/IntrospectiveTests/String.tests.cpp - ${SELF_TEST_DIR}/IntrospectiveTests/StringManip.tests.cpp - ${SELF_TEST_DIR}/IntrospectiveTests/Xml.tests.cpp - ${SELF_TEST_DIR}/IntrospectiveTests/Traits.tests.cpp - ${SELF_TEST_DIR}/IntrospectiveTests/ToString.tests.cpp - ${SELF_TEST_DIR}/IntrospectiveTests/UniquePtr.tests.cpp - ${SELF_TEST_DIR}/helpers/parse_test_spec.cpp - ${SELF_TEST_DIR}/TimingTests/Sleep.tests.cpp - ${SELF_TEST_DIR}/UsageTests/Approx.tests.cpp - ${SELF_TEST_DIR}/UsageTests/BDD.tests.cpp - ${SELF_TEST_DIR}/UsageTests/Benchmark.tests.cpp - ${SELF_TEST_DIR}/UsageTests/Class.tests.cpp - ${SELF_TEST_DIR}/UsageTests/Compilation.tests.cpp - ${SELF_TEST_DIR}/UsageTests/Condition.tests.cpp - ${SELF_TEST_DIR}/UsageTests/Decomposition.tests.cpp - ${SELF_TEST_DIR}/UsageTests/EnumToString.tests.cpp - ${SELF_TEST_DIR}/UsageTests/Exception.tests.cpp - ${SELF_TEST_DIR}/UsageTests/Generators.tests.cpp - ${SELF_TEST_DIR}/UsageTests/Message.tests.cpp - ${SELF_TEST_DIR}/UsageTests/Misc.tests.cpp - ${SELF_TEST_DIR}/UsageTests/Skip.tests.cpp - ${SELF_TEST_DIR}/UsageTests/ToStringByte.tests.cpp - ${SELF_TEST_DIR}/UsageTests/ToStringChrono.tests.cpp - ${SELF_TEST_DIR}/UsageTests/ToStringGeneral.tests.cpp - ${SELF_TEST_DIR}/UsageTests/ToStringOptional.tests.cpp - ${SELF_TEST_DIR}/UsageTests/ToStringPair.tests.cpp - ${SELF_TEST_DIR}/UsageTests/ToStringTuple.tests.cpp - ${SELF_TEST_DIR}/UsageTests/ToStringVariant.tests.cpp - ${SELF_TEST_DIR}/UsageTests/ToStringVector.tests.cpp - ${SELF_TEST_DIR}/UsageTests/ToStringWhich.tests.cpp - ${SELF_TEST_DIR}/UsageTests/Tricky.tests.cpp - ${SELF_TEST_DIR}/UsageTests/VariadicMacros.tests.cpp - ${SELF_TEST_DIR}/UsageTests/MatchersRanges.tests.cpp - ${SELF_TEST_DIR}/UsageTests/Matchers.tests.cpp - ) + ${SELF_TEST_DIR}/TestRegistrations.cpp + ${SELF_TEST_DIR}/IntrospectiveTests/Algorithms.tests.cpp + ${SELF_TEST_DIR}/IntrospectiveTests/AssertionHandler.tests.cpp + ${SELF_TEST_DIR}/IntrospectiveTests/Clara.tests.cpp + ${SELF_TEST_DIR}/IntrospectiveTests/CmdLine.tests.cpp + ${SELF_TEST_DIR}/IntrospectiveTests/CmdLineHelpers.tests.cpp + ${SELF_TEST_DIR}/IntrospectiveTests/ColourImpl.tests.cpp + ${SELF_TEST_DIR}/IntrospectiveTests/Details.tests.cpp + ${SELF_TEST_DIR}/IntrospectiveTests/FloatingPoint.tests.cpp + ${SELF_TEST_DIR}/IntrospectiveTests/GeneratorsImpl.tests.cpp + ${SELF_TEST_DIR}/IntrospectiveTests/Integer.tests.cpp + ${SELF_TEST_DIR}/IntrospectiveTests/InternalBenchmark.tests.cpp + ${SELF_TEST_DIR}/IntrospectiveTests/Json.tests.cpp + ${SELF_TEST_DIR}/IntrospectiveTests/Parse.tests.cpp + ${SELF_TEST_DIR}/IntrospectiveTests/PartTracker.tests.cpp + ${SELF_TEST_DIR}/IntrospectiveTests/RandomNumberGeneration.tests.cpp + ${SELF_TEST_DIR}/IntrospectiveTests/Reporters.tests.cpp + ${SELF_TEST_DIR}/IntrospectiveTests/Tag.tests.cpp + ${SELF_TEST_DIR}/IntrospectiveTests/TestCaseInfoHasher.tests.cpp + ${SELF_TEST_DIR}/IntrospectiveTests/TestSpec.tests.cpp + ${SELF_TEST_DIR}/IntrospectiveTests/TestSpecParser.tests.cpp + ${SELF_TEST_DIR}/IntrospectiveTests/TextFlow.tests.cpp + ${SELF_TEST_DIR}/IntrospectiveTests/Sharding.tests.cpp + ${SELF_TEST_DIR}/IntrospectiveTests/Stream.tests.cpp + ${SELF_TEST_DIR}/IntrospectiveTests/String.tests.cpp + ${SELF_TEST_DIR}/IntrospectiveTests/StringManip.tests.cpp + ${SELF_TEST_DIR}/IntrospectiveTests/Xml.tests.cpp + ${SELF_TEST_DIR}/IntrospectiveTests/Traits.tests.cpp + ${SELF_TEST_DIR}/IntrospectiveTests/ToString.tests.cpp + ${SELF_TEST_DIR}/IntrospectiveTests/UniquePtr.tests.cpp + ${SELF_TEST_DIR}/helpers/parse_test_spec.cpp + ${SELF_TEST_DIR}/TimingTests/Sleep.tests.cpp + ${SELF_TEST_DIR}/UsageTests/Approx.tests.cpp + ${SELF_TEST_DIR}/UsageTests/BDD.tests.cpp + ${SELF_TEST_DIR}/UsageTests/Benchmark.tests.cpp + ${SELF_TEST_DIR}/UsageTests/Class.tests.cpp + ${SELF_TEST_DIR}/UsageTests/Compilation.tests.cpp + ${SELF_TEST_DIR}/UsageTests/Condition.tests.cpp + ${SELF_TEST_DIR}/UsageTests/Decomposition.tests.cpp + ${SELF_TEST_DIR}/UsageTests/EnumToString.tests.cpp + ${SELF_TEST_DIR}/UsageTests/Exception.tests.cpp + ${SELF_TEST_DIR}/UsageTests/Generators.tests.cpp + ${SELF_TEST_DIR}/UsageTests/Message.tests.cpp + ${SELF_TEST_DIR}/UsageTests/Misc.tests.cpp + ${SELF_TEST_DIR}/UsageTests/Skip.tests.cpp + ${SELF_TEST_DIR}/UsageTests/ToStringByte.tests.cpp + ${SELF_TEST_DIR}/UsageTests/ToStringChrono.tests.cpp + ${SELF_TEST_DIR}/UsageTests/ToStringGeneral.tests.cpp + ${SELF_TEST_DIR}/UsageTests/ToStringOptional.tests.cpp + ${SELF_TEST_DIR}/UsageTests/ToStringPair.tests.cpp + ${SELF_TEST_DIR}/UsageTests/ToStringTuple.tests.cpp + ${SELF_TEST_DIR}/UsageTests/ToStringVariant.tests.cpp + ${SELF_TEST_DIR}/UsageTests/ToStringVector.tests.cpp + ${SELF_TEST_DIR}/UsageTests/ToStringWhich.tests.cpp + ${SELF_TEST_DIR}/UsageTests/Tricky.tests.cpp + ${SELF_TEST_DIR}/UsageTests/VariadicMacros.tests.cpp + ${SELF_TEST_DIR}/UsageTests/MatchersRanges.tests.cpp + ${SELF_TEST_DIR}/UsageTests/Matchers.tests.cpp +) set(TEST_HEADERS ${SELF_TEST_DIR}/helpers/parse_test_spec.hpp @@ -142,46 +134,45 @@ set(TEST_HEADERS ${SELF_TEST_DIR}/helpers/type_with_lit_0_comparisons.hpp ) - # Specify the headers, too, so CLion recognises them as project files set(HEADERS - ${TOP_LEVEL_HEADERS} - ${EXTERNAL_HEADERS} - ${INTERNAL_HEADERS} - ${REPORTER_HEADERS} - ${BENCHMARK_HEADERS} - ${BENCHMARK_SOURCES} + ${TOP_LEVEL_HEADERS} + ${EXTERNAL_HEADERS} + ${INTERNAL_HEADERS} + ${REPORTER_HEADERS} + ${BENCHMARK_HEADERS} + ${BENCHMARK_SOURCES} ) # Provide some groupings for IDEs -#SOURCE_GROUP("benchmark" FILES ${BENCHMARK_HEADERS} ${BENCHMARK_SOURCES}) -#SOURCE_GROUP("Tests" FILES ${TEST_SOURCES}) +#source_group("benchmark" FILES ${BENCHMARK_HEADERS} ${BENCHMARK_SOURCES}) +#source_group("Tests" FILES ${TEST_SOURCES}) include(CTest) add_executable(SelfTest ${TEST_SOURCES} ${TEST_HEADERS}) target_include_directories(SelfTest PRIVATE ${SELF_TEST_DIR}) target_link_libraries(SelfTest PRIVATE Catch2WithMain) -if (BUILD_SHARED_LIBS AND WIN32) - add_custom_command(TARGET SelfTest PRE_LINK - COMMAND ${CMAKE_COMMAND} -E copy_if_different $ - $ $ - ) +if(BUILD_SHARED_LIBS AND WIN32) + add_custom_command(TARGET SelfTest PRE_LINK + COMMAND ${CMAKE_COMMAND} -E copy_if_different $ + $ $ + ) endif() -if (CATCH_ENABLE_COVERAGE) - set(ENABLE_COVERAGE ON CACHE BOOL "Enable coverage build." FORCE) - find_package(codecov) - add_coverage(SelfTest) - list(APPEND LCOV_REMOVE_PATTERNS "'/usr/*'") - coverage_evaluate() +if(CATCH_ENABLE_COVERAGE) + set(ENABLE_COVERAGE ON CACHE BOOL "Enable coverage build." FORCE) + find_package(codecov) + add_coverage(SelfTest) + list(APPEND LCOV_REMOVE_PATTERNS "'/usr/*'") + coverage_evaluate() endif() # configure unit tests via CTest add_test(NAME RunTests COMMAND $ --order rand --rng-seed time) set_tests_properties(RunTests PROPERTIES - FAIL_REGULAR_EXPRESSION "Filters:" - COST 15 + FAIL_REGULAR_EXPRESSION "Filters:" + COST 15 ) # Because CTest does not allow us to check both return code _and_ expected @@ -189,8 +180,8 @@ set_tests_properties(RunTests PROPERTIES # the output, the second time we check the exit code. add_test(NAME List::Tests::Output COMMAND $ --list-tests --verbosity high) set_tests_properties(List::Tests::Output PROPERTIES - PASS_REGULAR_EXPRESSION "[0-9]+ test cases" - FAIL_REGULAR_EXPRESSION "Hidden Test" + PASS_REGULAR_EXPRESSION "[0-9]+ test cases" + FAIL_REGULAR_EXPRESSION "Hidden Test" ) # This should be equivalent to the old --list-test-names-only and be usable # with --input-file. @@ -198,35 +189,34 @@ add_test(NAME List::Tests::Quiet COMMAND $ --list-tests -- # Sadly we cannot ask for start-of-line and end-of-line in a ctest regex, # so we fail if we see space/tab at the start... set_tests_properties(List::Tests::Quiet PROPERTIES - PASS_REGULAR_EXPRESSION "\"#1905 -- test spec parser properly clears internal state between compound tests\"[\r\n]" - FAIL_REGULAR_EXPRESSION "[ \t]\"#1905 -- test spec parser properly clears internal state between compound tests\"" + PASS_REGULAR_EXPRESSION "\"#1905 -- test spec parser properly clears internal state between compound tests\"[\r\n]" + FAIL_REGULAR_EXPRESSION "[ \t]\"#1905 -- test spec parser properly clears internal state between compound tests\"" ) add_test(NAME List::Tests::ExitCode COMMAND $ --list-tests --verbosity high) add_test(NAME List::Tests::XmlOutput COMMAND $ --list-tests --verbosity high -r xml) set_tests_properties(List::Tests::XmlOutput PROPERTIES - PASS_REGULAR_EXPRESSION "[0-9]+" - FAIL_REGULAR_EXPRESSION "[0-9]+ test cases" + PASS_REGULAR_EXPRESSION "[0-9]+" + FAIL_REGULAR_EXPRESSION "[0-9]+ test cases" ) add_test(NAME List::Tags::Output COMMAND $ --list-tags) set_tests_properties(List::Tags::Output PROPERTIES - PASS_REGULAR_EXPRESSION "[0-9]+ tags" - FAIL_REGULAR_EXPRESSION "\\[\\.\\]") + PASS_REGULAR_EXPRESSION "[0-9]+ tags" + FAIL_REGULAR_EXPRESSION "\\[\\.\\]") add_test(NAME List::Tags::ExitCode COMMAND $ --list-tags) add_test(NAME List::Tags::XmlOutput COMMAND $ --list-tags -r xml) set_tests_properties(List::Tags::XmlOutput PROPERTIES - PASS_REGULAR_EXPRESSION "18" - FAIL_REGULAR_EXPRESSION "[0-9]+ tags" + PASS_REGULAR_EXPRESSION "18" + FAIL_REGULAR_EXPRESSION "[0-9]+ tags" ) - add_test(NAME List::Reporters::Output COMMAND $ --list-reporters) set_tests_properties(List::Reporters::Output PROPERTIES PASS_REGULAR_EXPRESSION "Available reporters:") add_test(NAME List::Reporters::ExitCode COMMAND $ --list-reporters) add_test(NAME List::Reporters::XmlOutput COMMAND $ --list-reporters -r xml) set_tests_properties(List::Reporters::XmlOutput PROPERTIES - PASS_REGULAR_EXPRESSION "compact" - FAIL_REGULAR_EXPRESSION "Available reporters:" + PASS_REGULAR_EXPRESSION "compact" + FAIL_REGULAR_EXPRESSION "Available reporters:" ) add_test(NAME List::Listeners::Output @@ -263,8 +253,8 @@ add_test(NAME TestSpecs::CombiningMatchingAndNonMatchingIsOk-1 COMMAND $ Tracker, "___nonexistent_test___") set_tests_properties(TestSpecs::CombiningMatchingAndNonMatchingIsOk-2 PROPERTIES - PASS_REGULAR_EXPRESSION "No test cases matched '\"___nonexistent_test___\"'" - FAIL_REGULAR_EXPRESSION "No tests ran" + PASS_REGULAR_EXPRESSION "No test cases matched '\"___nonexistent_test___\"'" + FAIL_REGULAR_EXPRESSION "No tests ran" ) add_test(NAME TestSpecs::NoMatchedTestsFail @@ -283,7 +273,7 @@ add_test(NAME TestSpecs::OverrideAllSkipFailure ) add_test(NAME TestSpecs::NonMatchingTestSpecIsRoundTrippable - COMMAND $ Tracker, "this test does not exist" "[nor does this tag]" + COMMAND $ Tracker, "this test does not exist" "[nor does this tag]" ) set_tests_properties(TestSpecs::NonMatchingTestSpecIsRoundTrippable PROPERTIES @@ -352,10 +342,9 @@ set_tests_properties(FilteredSection::GeneratorsDontCauseInfiniteLoop-2 PASS_REGULAR_EXPRESSION "All tests passed \\(4 assertions in 1 test case\\)" ) -# AppVeyor has a Python 2.7 in path, but doesn't have .py files as autorunnable add_test(NAME ApprovalTests COMMAND - ${PYTHON_EXECUTABLE} + Python3::Interpreter ${CATCH_DIR}/tools/scripts/approvalTests.py $ "${CMAKE_CURRENT_BINARY_DIR}" @@ -418,7 +407,7 @@ set_tests_properties(TagAlias PROPERTIES FAIL_REGULAR_EXPRESSION "0 matching test cases" ) -add_test(NAME RandomTestOrdering COMMAND ${PYTHON_EXECUTABLE} +add_test(NAME RandomTestOrdering COMMAND Python3::Interpreter ${CATCH_DIR}/tests/TestScripts/testRandomOrder.py $) set_tests_properties(RandomTestOrdering PROPERTIES @@ -427,7 +416,7 @@ set_tests_properties(RandomTestOrdering add_test(NAME CheckConvenienceHeaders COMMAND - ${PYTHON_EXECUTABLE} ${CATCH_DIR}/tools/scripts/checkConvenienceHeaders.py + Python3::Interpreter ${CATCH_DIR}/tools/scripts/checkConvenienceHeaders.py ) set_tests_properties(CheckConvenienceHeaders PROPERTIES @@ -447,7 +436,6 @@ set_tests_properties("Benchmarking::SkipBenchmarkMacros" FAIL_REGULAR_EXPRESSION "benchmark name" ) - add_test(NAME "Benchmarking::FailureReporting::OptimizedOut" COMMAND $ "Failing benchmarks" -c "empty" -r xml @@ -510,7 +498,7 @@ set_tests_properties("ErrorHandling::InvalidTestSpecExitsEarly" FAIL_REGULAR_EXPRESSION "No tests ran" ) -if (MSVC) +if(MSVC) set(_NullFile "NUL") else() set(_NullFile "/dev/null") @@ -519,7 +507,7 @@ endif() # This test checks that there is nothing written out from the process, # but if CMake is running the tests under Valgrind or similar tool, then # that will write its own output to stdout and the test would fail. -if (NOT MEMORYCHECK_COMMAND) +if(NOT MEMORYCHECK_COMMAND) add_test(NAME "MultiReporter::CapturingReportersDontPropagateStdOut" COMMAND $ "Sends stuff to stdout and stderr" @@ -608,76 +596,104 @@ set_tests_properties("Reporters::JUnit::NamespacesAreNormalized" PASS_REGULAR_EXPRESSION "testcase classname=\"SelfTest(\.exe)?\\.A\\.B\\.TestClass\"" ) -if (CATCH_ENABLE_CONFIGURE_TESTS) - foreach(testName "DefaultReporter" "Disable" "DisableStringification" - "ExperimentalRedirect") - - add_test(NAME "CMakeConfig::${testName}" - COMMAND - "${PYTHON_EXECUTABLE}" "${CMAKE_CURRENT_LIST_DIR}/TestScripts/testConfigure${testName}.py" "${CATCH_DIR}" "${CMAKE_CURRENT_BINARY_DIR}" - ) - set_tests_properties("CMakeConfig::${testName}" - PROPERTIES - COST 240 - LABELS "uses-python" - ) - - endforeach() -endif() - -if (CATCH_ENABLE_CMAKE_HELPER_TESTS) - add_test(NAME "CMakeHelper::DiscoverTests" +if(CATCH_ENABLE_CONFIGURE_TESTS) + foreach(testName "DefaultReporter" "Disable" "DisableStringification" + "ExperimentalRedirect") + add_test(NAME "CMakeConfig::${testName}" COMMAND - "${PYTHON_EXECUTABLE}" "${CMAKE_CURRENT_LIST_DIR}/TestScripts/DiscoverTests/VerifyRegistration.py" "${CATCH_DIR}" "${CMAKE_CURRENT_BINARY_DIR}" + Python3::Interpreter "${CMAKE_CURRENT_LIST_DIR}/TestScripts/testConfigure${testName}.py" "${CATCH_DIR}" "${CMAKE_CURRENT_BINARY_DIR}" ) - set_tests_properties("CMakeHelper::DiscoverTests" + set_tests_properties("CMakeConfig::${testName}" PROPERTIES COST 240 LABELS "uses-python" ) + endforeach() endif() -foreach (reporterName # "Automake" - the simple .trs format does not support any kind of comments/metadata - "compact" - "console" - "JUnit" - "SonarQube" - "TAP" - # "TeamCity" - does not seem to support test suite-level metadata/comments - "XML" - "JSON") - - add_test(NAME "Reporters:Filters:${reporterName}" - COMMAND - $ [comparisons][string-case] "CaseInsensitiveLess is case insensitive" - --reporter ${reporterName} - ) - # Different regex for these two reporters, because the commas end up xml-escaped - if (reporterName MATCHES "JUnit|XML") - set(testCaseNameFormat ""CaseInsensitiveLess is case insensitive"") - elseif(reporterName MATCHES "JSON") - set(testCaseNameFormat "\\\\\"CaseInsensitiveLess is case insensitive\\\\\"") - else() - set(testCaseNameFormat "\"CaseInsensitiveLess is case insensitive\"") - endif() - set_tests_properties("Reporters:Filters:${reporterName}" - PROPERTIES - PASS_REGULAR_EXPRESSION "[fF]ilters.+\\[comparisons\\] \\[string-case\\] ${testCaseNameFormat}" - ) +if(CATCH_ENABLE_CMAKE_HELPER_TESTS) + add_test(NAME "CMakeHelper::DiscoverTests" + COMMAND + Python3::Interpreter "${CMAKE_CURRENT_LIST_DIR}/TestScripts/DiscoverTests/VerifyRegistration.py" "${CATCH_DIR}" "${CMAKE_CURRENT_BINARY_DIR}" + ) + set_tests_properties("CMakeHelper::DiscoverTests" + PROPERTIES + COST 240 + LABELS "uses-python" + ) +endif() - add_test(NAME "Reporters:RngSeed:${reporterName}" - COMMAND - $ "Factorials are computed" - --reporter ${reporterName} - --rng-seed 18181818 - ) - set_tests_properties("Reporters:RngSeed:${reporterName}" - PROPERTIES - PASS_REGULAR_EXPRESSION "18181818" - ) +foreach(reporterName # "Automake" - the simple .trs format does not support any kind of comments/metadata + "compact" + "console" + "JUnit" + "SonarQube" + "TAP" + # "TeamCity" - does not seem to support test suite-level metadata/comments + "XML" + "JSON") + add_test(NAME "Reporters:Filters:${reporterName}" + COMMAND + $ [comparisons][string-case] "CaseInsensitiveLess is case insensitive" + --reporter ${reporterName} + ) + # Different regex for these two reporters, because the commas end up xml-escaped + if(reporterName MATCHES "JUnit|XML") + set(testCaseNameFormat ""CaseInsensitiveLess is case insensitive"") + elseif(reporterName MATCHES "JSON") + set(testCaseNameFormat "\\\\\"CaseInsensitiveLess is case insensitive\\\\\"") + else() + set(testCaseNameFormat "\"CaseInsensitiveLess is case insensitive\"") + endif() + set_tests_properties("Reporters:Filters:${reporterName}" + PROPERTIES + PASS_REGULAR_EXPRESSION "[fF]ilters.+\\[comparisons\\] \\[string-case\\] ${testCaseNameFormat}" + ) + add_test(NAME "Reporters:RngSeed:${reporterName}" + COMMAND + $ "Factorials are computed" + --reporter ${reporterName} + --rng-seed 18181818 + ) + set_tests_properties("Reporters:RngSeed:${reporterName}" + PROPERTIES + PASS_REGULAR_EXPRESSION "18181818" + ) endforeach() +add_test(NAME "Bazel::RngSeedEnvVar::JustEnv" + COMMAND + $ "Factorials are computed" +) +set_tests_properties("Bazel::RngSeedEnvVar::JustEnv" + PROPERTIES + ENVIRONMENT "BAZEL_TEST=1;TEST_RANDOM_SEED=18181818" + PASS_REGULAR_EXPRESSION "Randomness seeded to: 18181818" +) + +add_test(NAME "Bazel::RngSeedEnvVar::EnvHasPriorityOverCLI" + COMMAND + $ "Factorials are computed" + --rng-seed 17171717 +) +set_tests_properties("Bazel::RngSeedEnvVar::EnvHasPriorityOverCLI" + PROPERTIES + ENVIRONMENT "BAZEL_TEST=1;TEST_RANDOM_SEED=18181818" + PASS_REGULAR_EXPRESSION "Randomness seeded to: 18181818" +) + +add_test(NAME "Bazel::RngSeedEnvVar::MalformedValueIsIgnored" + COMMAND + $ "Factorials are computed" + --rng-seed 17171717 +) +set_tests_properties("Bazel::RngSeedEnvVar::MalformedValueIsIgnored" + PROPERTIES + ENVIRONMENT "BAZEL_TEST=1;TEST_RANDOM_SEED=XOXOXOXO" + PASS_REGULAR_EXPRESSION "Randomness seeded to: 17171717" +) + -list(APPEND CATCH_WARNING_TARGETS SelfTest) -set(CATCH_WARNING_TARGETS ${CATCH_WARNING_TARGETS} PARENT_SCOPE) +list(APPEND CATCH_TEST_TARGETS SelfTest) +set(CATCH_TEST_TARGETS ${CATCH_TEST_TARGETS} PARENT_SCOPE) diff --git a/tests/ExtraTests/CMakeLists.txt b/tests/ExtraTests/CMakeLists.txt index 3c38d67..fbcc346 100644 --- a/tests/ExtraTests/CMakeLists.txt +++ b/tests/ExtraTests/CMakeLists.txt @@ -1,16 +1,11 @@ -# -# Build extra tests. -# +cmake_minimum_required(VERSION 3.16) +project(Catch2ExtraTests LANGUAGES CXX) -cmake_minimum_required( VERSION 3.16 ) - -project( Catch2ExtraTests LANGUAGES CXX ) - -message( STATUS "Extra tests included" ) +message(STATUS "Extra tests included") add_test( NAME TestShardingIntegration - COMMAND ${PYTHON_EXECUTABLE} ${CATCH_DIR}/tests/TestScripts/testSharding.py $ + COMMAND Python3::Interpreter ${CATCH_DIR}/tests/TestScripts/testSharding.py $ ) set_tests_properties(TestShardingIntegration PROPERTIES @@ -21,8 +16,7 @@ add_test( NAME TestSharding::OverlyLargeShardIndex COMMAND $ --shard-index 5 --shard-count 5 ) -set_tests_properties( - TestSharding::OverlyLargeShardIndex +set_tests_properties(TestSharding::OverlyLargeShardIndex PROPERTIES PASS_REGULAR_EXPRESSION "The shard count \\(5\\) must be greater than the shard index \\(5\\)" ) @@ -31,8 +25,7 @@ set_tests_properties( # they have non-trivial execution time, so they are categorized as # extra tests, so that they are run less. add_test(NAME MinDuration::SimpleThreshold COMMAND $ --min-duration 0.950 [min_duration_test]) -set_tests_properties( - MinDuration::SimpleThreshold +set_tests_properties(MinDuration::SimpleThreshold PROPERTIES PASS_REGULAR_EXPRESSION "s: sleep_for_1000ms" FAIL_REGULAR_EXPRESSION "sleep_for_100ms" @@ -43,8 +36,7 @@ set_tests_properties( # -d yes overrides the threshold, so we should see the faster test even # with a ridiculous high min duration threshold add_test(NAME MinDuration::DurationOverrideYes COMMAND $ --min-duration 1.0 -d yes [min_duration_test]) -set_tests_properties( - MinDuration::DurationOverrideYes +set_tests_properties(MinDuration::DurationOverrideYes PROPERTIES PASS_REGULAR_EXPRESSION "s: sleep_for_100ms" ) @@ -52,27 +44,24 @@ set_tests_properties( # -d no overrides the threshold, so we should never see any tests even # with ridiculously low min duration threshold add_test(NAME MinDuration::DurationOverrideNo COMMAND $ --min-duration 0.0001 -d no [min_duration_test]) -set_tests_properties( - MinDuration::DurationOverrideNo +set_tests_properties(MinDuration::DurationOverrideNo PROPERTIES FAIL_REGULAR_EXPRESSION "sleep_for_250ms" ) - # ------------ end of duration reporting tests # define folders used: -set( TESTS_DIR ${CATCH_DIR}/tests/ExtraTests ) +set(TESTS_DIR ${CATCH_DIR}/tests/ExtraTests) add_executable(PrefixedMacros ${TESTS_DIR}/X01-PrefixedMacros.cpp) -target_compile_definitions( PrefixedMacros PRIVATE CATCH_CONFIG_PREFIX_ALL CATCH_CONFIG_RUNTIME_STATIC_REQUIRE ) +target_compile_definitions(PrefixedMacros PRIVATE CATCH_CONFIG_PREFIX_ALL CATCH_CONFIG_RUNTIME_STATIC_REQUIRE) # Macro configuration does not touch the compiled parts, so we can link # it against the main library -target_link_libraries( PrefixedMacros Catch2WithMain ) +target_link_libraries(PrefixedMacros Catch2WithMain) add_test(NAME CATCH_CONFIG_PREFIX_ALL COMMAND PrefixedMacros -s) -set_tests_properties( - CATCH_CONFIG_PREFIX_ALL +set_tests_properties(CATCH_CONFIG_PREFIX_ALL PROPERTIES PASS_REGULAR_EXPRESSION "CATCH_" FAIL_REGULAR_EXPRESSION @@ -81,67 +70,51 @@ set_tests_properties( " REQUIRE; REQUIRE_FALSE; REQUIRE_THROWS; REQUIRE_THROWS_AS; REQUIRE_THROWS_WITH; REQUIRE_THROWS_MATCHES; REQUIRE_NOTHROW; CHECK; CHECK_FALSE; CHECKED_IF; CHECKED_ELSE; CHECK_NOFAIL; CHECK_THROWS; CHECK_THROWS_AS; CHECK_THROWS_WITH; CHECK_THROWS_MATCHES; CHECK_NOTHROW; REQUIRE_THAT; CHECK_THAT" ) - add_executable(DisabledMacros ${TESTS_DIR}/X02-DisabledMacros.cpp) -target_compile_definitions( DisabledMacros PRIVATE CATCH_CONFIG_DISABLE ) +target_compile_definitions(DisabledMacros PRIVATE CATCH_CONFIG_DISABLE) # Macro configuration does not touch the compiled parts, so we can link # it against the main library -target_link_libraries( DisabledMacros Catch2WithMain ) +target_link_libraries(DisabledMacros Catch2WithMain) add_test(NAME CATCH_CONFIG_DISABLE-1 COMMAND DisabledMacros -s) -set_tests_properties( - CATCH_CONFIG_DISABLE-1 +set_tests_properties(CATCH_CONFIG_DISABLE-1 PROPERTIES PASS_REGULAR_EXPRESSION "No tests ran" FAIL_REGULAR_EXPRESSION "This should not happen" ) add_test(NAME CATCH_CONFIG_DISABLE-2 COMMAND DisabledMacros --list-tests) -set_tests_properties( - CATCH_CONFIG_DISABLE-2 +set_tests_properties(CATCH_CONFIG_DISABLE-2 PROPERTIES PASS_REGULAR_EXPRESSION "0 test cases" ) -add_executable( DisabledExceptions-DefaultHandler ${TESTS_DIR}/X03-DisabledExceptions-DefaultHandler.cpp ) -add_executable( DisabledExceptions-CustomHandler ${TESTS_DIR}/X04-DisabledExceptions-CustomHandler.cpp ) +add_executable(DisabledExceptions-DefaultHandler ${TESTS_DIR}/X03-DisabledExceptions-DefaultHandler.cpp) +add_executable(DisabledExceptions-CustomHandler ${TESTS_DIR}/X04-DisabledExceptions-CustomHandler.cpp) foreach(target DisabledExceptions-DefaultHandler DisabledExceptions-CustomHandler) - target_compile_options( ${target} + target_compile_options(${target} PUBLIC $<$:/EHs-c-;/D_HAS_EXCEPTIONS=0> - $<$,$,$>:-fno-exceptions> + $<$,$,$,$>:-fno-exceptions> ) target_link_libraries(${target} Catch2_buildall_interface) endforeach() -target_compile_definitions( DisabledExceptions-CustomHandler PUBLIC CATCH_CONFIG_DISABLE_EXCEPTIONS_CUSTOM_HANDLER ) +target_compile_definitions(DisabledExceptions-CustomHandler PUBLIC CATCH_CONFIG_DISABLE_EXCEPTIONS_CUSTOM_HANDLER) add_test(NAME CATCH_CONFIG_DISABLE_EXCEPTIONS-1 COMMAND DisabledExceptions-DefaultHandler "Tests that run") -set_tests_properties( - CATCH_CONFIG_DISABLE_EXCEPTIONS-1 +set_tests_properties(CATCH_CONFIG_DISABLE_EXCEPTIONS-1 PROPERTIES PASS_REGULAR_EXPRESSION "assertions: 4 \| 2 passed \| 2 failed" FAIL_REGULAR_EXPRESSION "abort;terminate;fatal" ) -add_executable( BazelReporter ${TESTS_DIR}/X30-BazelReporter.cpp ) -target_compile_definitions( BazelReporter PRIVATE CATCH_CONFIG_BAZEL_SUPPORT ) -target_link_libraries(BazelReporter Catch2_buildall_interface) -add_test(NAME CATCH_CONFIG_BAZEL_REPORTER-1 - COMMAND - "${PYTHON_EXECUTABLE}" "${CATCH_DIR}/tests/TestScripts/testBazelReporter.py" $ "${CMAKE_CURRENT_BINARY_DIR}" -) -set_tests_properties(CATCH_CONFIG_BAZEL_REPORTER-1 - PROPERTIES - LABELS "uses-python" -) - # We must now test this works without the build flag. -add_executable( BazelReporterNoCatchConfig ${TESTS_DIR}/X30-BazelReporter.cpp ) +add_executable(BazelReporterNoCatchConfig ${TESTS_DIR}/X30-BazelReporter.cpp) target_link_libraries(BazelReporterNoCatchConfig Catch2WithMain) add_test(NAME NO_CATCH_CONFIG_BAZEL_REPORTER-1 COMMAND - "${PYTHON_EXECUTABLE}" "${CATCH_DIR}/tests/TestScripts/testBazelReporter.py" $ "${CMAKE_CURRENT_BINARY_DIR}" + Python3::Interpreter "${CATCH_DIR}/tests/TestScripts/testBazelReporter.py" $ "${CMAKE_CURRENT_BINARY_DIR}" ) set_tests_properties(NO_CATCH_CONFIG_BAZEL_REPORTER-1 PROPERTIES @@ -159,10 +132,9 @@ set_tests_properties(BazelEnv::TESTBRIDGE_TEST_ONLY PASS_REGULAR_EXPRESSION "All tests passed \\(1 assertion in 1 test case\\)" ) - add_test(NAME BazelEnv::Sharding COMMAND - "${PYTHON_EXECUTABLE}" "${CATCH_DIR}/tests/TestScripts/testBazelSharding.py" + Python3::Interpreter "${CATCH_DIR}/tests/TestScripts/testBazelSharding.py" $ "${CMAKE_CURRENT_BINARY_DIR}" ) @@ -171,87 +143,74 @@ set_tests_properties(BazelEnv::Sharding LABELS "uses-python" ) - # The default handler on Windows leads to the just-in-time debugger firing, # which makes this test unsuitable for CI and headless runs, as it opens # up an interactive dialog. -if (NOT WIN32) +if(NOT WIN32) add_test(NAME CATCH_CONFIG_DISABLE_EXCEPTIONS-2 COMMAND DisabledExceptions-DefaultHandler "Tests that abort") - set_tests_properties( - CATCH_CONFIG_DISABLE_EXCEPTIONS-2 + set_tests_properties(CATCH_CONFIG_DISABLE_EXCEPTIONS-2 PROPERTIES PASS_REGULAR_EXPRESSION "Catch will terminate" ) endif() - add_test(NAME CATCH_CONFIG_DISABLE_EXCEPTIONS-3 COMMAND DisabledExceptions-CustomHandler "Tests that run") -set_tests_properties( - CATCH_CONFIG_DISABLE_EXCEPTIONS-3 +set_tests_properties(CATCH_CONFIG_DISABLE_EXCEPTIONS-3 PROPERTIES PASS_REGULAR_EXPRESSION "assertions: 4 \| 2 passed \| 2 failed" FAIL_REGULAR_EXPRESSION "====== CUSTOM HANDLER ======" ) add_test(NAME CATCH_CONFIG_DISABLE_EXCEPTIONS-4 COMMAND DisabledExceptions-CustomHandler "Tests that abort") -set_tests_properties( - CATCH_CONFIG_DISABLE_EXCEPTIONS-4 +set_tests_properties(CATCH_CONFIG_DISABLE_EXCEPTIONS-4 PROPERTIES PASS_REGULAR_EXPRESSION "====== CUSTOM HANDLER ======" ) - add_executable(DeferredStaticChecks ${TESTS_DIR}/X05-DeferredStaticChecks.cpp) target_link_libraries(DeferredStaticChecks PRIVATE Catch2WithMain) target_compile_definitions(DeferredStaticChecks PRIVATE "CATCH_CONFIG_RUNTIME_STATIC_REQUIRE") add_test(NAME DeferredStaticChecks COMMAND DeferredStaticChecks -r compact) -set_tests_properties( - DeferredStaticChecks +set_tests_properties(DeferredStaticChecks PROPERTIES PASS_REGULAR_EXPRESSION "test cases: 1 \\| 1 failed\nassertions: 3 \\| 3 failed" ) - add_executable(FallbackStringifier ${TESTS_DIR}/X10-FallbackStringifier.cpp) -target_compile_definitions( FallbackStringifier PRIVATE CATCH_CONFIG_FALLBACK_STRINGIFIER=fallbackStringifier ) -target_link_libraries( FallbackStringifier Catch2WithMain ) +target_compile_definitions(FallbackStringifier PRIVATE CATCH_CONFIG_FALLBACK_STRINGIFIER=fallbackStringifier) +target_link_libraries(FallbackStringifier Catch2WithMain) add_test(NAME FallbackStringifier COMMAND FallbackStringifier -r compact -s) -set_tests_properties( - FallbackStringifier +set_tests_properties(FallbackStringifier PROPERTIES PASS_REGULAR_EXPRESSION "foo{} for: { !!! }" ) - add_executable(DisableStringification ${TESTS_DIR}/X11-DisableStringification.cpp) -target_compile_definitions( DisableStringification PRIVATE CATCH_CONFIG_DISABLE_STRINGIFICATION ) +target_compile_definitions(DisableStringification PRIVATE CATCH_CONFIG_DISABLE_STRINGIFICATION) target_link_libraries(DisableStringification Catch2WithMain) add_test(NAME CATCH_CONFIG_DISABLE_STRINGIFICATION COMMAND DisableStringification -r compact -s) -set_tests_properties( - CATCH_CONFIG_DISABLE_STRINGIFICATION +set_tests_properties(CATCH_CONFIG_DISABLE_STRINGIFICATION PROPERTIES PASS_REGULAR_EXPRESSION "Disabled by CATCH_CONFIG_DISABLE_STRINGIFICATION" FAIL_REGULAR_EXPRESSION "Hidden{} == Hidden{}" ) - # This test touches windows.h, so it should only be compiled under msvc -if (MSVC) - # This test fails if it does not compile and succeeds otherwise - add_executable(WindowsHeader ${TESTS_DIR}/X90-WindowsHeaderInclusion.cpp) - target_link_libraries( WindowsHeader Catch2WithMain ) - add_test(NAME WindowsHeader COMMAND WindowsHeader -r compact) - list(APPEND CATCH_WARNING_TARGETS ${EXTRA_TEST_BINARIES} WindowsHeader) +if(MSVC) + # This test fails if it does not compile and succeeds otherwise + add_executable(WindowsHeader ${TESTS_DIR}/X90-WindowsHeaderInclusion.cpp) + target_link_libraries(WindowsHeader Catch2WithMain) + add_test(NAME WindowsHeader COMMAND WindowsHeader -r compact) + list(APPEND CATCH_TEST_TARGETS ${EXTRA_TEST_BINARIES} WindowsHeader) endif() - add_executable(PartialTestCaseEvents ${TESTS_DIR}/X21-PartialTestCaseEvents.cpp) target_link_libraries(PartialTestCaseEvents PRIVATE Catch2WithMain) add_test( NAME PartialTestCaseEvents - COMMAND ${PYTHON_EXECUTABLE} ${CATCH_DIR}/tests/TestScripts/testPartialTestCaseEvent.py $ + COMMAND Python3::Interpreter ${CATCH_DIR}/tests/TestScripts/testPartialTestCaseEvent.py $ ) set_tests_properties(PartialTestCaseEvents PROPERTIES @@ -264,22 +223,19 @@ add_test( NAME BenchmarksInCumulativeReporter COMMAND BenchmarksInCumulativeReporter --reporter testReporter ) -set_tests_properties( - BenchmarksInCumulativeReporter +set_tests_properties(BenchmarksInCumulativeReporter PROPERTIES PASS_REGULAR_EXPRESSION "1\n2\n3\n4\n5\n" COST 30 ) - add_executable(CasingInReporterNames ${TESTS_DIR}/X23-CasingInReporterNames.cpp) target_link_libraries(CasingInReporterNames PRIVATE Catch2::Catch2WithMain) add_test( NAME Reporters::registration-is-case-preserving COMMAND CasingInReporterNames --list-reporters ) -set_tests_properties( - Reporters::registration-is-case-preserving +set_tests_properties(Reporters::registration-is-case-preserving PROPERTIES PASS_REGULAR_EXPRESSION "testReporterCASED" ) @@ -287,8 +243,7 @@ add_test( NAME Reporters::selection-is-case-insensitive COMMAND CasingInReporterNames -r testReportercased ) -set_tests_properties( - Reporters::selection-is-case-insensitive +set_tests_properties(Reporters::selection-is-case-insensitive PROPERTIES PASS_REGULAR_EXPRESSION "TestReporter constructed" ) @@ -300,15 +255,13 @@ add_test( COMMAND CapturedStdoutInTestCaseEvents --reporter test-reporter ) -set_tests_properties( - Reporters::CapturedStdOutInEvents +set_tests_properties(Reporters::CapturedStdOutInEvents PROPERTIES PASS_REGULAR_EXPRESSION "X27 - TestReporter constructed" FAIL_REGULAR_EXPRESSION "X27 ERROR" ) - -if (MSVC) +if(MSVC) set(_NullFile "NUL") else() set(_NullFile "/dev/null") @@ -320,7 +273,7 @@ target_link_libraries(ListenerStdoutCaptureInMultireporter PRIVATE Catch2::Catch # This test checks that there is nothing written out from the process, # but if CMake is running the tests under Valgrind or similar tool, then # that will write its own output to stdout and the test would fail. -if (NOT MEMORYCHECK_COMMAND) +if(NOT MEMORYCHECK_COMMAND) add_test( NAME MultiReporter::NoncapturingListenerDoesntCauseStdoutPassThrough COMMAND ListenerStdoutCaptureInMultireporter @@ -328,15 +281,13 @@ if (NOT MEMORYCHECK_COMMAND) --reporter junit::out=${_NullFile} ) - set_tests_properties( - MultiReporter::NoncapturingListenerDoesntCauseStdoutPassThrough + set_tests_properties(MultiReporter::NoncapturingListenerDoesntCauseStdoutPassThrough PROPERTIES PASS_REGULAR_EXPRESSION "X24 - NonCapturingListener initialized" FAIL_REGULAR_EXPRESSION "X24 - FooBarBaz" ) endif() - add_executable(ListenerCanAskForCapturedStdout ${TESTS_DIR}/X25-ListenerCanAskForCapturedStdout.cpp) target_link_libraries(ListenerCanAskForCapturedStdout PRIVATE Catch2::Catch2WithMain) add_test( @@ -345,8 +296,7 @@ add_test( --reporter compact::out=${_NullFile} --reporter console::out=${_NullFile} ) -set_tests_properties( - MultiReporter::CapturingListenerCausesStdoutCapture +set_tests_properties(MultiReporter::CapturingListenerCausesStdoutCapture PROPERTIES PASS_REGULAR_EXPRESSION "CapturingListener initialized" FAIL_REGULAR_EXPRESSION "X25 - ERROR" @@ -359,8 +309,7 @@ add_test( COMMAND ReporterPreferencesForPassingAssertionsIsRespected --reporter test-reporter ) -set_tests_properties( - Reporters::PreferencesForPassingAssertionsIsRespected +set_tests_properties(Reporters::PreferencesForPassingAssertionsIsRespected PROPERTIES PASS_REGULAR_EXPRESSION "X26 - TestReporter constructed" FAIL_REGULAR_EXPRESSION "X26 - assertionEnded" @@ -371,8 +320,7 @@ add_test( --reporter test-reporter --reporter console::out=${_NullFile} ) -set_tests_properties( - MultiReporter::PreferencesForPassingAssertionsIsRespected +set_tests_properties(MultiReporter::PreferencesForPassingAssertionsIsRespected PROPERTIES PASS_REGULAR_EXPRESSION "X26 - TestReporter constructed" FAIL_REGULAR_EXPRESSION "X26 - assertionEnded" @@ -384,8 +332,7 @@ add_test( NAME ListenersGetEventsBeforeReporters COMMAND ListenersGetEventsBeforeReporters --reporter test-reporter ) -set_tests_properties( - ListenersGetEventsBeforeReporters +set_tests_properties(ListenersGetEventsBeforeReporters PROPERTIES PASS_REGULAR_EXPRESSION "X28 - TestReporter constructed" FAIL_REGULAR_EXPRESSION "X28 - ERROR" @@ -398,21 +345,18 @@ add_test( COMMAND CustomArgumentsForReporters --reporter "test-reporter::Xa b=c 1::Xz:e = 1234" ) -set_tests_properties( - CustomArgumentsForReporters +set_tests_properties(CustomArgumentsForReporters PROPERTIES PASS_REGULAR_EXPRESSION "Xa b=c 1::Xz:e = 1234" ) - add_executable(DuplicatedTestCases-SameNameAndTags ${TESTS_DIR}/X31-DuplicatedTestCases.cpp) target_link_libraries(DuplicatedTestCases-SameNameAndTags PRIVATE Catch2::Catch2WithMain) add_test( NAME DuplicatedTestCases::SameNameAndTags COMMAND $ ) -set_tests_properties( - DuplicatedTestCases::SameNameAndTags +set_tests_properties(DuplicatedTestCases::SameNameAndTags PROPERTIES PASS_REGULAR_EXPRESSION "error: .* already defined\\." ) @@ -423,8 +367,7 @@ add_test( NAME DuplicatedTestCases::SameNameDifferentTags COMMAND $ ) -set_tests_properties( - DuplicatedTestCases::SameNameDifferentTags +set_tests_properties(DuplicatedTestCases::SameNameDifferentTags PROPERTIES FAIL_REGULAR_EXPRESSION "error: .* already defined\\." ) @@ -435,8 +378,7 @@ add_test( NAME DuplicatedTestCases::DuplicatedTestCaseMethods COMMAND $ ) -set_tests_properties( - DuplicatedTestCases::DuplicatedTestCaseMethods +set_tests_properties(DuplicatedTestCases::DuplicatedTestCaseMethods PROPERTIES PASS_REGULAR_EXPRESSION "error: .* already defined\\." ) @@ -447,21 +389,18 @@ add_test( NAME DuplicatedTestCases::DuplicatedTestCaseMethodsDifferentFixtures COMMAND $ ) -set_tests_properties( - DuplicatedTestCases::DuplicatedTestCaseMethodsDifferentFixtures +set_tests_properties(DuplicatedTestCases::DuplicatedTestCaseMethodsDifferentFixtures PROPERTIES FAIL_REGULAR_EXPRESSION "error: .* already defined\\." ) - add_executable(DuplicatedReporters ${TESTS_DIR}/X35-DuplicatedReporterNames.cpp) target_link_libraries(DuplicatedReporters PRIVATE Catch2::Catch2WithMain) add_test( NAME Reporters::RegistrationErrorsAreCaught COMMAND $ ) -set_tests_properties( - Reporters::RegistrationErrorsAreCaught +set_tests_properties(Reporters::RegistrationErrorsAreCaught PROPERTIES PASS_REGULAR_EXPRESSION "Errors occurred during startup!" ) @@ -472,13 +411,56 @@ add_test( NAME Reporters::CrashInJunitReporter COMMAND ${CMAKE_COMMAND} -E env $ --reporter JUnit ) -set_tests_properties( - Reporters::CrashInJunitReporter +set_tests_properties(Reporters::CrashInJunitReporter PROPERTIES PASS_REGULAR_EXPRESSION "" LABELS "uses-signals" ) + +add_executable(QuickExitInTest ${TESTS_DIR}/X40-QuickExit.cpp) +target_link_libraries(QuickExitInTest PRIVATE Catch2::Catch2WithMain) +add_test( + NAME BazelEnv::TEST_PREMATURE_EXIT_FILE + COMMAND + Python3::Interpreter + "${CATCH_DIR}/tests/TestScripts/testBazelExitGuardFile.py" + $ + "${CMAKE_CURRENT_BINARY_DIR}" + "bazel" +) +set_tests_properties(BazelEnv::TEST_PREMATURE_EXIT_FILE + PROPERTIES + LABELS "uses-python" +) +add_test( + NAME PrematureExitGuardFileCanBeUsedFromCLI::CheckAfterCrash + COMMAND + Python3::Interpreter + "${CATCH_DIR}/tests/TestScripts/testBazelExitGuardFile.py" + $ + "${CMAKE_CURRENT_BINARY_DIR}" + "cli" +) +set_tests_properties(PrematureExitGuardFileCanBeUsedFromCLI::CheckAfterCrash + PROPERTIES + LABELS "uses-python" +) +add_test( + NAME PrematureExitGuardFileCanBeUsedFromCLI::CheckWithoutCrash + COMMAND + Python3::Interpreter + "${CATCH_DIR}/tests/TestScripts/testBazelExitGuardFile.py" + $ + "${CMAKE_CURRENT_BINARY_DIR}" + "no-crash" +) +set_tests_properties(PrematureExitGuardFileCanBeUsedFromCLI::CheckWithoutCrash + PROPERTIES + LABELS "uses-python" +) + + add_executable(AssertionStartingEventGoesBeforeAssertionIsEvaluated X20-AssertionStartingEventGoesBeforeAssertionIsEvaluated.cpp ) @@ -493,8 +475,7 @@ add_test( #add_executable(DebugBreakMacros ${TESTS_DIR}/X12-CustomDebugBreakMacro.cpp) #target_link_libraries(DebugBreakMacros Catch2) #add_test(NAME DebugBreakMacros COMMAND DebugBreakMacros --break) -#set_tests_properties( -# DebugBreakMacros +#set_tests_properties(DebugBreakMacros # PROPERTIES # PASS_REGULAR_EXPRESSION "Pretty please, break into debugger" #) @@ -521,7 +502,6 @@ add_test( COMMAND $ --list-listeners ) - add_executable(AllSkipped ${TESTS_DIR}/X93-AllSkipped.cpp) target_link_libraries(AllSkipped PRIVATE Catch2::Catch2WithMain) @@ -534,29 +514,28 @@ set_tests_properties(TestSpecs::SkippingAllTestsFails WILL_FAIL ON ) -set( EXTRA_TEST_BINARIES - AllSkipped - PrefixedMacros - DisabledMacros - DisabledExceptions-DefaultHandler - DisabledExceptions-CustomHandler - FallbackStringifier - DisableStringification - PartialTestCaseEvents - DuplicatedTestCases-SameNameAndTags - DuplicatedTestCases-SameNameDifferentTags - DuplicatedTestCases-DuplicatedTestCaseMethods - NoTests - ListenersGetEventsBeforeReporters -# DebugBreakMacros +set(EXTRA_TEST_BINARIES + AllSkipped + PrefixedMacros + DisabledMacros + DisabledExceptions-DefaultHandler + DisabledExceptions-CustomHandler + FallbackStringifier + DisableStringification + PartialTestCaseEvents + DuplicatedTestCases-SameNameAndTags + DuplicatedTestCases-SameNameDifferentTags + DuplicatedTestCases-DuplicatedTestCaseMethods + NoTests + ListenersGetEventsBeforeReporters +# DebugBreakMacros ) # Notice that we are modifying EXTRA_TEST_BINARIES destructively, do not # use it after this point! list(FILTER EXTRA_TEST_BINARIES EXCLUDE REGEX "DisabledExceptions.*") -list(APPEND CATCH_WARNING_TARGETS ${EXTRA_TEST_BINARIES}) -set(CATCH_WARNING_TARGETS ${CATCH_WARNING_TARGETS} PARENT_SCOPE) - +list(APPEND CATCH_TEST_TARGETS ${EXTRA_TEST_BINARIES}) +set(CATCH_TEST_TARGETS ${CATCH_TEST_TARGETS} PARENT_SCOPE) # This sets up a one-off executable that compiles against the amalgamated # files, and then runs it for a super simple check that the amalgamated @@ -570,8 +549,7 @@ target_include_directories(AmalgamatedTestCompilation PRIVATE ${CATCH_DIR}/extra target_compile_features(AmalgamatedTestCompilation PRIVATE cxx_std_14) add_test(NAME AmalgamatedFileTest COMMAND AmalgamatedTestCompilation) -set_tests_properties( - AmalgamatedFileTest +set_tests_properties(AmalgamatedFileTest PROPERTIES PASS_REGULAR_EXPRESSION "All tests passed \\(14 assertions in 3 test cases\\)" ) diff --git a/tests/ExtraTests/X40-QuickExit.cpp b/tests/ExtraTests/X40-QuickExit.cpp new file mode 100644 index 0000000..3afb7b5 --- /dev/null +++ b/tests/ExtraTests/X40-QuickExit.cpp @@ -0,0 +1,28 @@ + +// 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 + * Call ~~quick_exit~~ inside a test. + * + * This is used to test whether Catch2 properly creates the crash guard + * file based on provided arguments. + */ + +#include + +#include + +TEST_CASE("quick_exit", "[quick_exit]") { + // Return 0 as fake "successful" exit, while there should be a guard + // file created and kept. + std::exit(0); + // We cannot use quick_exit because libstdc++ on older MacOS versions didn't support it yet. + // std::quick_exit(0); +} + +TEST_CASE("pass") {} diff --git a/tests/SelfTest/Baselines/automake.sw.approved.txt b/tests/SelfTest/Baselines/automake.sw.approved.txt index 7be343a..4a6886d 100644 --- a/tests/SelfTest/Baselines/automake.sw.approved.txt +++ b/tests/SelfTest/Baselines/automake.sw.approved.txt @@ -26,6 +26,7 @@ Nor would this :test-result: PASS #2152 - ULP checks between differently signed values were wrong - double :test-result: PASS #2152 - ULP checks between differently signed values were wrong - float :test-result: XFAIL #2615 - Throwing in constructor generator fails test case but does not abort +:test-result: PASS #3001: Enum-based bitfields can be captured :test-result: XFAIL #748 - captures with unexpected exceptions :test-result: PASS #809 :test-result: PASS #833 @@ -95,6 +96,8 @@ Nor would this :test-result: PASS Approximate comparisons with mixed numeric types :test-result: PASS Arbitrary predicate matcher :test-result: PASS Assertion macros support bit operators and bool conversions +:test-result: XFAIL Assertions can be nested - CHECK +:test-result: XFAIL Assertions can be nested - REQUIRE :test-result: PASS Assertions then sections :test-result: PASS Basic use of the Contains range matcher :test-result: PASS Basic use of the Empty range matcher @@ -102,6 +105,8 @@ Nor would this :test-result: PASS CAPTURE can deal with complex expressions involving commas :test-result: PASS CAPTURE parses string and character constants :test-result: PASS Capture and info messages +:test-result: FAIL Captures do not leave block with an exception +:test-result: FAIL Captures outlive section end :test-result: PASS CaseInsensitiveEqualsTo is case insensitive :test-result: PASS CaseInsensitiveLess is case insensitive :test-result: PASS Character pretty printing @@ -117,6 +122,7 @@ Nor would this :test-result: PASS Combining templated and concrete matchers :test-result: PASS Combining templated matchers :test-result: PASS Commas in various macros are allowed +:test-result: PASS Comparing (and stringifying) volatile pointers works :test-result: PASS Comparing function pointers :test-result: PASS Comparison ops :test-result: PASS Comparison with explicitly convertible types @@ -148,9 +154,11 @@ Nor would this :test-result: PASS Exception matchers that succeed :test-result: PASS Exception message can be matched :test-result: PASS Exception messages can be tested for +:test-result: PASS Exception thrown inside stringify does not fail the test :test-result: PASS Exceptions matchers :test-result: FAIL Expected exceptions that don't throw or unexpected exceptions fail the test :test-result: FAIL FAIL aborts the test +:test-result: XFAIL FAIL can be nested in assertion :test-result: FAIL FAIL does not require an argument :test-result: FAIL FAIL_CHECK does not abort the test :test-result: PASS Factorials are computed @@ -175,7 +183,7 @@ Nor would this :test-result: XFAIL Inequality checks that should fail :test-result: PASS Inequality checks that should succeed :test-result: PASS JsonWriter -:test-result: PASS JsonWriter escapes charaters in strings properly +:test-result: PASS JsonWriter escapes characters in strings properly :test-result: PASS Lambdas in assertions :test-result: PASS Less-than inequalities with different epsilons :test-result: PASS ManuallyRegistered @@ -226,6 +234,9 @@ Nor would this :test-result: PASS Scenario: Do that thing with the thing :test-result: PASS Scenario: This is a really long scenario name to see how the list command deals with wrapping :test-result: PASS Scenario: Vector resizing affects size and capacity +:test-result: FAIL Scoped message applies to all assertions in scope +:test-result: FAIL Scoped messages do not leave block with an exception +:test-result: FAIL Scoped messages outlive section end A string sent directly to stdout A string sent directly to stderr A string sent to stderr via clog @@ -262,6 +273,10 @@ Message from section two :test-result: PASS TemplateTest: vectors can be sized and resized - int :test-result: PASS TemplateTest: vectors can be sized and resized - std::string :test-result: PASS TemplateTest: vectors can be sized and resized - std::tuple +:test-result: FAIL TemplateTestSig: compiles with a single int parameter - 1 +:test-result: FAIL TemplateTestSig: compiles with a single int parameter - 3 +:test-result: FAIL TemplateTestSig: compiles with a single int parameter - 6 +:test-result: FAIL TemplateTestSig: compiles with two type parameters - int,int :test-result: PASS TemplateTestSig: vectors can be sized and resized - (std::tuple), 6 :test-result: PASS TemplateTestSig: vectors can be sized and resized - float,4 :test-result: PASS TemplateTestSig: vectors can be sized and resized - int,5 @@ -399,6 +414,7 @@ b1! :test-result: PASS stringify( vectors ) :test-result: PASS stringify( vectors ) :test-result: PASS strlen3 +:test-result: PASS system_clock timepoint with non-default duration :test-result: PASS tables :test-result: PASS tags with dots in later positions are not parsed as hidden :test-result: SKIP tests can be skipped dynamically at runtime diff --git a/tests/SelfTest/Baselines/automake.sw.multi.approved.txt b/tests/SelfTest/Baselines/automake.sw.multi.approved.txt index 0cd24e4..b0d30b8 100644 --- a/tests/SelfTest/Baselines/automake.sw.multi.approved.txt +++ b/tests/SelfTest/Baselines/automake.sw.multi.approved.txt @@ -24,6 +24,7 @@ :test-result: PASS #2152 - ULP checks between differently signed values were wrong - double :test-result: PASS #2152 - ULP checks between differently signed values were wrong - float :test-result: XFAIL #2615 - Throwing in constructor generator fails test case but does not abort +:test-result: PASS #3001: Enum-based bitfields can be captured :test-result: XFAIL #748 - captures with unexpected exceptions :test-result: PASS #809 :test-result: PASS #833 @@ -93,6 +94,8 @@ :test-result: PASS Approximate comparisons with mixed numeric types :test-result: PASS Arbitrary predicate matcher :test-result: PASS Assertion macros support bit operators and bool conversions +:test-result: XFAIL Assertions can be nested - CHECK +:test-result: XFAIL Assertions can be nested - REQUIRE :test-result: PASS Assertions then sections :test-result: PASS Basic use of the Contains range matcher :test-result: PASS Basic use of the Empty range matcher @@ -100,6 +103,8 @@ :test-result: PASS CAPTURE can deal with complex expressions involving commas :test-result: PASS CAPTURE parses string and character constants :test-result: PASS Capture and info messages +:test-result: FAIL Captures do not leave block with an exception +:test-result: FAIL Captures outlive section end :test-result: PASS CaseInsensitiveEqualsTo is case insensitive :test-result: PASS CaseInsensitiveLess is case insensitive :test-result: PASS Character pretty printing @@ -115,6 +120,7 @@ :test-result: PASS Combining templated and concrete matchers :test-result: PASS Combining templated matchers :test-result: PASS Commas in various macros are allowed +:test-result: PASS Comparing (and stringifying) volatile pointers works :test-result: PASS Comparing function pointers :test-result: PASS Comparison ops :test-result: PASS Comparison with explicitly convertible types @@ -146,9 +152,11 @@ :test-result: PASS Exception matchers that succeed :test-result: PASS Exception message can be matched :test-result: PASS Exception messages can be tested for +:test-result: PASS Exception thrown inside stringify does not fail the test :test-result: PASS Exceptions matchers :test-result: FAIL Expected exceptions that don't throw or unexpected exceptions fail the test :test-result: FAIL FAIL aborts the test +:test-result: XFAIL FAIL can be nested in assertion :test-result: FAIL FAIL does not require an argument :test-result: FAIL FAIL_CHECK does not abort the test :test-result: PASS Factorials are computed @@ -173,7 +181,7 @@ :test-result: XFAIL Inequality checks that should fail :test-result: PASS Inequality checks that should succeed :test-result: PASS JsonWriter -:test-result: PASS JsonWriter escapes charaters in strings properly +:test-result: PASS JsonWriter escapes characters in strings properly :test-result: PASS Lambdas in assertions :test-result: PASS Less-than inequalities with different epsilons :test-result: PASS ManuallyRegistered @@ -224,6 +232,9 @@ :test-result: PASS Scenario: Do that thing with the thing :test-result: PASS Scenario: This is a really long scenario name to see how the list command deals with wrapping :test-result: PASS Scenario: Vector resizing affects size and capacity +:test-result: FAIL Scoped message applies to all assertions in scope +:test-result: FAIL Scoped messages do not leave block with an exception +:test-result: FAIL Scoped messages outlive section end :test-result: FAIL Sends stuff to stdout and stderr :test-result: PASS Some simple comparisons between doubles :test-result: FAIL Standard output from all sections is reported @@ -255,6 +266,10 @@ :test-result: PASS TemplateTest: vectors can be sized and resized - int :test-result: PASS TemplateTest: vectors can be sized and resized - std::string :test-result: PASS TemplateTest: vectors can be sized and resized - std::tuple +:test-result: FAIL TemplateTestSig: compiles with a single int parameter - 1 +:test-result: FAIL TemplateTestSig: compiles with a single int parameter - 3 +:test-result: FAIL TemplateTestSig: compiles with a single int parameter - 6 +:test-result: FAIL TemplateTestSig: compiles with two type parameters - int,int :test-result: PASS TemplateTestSig: vectors can be sized and resized - (std::tuple), 6 :test-result: PASS TemplateTestSig: vectors can be sized and resized - float,4 :test-result: PASS TemplateTestSig: vectors can be sized and resized - int,5 @@ -388,6 +403,7 @@ :test-result: PASS stringify( vectors ) :test-result: PASS stringify( vectors ) :test-result: PASS strlen3 +:test-result: PASS system_clock timepoint with non-default duration :test-result: PASS tables :test-result: PASS tags with dots in later positions are not parsed as hidden :test-result: SKIP tests can be skipped dynamically at runtime diff --git a/tests/SelfTest/Baselines/compact.sw.approved.txt b/tests/SelfTest/Baselines/compact.sw.approved.txt index b7f48cb..cacb898 100644 --- a/tests/SelfTest/Baselines/compact.sw.approved.txt +++ b/tests/SelfTest/Baselines/compact.sw.approved.txt @@ -84,8 +84,10 @@ Matchers.tests.cpp:: passed: smallest_non_zero, WithinULP( -smalles Matchers.tests.cpp:: passed: smallest_non_zero, !WithinULP( -smallest_non_zero, 1 ) for: 0.0 not is within 1 ULPs of -4.9406564584124654e-324 ([-9.8813129168249309e-324, -0.0000000000000000e+00]) Matchers.tests.cpp:: passed: smallest_non_zero, WithinULP( -smallest_non_zero, 2 ) for: 0.0f is within 2 ULPs of -1.40129846e-45f ([-4.20389539e-45, 1.40129846e-45]) Matchers.tests.cpp:: passed: smallest_non_zero, !WithinULP( -smallest_non_zero, 1 ) for: 0.0f not is within 1 ULPs of -1.40129846e-45f ([-2.80259693e-45, -0.00000000e+00]) -Generators.tests.cpp:: failed: unexpected exception with message: 'failure to init' -Exception.tests.cpp:: failed: unexpected exception with message: 'answer := 42' with 1 message: 'expected exception' +Generators.tests.cpp:: failed: unexpected exception with message: 'failure to init'; expression was: {Unknown expression after the reported line} +Compilation.tests.cpp:: passed: bf.e == 1 for: 1 == 1 +Compilation.tests.cpp:: passed: 1 == bf.e for: 1 == 1 +Exception.tests.cpp:: failed: unexpected exception with message: 'expected exception'; expression was: {Unknown expression after the reported line} Exception.tests.cpp:: failed: unexpected exception with message: 'answer := 42'; expression was: thisThrows() with 1 message: 'expected exception' Exception.tests.cpp:: passed: thisThrows() with 1 message: 'answer := 42' Compilation.tests.cpp:: passed: 42 == f for: 42 == {?} @@ -332,6 +334,11 @@ Compilation.tests.cpp:: passed: !(lhs & rhs) for: !(Val: 1 & Val: 2 Compilation.tests.cpp:: passed: HasBitOperators{ 1 } & HasBitOperators{ 1 } for: Val: 1 & Val: 1 Compilation.tests.cpp:: passed: lhs ^ rhs for: Val: 1 ^ Val: 2 Compilation.tests.cpp:: passed: !(lhs ^ lhs) for: !(Val: 1 ^ Val: 1) +AssertionHandler.tests.cpp:: failed: i > 10 for: 2 > 10 +AssertionHandler.tests.cpp:: failed: unexpected exception with message: '{ nested assertion failed }'; expression was: foo( 2 ) == 2 +AssertionHandler.tests.cpp:: passed: true +AssertionHandler.tests.cpp:: failed: i > 10 for: 2 > 10 +AssertionHandler.tests.cpp:: failed: unexpected exception with message: '{ nested assertion failed }'; expression was: foo( 2 ) == 2 Tricky.tests.cpp:: passed: true Tricky.tests.cpp:: passed: true Tricky.tests.cpp:: passed: true @@ -363,6 +370,9 @@ Message.tests.cpp:: passed: with 7 messages: 'custom_index_op{ Message.tests.cpp:: passed: with 11 messages: '("comma, in string", "escaped, \", ") := "escaped, ", "' and '"single quote in string,'," := "single quote in string,',"' and '"some escapes, \\,\\\\" := "some escapes, \,\\"' and '"some, ), unmatched, } prenheses {[<" := "some, ), unmatched, } prenheses {[<"' and ''"' := '"'' and ''\'' := '''' and '',' := ','' and ''}' := '}'' and '')' := ')'' and ''(' := '('' and ''{' := '{'' ToStringGeneral.tests.cpp:: passed: true with 1 message: 'i := 2' ToStringGeneral.tests.cpp:: passed: true with 1 message: '3' +Message.tests.cpp:: failed: false with 1 message: 'a := 1' +Message.tests.cpp:: passed: true with 1 message: 'a := 1' +Message.tests.cpp:: failed: false with 1 message: 'a := 1' Details.tests.cpp:: passed: eq( "", "" ) for: true Details.tests.cpp:: passed: !(eq( "", "a" )) for: !false Details.tests.cpp:: passed: eq( "a", "a" ) for: true @@ -473,6 +483,13 @@ Tricky.tests.cpp:: passed: std::vector{1, 2} == std::vector: passed: std::vector{1, 2} == std::vector{1, 2} for: { 1, 2 } == { 1, 2 } Tricky.tests.cpp:: passed: true Tricky.tests.cpp:: passed: std::vector{1, 2} == std::vector{1, 2} for: { 1, 2 } == { 1, 2 } +Misc.tests.cpp:: passed: !(ptr) for: !0 +Misc.tests.cpp:: passed: ptr == ptr for: 0 == 0 +Misc.tests.cpp:: passed: !(ptr != ptr) for: !(0 != 0) +Misc.tests.cpp:: passed: !(ptr < ptr) for: !(0 < 0) +Misc.tests.cpp:: passed: ptr <= ptr for: 0 <= 0 +Misc.tests.cpp:: passed: !(ptr > ptr) for: !(0 > 0) +Misc.tests.cpp:: passed: ptr >= ptr for: 0 >= 0 Tricky.tests.cpp:: passed: a for: 0x Tricky.tests.cpp:: passed: a == &foo for: 0x == 0x RandomNumberGeneration.tests.cpp:: passed: SimplePcg32{} == SimplePcg32{} for: {?} == {?} @@ -625,6 +642,9 @@ Exception.tests.cpp:: passed: thisThrows(), StartsWith( "expected" Exception.tests.cpp:: passed: thisThrows(), EndsWith( "exception" ) for: "expected exception" ends with: "exception" Exception.tests.cpp:: passed: thisThrows(), ContainsSubstring( "except" ) for: "expected exception" contains: "except" Exception.tests.cpp:: passed: thisThrows(), ContainsSubstring( "exCept", Catch::CaseSensitive::No ) for: "expected exception" contains: "except" (case insensitive) +ToString.tests.cpp:: passed: tos == tos for: { stringification failed with an exception: "Invalid" } +== +{ stringification failed with an exception: "Invalid" } Matchers.tests.cpp:: passed: throwsDerivedException(), DerivedException, Message( "DerivedException::what" ) for: DerivedException::what exception message matches "DerivedException::what" Matchers.tests.cpp:: passed: throwsDerivedException(), DerivedException, !Message( "derivedexception::what" ) for: DerivedException::what not exception message matches "derivedexception::what" Matchers.tests.cpp:: passed: throwsSpecialException( 2 ), SpecialException, !Message( "DerivedException::what" ) for: SpecialException::what not exception message matches "DerivedException::what" @@ -633,6 +653,8 @@ Exception.tests.cpp:: failed: unexpected exception with message: 'e Exception.tests.cpp:: failed: expected exception, got none; expression was: thisDoesntThrow(), std::domain_error Exception.tests.cpp:: failed: unexpected exception with message: 'expected exception'; expression was: thisThrows() Message.tests.cpp:: failed: explicitly with 1 message: 'This is a failure' +AssertionHandler.tests.cpp:: failed: explicitly with 1 message: 'Throw a Catch::TestFailureException' +AssertionHandler.tests.cpp:: failed: unexpected exception with message: '{ nested assertion failed }'; expression was: do_fail() Message.tests.cpp:: failed: explicitly Message.tests.cpp:: failed: explicitly with 1 message: 'This is a failure' Message.tests.cpp:: warning: 'This message appears in the output' @@ -802,6 +824,10 @@ GeneratorsImpl.tests.cpp:: passed: filter([](int) { return false; } GeneratorsImpl.tests.cpp:: passed: filter([](int) { return false; }, values({ 1, 2, 3 })), Catch::GeneratorException GeneratorsImpl.tests.cpp:: passed: gen.get() == 1 for: 1 == 1 GeneratorsImpl.tests.cpp:: passed: gen.next() for: true +GeneratorsImpl.tests.cpp:: passed: gen.get() == 3 for: 3 == 3 +GeneratorsImpl.tests.cpp:: passed: !(gen.next()) for: !false +GeneratorsImpl.tests.cpp:: passed: gen.get() == 1 for: 1 == 1 +GeneratorsImpl.tests.cpp:: passed: gen.next() for: true GeneratorsImpl.tests.cpp:: passed: gen.get() == 2 for: 2 == 2 GeneratorsImpl.tests.cpp:: passed: !(gen.next()) for: !false GeneratorsImpl.tests.cpp:: passed: gen.get() == 1 for: 1 == 1 @@ -1732,6 +1758,11 @@ BDD.tests.cpp:: passed: v.capacity() >= 10 for: 10 >= 10 BDD.tests.cpp:: passed: v.size() == 0 for: 0 == 0 BDD.tests.cpp:: passed: v.capacity() >= 10 for: 10 >= 10 BDD.tests.cpp:: passed: v.size() == 0 for: 0 == 0 +Message.tests.cpp:: failed: false with 1 message: 'This will be reported multiple times' +Message.tests.cpp:: failed: false with 1 message: 'This will be reported multiple times' +Message.tests.cpp:: failed: false with 1 message: 'Should be in scope at the end' +Message.tests.cpp:: passed: true with 1 message: 'Should survive a section end' +Message.tests.cpp:: failed: false with 1 message: 'Should survive a section end' A string sent directly to stdout A string sent directly to stderr A string sent to stderr via clog @@ -2392,7 +2423,7 @@ Exception.tests.cpp:: failed: unexpected exception with message: 'u Exception.tests.cpp:: failed: unexpected exception with message: 'expected exception'; expression was: thisThrows() == 0 Exception.tests.cpp:: failed: unexpected exception with message: 'expected exception'; expression was: thisThrows() == 0 Exception.tests.cpp:: failed: unexpected exception with message: 'expected exception'; expression was: thisThrows() == 0 -Exception.tests.cpp:: failed: unexpected exception with message: 'unexpected exception' +Exception.tests.cpp:: failed: unexpected exception with message: 'unexpected exception'; expression was: {Unknown expression after the reported line} Tricky.tests.cpp:: passed: Tricky.tests.cpp:: passed: Tricky.tests.cpp:: passed: @@ -2534,18 +2565,18 @@ Skip.tests.cpp:: skipped: Skip.tests.cpp:: skipped: Skip.tests.cpp:: failed: explicitly loose text artifact -Clara.tests.cpp:: passed: with 1 message: 'Catch::Clara::Detail::is_unary_function::value' -Clara.tests.cpp:: passed: with 1 message: 'Catch::Clara::Detail::is_unary_function::value' -Clara.tests.cpp:: passed: with 1 message: 'Catch::Clara::Detail::is_unary_function::value' -Clara.tests.cpp:: passed: with 1 message: 'Catch::Clara::Detail::is_unary_function::value' -Clara.tests.cpp:: passed: with 1 message: 'Catch::Clara::Detail::is_unary_function::value' -Clara.tests.cpp:: passed: with 1 message: 'Catch::Clara::Detail::is_unary_function::value' -Clara.tests.cpp:: passed: with 1 message: '!(Catch::Clara::Detail::is_unary_function::value)' -Clara.tests.cpp:: passed: with 1 message: '!(Catch::Clara::Detail::is_unary_function::value)' -Clara.tests.cpp:: passed: with 1 message: '!(Catch::Clara::Detail::is_unary_function::value)' -Clara.tests.cpp:: passed: with 1 message: '!(Catch::Clara::Detail::is_unary_function::value)' -Clara.tests.cpp:: passed: with 1 message: '!(Catch::Clara::Detail::is_unary_function::value)' -Clara.tests.cpp:: passed: with 1 message: '!(Catch::Clara::Detail::is_unary_function::value)' +Clara.tests.cpp:: passed: with 1 message: 'Catch::Clara::Detail::is_unary_function_v' +Clara.tests.cpp:: passed: with 1 message: 'Catch::Clara::Detail::is_unary_function_v' +Clara.tests.cpp:: passed: with 1 message: 'Catch::Clara::Detail::is_unary_function_v' +Clara.tests.cpp:: passed: with 1 message: 'Catch::Clara::Detail::is_unary_function_v' +Clara.tests.cpp:: passed: with 1 message: 'Catch::Clara::Detail::is_unary_function_v' +Clara.tests.cpp:: passed: with 1 message: 'Catch::Clara::Detail::is_unary_function_v' +Clara.tests.cpp:: passed: with 1 message: '!(Catch::Clara::Detail::is_unary_function_v)' +Clara.tests.cpp:: passed: with 1 message: '!(Catch::Clara::Detail::is_unary_function_v)' +Clara.tests.cpp:: passed: with 1 message: '!(Catch::Clara::Detail::is_unary_function_v)' +Clara.tests.cpp:: passed: with 1 message: '!(Catch::Clara::Detail::is_unary_function_v)' +Clara.tests.cpp:: passed: with 1 message: '!(Catch::Clara::Detail::is_unary_function_v)' +Clara.tests.cpp:: passed: with 1 message: '!(Catch::Clara::Detail::is_unary_function_v)' Message.tests.cpp:: failed: explicitly with 1 message: 'Previous info should not be seen' Message.tests.cpp:: failed: explicitly with 1 message: 'previous unscoped info SHOULD not be seen' Misc.tests.cpp:: passed: l == std::numeric_limits::max() for: 9223372036854775807 (0x) @@ -2749,6 +2780,9 @@ Generators.tests.cpp:: passed: data.str.size() == data.len for: 3 = Generators.tests.cpp:: passed: data.str.size() == data.len for: 3 == 3 Generators.tests.cpp:: passed: data.str.size() == data.len for: 5 == 5 Generators.tests.cpp:: passed: data.str.size() == data.len for: 4 == 4 +ToStringChrono.tests.cpp:: passed: tp1 == tp2 for: {iso8601-timestamp} +== +{iso8601-timestamp} Generators.tests.cpp:: passed: strlen(std::get<0>(data)) == static_cast(std::get<1>(data)) for: 5 == 5 Generators.tests.cpp:: passed: strlen(std::get<0>(data)) == static_cast(std::get<1>(data)) for: 6 == 6 Generators.tests.cpp:: passed: strlen(std::get<0>(data)) == static_cast(std::get<1>(data)) for: 5 == 5 @@ -2854,7 +2888,7 @@ InternalBenchmark.tests.cpp:: passed: med == 18. for: 18.0 == 18.0 InternalBenchmark.tests.cpp:: passed: q3 == 23. for: 23.0 == 23.0 Misc.tests.cpp:: passed: Misc.tests.cpp:: passed: -test cases: 419 | 313 passed | 86 failed | 6 skipped | 14 failed as expected -assertions: 2269 | 2087 passed | 147 failed | 35 failed as expected +test cases: 435 | 317 passed | 95 failed | 6 skipped | 17 failed as expected +assertions: 2303 | 2105 passed | 157 failed | 41 failed as expected diff --git a/tests/SelfTest/Baselines/compact.sw.multi.approved.txt b/tests/SelfTest/Baselines/compact.sw.multi.approved.txt index 9e3e537..10395c9 100644 --- a/tests/SelfTest/Baselines/compact.sw.multi.approved.txt +++ b/tests/SelfTest/Baselines/compact.sw.multi.approved.txt @@ -82,8 +82,10 @@ Matchers.tests.cpp:: passed: smallest_non_zero, WithinULP( -smalles Matchers.tests.cpp:: passed: smallest_non_zero, !WithinULP( -smallest_non_zero, 1 ) for: 0.0 not is within 1 ULPs of -4.9406564584124654e-324 ([-9.8813129168249309e-324, -0.0000000000000000e+00]) Matchers.tests.cpp:: passed: smallest_non_zero, WithinULP( -smallest_non_zero, 2 ) for: 0.0f is within 2 ULPs of -1.40129846e-45f ([-4.20389539e-45, 1.40129846e-45]) Matchers.tests.cpp:: passed: smallest_non_zero, !WithinULP( -smallest_non_zero, 1 ) for: 0.0f not is within 1 ULPs of -1.40129846e-45f ([-2.80259693e-45, -0.00000000e+00]) -Generators.tests.cpp:: failed: unexpected exception with message: 'failure to init' -Exception.tests.cpp:: failed: unexpected exception with message: 'answer := 42' with 1 message: 'expected exception' +Generators.tests.cpp:: failed: unexpected exception with message: 'failure to init'; expression was: {Unknown expression after the reported line} +Compilation.tests.cpp:: passed: bf.e == 1 for: 1 == 1 +Compilation.tests.cpp:: passed: 1 == bf.e for: 1 == 1 +Exception.tests.cpp:: failed: unexpected exception with message: 'expected exception'; expression was: {Unknown expression after the reported line} Exception.tests.cpp:: failed: unexpected exception with message: 'answer := 42'; expression was: thisThrows() with 1 message: 'expected exception' Exception.tests.cpp:: passed: thisThrows() with 1 message: 'answer := 42' Compilation.tests.cpp:: passed: 42 == f for: 42 == {?} @@ -330,6 +332,11 @@ Compilation.tests.cpp:: passed: !(lhs & rhs) for: !(Val: 1 & Val: 2 Compilation.tests.cpp:: passed: HasBitOperators{ 1 } & HasBitOperators{ 1 } for: Val: 1 & Val: 1 Compilation.tests.cpp:: passed: lhs ^ rhs for: Val: 1 ^ Val: 2 Compilation.tests.cpp:: passed: !(lhs ^ lhs) for: !(Val: 1 ^ Val: 1) +AssertionHandler.tests.cpp:: failed: i > 10 for: 2 > 10 +AssertionHandler.tests.cpp:: failed: unexpected exception with message: '{ nested assertion failed }'; expression was: foo( 2 ) == 2 +AssertionHandler.tests.cpp:: passed: true +AssertionHandler.tests.cpp:: failed: i > 10 for: 2 > 10 +AssertionHandler.tests.cpp:: failed: unexpected exception with message: '{ nested assertion failed }'; expression was: foo( 2 ) == 2 Tricky.tests.cpp:: passed: true Tricky.tests.cpp:: passed: true Tricky.tests.cpp:: passed: true @@ -361,6 +368,9 @@ Message.tests.cpp:: passed: with 7 messages: 'custom_index_op{ Message.tests.cpp:: passed: with 11 messages: '("comma, in string", "escaped, \", ") := "escaped, ", "' and '"single quote in string,'," := "single quote in string,',"' and '"some escapes, \\,\\\\" := "some escapes, \,\\"' and '"some, ), unmatched, } prenheses {[<" := "some, ), unmatched, } prenheses {[<"' and ''"' := '"'' and ''\'' := '''' and '',' := ','' and ''}' := '}'' and '')' := ')'' and ''(' := '('' and ''{' := '{'' ToStringGeneral.tests.cpp:: passed: true with 1 message: 'i := 2' ToStringGeneral.tests.cpp:: passed: true with 1 message: '3' +Message.tests.cpp:: failed: false with 1 message: 'a := 1' +Message.tests.cpp:: passed: true with 1 message: 'a := 1' +Message.tests.cpp:: failed: false with 1 message: 'a := 1' Details.tests.cpp:: passed: eq( "", "" ) for: true Details.tests.cpp:: passed: !(eq( "", "a" )) for: !false Details.tests.cpp:: passed: eq( "a", "a" ) for: true @@ -471,6 +481,13 @@ Tricky.tests.cpp:: passed: std::vector{1, 2} == std::vector: passed: std::vector{1, 2} == std::vector{1, 2} for: { 1, 2 } == { 1, 2 } Tricky.tests.cpp:: passed: true Tricky.tests.cpp:: passed: std::vector{1, 2} == std::vector{1, 2} for: { 1, 2 } == { 1, 2 } +Misc.tests.cpp:: passed: !(ptr) for: !0 +Misc.tests.cpp:: passed: ptr == ptr for: 0 == 0 +Misc.tests.cpp:: passed: !(ptr != ptr) for: !(0 != 0) +Misc.tests.cpp:: passed: !(ptr < ptr) for: !(0 < 0) +Misc.tests.cpp:: passed: ptr <= ptr for: 0 <= 0 +Misc.tests.cpp:: passed: !(ptr > ptr) for: !(0 > 0) +Misc.tests.cpp:: passed: ptr >= ptr for: 0 >= 0 Tricky.tests.cpp:: passed: a for: 0x Tricky.tests.cpp:: passed: a == &foo for: 0x == 0x RandomNumberGeneration.tests.cpp:: passed: SimplePcg32{} == SimplePcg32{} for: {?} == {?} @@ -623,6 +640,9 @@ Exception.tests.cpp:: passed: thisThrows(), StartsWith( "expected" Exception.tests.cpp:: passed: thisThrows(), EndsWith( "exception" ) for: "expected exception" ends with: "exception" Exception.tests.cpp:: passed: thisThrows(), ContainsSubstring( "except" ) for: "expected exception" contains: "except" Exception.tests.cpp:: passed: thisThrows(), ContainsSubstring( "exCept", Catch::CaseSensitive::No ) for: "expected exception" contains: "except" (case insensitive) +ToString.tests.cpp:: passed: tos == tos for: { stringification failed with an exception: "Invalid" } +== +{ stringification failed with an exception: "Invalid" } Matchers.tests.cpp:: passed: throwsDerivedException(), DerivedException, Message( "DerivedException::what" ) for: DerivedException::what exception message matches "DerivedException::what" Matchers.tests.cpp:: passed: throwsDerivedException(), DerivedException, !Message( "derivedexception::what" ) for: DerivedException::what not exception message matches "derivedexception::what" Matchers.tests.cpp:: passed: throwsSpecialException( 2 ), SpecialException, !Message( "DerivedException::what" ) for: SpecialException::what not exception message matches "DerivedException::what" @@ -631,6 +651,8 @@ Exception.tests.cpp:: failed: unexpected exception with message: 'e Exception.tests.cpp:: failed: expected exception, got none; expression was: thisDoesntThrow(), std::domain_error Exception.tests.cpp:: failed: unexpected exception with message: 'expected exception'; expression was: thisThrows() Message.tests.cpp:: failed: explicitly with 1 message: 'This is a failure' +AssertionHandler.tests.cpp:: failed: explicitly with 1 message: 'Throw a Catch::TestFailureException' +AssertionHandler.tests.cpp:: failed: unexpected exception with message: '{ nested assertion failed }'; expression was: do_fail() Message.tests.cpp:: failed: explicitly Message.tests.cpp:: failed: explicitly with 1 message: 'This is a failure' Message.tests.cpp:: warning: 'This message appears in the output' @@ -800,6 +822,10 @@ GeneratorsImpl.tests.cpp:: passed: filter([](int) { return false; } GeneratorsImpl.tests.cpp:: passed: filter([](int) { return false; }, values({ 1, 2, 3 })), Catch::GeneratorException GeneratorsImpl.tests.cpp:: passed: gen.get() == 1 for: 1 == 1 GeneratorsImpl.tests.cpp:: passed: gen.next() for: true +GeneratorsImpl.tests.cpp:: passed: gen.get() == 3 for: 3 == 3 +GeneratorsImpl.tests.cpp:: passed: !(gen.next()) for: !false +GeneratorsImpl.tests.cpp:: passed: gen.get() == 1 for: 1 == 1 +GeneratorsImpl.tests.cpp:: passed: gen.next() for: true GeneratorsImpl.tests.cpp:: passed: gen.get() == 2 for: 2 == 2 GeneratorsImpl.tests.cpp:: passed: !(gen.next()) for: !false GeneratorsImpl.tests.cpp:: passed: gen.get() == 1 for: 1 == 1 @@ -1730,6 +1756,11 @@ BDD.tests.cpp:: passed: v.capacity() >= 10 for: 10 >= 10 BDD.tests.cpp:: passed: v.size() == 0 for: 0 == 0 BDD.tests.cpp:: passed: v.capacity() >= 10 for: 10 >= 10 BDD.tests.cpp:: passed: v.size() == 0 for: 0 == 0 +Message.tests.cpp:: failed: false with 1 message: 'This will be reported multiple times' +Message.tests.cpp:: failed: false with 1 message: 'This will be reported multiple times' +Message.tests.cpp:: failed: false with 1 message: 'Should be in scope at the end' +Message.tests.cpp:: passed: true with 1 message: 'Should survive a section end' +Message.tests.cpp:: failed: false with 1 message: 'Should survive a section end' Approx.tests.cpp:: passed: d == Approx( 1.23 ) for: 1.22999999999999998 == Approx( 1.22999999999999998 ) @@ -2385,7 +2416,7 @@ Exception.tests.cpp:: failed: unexpected exception with message: 'u Exception.tests.cpp:: failed: unexpected exception with message: 'expected exception'; expression was: thisThrows() == 0 Exception.tests.cpp:: failed: unexpected exception with message: 'expected exception'; expression was: thisThrows() == 0 Exception.tests.cpp:: failed: unexpected exception with message: 'expected exception'; expression was: thisThrows() == 0 -Exception.tests.cpp:: failed: unexpected exception with message: 'unexpected exception' +Exception.tests.cpp:: failed: unexpected exception with message: 'unexpected exception'; expression was: {Unknown expression after the reported line} Tricky.tests.cpp:: passed: Tricky.tests.cpp:: passed: Tricky.tests.cpp:: passed: @@ -2526,18 +2557,18 @@ Skip.tests.cpp:: failed: explicitly Skip.tests.cpp:: skipped: Skip.tests.cpp:: skipped: Skip.tests.cpp:: failed: explicitly -Clara.tests.cpp:: passed: with 1 message: 'Catch::Clara::Detail::is_unary_function::value' -Clara.tests.cpp:: passed: with 1 message: 'Catch::Clara::Detail::is_unary_function::value' -Clara.tests.cpp:: passed: with 1 message: 'Catch::Clara::Detail::is_unary_function::value' -Clara.tests.cpp:: passed: with 1 message: 'Catch::Clara::Detail::is_unary_function::value' -Clara.tests.cpp:: passed: with 1 message: 'Catch::Clara::Detail::is_unary_function::value' -Clara.tests.cpp:: passed: with 1 message: 'Catch::Clara::Detail::is_unary_function::value' -Clara.tests.cpp:: passed: with 1 message: '!(Catch::Clara::Detail::is_unary_function::value)' -Clara.tests.cpp:: passed: with 1 message: '!(Catch::Clara::Detail::is_unary_function::value)' -Clara.tests.cpp:: passed: with 1 message: '!(Catch::Clara::Detail::is_unary_function::value)' -Clara.tests.cpp:: passed: with 1 message: '!(Catch::Clara::Detail::is_unary_function::value)' -Clara.tests.cpp:: passed: with 1 message: '!(Catch::Clara::Detail::is_unary_function::value)' -Clara.tests.cpp:: passed: with 1 message: '!(Catch::Clara::Detail::is_unary_function::value)' +Clara.tests.cpp:: passed: with 1 message: 'Catch::Clara::Detail::is_unary_function_v' +Clara.tests.cpp:: passed: with 1 message: 'Catch::Clara::Detail::is_unary_function_v' +Clara.tests.cpp:: passed: with 1 message: 'Catch::Clara::Detail::is_unary_function_v' +Clara.tests.cpp:: passed: with 1 message: 'Catch::Clara::Detail::is_unary_function_v' +Clara.tests.cpp:: passed: with 1 message: 'Catch::Clara::Detail::is_unary_function_v' +Clara.tests.cpp:: passed: with 1 message: 'Catch::Clara::Detail::is_unary_function_v' +Clara.tests.cpp:: passed: with 1 message: '!(Catch::Clara::Detail::is_unary_function_v)' +Clara.tests.cpp:: passed: with 1 message: '!(Catch::Clara::Detail::is_unary_function_v)' +Clara.tests.cpp:: passed: with 1 message: '!(Catch::Clara::Detail::is_unary_function_v)' +Clara.tests.cpp:: passed: with 1 message: '!(Catch::Clara::Detail::is_unary_function_v)' +Clara.tests.cpp:: passed: with 1 message: '!(Catch::Clara::Detail::is_unary_function_v)' +Clara.tests.cpp:: passed: with 1 message: '!(Catch::Clara::Detail::is_unary_function_v)' Message.tests.cpp:: failed: explicitly with 1 message: 'Previous info should not be seen' Message.tests.cpp:: failed: explicitly with 1 message: 'previous unscoped info SHOULD not be seen' Misc.tests.cpp:: passed: l == std::numeric_limits::max() for: 9223372036854775807 (0x) @@ -2738,6 +2769,9 @@ Generators.tests.cpp:: passed: data.str.size() == data.len for: 3 = Generators.tests.cpp:: passed: data.str.size() == data.len for: 3 == 3 Generators.tests.cpp:: passed: data.str.size() == data.len for: 5 == 5 Generators.tests.cpp:: passed: data.str.size() == data.len for: 4 == 4 +ToStringChrono.tests.cpp:: passed: tp1 == tp2 for: {iso8601-timestamp} +== +{iso8601-timestamp} Generators.tests.cpp:: passed: strlen(std::get<0>(data)) == static_cast(std::get<1>(data)) for: 5 == 5 Generators.tests.cpp:: passed: strlen(std::get<0>(data)) == static_cast(std::get<1>(data)) for: 6 == 6 Generators.tests.cpp:: passed: strlen(std::get<0>(data)) == static_cast(std::get<1>(data)) for: 5 == 5 @@ -2843,7 +2877,7 @@ InternalBenchmark.tests.cpp:: passed: med == 18. for: 18.0 == 18.0 InternalBenchmark.tests.cpp:: passed: q3 == 23. for: 23.0 == 23.0 Misc.tests.cpp:: passed: Misc.tests.cpp:: passed: -test cases: 419 | 313 passed | 86 failed | 6 skipped | 14 failed as expected -assertions: 2269 | 2087 passed | 147 failed | 35 failed as expected +test cases: 435 | 317 passed | 95 failed | 6 skipped | 17 failed as expected +assertions: 2303 | 2105 passed | 157 failed | 41 failed as expected diff --git a/tests/SelfTest/Baselines/console.std.approved.txt b/tests/SelfTest/Baselines/console.std.approved.txt index 495264b..c1512db 100644 --- a/tests/SelfTest/Baselines/console.std.approved.txt +++ b/tests/SelfTest/Baselines/console.std.approved.txt @@ -34,6 +34,7 @@ Generators.tests.cpp: ............................................................................... Generators.tests.cpp:: FAILED: + {Unknown expression after the reported line} due to unexpected exception with message: failure to init @@ -45,8 +46,8 @@ Exception.tests.cpp: ............................................................................... Exception.tests.cpp:: FAILED: -due to unexpected exception with messages: - answer := 42 + {Unknown expression after the reported line} +due to unexpected exception with message: expected exception ------------------------------------------------------------------------------- @@ -346,6 +347,60 @@ Exception.tests.cpp:: FAILED: due to unexpected exception with message: unexpected exception +------------------------------------------------------------------------------- +Assertions can be nested - CHECK +------------------------------------------------------------------------------- +AssertionHandler.tests.cpp: +............................................................................... + +AssertionHandler.tests.cpp:: FAILED: + REQUIRE( i > 10 ) +with expansion: + 2 > 10 + +AssertionHandler.tests.cpp:: FAILED: + CHECK( foo( 2 ) == 2 ) +due to unexpected exception with message: + { nested assertion failed } + +------------------------------------------------------------------------------- +Assertions can be nested - REQUIRE +------------------------------------------------------------------------------- +AssertionHandler.tests.cpp: +............................................................................... + +AssertionHandler.tests.cpp:: FAILED: + REQUIRE( i > 10 ) +with expansion: + 2 > 10 + +AssertionHandler.tests.cpp:: FAILED: + REQUIRE( foo( 2 ) == 2 ) +due to unexpected exception with message: + { nested assertion failed } + +------------------------------------------------------------------------------- +Captures do not leave block with an exception +------------------------------------------------------------------------------- +Message.tests.cpp: +............................................................................... + +Message.tests.cpp:: FAILED: + REQUIRE( false ) +with message: + a := 1 + +------------------------------------------------------------------------------- +Captures outlive section end +------------------------------------------------------------------------------- +Message.tests.cpp: +............................................................................... + +Message.tests.cpp:: FAILED: + REQUIRE( false ) +with message: + a := 1 + ------------------------------------------------------------------------------- Contains string matcher ------------------------------------------------------------------------------- @@ -596,6 +651,21 @@ Message.tests.cpp:: FAILED: explicitly with message: This is a failure +------------------------------------------------------------------------------- +FAIL can be nested in assertion +------------------------------------------------------------------------------- +AssertionHandler.tests.cpp: +............................................................................... + +AssertionHandler.tests.cpp:: FAILED: +explicitly with message: + Throw a Catch::TestFailureException + +AssertionHandler.tests.cpp:: FAILED: + CHECK_NOTHROW( do_fail() ) +due to unexpected exception with message: + { nested assertion failed } + ------------------------------------------------------------------------------- FAIL does not require an argument ------------------------------------------------------------------------------- @@ -986,6 +1056,44 @@ with expansion: "this string contains 'abc' as a substring" matches "this string contains 'abc' as a" case sensitively +------------------------------------------------------------------------------- +Scoped message applies to all assertions in scope +------------------------------------------------------------------------------- +Message.tests.cpp: +............................................................................... + +Message.tests.cpp:: FAILED: + CHECK( false ) +with message: + This will be reported multiple times + +Message.tests.cpp:: FAILED: + CHECK( false ) +with message: + This will be reported multiple times + +------------------------------------------------------------------------------- +Scoped messages do not leave block with an exception +------------------------------------------------------------------------------- +Message.tests.cpp: +............................................................................... + +Message.tests.cpp:: FAILED: + REQUIRE( false ) +with message: + Should be in scope at the end + +------------------------------------------------------------------------------- +Scoped messages outlive section end +------------------------------------------------------------------------------- +Message.tests.cpp: +............................................................................... + +Message.tests.cpp:: FAILED: + REQUIRE( false ) +with message: + Should survive a section end + A string sent directly to stdout A string sent directly to stderr A string sent to stderr via clog @@ -1248,6 +1356,7 @@ Exception.tests.cpp: ............................................................................... Exception.tests.cpp:: FAILED: + {Unknown expression after the reported line} due to unexpected exception with message: unexpected exception @@ -1610,6 +1719,6 @@ due to unexpected exception with message: Why would you throw a std::string? =============================================================================== -test cases: 419 | 327 passed | 71 failed | 7 skipped | 14 failed as expected -assertions: 2252 | 2087 passed | 130 failed | 35 failed as expected +test cases: 435 | 335 passed | 76 failed | 7 skipped | 17 failed as expected +assertions: 2282 | 2105 passed | 136 failed | 41 failed as expected diff --git a/tests/SelfTest/Baselines/console.sw.approved.txt b/tests/SelfTest/Baselines/console.sw.approved.txt index a53f40d..7fd5951 100644 --- a/tests/SelfTest/Baselines/console.sw.approved.txt +++ b/tests/SelfTest/Baselines/console.sw.approved.txt @@ -768,9 +768,26 @@ Generators.tests.cpp: ............................................................................... Generators.tests.cpp:: FAILED: + {Unknown expression after the reported line} due to unexpected exception with message: failure to init +------------------------------------------------------------------------------- +#3001: Enum-based bitfields can be captured +------------------------------------------------------------------------------- +Compilation.tests.cpp: +............................................................................... + +Compilation.tests.cpp:: PASSED: + REQUIRE( bf.e == 1 ) +with expansion: + 1 == 1 + +Compilation.tests.cpp:: PASSED: + REQUIRE( 1 == bf.e ) +with expansion: + 1 == 1 + ------------------------------------------------------------------------------- #748 - captures with unexpected exceptions outside assertions @@ -779,8 +796,8 @@ Exception.tests.cpp: ............................................................................... Exception.tests.cpp:: FAILED: -due to unexpected exception with messages: - answer := 42 + {Unknown expression after the reported line} +due to unexpected exception with message: expected exception ------------------------------------------------------------------------------- @@ -2565,6 +2582,41 @@ Compilation.tests.cpp:: PASSED: with expansion: !(Val: 1 ^ Val: 1) +------------------------------------------------------------------------------- +Assertions can be nested - CHECK +------------------------------------------------------------------------------- +AssertionHandler.tests.cpp: +............................................................................... + +AssertionHandler.tests.cpp:: FAILED: + REQUIRE( i > 10 ) +with expansion: + 2 > 10 + +AssertionHandler.tests.cpp:: FAILED: + CHECK( foo( 2 ) == 2 ) +due to unexpected exception with message: + { nested assertion failed } + +AssertionHandler.tests.cpp:: PASSED: + CHECK( true ) + +------------------------------------------------------------------------------- +Assertions can be nested - REQUIRE +------------------------------------------------------------------------------- +AssertionHandler.tests.cpp: +............................................................................... + +AssertionHandler.tests.cpp:: FAILED: + REQUIRE( i > 10 ) +with expansion: + 2 > 10 + +AssertionHandler.tests.cpp:: FAILED: + REQUIRE( foo( 2 ) == 2 ) +due to unexpected exception with message: + { nested assertion failed } + ------------------------------------------------------------------------------- Assertions then sections ------------------------------------------------------------------------------- @@ -2865,6 +2917,40 @@ ToStringGeneral.tests.cpp:: PASSED: with message: 3 +------------------------------------------------------------------------------- +Captures do not leave block with an exception +------------------------------------------------------------------------------- +Message.tests.cpp: +............................................................................... + +Message.tests.cpp:: FAILED: + REQUIRE( false ) +with message: + a := 1 + +------------------------------------------------------------------------------- +Captures outlive section end + Dummy section +------------------------------------------------------------------------------- +Message.tests.cpp: +............................................................................... + +Message.tests.cpp:: PASSED: + CHECK( true ) +with message: + a := 1 + +------------------------------------------------------------------------------- +Captures outlive section end +------------------------------------------------------------------------------- +Message.tests.cpp: +............................................................................... + +Message.tests.cpp:: FAILED: + REQUIRE( false ) +with message: + a := 1 + ------------------------------------------------------------------------------- CaseInsensitiveEqualsTo is case insensitive Degenerate cases @@ -3469,6 +3555,47 @@ Tricky.tests.cpp:: PASSED: with expansion: { 1, 2 } == { 1, 2 } +------------------------------------------------------------------------------- +Comparing (and stringifying) volatile pointers works +------------------------------------------------------------------------------- +Misc.tests.cpp: +............................................................................... + +Misc.tests.cpp:: PASSED: + REQUIRE_FALSE( ptr ) +with expansion: + !0 + +Misc.tests.cpp:: PASSED: + REQUIRE( ptr == ptr ) +with expansion: + 0 == 0 + +Misc.tests.cpp:: PASSED: + REQUIRE_FALSE( ptr != ptr ) +with expansion: + !(0 != 0) + +Misc.tests.cpp:: PASSED: + REQUIRE_FALSE( ptr < ptr ) +with expansion: + !(0 < 0) + +Misc.tests.cpp:: PASSED: + REQUIRE( ptr <= ptr ) +with expansion: + 0 <= 0 + +Misc.tests.cpp:: PASSED: + REQUIRE_FALSE( ptr > ptr ) +with expansion: + !(0 > 0) + +Misc.tests.cpp:: PASSED: + REQUIRE( ptr >= ptr ) +with expansion: + 0 >= 0 + ------------------------------------------------------------------------------- Comparing function pointers ------------------------------------------------------------------------------- @@ -4470,6 +4597,19 @@ Exception.tests.cpp:: PASSED: with expansion: "expected exception" contains: "except" (case insensitive) +------------------------------------------------------------------------------- +Exception thrown inside stringify does not fail the test +------------------------------------------------------------------------------- +ToString.tests.cpp: +............................................................................... + +ToString.tests.cpp:: PASSED: + CHECK( tos == tos ) +with expansion: + { stringification failed with an exception: "Invalid" } + == + { stringification failed with an exception: "Invalid" } + ------------------------------------------------------------------------------- Exceptions matchers ------------------------------------------------------------------------------- @@ -4526,6 +4666,21 @@ Message.tests.cpp:: FAILED: explicitly with message: This is a failure +------------------------------------------------------------------------------- +FAIL can be nested in assertion +------------------------------------------------------------------------------- +AssertionHandler.tests.cpp: +............................................................................... + +AssertionHandler.tests.cpp:: FAILED: +explicitly with message: + Throw a Catch::TestFailureException + +AssertionHandler.tests.cpp:: FAILED: + CHECK_NOTHROW( do_fail() ) +due to unexpected exception with message: + { nested assertion failed } + ------------------------------------------------------------------------------- FAIL does not require an argument ------------------------------------------------------------------------------- @@ -5899,6 +6054,34 @@ GeneratorsImpl.tests.cpp:: PASSED: GeneratorsImpl.tests.cpp:: PASSED: REQUIRE_THROWS_AS( filter([](int) { return false; }, values({ 1, 2, 3 })), Catch::GeneratorException ) +------------------------------------------------------------------------------- +Generators internals + Filter generator + Out-of-line predicates are copied into the generator +------------------------------------------------------------------------------- +GeneratorsImpl.tests.cpp: +............................................................................... + +GeneratorsImpl.tests.cpp:: PASSED: + REQUIRE( gen.get() == 1 ) +with expansion: + 1 == 1 + +GeneratorsImpl.tests.cpp:: PASSED: + REQUIRE( gen.next() ) +with expansion: + true + +GeneratorsImpl.tests.cpp:: PASSED: + REQUIRE( gen.get() == 3 ) +with expansion: + 3 == 3 + +GeneratorsImpl.tests.cpp:: PASSED: + REQUIRE_FALSE( gen.next() ) +with expansion: + !false + ------------------------------------------------------------------------------- Generators internals Take generator @@ -7652,7 +7835,7 @@ with expansion: ""custom"" == ""custom"" ------------------------------------------------------------------------------- -JsonWriter escapes charaters in strings properly +JsonWriter escapes characters in strings properly Quote in a string is escaped ------------------------------------------------------------------------------- Json.tests.cpp: @@ -7664,7 +7847,7 @@ with expansion: ""\""" == ""\""" ------------------------------------------------------------------------------- -JsonWriter escapes charaters in strings properly +JsonWriter escapes characters in strings properly Backslash in a string is escaped ------------------------------------------------------------------------------- Json.tests.cpp: @@ -7676,7 +7859,7 @@ with expansion: ""\\"" == ""\\"" ------------------------------------------------------------------------------- -JsonWriter escapes charaters in strings properly +JsonWriter escapes characters in strings properly Forward slash in a string is **not** escaped ------------------------------------------------------------------------------- Json.tests.cpp: @@ -7688,7 +7871,7 @@ with expansion: ""/"" == ""/"" ------------------------------------------------------------------------------- -JsonWriter escapes charaters in strings properly +JsonWriter escapes characters in strings properly Backspace in a string is escaped ------------------------------------------------------------------------------- Json.tests.cpp: @@ -7700,7 +7883,7 @@ with expansion: ""\b"" == ""\b"" ------------------------------------------------------------------------------- -JsonWriter escapes charaters in strings properly +JsonWriter escapes characters in strings properly Formfeed in a string is escaped ------------------------------------------------------------------------------- Json.tests.cpp: @@ -7712,7 +7895,7 @@ with expansion: ""\f"" == ""\f"" ------------------------------------------------------------------------------- -JsonWriter escapes charaters in strings properly +JsonWriter escapes characters in strings properly linefeed in a string is escaped ------------------------------------------------------------------------------- Json.tests.cpp: @@ -7724,7 +7907,7 @@ with expansion: ""\n"" == ""\n"" ------------------------------------------------------------------------------- -JsonWriter escapes charaters in strings properly +JsonWriter escapes characters in strings properly carriage return in a string is escaped ------------------------------------------------------------------------------- Json.tests.cpp: @@ -7736,7 +7919,7 @@ with expansion: ""\r"" == ""\r"" ------------------------------------------------------------------------------- -JsonWriter escapes charaters in strings properly +JsonWriter escapes characters in strings properly tab in a string is escaped ------------------------------------------------------------------------------- Json.tests.cpp: @@ -7748,7 +7931,7 @@ with expansion: ""\t"" == ""\t"" ------------------------------------------------------------------------------- -JsonWriter escapes charaters in strings properly +JsonWriter escapes characters in strings properly combination of characters is escaped ------------------------------------------------------------------------------- Json.tests.cpp: @@ -11024,6 +11207,56 @@ BDD.tests.cpp:: PASSED: with expansion: 0 == 0 +------------------------------------------------------------------------------- +Scoped message applies to all assertions in scope +------------------------------------------------------------------------------- +Message.tests.cpp: +............................................................................... + +Message.tests.cpp:: FAILED: + CHECK( false ) +with message: + This will be reported multiple times + +Message.tests.cpp:: FAILED: + CHECK( false ) +with message: + This will be reported multiple times + +------------------------------------------------------------------------------- +Scoped messages do not leave block with an exception +------------------------------------------------------------------------------- +Message.tests.cpp: +............................................................................... + +Message.tests.cpp:: FAILED: + REQUIRE( false ) +with message: + Should be in scope at the end + +------------------------------------------------------------------------------- +Scoped messages outlive section end + Dummy section +------------------------------------------------------------------------------- +Message.tests.cpp: +............................................................................... + +Message.tests.cpp:: PASSED: + CHECK( true ) +with message: + Should survive a section end + +------------------------------------------------------------------------------- +Scoped messages outlive section end +------------------------------------------------------------------------------- +Message.tests.cpp: +............................................................................... + +Message.tests.cpp:: FAILED: + REQUIRE( false ) +with message: + Should survive a section end + A string sent directly to stdout A string sent directly to stderr A string sent to stderr via clog @@ -12526,6 +12759,42 @@ Misc.tests.cpp:: PASSED: with expansion: 5 >= 5 +------------------------------------------------------------------------------- +TemplateTestSig: compiles with a single int parameter - 1 +------------------------------------------------------------------------------- +Misc.tests.cpp: +............................................................................... + + +No assertions in test case 'TemplateTestSig: compiles with a single int parameter - 1' + +------------------------------------------------------------------------------- +TemplateTestSig: compiles with a single int parameter - 3 +------------------------------------------------------------------------------- +Misc.tests.cpp: +............................................................................... + + +No assertions in test case 'TemplateTestSig: compiles with a single int parameter - 3' + +------------------------------------------------------------------------------- +TemplateTestSig: compiles with a single int parameter - 6 +------------------------------------------------------------------------------- +Misc.tests.cpp: +............................................................................... + + +No assertions in test case 'TemplateTestSig: compiles with a single int parameter - 6' + +------------------------------------------------------------------------------- +TemplateTestSig: compiles with two type parameters - int,int +------------------------------------------------------------------------------- +Misc.tests.cpp: +............................................................................... + + +No assertions in test case 'TemplateTestSig: compiles with two type parameters - int,int' + ------------------------------------------------------------------------------- TemplateTestSig: vectors can be sized and resized - (std::tuple), 6 ------------------------------------------------------------------------------- @@ -15747,6 +16016,7 @@ Exception.tests.cpp: ............................................................................... Exception.tests.cpp:: FAILED: + {Unknown expression after the reported line} due to unexpected exception with message: unexpected exception @@ -16668,51 +16938,51 @@ Clara.tests.cpp: Clara.tests.cpp:: PASSED: with message: - Catch::Clara::Detail::is_unary_function::value + Catch::Clara::Detail::is_unary_function_v Clara.tests.cpp:: PASSED: with message: - Catch::Clara::Detail::is_unary_function::value + Catch::Clara::Detail::is_unary_function_v Clara.tests.cpp:: PASSED: with message: - Catch::Clara::Detail::is_unary_function::value + Catch::Clara::Detail::is_unary_function_v Clara.tests.cpp:: PASSED: with message: - Catch::Clara::Detail::is_unary_function::value + Catch::Clara::Detail::is_unary_function_v Clara.tests.cpp:: PASSED: with message: - Catch::Clara::Detail::is_unary_function::value + Catch::Clara::Detail::is_unary_function_v Clara.tests.cpp:: PASSED: with message: - Catch::Clara::Detail::is_unary_function::value + Catch::Clara::Detail::is_unary_function_v Clara.tests.cpp:: PASSED: with message: - !(Catch::Clara::Detail::is_unary_function::value) + !(Catch::Clara::Detail::is_unary_function_v) Clara.tests.cpp:: PASSED: with message: - !(Catch::Clara::Detail::is_unary_function::value) + !(Catch::Clara::Detail::is_unary_function_v) Clara.tests.cpp:: PASSED: with message: - !(Catch::Clara::Detail::is_unary_function::value) + !(Catch::Clara::Detail::is_unary_function_v) Clara.tests.cpp:: PASSED: with message: - !(Catch::Clara::Detail::is_unary_function::value) + !(Catch::Clara::Detail::is_unary_function_v) Clara.tests.cpp:: PASSED: with message: - !(Catch::Clara::Detail::is_unary_function::value) + !(Catch::Clara::Detail::is_unary_function_v) Clara.tests.cpp:: PASSED: with message: - !(Catch::Clara::Detail::is_unary_function::value) + !(Catch::Clara::Detail::is_unary_function_v) ------------------------------------------------------------------------------- just failure @@ -18221,6 +18491,19 @@ Generators.tests.cpp:: PASSED: with expansion: 4 == 4 +------------------------------------------------------------------------------- +system_clock timepoint with non-default duration +------------------------------------------------------------------------------- +ToStringChrono.tests.cpp: +............................................................................... + +ToStringChrono.tests.cpp:: PASSED: + CHECK( tp1 == tp2 ) +with expansion: + {iso8601-timestamp} + == + {iso8601-timestamp} + ------------------------------------------------------------------------------- tables ------------------------------------------------------------------------------- @@ -19012,6 +19295,6 @@ Misc.tests.cpp: Misc.tests.cpp:: PASSED: =============================================================================== -test cases: 419 | 313 passed | 86 failed | 6 skipped | 14 failed as expected -assertions: 2269 | 2087 passed | 147 failed | 35 failed as expected +test cases: 435 | 317 passed | 95 failed | 6 skipped | 17 failed as expected +assertions: 2303 | 2105 passed | 157 failed | 41 failed as expected diff --git a/tests/SelfTest/Baselines/console.sw.multi.approved.txt b/tests/SelfTest/Baselines/console.sw.multi.approved.txt index 418ccf7..08ab1b2 100644 --- a/tests/SelfTest/Baselines/console.sw.multi.approved.txt +++ b/tests/SelfTest/Baselines/console.sw.multi.approved.txt @@ -766,9 +766,26 @@ Generators.tests.cpp: ............................................................................... Generators.tests.cpp:: FAILED: + {Unknown expression after the reported line} due to unexpected exception with message: failure to init +------------------------------------------------------------------------------- +#3001: Enum-based bitfields can be captured +------------------------------------------------------------------------------- +Compilation.tests.cpp: +............................................................................... + +Compilation.tests.cpp:: PASSED: + REQUIRE( bf.e == 1 ) +with expansion: + 1 == 1 + +Compilation.tests.cpp:: PASSED: + REQUIRE( 1 == bf.e ) +with expansion: + 1 == 1 + ------------------------------------------------------------------------------- #748 - captures with unexpected exceptions outside assertions @@ -777,8 +794,8 @@ Exception.tests.cpp: ............................................................................... Exception.tests.cpp:: FAILED: -due to unexpected exception with messages: - answer := 42 + {Unknown expression after the reported line} +due to unexpected exception with message: expected exception ------------------------------------------------------------------------------- @@ -2563,6 +2580,41 @@ Compilation.tests.cpp:: PASSED: with expansion: !(Val: 1 ^ Val: 1) +------------------------------------------------------------------------------- +Assertions can be nested - CHECK +------------------------------------------------------------------------------- +AssertionHandler.tests.cpp: +............................................................................... + +AssertionHandler.tests.cpp:: FAILED: + REQUIRE( i > 10 ) +with expansion: + 2 > 10 + +AssertionHandler.tests.cpp:: FAILED: + CHECK( foo( 2 ) == 2 ) +due to unexpected exception with message: + { nested assertion failed } + +AssertionHandler.tests.cpp:: PASSED: + CHECK( true ) + +------------------------------------------------------------------------------- +Assertions can be nested - REQUIRE +------------------------------------------------------------------------------- +AssertionHandler.tests.cpp: +............................................................................... + +AssertionHandler.tests.cpp:: FAILED: + REQUIRE( i > 10 ) +with expansion: + 2 > 10 + +AssertionHandler.tests.cpp:: FAILED: + REQUIRE( foo( 2 ) == 2 ) +due to unexpected exception with message: + { nested assertion failed } + ------------------------------------------------------------------------------- Assertions then sections ------------------------------------------------------------------------------- @@ -2863,6 +2915,40 @@ ToStringGeneral.tests.cpp:: PASSED: with message: 3 +------------------------------------------------------------------------------- +Captures do not leave block with an exception +------------------------------------------------------------------------------- +Message.tests.cpp: +............................................................................... + +Message.tests.cpp:: FAILED: + REQUIRE( false ) +with message: + a := 1 + +------------------------------------------------------------------------------- +Captures outlive section end + Dummy section +------------------------------------------------------------------------------- +Message.tests.cpp: +............................................................................... + +Message.tests.cpp:: PASSED: + CHECK( true ) +with message: + a := 1 + +------------------------------------------------------------------------------- +Captures outlive section end +------------------------------------------------------------------------------- +Message.tests.cpp: +............................................................................... + +Message.tests.cpp:: FAILED: + REQUIRE( false ) +with message: + a := 1 + ------------------------------------------------------------------------------- CaseInsensitiveEqualsTo is case insensitive Degenerate cases @@ -3467,6 +3553,47 @@ Tricky.tests.cpp:: PASSED: with expansion: { 1, 2 } == { 1, 2 } +------------------------------------------------------------------------------- +Comparing (and stringifying) volatile pointers works +------------------------------------------------------------------------------- +Misc.tests.cpp: +............................................................................... + +Misc.tests.cpp:: PASSED: + REQUIRE_FALSE( ptr ) +with expansion: + !0 + +Misc.tests.cpp:: PASSED: + REQUIRE( ptr == ptr ) +with expansion: + 0 == 0 + +Misc.tests.cpp:: PASSED: + REQUIRE_FALSE( ptr != ptr ) +with expansion: + !(0 != 0) + +Misc.tests.cpp:: PASSED: + REQUIRE_FALSE( ptr < ptr ) +with expansion: + !(0 < 0) + +Misc.tests.cpp:: PASSED: + REQUIRE( ptr <= ptr ) +with expansion: + 0 <= 0 + +Misc.tests.cpp:: PASSED: + REQUIRE_FALSE( ptr > ptr ) +with expansion: + !(0 > 0) + +Misc.tests.cpp:: PASSED: + REQUIRE( ptr >= ptr ) +with expansion: + 0 >= 0 + ------------------------------------------------------------------------------- Comparing function pointers ------------------------------------------------------------------------------- @@ -4468,6 +4595,19 @@ Exception.tests.cpp:: PASSED: with expansion: "expected exception" contains: "except" (case insensitive) +------------------------------------------------------------------------------- +Exception thrown inside stringify does not fail the test +------------------------------------------------------------------------------- +ToString.tests.cpp: +............................................................................... + +ToString.tests.cpp:: PASSED: + CHECK( tos == tos ) +with expansion: + { stringification failed with an exception: "Invalid" } + == + { stringification failed with an exception: "Invalid" } + ------------------------------------------------------------------------------- Exceptions matchers ------------------------------------------------------------------------------- @@ -4524,6 +4664,21 @@ Message.tests.cpp:: FAILED: explicitly with message: This is a failure +------------------------------------------------------------------------------- +FAIL can be nested in assertion +------------------------------------------------------------------------------- +AssertionHandler.tests.cpp: +............................................................................... + +AssertionHandler.tests.cpp:: FAILED: +explicitly with message: + Throw a Catch::TestFailureException + +AssertionHandler.tests.cpp:: FAILED: + CHECK_NOTHROW( do_fail() ) +due to unexpected exception with message: + { nested assertion failed } + ------------------------------------------------------------------------------- FAIL does not require an argument ------------------------------------------------------------------------------- @@ -5897,6 +6052,34 @@ GeneratorsImpl.tests.cpp:: PASSED: GeneratorsImpl.tests.cpp:: PASSED: REQUIRE_THROWS_AS( filter([](int) { return false; }, values({ 1, 2, 3 })), Catch::GeneratorException ) +------------------------------------------------------------------------------- +Generators internals + Filter generator + Out-of-line predicates are copied into the generator +------------------------------------------------------------------------------- +GeneratorsImpl.tests.cpp: +............................................................................... + +GeneratorsImpl.tests.cpp:: PASSED: + REQUIRE( gen.get() == 1 ) +with expansion: + 1 == 1 + +GeneratorsImpl.tests.cpp:: PASSED: + REQUIRE( gen.next() ) +with expansion: + true + +GeneratorsImpl.tests.cpp:: PASSED: + REQUIRE( gen.get() == 3 ) +with expansion: + 3 == 3 + +GeneratorsImpl.tests.cpp:: PASSED: + REQUIRE_FALSE( gen.next() ) +with expansion: + !false + ------------------------------------------------------------------------------- Generators internals Take generator @@ -7650,7 +7833,7 @@ with expansion: ""custom"" == ""custom"" ------------------------------------------------------------------------------- -JsonWriter escapes charaters in strings properly +JsonWriter escapes characters in strings properly Quote in a string is escaped ------------------------------------------------------------------------------- Json.tests.cpp: @@ -7662,7 +7845,7 @@ with expansion: ""\""" == ""\""" ------------------------------------------------------------------------------- -JsonWriter escapes charaters in strings properly +JsonWriter escapes characters in strings properly Backslash in a string is escaped ------------------------------------------------------------------------------- Json.tests.cpp: @@ -7674,7 +7857,7 @@ with expansion: ""\\"" == ""\\"" ------------------------------------------------------------------------------- -JsonWriter escapes charaters in strings properly +JsonWriter escapes characters in strings properly Forward slash in a string is **not** escaped ------------------------------------------------------------------------------- Json.tests.cpp: @@ -7686,7 +7869,7 @@ with expansion: ""/"" == ""/"" ------------------------------------------------------------------------------- -JsonWriter escapes charaters in strings properly +JsonWriter escapes characters in strings properly Backspace in a string is escaped ------------------------------------------------------------------------------- Json.tests.cpp: @@ -7698,7 +7881,7 @@ with expansion: ""\b"" == ""\b"" ------------------------------------------------------------------------------- -JsonWriter escapes charaters in strings properly +JsonWriter escapes characters in strings properly Formfeed in a string is escaped ------------------------------------------------------------------------------- Json.tests.cpp: @@ -7710,7 +7893,7 @@ with expansion: ""\f"" == ""\f"" ------------------------------------------------------------------------------- -JsonWriter escapes charaters in strings properly +JsonWriter escapes characters in strings properly linefeed in a string is escaped ------------------------------------------------------------------------------- Json.tests.cpp: @@ -7722,7 +7905,7 @@ with expansion: ""\n"" == ""\n"" ------------------------------------------------------------------------------- -JsonWriter escapes charaters in strings properly +JsonWriter escapes characters in strings properly carriage return in a string is escaped ------------------------------------------------------------------------------- Json.tests.cpp: @@ -7734,7 +7917,7 @@ with expansion: ""\r"" == ""\r"" ------------------------------------------------------------------------------- -JsonWriter escapes charaters in strings properly +JsonWriter escapes characters in strings properly tab in a string is escaped ------------------------------------------------------------------------------- Json.tests.cpp: @@ -7746,7 +7929,7 @@ with expansion: ""\t"" == ""\t"" ------------------------------------------------------------------------------- -JsonWriter escapes charaters in strings properly +JsonWriter escapes characters in strings properly combination of characters is escaped ------------------------------------------------------------------------------- Json.tests.cpp: @@ -11022,6 +11205,56 @@ BDD.tests.cpp:: PASSED: with expansion: 0 == 0 +------------------------------------------------------------------------------- +Scoped message applies to all assertions in scope +------------------------------------------------------------------------------- +Message.tests.cpp: +............................................................................... + +Message.tests.cpp:: FAILED: + CHECK( false ) +with message: + This will be reported multiple times + +Message.tests.cpp:: FAILED: + CHECK( false ) +with message: + This will be reported multiple times + +------------------------------------------------------------------------------- +Scoped messages do not leave block with an exception +------------------------------------------------------------------------------- +Message.tests.cpp: +............................................................................... + +Message.tests.cpp:: FAILED: + REQUIRE( false ) +with message: + Should be in scope at the end + +------------------------------------------------------------------------------- +Scoped messages outlive section end + Dummy section +------------------------------------------------------------------------------- +Message.tests.cpp: +............................................................................... + +Message.tests.cpp:: PASSED: + CHECK( true ) +with message: + Should survive a section end + +------------------------------------------------------------------------------- +Scoped messages outlive section end +------------------------------------------------------------------------------- +Message.tests.cpp: +............................................................................... + +Message.tests.cpp:: FAILED: + REQUIRE( false ) +with message: + Should survive a section end + ------------------------------------------------------------------------------- Sends stuff to stdout and stderr ------------------------------------------------------------------------------- @@ -12519,6 +12752,42 @@ Misc.tests.cpp:: PASSED: with expansion: 5 >= 5 +------------------------------------------------------------------------------- +TemplateTestSig: compiles with a single int parameter - 1 +------------------------------------------------------------------------------- +Misc.tests.cpp: +............................................................................... + + +No assertions in test case 'TemplateTestSig: compiles with a single int parameter - 1' + +------------------------------------------------------------------------------- +TemplateTestSig: compiles with a single int parameter - 3 +------------------------------------------------------------------------------- +Misc.tests.cpp: +............................................................................... + + +No assertions in test case 'TemplateTestSig: compiles with a single int parameter - 3' + +------------------------------------------------------------------------------- +TemplateTestSig: compiles with a single int parameter - 6 +------------------------------------------------------------------------------- +Misc.tests.cpp: +............................................................................... + + +No assertions in test case 'TemplateTestSig: compiles with a single int parameter - 6' + +------------------------------------------------------------------------------- +TemplateTestSig: compiles with two type parameters - int,int +------------------------------------------------------------------------------- +Misc.tests.cpp: +............................................................................... + + +No assertions in test case 'TemplateTestSig: compiles with two type parameters - int,int' + ------------------------------------------------------------------------------- TemplateTestSig: vectors can be sized and resized - (std::tuple), 6 ------------------------------------------------------------------------------- @@ -15740,6 +16009,7 @@ Exception.tests.cpp: ............................................................................... Exception.tests.cpp:: FAILED: + {Unknown expression after the reported line} due to unexpected exception with message: unexpected exception @@ -16660,51 +16930,51 @@ Clara.tests.cpp: Clara.tests.cpp:: PASSED: with message: - Catch::Clara::Detail::is_unary_function::value + Catch::Clara::Detail::is_unary_function_v Clara.tests.cpp:: PASSED: with message: - Catch::Clara::Detail::is_unary_function::value + Catch::Clara::Detail::is_unary_function_v Clara.tests.cpp:: PASSED: with message: - Catch::Clara::Detail::is_unary_function::value + Catch::Clara::Detail::is_unary_function_v Clara.tests.cpp:: PASSED: with message: - Catch::Clara::Detail::is_unary_function::value + Catch::Clara::Detail::is_unary_function_v Clara.tests.cpp:: PASSED: with message: - Catch::Clara::Detail::is_unary_function::value + Catch::Clara::Detail::is_unary_function_v Clara.tests.cpp:: PASSED: with message: - Catch::Clara::Detail::is_unary_function::value + Catch::Clara::Detail::is_unary_function_v Clara.tests.cpp:: PASSED: with message: - !(Catch::Clara::Detail::is_unary_function::value) + !(Catch::Clara::Detail::is_unary_function_v) Clara.tests.cpp:: PASSED: with message: - !(Catch::Clara::Detail::is_unary_function::value) + !(Catch::Clara::Detail::is_unary_function_v) Clara.tests.cpp:: PASSED: with message: - !(Catch::Clara::Detail::is_unary_function::value) + !(Catch::Clara::Detail::is_unary_function_v) Clara.tests.cpp:: PASSED: with message: - !(Catch::Clara::Detail::is_unary_function::value) + !(Catch::Clara::Detail::is_unary_function_v) Clara.tests.cpp:: PASSED: with message: - !(Catch::Clara::Detail::is_unary_function::value) + !(Catch::Clara::Detail::is_unary_function_v) Clara.tests.cpp:: PASSED: with message: - !(Catch::Clara::Detail::is_unary_function::value) + !(Catch::Clara::Detail::is_unary_function_v) ------------------------------------------------------------------------------- just failure @@ -18210,6 +18480,19 @@ Generators.tests.cpp:: PASSED: with expansion: 4 == 4 +------------------------------------------------------------------------------- +system_clock timepoint with non-default duration +------------------------------------------------------------------------------- +ToStringChrono.tests.cpp: +............................................................................... + +ToStringChrono.tests.cpp:: PASSED: + CHECK( tp1 == tp2 ) +with expansion: + {iso8601-timestamp} + == + {iso8601-timestamp} + ------------------------------------------------------------------------------- tables ------------------------------------------------------------------------------- @@ -19001,6 +19284,6 @@ Misc.tests.cpp: Misc.tests.cpp:: PASSED: =============================================================================== -test cases: 419 | 313 passed | 86 failed | 6 skipped | 14 failed as expected -assertions: 2269 | 2087 passed | 147 failed | 35 failed as expected +test cases: 435 | 317 passed | 95 failed | 6 skipped | 17 failed as expected +assertions: 2303 | 2105 passed | 157 failed | 41 failed as expected diff --git a/tests/SelfTest/Baselines/console.swa4.approved.txt b/tests/SelfTest/Baselines/console.swa4.approved.txt index 41b7612..63fb211 100644 --- a/tests/SelfTest/Baselines/console.swa4.approved.txt +++ b/tests/SelfTest/Baselines/console.swa4.approved.txt @@ -768,9 +768,26 @@ Generators.tests.cpp: ............................................................................... Generators.tests.cpp:: FAILED: + {Unknown expression after the reported line} due to unexpected exception with message: failure to init +------------------------------------------------------------------------------- +#3001: Enum-based bitfields can be captured +------------------------------------------------------------------------------- +Compilation.tests.cpp: +............................................................................... + +Compilation.tests.cpp:: PASSED: + REQUIRE( bf.e == 1 ) +with expansion: + 1 == 1 + +Compilation.tests.cpp:: PASSED: + REQUIRE( 1 == bf.e ) +with expansion: + 1 == 1 + ------------------------------------------------------------------------------- #748 - captures with unexpected exceptions outside assertions @@ -779,8 +796,8 @@ Exception.tests.cpp: ............................................................................... Exception.tests.cpp:: FAILED: -due to unexpected exception with messages: - answer := 42 + {Unknown expression after the reported line} +due to unexpected exception with message: expected exception ------------------------------------------------------------------------------- @@ -951,6 +968,6 @@ Condition.tests.cpp:: FAILED: CHECK( true != true ) =============================================================================== -test cases: 33 | 27 passed | 3 failed | 3 failed as expected -assertions: 102 | 94 passed | 4 failed | 4 failed as expected +test cases: 34 | 28 passed | 3 failed | 3 failed as expected +assertions: 104 | 96 passed | 4 failed | 4 failed as expected diff --git a/tests/SelfTest/Baselines/junit.sw.approved.txt b/tests/SelfTest/Baselines/junit.sw.approved.txt index c3571f3..c12b617 100644 --- a/tests/SelfTest/Baselines/junit.sw.approved.txt +++ b/tests/SelfTest/Baselines/junit.sw.approved.txt @@ -1,7 +1,7 @@ - + @@ -53,19 +53,21 @@ Nor would this - + FAILED: + {Unknown expression after the reported line} failure to init at Generators.tests.cpp: + - + FAILED: + {Unknown expression after the reported line} expected exception -answer := 42 at Exception.tests.cpp: @@ -389,6 +391,38 @@ at Exception.tests.cpp: + + + +FAILED: + REQUIRE( i > 10 ) +with expansion: + 2 > 10 +at AssertionHandler.tests.cpp: + + +FAILED: + CHECK( foo( 2 ) == 2 ) +{ nested assertion failed } +at AssertionHandler.tests.cpp: + + + + + +FAILED: + REQUIRE( i > 10 ) +with expansion: + 2 > 10 +at AssertionHandler.tests.cpp: + + +FAILED: + REQUIRE( foo( 2 ) == 2 ) +{ nested assertion failed } +at AssertionHandler.tests.cpp: + + @@ -410,6 +444,23 @@ at Exception.tests.cpp: + + +FAILED: + REQUIRE( false ) +a := 1 +at Message.tests.cpp: + + + + +FAILED: + REQUIRE( false ) +a := 1 +at Message.tests.cpp: + + + @@ -437,6 +488,7 @@ at Exception.tests.cpp: + @@ -693,6 +745,7 @@ at Matchers.tests.cpp: + @@ -720,6 +773,20 @@ This is a failure at Message.tests.cpp: + + + +FAILED: +Throw a Catch::TestFailureException +at AssertionHandler.tests.cpp: + + +FAILED: + CHECK_NOTHROW( do_fail() ) +{ nested assertion failed } +at AssertionHandler.tests.cpp: + + FAILED: @@ -779,6 +846,7 @@ at Message.tests.cpp: + @@ -929,16 +997,16 @@ at Condition.tests.cpp: - - - - - - - - - - + + + + + + + + + + @@ -1348,6 +1416,37 @@ at Matchers.tests.cpp: + + +FAILED: + CHECK( false ) +This will be reported multiple times +at Message.tests.cpp: + + +FAILED: + CHECK( false ) +This will be reported multiple times +at Message.tests.cpp: + + + + +FAILED: + REQUIRE( false ) +Should be in scope at the end +at Message.tests.cpp: + + + + +FAILED: + REQUIRE( false ) +Should survive a section end +at Message.tests.cpp: + + + A string sent directly to stdout @@ -1475,6 +1574,10 @@ at Misc.tests.cpp: + + + + @@ -1833,8 +1936,9 @@ at Exception.tests.cpp: - + FAILED: + {Unknown expression after the reported line} unexpected exception at Exception.tests.cpp: @@ -2232,6 +2336,7 @@ at Message.tests.cpp: + diff --git a/tests/SelfTest/Baselines/junit.sw.multi.approved.txt b/tests/SelfTest/Baselines/junit.sw.multi.approved.txt index 55f5520..e844ca6 100644 --- a/tests/SelfTest/Baselines/junit.sw.multi.approved.txt +++ b/tests/SelfTest/Baselines/junit.sw.multi.approved.txt @@ -1,6 +1,6 @@ - + @@ -52,19 +52,21 @@ Nor would this - + FAILED: + {Unknown expression after the reported line} failure to init at Generators.tests.cpp: + - + FAILED: + {Unknown expression after the reported line} expected exception -answer := 42 at Exception.tests.cpp: @@ -388,6 +390,38 @@ at Exception.tests.cpp: + + + +FAILED: + REQUIRE( i > 10 ) +with expansion: + 2 > 10 +at AssertionHandler.tests.cpp: + + +FAILED: + CHECK( foo( 2 ) == 2 ) +{ nested assertion failed } +at AssertionHandler.tests.cpp: + + + + + +FAILED: + REQUIRE( i > 10 ) +with expansion: + 2 > 10 +at AssertionHandler.tests.cpp: + + +FAILED: + REQUIRE( foo( 2 ) == 2 ) +{ nested assertion failed } +at AssertionHandler.tests.cpp: + + @@ -409,6 +443,23 @@ at Exception.tests.cpp: + + +FAILED: + REQUIRE( false ) +a := 1 +at Message.tests.cpp: + + + + +FAILED: + REQUIRE( false ) +a := 1 +at Message.tests.cpp: + + + @@ -436,6 +487,7 @@ at Exception.tests.cpp: + @@ -692,6 +744,7 @@ at Matchers.tests.cpp: + @@ -719,6 +772,20 @@ This is a failure at Message.tests.cpp: + + + +FAILED: +Throw a Catch::TestFailureException +at AssertionHandler.tests.cpp: + + +FAILED: + CHECK_NOTHROW( do_fail() ) +{ nested assertion failed } +at AssertionHandler.tests.cpp: + + FAILED: @@ -778,6 +845,7 @@ at Message.tests.cpp: + @@ -928,16 +996,16 @@ at Condition.tests.cpp: - - - - - - - - - - + + + + + + + + + + @@ -1347,6 +1415,37 @@ at Matchers.tests.cpp: + + +FAILED: + CHECK( false ) +This will be reported multiple times +at Message.tests.cpp: + + +FAILED: + CHECK( false ) +This will be reported multiple times +at Message.tests.cpp: + + + + +FAILED: + REQUIRE( false ) +Should be in scope at the end +at Message.tests.cpp: + + + + +FAILED: + REQUIRE( false ) +Should survive a section end +at Message.tests.cpp: + + + A string sent directly to stdout @@ -1474,6 +1573,10 @@ at Misc.tests.cpp: + + + + @@ -1832,8 +1935,9 @@ at Exception.tests.cpp: - + FAILED: + {Unknown expression after the reported line} unexpected exception at Exception.tests.cpp: @@ -2231,6 +2335,7 @@ at Message.tests.cpp: + diff --git a/tests/SelfTest/Baselines/sonarqube.sw.approved.txt b/tests/SelfTest/Baselines/sonarqube.sw.approved.txt index af06793..d79abd3 100644 --- a/tests/SelfTest/Baselines/sonarqube.sw.approved.txt +++ b/tests/SelfTest/Baselines/sonarqube.sw.approved.txt @@ -3,6 +3,49 @@ + + +FAILED: + REQUIRE( i > 10 ) +with expansion: + 2 > 10 +at AssertionHandler.tests.cpp: + + +FAILED: + CHECK( foo( 2 ) == 2 ) +{ nested assertion failed } +at AssertionHandler.tests.cpp: + + + + +FAILED: + REQUIRE( i > 10 ) +with expansion: + 2 > 10 +at AssertionHandler.tests.cpp: + + +FAILED: + REQUIRE( foo( 2 ) == 2 ) +{ nested assertion failed } +at AssertionHandler.tests.cpp: + + + + +FAILED: +Throw a Catch::TestFailureException +at AssertionHandler.tests.cpp: + + +FAILED: + CHECK_NOTHROW( do_fail() ) +{ nested assertion failed } +at AssertionHandler.tests.cpp: + + FAILED: @@ -110,6 +153,7 @@ at AssertionHandler.tests.cpp: + @@ -173,16 +217,16 @@ at AssertionHandler.tests.cpp: - - - - - - - - - - + + + + + + + + + + @@ -344,6 +388,7 @@ at AssertionHandler.tests.cpp: + @@ -612,6 +657,7 @@ at Class.tests.cpp: + @@ -1008,10 +1054,10 @@ at Decomposition.tests.cpp: - + FAILED: + {Unknown expression after the reported line} expected exception -answer := 42 at Exception.tests.cpp: @@ -1142,8 +1188,9 @@ at Exception.tests.cpp: - + FAILED: + {Unknown expression after the reported line} unexpected exception at Exception.tests.cpp: @@ -1161,8 +1208,9 @@ at Exception.tests.cpp: - + FAILED: + {Unknown expression after the reported line} failure to init at Generators.tests.cpp: @@ -1634,6 +1682,23 @@ at Matchers.tests.cpp: + + +FAILED: + REQUIRE( false ) +a := 1 +at Message.tests.cpp: + + + + +FAILED: + REQUIRE( false ) +a := 1 +at Message.tests.cpp: + + + FAILED: @@ -1723,6 +1788,37 @@ at Message.tests.cpp: + + +FAILED: + CHECK( false ) +This will be reported multiple times +at Message.tests.cpp: + + +FAILED: + CHECK( false ) +This will be reported multiple times +at Message.tests.cpp: + + + + +FAILED: + REQUIRE( false ) +Should be in scope at the end +at Message.tests.cpp: + + + + +FAILED: + REQUIRE( false ) +Should survive a section end +at Message.tests.cpp: + + + @@ -1839,6 +1935,7 @@ at Misc.tests.cpp: + @@ -1893,6 +1990,10 @@ at Misc.tests.cpp: + + + + @@ -2200,6 +2301,7 @@ at Skip.tests.cpp: + diff --git a/tests/SelfTest/Baselines/sonarqube.sw.multi.approved.txt b/tests/SelfTest/Baselines/sonarqube.sw.multi.approved.txt index 2d8d510..98a06c1 100644 --- a/tests/SelfTest/Baselines/sonarqube.sw.multi.approved.txt +++ b/tests/SelfTest/Baselines/sonarqube.sw.multi.approved.txt @@ -2,6 +2,49 @@ + + +FAILED: + REQUIRE( i > 10 ) +with expansion: + 2 > 10 +at AssertionHandler.tests.cpp: + + +FAILED: + CHECK( foo( 2 ) == 2 ) +{ nested assertion failed } +at AssertionHandler.tests.cpp: + + + + +FAILED: + REQUIRE( i > 10 ) +with expansion: + 2 > 10 +at AssertionHandler.tests.cpp: + + +FAILED: + REQUIRE( foo( 2 ) == 2 ) +{ nested assertion failed } +at AssertionHandler.tests.cpp: + + + + +FAILED: +Throw a Catch::TestFailureException +at AssertionHandler.tests.cpp: + + +FAILED: + CHECK_NOTHROW( do_fail() ) +{ nested assertion failed } +at AssertionHandler.tests.cpp: + + FAILED: @@ -109,6 +152,7 @@ at AssertionHandler.tests.cpp: + @@ -172,16 +216,16 @@ at AssertionHandler.tests.cpp: - - - - - - - - - - + + + + + + + + + + @@ -343,6 +387,7 @@ at AssertionHandler.tests.cpp: + @@ -611,6 +656,7 @@ at Class.tests.cpp: + @@ -1007,10 +1053,10 @@ at Decomposition.tests.cpp: - + FAILED: + {Unknown expression after the reported line} expected exception -answer := 42 at Exception.tests.cpp: @@ -1141,8 +1187,9 @@ at Exception.tests.cpp: - + FAILED: + {Unknown expression after the reported line} unexpected exception at Exception.tests.cpp: @@ -1160,8 +1207,9 @@ at Exception.tests.cpp: - + FAILED: + {Unknown expression after the reported line} failure to init at Generators.tests.cpp: @@ -1633,6 +1681,23 @@ at Matchers.tests.cpp: + + +FAILED: + REQUIRE( false ) +a := 1 +at Message.tests.cpp: + + + + +FAILED: + REQUIRE( false ) +a := 1 +at Message.tests.cpp: + + + FAILED: @@ -1722,6 +1787,37 @@ at Message.tests.cpp: + + +FAILED: + CHECK( false ) +This will be reported multiple times +at Message.tests.cpp: + + +FAILED: + CHECK( false ) +This will be reported multiple times +at Message.tests.cpp: + + + + +FAILED: + REQUIRE( false ) +Should be in scope at the end +at Message.tests.cpp: + + + + +FAILED: + REQUIRE( false ) +Should survive a section end +at Message.tests.cpp: + + + @@ -1838,6 +1934,7 @@ at Misc.tests.cpp: + @@ -1892,6 +1989,10 @@ at Misc.tests.cpp: + + + + @@ -2199,6 +2300,7 @@ at Skip.tests.cpp: + diff --git a/tests/SelfTest/Baselines/tap.sw.approved.txt b/tests/SelfTest/Baselines/tap.sw.approved.txt index 801946d..3e5d183 100644 --- a/tests/SelfTest/Baselines/tap.sw.approved.txt +++ b/tests/SelfTest/Baselines/tap.sw.approved.txt @@ -165,9 +165,13 @@ ok {test-number} - smallest_non_zero, WithinULP( -smallest_non_zero, 2 ) for: 0. # #2152 - ULP checks between differently signed values were wrong - float ok {test-number} - smallest_non_zero, !WithinULP( -smallest_non_zero, 1 ) for: 0.0f not is within 1 ULPs of -1.40129846e-45f ([-2.80259693e-45, -0.00000000e+00]) # #2615 - Throwing in constructor generator fails test case but does not abort -not ok {test-number} - unexpected exception with message: 'failure to init' +not ok {test-number} - unexpected exception with message: 'failure to init'; expression was: {Unknown expression after the reported line} +# #3001: Enum-based bitfields can be captured +ok {test-number} - bf.e == 1 for: 1 == 1 +# #3001: Enum-based bitfields can be captured +ok {test-number} - 1 == bf.e for: 1 == 1 # #748 - captures with unexpected exceptions -not ok {test-number} - unexpected exception with message: 'answer := 42' with 1 message: 'expected exception' +not ok {test-number} - unexpected exception with message: 'expected exception'; expression was: {Unknown expression after the reported line} # #748 - captures with unexpected exceptions not ok {test-number} - unexpected exception with message: 'answer := 42'; expression was: thisThrows() with 1 message: 'expected exception' # #748 - captures with unexpected exceptions @@ -612,6 +616,16 @@ ok {test-number} - HasBitOperators{ 1 } & HasBitOperators{ 1 } for: Val: 1 & Val ok {test-number} - lhs ^ rhs for: Val: 1 ^ Val: 2 # Assertion macros support bit operators and bool conversions ok {test-number} - !(lhs ^ lhs) for: !(Val: 1 ^ Val: 1) +# Assertions can be nested - CHECK +not ok {test-number} - i > 10 for: 2 > 10 +# Assertions can be nested - CHECK +not ok {test-number} - unexpected exception with message: '{ nested assertion failed }'; expression was: foo( 2 ) == 2 +# Assertions can be nested - CHECK +ok {test-number} - true +# Assertions can be nested - REQUIRE +not ok {test-number} - i > 10 for: 2 > 10 +# Assertions can be nested - REQUIRE +not ok {test-number} - unexpected exception with message: '{ nested assertion failed }'; expression was: foo( 2 ) == 2 # Assertions then sections ok {test-number} - true # Assertions then sections @@ -674,6 +688,12 @@ ok {test-number} - with 11 messages: '("comma, in string", "escaped, \", ") := " ok {test-number} - true with 1 message: 'i := 2' # Capture and info messages ok {test-number} - true with 1 message: '3' +# Captures do not leave block with an exception +not ok {test-number} - false with 1 message: 'a := 1' +# Captures outlive section end +ok {test-number} - true with 1 message: 'a := 1' +# Captures outlive section end +not ok {test-number} - false with 1 message: 'a := 1' # CaseInsensitiveEqualsTo is case insensitive ok {test-number} - eq( "", "" ) for: true # CaseInsensitiveEqualsTo is case insensitive @@ -846,6 +866,20 @@ ok {test-number} - std::vector{1, 2} == std::vector{1, 2} for: { 1, 2 ok {test-number} - true # Commas in various macros are allowed ok {test-number} - std::vector{1, 2} == std::vector{1, 2} for: { 1, 2 } == { 1, 2 } +# Comparing (and stringifying) volatile pointers works +ok {test-number} - !(ptr) for: !0 +# Comparing (and stringifying) volatile pointers works +ok {test-number} - ptr == ptr for: 0 == 0 +# Comparing (and stringifying) volatile pointers works +ok {test-number} - !(ptr != ptr) for: !(0 != 0) +# Comparing (and stringifying) volatile pointers works +ok {test-number} - !(ptr < ptr) for: !(0 < 0) +# Comparing (and stringifying) volatile pointers works +ok {test-number} - ptr <= ptr for: 0 <= 0 +# Comparing (and stringifying) volatile pointers works +ok {test-number} - !(ptr > ptr) for: !(0 > 0) +# Comparing (and stringifying) volatile pointers works +ok {test-number} - ptr >= ptr for: 0 >= 0 # Comparing function pointers ok {test-number} - a for: 0x # Comparing function pointers @@ -1110,6 +1144,8 @@ ok {test-number} - thisThrows(), EndsWith( "exception" ) for: "expected exceptio ok {test-number} - thisThrows(), ContainsSubstring( "except" ) for: "expected exception" contains: "except" # Exception messages can be tested for ok {test-number} - thisThrows(), ContainsSubstring( "exCept", Catch::CaseSensitive::No ) for: "expected exception" contains: "except" (case insensitive) +# Exception thrown inside stringify does not fail the test +ok {test-number} - tos == tos for: { stringification failed with an exception: "Invalid" } == { stringification failed with an exception: "Invalid" } # Exceptions matchers ok {test-number} - throwsDerivedException(), DerivedException, Message( "DerivedException::what" ) for: DerivedException::what exception message matches "DerivedException::what" # Exceptions matchers @@ -1126,6 +1162,10 @@ not ok {test-number} - expected exception, got none; expression was: thisDoesntT not ok {test-number} - unexpected exception with message: 'expected exception'; expression was: thisThrows() # FAIL aborts the test not ok {test-number} - explicitly with 1 message: 'This is a failure' +# FAIL can be nested in assertion +not ok {test-number} - explicitly with 1 message: 'Throw a Catch::TestFailureException' +# FAIL can be nested in assertion +not ok {test-number} - unexpected exception with message: '{ nested assertion failed }'; expression was: do_fail() # FAIL does not require an argument not ok {test-number} - explicitly # FAIL_CHECK does not abort the test @@ -1465,6 +1505,14 @@ ok {test-number} - gen.get() == 1 for: 1 == 1 # Generators internals ok {test-number} - gen.next() for: true # Generators internals +ok {test-number} - gen.get() == 3 for: 3 == 3 +# Generators internals +ok {test-number} - !(gen.next()) for: !false +# Generators internals +ok {test-number} - gen.get() == 1 for: 1 == 1 +# Generators internals +ok {test-number} - gen.next() for: true +# Generators internals ok {test-number} - gen.get() == 2 for: 2 == 2 # Generators internals ok {test-number} - !(gen.next()) for: !false @@ -1904,23 +1952,23 @@ ok {test-number} - stream.str() == "{\n}" for: "{ }" == "{ }" ok {test-number} - stream.str() == "[\n]" for: "[ ]" == "[ ]" # JsonWriter ok {test-number} - stream.str() == "\"custom\"" for: ""custom"" == ""custom"" -# JsonWriter escapes charaters in strings properly +# JsonWriter escapes characters in strings properly ok {test-number} - sstream.str() == "\"\\\"\"" for: ""\""" == ""\""" -# JsonWriter escapes charaters in strings properly +# JsonWriter escapes characters in strings properly ok {test-number} - sstream.str() == "\"\\\\\"" for: ""\\"" == ""\\"" -# JsonWriter escapes charaters in strings properly +# JsonWriter escapes characters in strings properly ok {test-number} - sstream.str() == "\"/\"" for: ""/"" == ""/"" -# JsonWriter escapes charaters in strings properly +# JsonWriter escapes characters in strings properly ok {test-number} - sstream.str() == "\"\\b\"" for: ""\b"" == ""\b"" -# JsonWriter escapes charaters in strings properly +# JsonWriter escapes characters in strings properly ok {test-number} - sstream.str() == "\"\\f\"" for: ""\f"" == ""\f"" -# JsonWriter escapes charaters in strings properly +# JsonWriter escapes characters in strings properly ok {test-number} - sstream.str() == "\"\\n\"" for: ""\n"" == ""\n"" -# JsonWriter escapes charaters in strings properly +# JsonWriter escapes characters in strings properly ok {test-number} - sstream.str() == "\"\\r\"" for: ""\r"" == ""\r"" -# JsonWriter escapes charaters in strings properly +# JsonWriter escapes characters in strings properly ok {test-number} - sstream.str() == "\"\\t\"" for: ""\t"" == ""\t"" -# JsonWriter escapes charaters in strings properly +# JsonWriter escapes characters in strings properly ok {test-number} - sstream.str() == "\"\\\\/\\t\\r\\n\"" for: ""\\/\t\r\n"" == ""\\/\t\r\n"" # Lambdas in assertions ok {test-number} - []() { return true; }() for: true @@ -2616,6 +2664,16 @@ ok {test-number} - v.size() == 0 for: 0 == 0 ok {test-number} - v.capacity() >= 10 for: 10 >= 10 # Scenario: Vector resizing affects size and capacity ok {test-number} - v.size() == 0 for: 0 == 0 +# Scoped message applies to all assertions in scope +not ok {test-number} - false with 1 message: 'This will be reported multiple times' +# Scoped message applies to all assertions in scope +not ok {test-number} - false with 1 message: 'This will be reported multiple times' +# Scoped messages do not leave block with an exception +not ok {test-number} - false with 1 message: 'Should be in scope at the end' +# Scoped messages outlive section end +ok {test-number} - true with 1 message: 'Should survive a section end' +# Scoped messages outlive section end +not ok {test-number} - false with 1 message: 'Should survive a section end' A string sent directly to stdout A string sent directly to stderr A string sent to stderr via clog @@ -3772,7 +3830,7 @@ not ok {test-number} - unexpected exception with message: 'expected exception'; # When unchecked exceptions are thrown from functions they are always failures not ok {test-number} - unexpected exception with message: 'expected exception'; expression was: thisThrows() == 0 # When unchecked exceptions are thrown from sections they are always failures -not ok {test-number} - unexpected exception with message: 'unexpected exception' +not ok {test-number} - unexpected exception with message: 'unexpected exception'; expression was: {Unknown expression after the reported line} # X/level/0/a ok {test-number} - # X/level/0/b @@ -4019,29 +4077,29 @@ ok {test-number} - # SKIP not ok {test-number} - explicitly loose text artifact # is_unary_function -ok {test-number} - with 1 message: 'Catch::Clara::Detail::is_unary_function::value' +ok {test-number} - with 1 message: 'Catch::Clara::Detail::is_unary_function_v' # is_unary_function -ok {test-number} - with 1 message: 'Catch::Clara::Detail::is_unary_function::value' +ok {test-number} - with 1 message: 'Catch::Clara::Detail::is_unary_function_v' # is_unary_function -ok {test-number} - with 1 message: 'Catch::Clara::Detail::is_unary_function::value' +ok {test-number} - with 1 message: 'Catch::Clara::Detail::is_unary_function_v' # is_unary_function -ok {test-number} - with 1 message: 'Catch::Clara::Detail::is_unary_function::value' +ok {test-number} - with 1 message: 'Catch::Clara::Detail::is_unary_function_v' # is_unary_function -ok {test-number} - with 1 message: 'Catch::Clara::Detail::is_unary_function::value' +ok {test-number} - with 1 message: 'Catch::Clara::Detail::is_unary_function_v' # is_unary_function -ok {test-number} - with 1 message: 'Catch::Clara::Detail::is_unary_function::value' +ok {test-number} - with 1 message: 'Catch::Clara::Detail::is_unary_function_v' # is_unary_function -ok {test-number} - with 1 message: '!(Catch::Clara::Detail::is_unary_function::value)' +ok {test-number} - with 1 message: '!(Catch::Clara::Detail::is_unary_function_v)' # is_unary_function -ok {test-number} - with 1 message: '!(Catch::Clara::Detail::is_unary_function::value)' +ok {test-number} - with 1 message: '!(Catch::Clara::Detail::is_unary_function_v)' # is_unary_function -ok {test-number} - with 1 message: '!(Catch::Clara::Detail::is_unary_function::value)' +ok {test-number} - with 1 message: '!(Catch::Clara::Detail::is_unary_function_v)' # is_unary_function -ok {test-number} - with 1 message: '!(Catch::Clara::Detail::is_unary_function::value)' +ok {test-number} - with 1 message: '!(Catch::Clara::Detail::is_unary_function_v)' # is_unary_function -ok {test-number} - with 1 message: '!(Catch::Clara::Detail::is_unary_function::value)' +ok {test-number} - with 1 message: '!(Catch::Clara::Detail::is_unary_function_v)' # is_unary_function -ok {test-number} - with 1 message: '!(Catch::Clara::Detail::is_unary_function::value)' +ok {test-number} - with 1 message: '!(Catch::Clara::Detail::is_unary_function_v)' # just failure not ok {test-number} - explicitly with 1 message: 'Previous info should not be seen' # just failure after unscoped info @@ -4377,6 +4435,8 @@ ok {test-number} - data.str.size() == data.len for: 3 == 3 ok {test-number} - data.str.size() == data.len for: 5 == 5 # strlen3 ok {test-number} - data.str.size() == data.len for: 4 == 4 +# system_clock timepoint with non-default duration +ok {test-number} - tp1 == tp2 for: {iso8601-timestamp} == {iso8601-timestamp} # tables ok {test-number} - strlen(std::get<0>(data)) == static_cast(std::get<1>(data)) for: 5 == 5 # tables @@ -4567,5 +4627,5 @@ ok {test-number} - q3 == 23. for: 23.0 == 23.0 ok {test-number} - # xmlentitycheck ok {test-number} - -1..2281 +1..2315 diff --git a/tests/SelfTest/Baselines/tap.sw.multi.approved.txt b/tests/SelfTest/Baselines/tap.sw.multi.approved.txt index 7f281f6..0fdb266 100644 --- a/tests/SelfTest/Baselines/tap.sw.multi.approved.txt +++ b/tests/SelfTest/Baselines/tap.sw.multi.approved.txt @@ -163,9 +163,13 @@ ok {test-number} - smallest_non_zero, WithinULP( -smallest_non_zero, 2 ) for: 0. # #2152 - ULP checks between differently signed values were wrong - float ok {test-number} - smallest_non_zero, !WithinULP( -smallest_non_zero, 1 ) for: 0.0f not is within 1 ULPs of -1.40129846e-45f ([-2.80259693e-45, -0.00000000e+00]) # #2615 - Throwing in constructor generator fails test case but does not abort -not ok {test-number} - unexpected exception with message: 'failure to init' +not ok {test-number} - unexpected exception with message: 'failure to init'; expression was: {Unknown expression after the reported line} +# #3001: Enum-based bitfields can be captured +ok {test-number} - bf.e == 1 for: 1 == 1 +# #3001: Enum-based bitfields can be captured +ok {test-number} - 1 == bf.e for: 1 == 1 # #748 - captures with unexpected exceptions -not ok {test-number} - unexpected exception with message: 'answer := 42' with 1 message: 'expected exception' +not ok {test-number} - unexpected exception with message: 'expected exception'; expression was: {Unknown expression after the reported line} # #748 - captures with unexpected exceptions not ok {test-number} - unexpected exception with message: 'answer := 42'; expression was: thisThrows() with 1 message: 'expected exception' # #748 - captures with unexpected exceptions @@ -610,6 +614,16 @@ ok {test-number} - HasBitOperators{ 1 } & HasBitOperators{ 1 } for: Val: 1 & Val ok {test-number} - lhs ^ rhs for: Val: 1 ^ Val: 2 # Assertion macros support bit operators and bool conversions ok {test-number} - !(lhs ^ lhs) for: !(Val: 1 ^ Val: 1) +# Assertions can be nested - CHECK +not ok {test-number} - i > 10 for: 2 > 10 +# Assertions can be nested - CHECK +not ok {test-number} - unexpected exception with message: '{ nested assertion failed }'; expression was: foo( 2 ) == 2 +# Assertions can be nested - CHECK +ok {test-number} - true +# Assertions can be nested - REQUIRE +not ok {test-number} - i > 10 for: 2 > 10 +# Assertions can be nested - REQUIRE +not ok {test-number} - unexpected exception with message: '{ nested assertion failed }'; expression was: foo( 2 ) == 2 # Assertions then sections ok {test-number} - true # Assertions then sections @@ -672,6 +686,12 @@ ok {test-number} - with 11 messages: '("comma, in string", "escaped, \", ") := " ok {test-number} - true with 1 message: 'i := 2' # Capture and info messages ok {test-number} - true with 1 message: '3' +# Captures do not leave block with an exception +not ok {test-number} - false with 1 message: 'a := 1' +# Captures outlive section end +ok {test-number} - true with 1 message: 'a := 1' +# Captures outlive section end +not ok {test-number} - false with 1 message: 'a := 1' # CaseInsensitiveEqualsTo is case insensitive ok {test-number} - eq( "", "" ) for: true # CaseInsensitiveEqualsTo is case insensitive @@ -844,6 +864,20 @@ ok {test-number} - std::vector{1, 2} == std::vector{1, 2} for: { 1, 2 ok {test-number} - true # Commas in various macros are allowed ok {test-number} - std::vector{1, 2} == std::vector{1, 2} for: { 1, 2 } == { 1, 2 } +# Comparing (and stringifying) volatile pointers works +ok {test-number} - !(ptr) for: !0 +# Comparing (and stringifying) volatile pointers works +ok {test-number} - ptr == ptr for: 0 == 0 +# Comparing (and stringifying) volatile pointers works +ok {test-number} - !(ptr != ptr) for: !(0 != 0) +# Comparing (and stringifying) volatile pointers works +ok {test-number} - !(ptr < ptr) for: !(0 < 0) +# Comparing (and stringifying) volatile pointers works +ok {test-number} - ptr <= ptr for: 0 <= 0 +# Comparing (and stringifying) volatile pointers works +ok {test-number} - !(ptr > ptr) for: !(0 > 0) +# Comparing (and stringifying) volatile pointers works +ok {test-number} - ptr >= ptr for: 0 >= 0 # Comparing function pointers ok {test-number} - a for: 0x # Comparing function pointers @@ -1108,6 +1142,8 @@ ok {test-number} - thisThrows(), EndsWith( "exception" ) for: "expected exceptio ok {test-number} - thisThrows(), ContainsSubstring( "except" ) for: "expected exception" contains: "except" # Exception messages can be tested for ok {test-number} - thisThrows(), ContainsSubstring( "exCept", Catch::CaseSensitive::No ) for: "expected exception" contains: "except" (case insensitive) +# Exception thrown inside stringify does not fail the test +ok {test-number} - tos == tos for: { stringification failed with an exception: "Invalid" } == { stringification failed with an exception: "Invalid" } # Exceptions matchers ok {test-number} - throwsDerivedException(), DerivedException, Message( "DerivedException::what" ) for: DerivedException::what exception message matches "DerivedException::what" # Exceptions matchers @@ -1124,6 +1160,10 @@ not ok {test-number} - expected exception, got none; expression was: thisDoesntT not ok {test-number} - unexpected exception with message: 'expected exception'; expression was: thisThrows() # FAIL aborts the test not ok {test-number} - explicitly with 1 message: 'This is a failure' +# FAIL can be nested in assertion +not ok {test-number} - explicitly with 1 message: 'Throw a Catch::TestFailureException' +# FAIL can be nested in assertion +not ok {test-number} - unexpected exception with message: '{ nested assertion failed }'; expression was: do_fail() # FAIL does not require an argument not ok {test-number} - explicitly # FAIL_CHECK does not abort the test @@ -1463,6 +1503,14 @@ ok {test-number} - gen.get() == 1 for: 1 == 1 # Generators internals ok {test-number} - gen.next() for: true # Generators internals +ok {test-number} - gen.get() == 3 for: 3 == 3 +# Generators internals +ok {test-number} - !(gen.next()) for: !false +# Generators internals +ok {test-number} - gen.get() == 1 for: 1 == 1 +# Generators internals +ok {test-number} - gen.next() for: true +# Generators internals ok {test-number} - gen.get() == 2 for: 2 == 2 # Generators internals ok {test-number} - !(gen.next()) for: !false @@ -1902,23 +1950,23 @@ ok {test-number} - stream.str() == "{\n}" for: "{ }" == "{ }" ok {test-number} - stream.str() == "[\n]" for: "[ ]" == "[ ]" # JsonWriter ok {test-number} - stream.str() == "\"custom\"" for: ""custom"" == ""custom"" -# JsonWriter escapes charaters in strings properly +# JsonWriter escapes characters in strings properly ok {test-number} - sstream.str() == "\"\\\"\"" for: ""\""" == ""\""" -# JsonWriter escapes charaters in strings properly +# JsonWriter escapes characters in strings properly ok {test-number} - sstream.str() == "\"\\\\\"" for: ""\\"" == ""\\"" -# JsonWriter escapes charaters in strings properly +# JsonWriter escapes characters in strings properly ok {test-number} - sstream.str() == "\"/\"" for: ""/"" == ""/"" -# JsonWriter escapes charaters in strings properly +# JsonWriter escapes characters in strings properly ok {test-number} - sstream.str() == "\"\\b\"" for: ""\b"" == ""\b"" -# JsonWriter escapes charaters in strings properly +# JsonWriter escapes characters in strings properly ok {test-number} - sstream.str() == "\"\\f\"" for: ""\f"" == ""\f"" -# JsonWriter escapes charaters in strings properly +# JsonWriter escapes characters in strings properly ok {test-number} - sstream.str() == "\"\\n\"" for: ""\n"" == ""\n"" -# JsonWriter escapes charaters in strings properly +# JsonWriter escapes characters in strings properly ok {test-number} - sstream.str() == "\"\\r\"" for: ""\r"" == ""\r"" -# JsonWriter escapes charaters in strings properly +# JsonWriter escapes characters in strings properly ok {test-number} - sstream.str() == "\"\\t\"" for: ""\t"" == ""\t"" -# JsonWriter escapes charaters in strings properly +# JsonWriter escapes characters in strings properly ok {test-number} - sstream.str() == "\"\\\\/\\t\\r\\n\"" for: ""\\/\t\r\n"" == ""\\/\t\r\n"" # Lambdas in assertions ok {test-number} - []() { return true; }() for: true @@ -2614,6 +2662,16 @@ ok {test-number} - v.size() == 0 for: 0 == 0 ok {test-number} - v.capacity() >= 10 for: 10 >= 10 # Scenario: Vector resizing affects size and capacity ok {test-number} - v.size() == 0 for: 0 == 0 +# Scoped message applies to all assertions in scope +not ok {test-number} - false with 1 message: 'This will be reported multiple times' +# Scoped message applies to all assertions in scope +not ok {test-number} - false with 1 message: 'This will be reported multiple times' +# Scoped messages do not leave block with an exception +not ok {test-number} - false with 1 message: 'Should be in scope at the end' +# Scoped messages outlive section end +ok {test-number} - true with 1 message: 'Should survive a section end' +# Scoped messages outlive section end +not ok {test-number} - false with 1 message: 'Should survive a section end' # Some simple comparisons between doubles ok {test-number} - d == Approx( 1.23 ) for: 1.22999999999999998 == Approx( 1.22999999999999998 ) # Some simple comparisons between doubles @@ -3765,7 +3823,7 @@ not ok {test-number} - unexpected exception with message: 'expected exception'; # When unchecked exceptions are thrown from functions they are always failures not ok {test-number} - unexpected exception with message: 'expected exception'; expression was: thisThrows() == 0 # When unchecked exceptions are thrown from sections they are always failures -not ok {test-number} - unexpected exception with message: 'unexpected exception' +not ok {test-number} - unexpected exception with message: 'unexpected exception'; expression was: {Unknown expression after the reported line} # X/level/0/a ok {test-number} - # X/level/0/b @@ -4011,29 +4069,29 @@ ok {test-number} - # SKIP # failing in some unskipped sections causes entire test case to fail not ok {test-number} - explicitly # is_unary_function -ok {test-number} - with 1 message: 'Catch::Clara::Detail::is_unary_function::value' +ok {test-number} - with 1 message: 'Catch::Clara::Detail::is_unary_function_v' # is_unary_function -ok {test-number} - with 1 message: 'Catch::Clara::Detail::is_unary_function::value' +ok {test-number} - with 1 message: 'Catch::Clara::Detail::is_unary_function_v' # is_unary_function -ok {test-number} - with 1 message: 'Catch::Clara::Detail::is_unary_function::value' +ok {test-number} - with 1 message: 'Catch::Clara::Detail::is_unary_function_v' # is_unary_function -ok {test-number} - with 1 message: 'Catch::Clara::Detail::is_unary_function::value' +ok {test-number} - with 1 message: 'Catch::Clara::Detail::is_unary_function_v' # is_unary_function -ok {test-number} - with 1 message: 'Catch::Clara::Detail::is_unary_function::value' +ok {test-number} - with 1 message: 'Catch::Clara::Detail::is_unary_function_v' # is_unary_function -ok {test-number} - with 1 message: 'Catch::Clara::Detail::is_unary_function::value' +ok {test-number} - with 1 message: 'Catch::Clara::Detail::is_unary_function_v' # is_unary_function -ok {test-number} - with 1 message: '!(Catch::Clara::Detail::is_unary_function::value)' +ok {test-number} - with 1 message: '!(Catch::Clara::Detail::is_unary_function_v)' # is_unary_function -ok {test-number} - with 1 message: '!(Catch::Clara::Detail::is_unary_function::value)' +ok {test-number} - with 1 message: '!(Catch::Clara::Detail::is_unary_function_v)' # is_unary_function -ok {test-number} - with 1 message: '!(Catch::Clara::Detail::is_unary_function::value)' +ok {test-number} - with 1 message: '!(Catch::Clara::Detail::is_unary_function_v)' # is_unary_function -ok {test-number} - with 1 message: '!(Catch::Clara::Detail::is_unary_function::value)' +ok {test-number} - with 1 message: '!(Catch::Clara::Detail::is_unary_function_v)' # is_unary_function -ok {test-number} - with 1 message: '!(Catch::Clara::Detail::is_unary_function::value)' +ok {test-number} - with 1 message: '!(Catch::Clara::Detail::is_unary_function_v)' # is_unary_function -ok {test-number} - with 1 message: '!(Catch::Clara::Detail::is_unary_function::value)' +ok {test-number} - with 1 message: '!(Catch::Clara::Detail::is_unary_function_v)' # just failure not ok {test-number} - explicitly with 1 message: 'Previous info should not be seen' # just failure after unscoped info @@ -4366,6 +4424,8 @@ ok {test-number} - data.str.size() == data.len for: 3 == 3 ok {test-number} - data.str.size() == data.len for: 5 == 5 # strlen3 ok {test-number} - data.str.size() == data.len for: 4 == 4 +# system_clock timepoint with non-default duration +ok {test-number} - tp1 == tp2 for: {iso8601-timestamp} == {iso8601-timestamp} # tables ok {test-number} - strlen(std::get<0>(data)) == static_cast(std::get<1>(data)) for: 5 == 5 # tables @@ -4556,5 +4616,5 @@ ok {test-number} - q3 == 23. for: 23.0 == 23.0 ok {test-number} - # xmlentitycheck ok {test-number} - -1..2281 +1..2315 diff --git a/tests/SelfTest/Baselines/teamcity.sw.approved.txt b/tests/SelfTest/Baselines/teamcity.sw.approved.txt index db48552..853f3d8 100644 --- a/tests/SelfTest/Baselines/teamcity.sw.approved.txt +++ b/tests/SelfTest/Baselines/teamcity.sw.approved.txt @@ -53,10 +53,12 @@ ##teamcity[testStarted name='#2152 - ULP checks between differently signed values were wrong - float'] ##teamcity[testFinished name='#2152 - ULP checks between differently signed values were wrong - float' duration="{duration}"] ##teamcity[testStarted name='#2615 - Throwing in constructor generator fails test case but does not abort'] -##teamcity[testIgnored name='#2615 - Throwing in constructor generator fails test case but does not abort' message='Generators.tests.cpp:|n...............................................................................|n|nGenerators.tests.cpp:|nunexpected exception with message:|n "failure to init"- failure ignore as test marked as |'ok to fail|'|n'] +##teamcity[testIgnored name='#2615 - Throwing in constructor generator fails test case but does not abort' message='Generators.tests.cpp:|n...............................................................................|n|nGenerators.tests.cpp:|nunexpected exception with message:|n "failure to init"|n {Unknown expression after the reported line}|nwith expansion:|n {Unknown expression after the reported line}|n- failure ignore as test marked as |'ok to fail|'|n'] ##teamcity[testFinished name='#2615 - Throwing in constructor generator fails test case but does not abort' duration="{duration}"] +##teamcity[testStarted name='#3001: Enum-based bitfields can be captured'] +##teamcity[testFinished name='#3001: Enum-based bitfields can be captured' duration="{duration}"] ##teamcity[testStarted name='#748 - captures with unexpected exceptions'] -##teamcity[testIgnored name='#748 - captures with unexpected exceptions' message='-------------------------------------------------------------------------------|noutside assertions|n-------------------------------------------------------------------------------|nException.tests.cpp:|n...............................................................................|n|nException.tests.cpp:|nunexpected exception with messages:|n "answer := 42"|n "expected exception"- failure ignore as test marked as |'ok to fail|'|n'] +##teamcity[testIgnored name='#748 - captures with unexpected exceptions' message='-------------------------------------------------------------------------------|noutside assertions|n-------------------------------------------------------------------------------|nException.tests.cpp:|n...............................................................................|n|nException.tests.cpp:|nunexpected exception with message:|n "expected exception"|n {Unknown expression after the reported line}|nwith expansion:|n {Unknown expression after the reported line}|n- failure ignore as test marked as |'ok to fail|'|n'] ##teamcity[testIgnored name='#748 - captures with unexpected exceptions' message='-------------------------------------------------------------------------------|ninside REQUIRE_NOTHROW|n-------------------------------------------------------------------------------|nException.tests.cpp:|n...............................................................................|n|nException.tests.cpp:|nunexpected exception with messages:|n "answer := 42"|n "expected exception"|n REQUIRE_NOTHROW( thisThrows() )|nwith expansion:|n thisThrows()|n- failure ignore as test marked as |'ok to fail|'|n'] ##teamcity[testFinished name='#748 - captures with unexpected exceptions' duration="{duration}"] ##teamcity[testStarted name='#809'] @@ -225,6 +227,14 @@ ##teamcity[testFinished name='Arbitrary predicate matcher' duration="{duration}"] ##teamcity[testStarted name='Assertion macros support bit operators and bool conversions'] ##teamcity[testFinished name='Assertion macros support bit operators and bool conversions' duration="{duration}"] +##teamcity[testStarted name='Assertions can be nested - CHECK'] +##teamcity[testIgnored name='Assertions can be nested - CHECK' message='AssertionHandler.tests.cpp:|n...............................................................................|n|nAssertionHandler.tests.cpp:|nexpression failed|n REQUIRE( i > 10 )|nwith expansion:|n 2 > 10|n- failure ignore as test marked as |'ok to fail|'|n'] +##teamcity[testIgnored name='Assertions can be nested - CHECK' message='AssertionHandler.tests.cpp:|nunexpected exception with message:|n "{ nested assertion failed }"|n CHECK( foo( 2 ) == 2 )|nwith expansion:|n foo( 2 ) == 2|n- failure ignore as test marked as |'ok to fail|'|n'] +##teamcity[testFinished name='Assertions can be nested - CHECK' duration="{duration}"] +##teamcity[testStarted name='Assertions can be nested - REQUIRE'] +##teamcity[testIgnored name='Assertions can be nested - REQUIRE' message='AssertionHandler.tests.cpp:|n...............................................................................|n|nAssertionHandler.tests.cpp:|nexpression failed|n REQUIRE( i > 10 )|nwith expansion:|n 2 > 10|n- failure ignore as test marked as |'ok to fail|'|n'] +##teamcity[testIgnored name='Assertions can be nested - REQUIRE' message='AssertionHandler.tests.cpp:|nunexpected exception with message:|n "{ nested assertion failed }"|n REQUIRE( foo( 2 ) == 2 )|nwith expansion:|n foo( 2 ) == 2|n- failure ignore as test marked as |'ok to fail|'|n'] +##teamcity[testFinished name='Assertions can be nested - REQUIRE' duration="{duration}"] ##teamcity[testStarted name='Assertions then sections'] ##teamcity[testFinished name='Assertions then sections' duration="{duration}"] ##teamcity[testStarted name='Basic use of the Contains range matcher'] @@ -239,6 +249,12 @@ ##teamcity[testFinished name='CAPTURE parses string and character constants' duration="{duration}"] ##teamcity[testStarted name='Capture and info messages'] ##teamcity[testFinished name='Capture and info messages' duration="{duration}"] +##teamcity[testStarted name='Captures do not leave block with an exception'] +##teamcity[testFailed name='Captures do not leave block with an exception' message='Message.tests.cpp:|n...............................................................................|n|nMessage.tests.cpp:|nexpression failed with message:|n "a := 1"|n REQUIRE( false )|nwith expansion:|n false|n'] +##teamcity[testFinished name='Captures do not leave block with an exception' duration="{duration}"] +##teamcity[testStarted name='Captures outlive section end'] +##teamcity[testFailed name='Captures outlive section end' message='Message.tests.cpp:|n...............................................................................|n|nMessage.tests.cpp:|nexpression failed with message:|n "a := 1"|n REQUIRE( false )|nwith expansion:|n false|n'] +##teamcity[testFinished name='Captures outlive section end' duration="{duration}"] ##teamcity[testStarted name='CaseInsensitiveEqualsTo is case insensitive'] ##teamcity[testFinished name='CaseInsensitiveEqualsTo is case insensitive' duration="{duration}"] ##teamcity[testStarted name='CaseInsensitiveLess is case insensitive'] @@ -269,6 +285,8 @@ ##teamcity[testFinished name='Combining templated matchers' duration="{duration}"] ##teamcity[testStarted name='Commas in various macros are allowed'] ##teamcity[testFinished name='Commas in various macros are allowed' duration="{duration}"] +##teamcity[testStarted name='Comparing (and stringifying) volatile pointers works'] +##teamcity[testFinished name='Comparing (and stringifying) volatile pointers works' duration="{duration}"] ##teamcity[testStarted name='Comparing function pointers'] ##teamcity[testFinished name='Comparing function pointers' duration="{duration}"] ##teamcity[testStarted name='Comparison ops'] @@ -360,6 +378,8 @@ ##teamcity[testFinished name='Exception message can be matched' duration="{duration}"] ##teamcity[testStarted name='Exception messages can be tested for'] ##teamcity[testFinished name='Exception messages can be tested for' duration="{duration}"] +##teamcity[testStarted name='Exception thrown inside stringify does not fail the test'] +##teamcity[testFinished name='Exception thrown inside stringify does not fail the test' duration="{duration}"] ##teamcity[testStarted name='Exceptions matchers'] ##teamcity[testFinished name='Exceptions matchers' duration="{duration}"] ##teamcity[testStarted name='Expected exceptions that don|'t throw or unexpected exceptions fail the test'] @@ -370,6 +390,10 @@ ##teamcity[testStarted name='FAIL aborts the test'] ##teamcity[testFailed name='FAIL aborts the test' message='Message.tests.cpp:|n...............................................................................|n|nMessage.tests.cpp:|nexplicit failure with message:|n "This is a failure"'] ##teamcity[testFinished name='FAIL aborts the test' duration="{duration}"] +##teamcity[testStarted name='FAIL can be nested in assertion'] +##teamcity[testIgnored name='FAIL can be nested in assertion' message='AssertionHandler.tests.cpp:|n...............................................................................|n|nAssertionHandler.tests.cpp:|nexplicit failure with message:|n "Throw a Catch::TestFailureException"- failure ignore as test marked as |'ok to fail|'|n'] +##teamcity[testIgnored name='FAIL can be nested in assertion' message='AssertionHandler.tests.cpp:|nunexpected exception with message:|n "{ nested assertion failed }"|n CHECK_NOTHROW( do_fail() )|nwith expansion:|n do_fail()|n- failure ignore as test marked as |'ok to fail|'|n'] +##teamcity[testFinished name='FAIL can be nested in assertion' duration="{duration}"] ##teamcity[testStarted name='FAIL does not require an argument'] ##teamcity[testFailed name='FAIL does not require an argument' message='Message.tests.cpp:|n...............................................................................|n|nMessage.tests.cpp:|nexplicit failure'] ##teamcity[testFinished name='FAIL does not require an argument' duration="{duration}"] @@ -431,8 +455,8 @@ ##teamcity[testFinished name='Inequality checks that should succeed' duration="{duration}"] ##teamcity[testStarted name='JsonWriter'] ##teamcity[testFinished name='JsonWriter' duration="{duration}"] -##teamcity[testStarted name='JsonWriter escapes charaters in strings properly'] -##teamcity[testFinished name='JsonWriter escapes charaters in strings properly' duration="{duration}"] +##teamcity[testStarted name='JsonWriter escapes characters in strings properly'] +##teamcity[testFinished name='JsonWriter escapes characters in strings properly' duration="{duration}"] ##teamcity[testStarted name='Lambdas in assertions'] ##teamcity[testFinished name='Lambdas in assertions' duration="{duration}"] ##teamcity[testStarted name='Less-than inequalities with different epsilons'] @@ -566,6 +590,16 @@ ##teamcity[testFinished name='Scenario: This is a really long scenario name to see how the list command deals with wrapping' duration="{duration}"] ##teamcity[testStarted name='Scenario: Vector resizing affects size and capacity'] ##teamcity[testFinished name='Scenario: Vector resizing affects size and capacity' duration="{duration}"] +##teamcity[testStarted name='Scoped message applies to all assertions in scope'] +##teamcity[testFailed name='Scoped message applies to all assertions in scope' message='Message.tests.cpp:|n...............................................................................|n|nMessage.tests.cpp:|nexpression failed with message:|n "This will be reported multiple times"|n CHECK( false )|nwith expansion:|n false|n'] +##teamcity[testFailed name='Scoped message applies to all assertions in scope' message='Message.tests.cpp:|nexpression failed with message:|n "This will be reported multiple times"|n CHECK( false )|nwith expansion:|n false|n'] +##teamcity[testFinished name='Scoped message applies to all assertions in scope' duration="{duration}"] +##teamcity[testStarted name='Scoped messages do not leave block with an exception'] +##teamcity[testFailed name='Scoped messages do not leave block with an exception' message='Message.tests.cpp:|n...............................................................................|n|nMessage.tests.cpp:|nexpression failed with message:|n "Should be in scope at the end"|n REQUIRE( false )|nwith expansion:|n false|n'] +##teamcity[testFinished name='Scoped messages do not leave block with an exception' duration="{duration}"] +##teamcity[testStarted name='Scoped messages outlive section end'] +##teamcity[testFailed name='Scoped messages outlive section end' message='Message.tests.cpp:|n...............................................................................|n|nMessage.tests.cpp:|nexpression failed with message:|n "Should survive a section end"|n REQUIRE( false )|nwith expansion:|n false|n'] +##teamcity[testFinished name='Scoped messages outlive section end' duration="{duration}"] ##teamcity[testStarted name='Sends stuff to stdout and stderr'] ##teamcity[testStdOut name='Sends stuff to stdout and stderr' out='A string sent directly to stdout|n'] ##teamcity[testStdErr name='Sends stuff to stdout and stderr' out='A string sent directly to stderr|nA string sent to stderr via clog|n'] @@ -634,6 +668,14 @@ ##teamcity[testFinished name='TemplateTest: vectors can be sized and resized - std::string' duration="{duration}"] ##teamcity[testStarted name='TemplateTest: vectors can be sized and resized - std::tuple'] ##teamcity[testFinished name='TemplateTest: vectors can be sized and resized - std::tuple' duration="{duration}"] +##teamcity[testStarted name='TemplateTestSig: compiles with a single int parameter - 1'] +##teamcity[testFinished name='TemplateTestSig: compiles with a single int parameter - 1' duration="{duration}"] +##teamcity[testStarted name='TemplateTestSig: compiles with a single int parameter - 3'] +##teamcity[testFinished name='TemplateTestSig: compiles with a single int parameter - 3' duration="{duration}"] +##teamcity[testStarted name='TemplateTestSig: compiles with a single int parameter - 6'] +##teamcity[testFinished name='TemplateTestSig: compiles with a single int parameter - 6' duration="{duration}"] +##teamcity[testStarted name='TemplateTestSig: compiles with two type parameters - int,int'] +##teamcity[testFinished name='TemplateTestSig: compiles with two type parameters - int,int' duration="{duration}"] ##teamcity[testStarted name='TemplateTestSig: vectors can be sized and resized - (std::tuple), 6'] ##teamcity[testFinished name='TemplateTestSig: vectors can be sized and resized - (std::tuple), 6' duration="{duration}"] ##teamcity[testStarted name='TemplateTestSig: vectors can be sized and resized - float,4'] @@ -743,7 +785,7 @@ ##teamcity[testFailed name='When unchecked exceptions are thrown from functions they are always failures' message='Exception.tests.cpp:|n...............................................................................|n|nException.tests.cpp:|nunexpected exception with message:|n "expected exception"|n CHECK( thisThrows() == 0 )|nwith expansion:|n thisThrows() == 0|n'] ##teamcity[testFinished name='When unchecked exceptions are thrown from functions they are always failures' duration="{duration}"] ##teamcity[testStarted name='When unchecked exceptions are thrown from sections they are always failures'] -##teamcity[testFailed name='When unchecked exceptions are thrown from sections they are always failures' message='-------------------------------------------------------------------------------|nsection name|n-------------------------------------------------------------------------------|nException.tests.cpp:|n...............................................................................|n|nException.tests.cpp:|nunexpected exception with message:|n "unexpected exception"'] +##teamcity[testFailed name='When unchecked exceptions are thrown from sections they are always failures' message='-------------------------------------------------------------------------------|nsection name|n-------------------------------------------------------------------------------|nException.tests.cpp:|n...............................................................................|n|nException.tests.cpp:|nunexpected exception with message:|n "unexpected exception"|n {Unknown expression after the reported line}|nwith expansion:|n {Unknown expression after the reported line}|n'] ##teamcity[testFinished name='When unchecked exceptions are thrown from sections they are always failures' duration="{duration}"] ##teamcity[testStarted name='When unchecked exceptions are thrown, but caught, they do not affect the test'] ##teamcity[testFinished name='When unchecked exceptions are thrown, but caught, they do not affect the test' duration="{duration}"] @@ -961,6 +1003,8 @@ loose text artifact ##teamcity[testFinished name='stringify( vectors )' duration="{duration}"] ##teamcity[testStarted name='strlen3'] ##teamcity[testFinished name='strlen3' duration="{duration}"] +##teamcity[testStarted name='system_clock timepoint with non-default duration'] +##teamcity[testFinished name='system_clock timepoint with non-default duration' duration="{duration}"] ##teamcity[testStarted name='tables'] ##teamcity[testFinished name='tables' duration="{duration}"] ##teamcity[testStarted name='tags with dots in later positions are not parsed as hidden'] diff --git a/tests/SelfTest/Baselines/teamcity.sw.multi.approved.txt b/tests/SelfTest/Baselines/teamcity.sw.multi.approved.txt index dde8a68..10e318d 100644 --- a/tests/SelfTest/Baselines/teamcity.sw.multi.approved.txt +++ b/tests/SelfTest/Baselines/teamcity.sw.multi.approved.txt @@ -53,10 +53,12 @@ ##teamcity[testStarted name='#2152 - ULP checks between differently signed values were wrong - float'] ##teamcity[testFinished name='#2152 - ULP checks between differently signed values were wrong - float' duration="{duration}"] ##teamcity[testStarted name='#2615 - Throwing in constructor generator fails test case but does not abort'] -##teamcity[testIgnored name='#2615 - Throwing in constructor generator fails test case but does not abort' message='Generators.tests.cpp:|n...............................................................................|n|nGenerators.tests.cpp:|nunexpected exception with message:|n "failure to init"- failure ignore as test marked as |'ok to fail|'|n'] +##teamcity[testIgnored name='#2615 - Throwing in constructor generator fails test case but does not abort' message='Generators.tests.cpp:|n...............................................................................|n|nGenerators.tests.cpp:|nunexpected exception with message:|n "failure to init"|n {Unknown expression after the reported line}|nwith expansion:|n {Unknown expression after the reported line}|n- failure ignore as test marked as |'ok to fail|'|n'] ##teamcity[testFinished name='#2615 - Throwing in constructor generator fails test case but does not abort' duration="{duration}"] +##teamcity[testStarted name='#3001: Enum-based bitfields can be captured'] +##teamcity[testFinished name='#3001: Enum-based bitfields can be captured' duration="{duration}"] ##teamcity[testStarted name='#748 - captures with unexpected exceptions'] -##teamcity[testIgnored name='#748 - captures with unexpected exceptions' message='-------------------------------------------------------------------------------|noutside assertions|n-------------------------------------------------------------------------------|nException.tests.cpp:|n...............................................................................|n|nException.tests.cpp:|nunexpected exception with messages:|n "answer := 42"|n "expected exception"- failure ignore as test marked as |'ok to fail|'|n'] +##teamcity[testIgnored name='#748 - captures with unexpected exceptions' message='-------------------------------------------------------------------------------|noutside assertions|n-------------------------------------------------------------------------------|nException.tests.cpp:|n...............................................................................|n|nException.tests.cpp:|nunexpected exception with message:|n "expected exception"|n {Unknown expression after the reported line}|nwith expansion:|n {Unknown expression after the reported line}|n- failure ignore as test marked as |'ok to fail|'|n'] ##teamcity[testIgnored name='#748 - captures with unexpected exceptions' message='-------------------------------------------------------------------------------|ninside REQUIRE_NOTHROW|n-------------------------------------------------------------------------------|nException.tests.cpp:|n...............................................................................|n|nException.tests.cpp:|nunexpected exception with messages:|n "answer := 42"|n "expected exception"|n REQUIRE_NOTHROW( thisThrows() )|nwith expansion:|n thisThrows()|n- failure ignore as test marked as |'ok to fail|'|n'] ##teamcity[testFinished name='#748 - captures with unexpected exceptions' duration="{duration}"] ##teamcity[testStarted name='#809'] @@ -225,6 +227,14 @@ ##teamcity[testFinished name='Arbitrary predicate matcher' duration="{duration}"] ##teamcity[testStarted name='Assertion macros support bit operators and bool conversions'] ##teamcity[testFinished name='Assertion macros support bit operators and bool conversions' duration="{duration}"] +##teamcity[testStarted name='Assertions can be nested - CHECK'] +##teamcity[testIgnored name='Assertions can be nested - CHECK' message='AssertionHandler.tests.cpp:|n...............................................................................|n|nAssertionHandler.tests.cpp:|nexpression failed|n REQUIRE( i > 10 )|nwith expansion:|n 2 > 10|n- failure ignore as test marked as |'ok to fail|'|n'] +##teamcity[testIgnored name='Assertions can be nested - CHECK' message='AssertionHandler.tests.cpp:|nunexpected exception with message:|n "{ nested assertion failed }"|n CHECK( foo( 2 ) == 2 )|nwith expansion:|n foo( 2 ) == 2|n- failure ignore as test marked as |'ok to fail|'|n'] +##teamcity[testFinished name='Assertions can be nested - CHECK' duration="{duration}"] +##teamcity[testStarted name='Assertions can be nested - REQUIRE'] +##teamcity[testIgnored name='Assertions can be nested - REQUIRE' message='AssertionHandler.tests.cpp:|n...............................................................................|n|nAssertionHandler.tests.cpp:|nexpression failed|n REQUIRE( i > 10 )|nwith expansion:|n 2 > 10|n- failure ignore as test marked as |'ok to fail|'|n'] +##teamcity[testIgnored name='Assertions can be nested - REQUIRE' message='AssertionHandler.tests.cpp:|nunexpected exception with message:|n "{ nested assertion failed }"|n REQUIRE( foo( 2 ) == 2 )|nwith expansion:|n foo( 2 ) == 2|n- failure ignore as test marked as |'ok to fail|'|n'] +##teamcity[testFinished name='Assertions can be nested - REQUIRE' duration="{duration}"] ##teamcity[testStarted name='Assertions then sections'] ##teamcity[testFinished name='Assertions then sections' duration="{duration}"] ##teamcity[testStarted name='Basic use of the Contains range matcher'] @@ -239,6 +249,12 @@ ##teamcity[testFinished name='CAPTURE parses string and character constants' duration="{duration}"] ##teamcity[testStarted name='Capture and info messages'] ##teamcity[testFinished name='Capture and info messages' duration="{duration}"] +##teamcity[testStarted name='Captures do not leave block with an exception'] +##teamcity[testFailed name='Captures do not leave block with an exception' message='Message.tests.cpp:|n...............................................................................|n|nMessage.tests.cpp:|nexpression failed with message:|n "a := 1"|n REQUIRE( false )|nwith expansion:|n false|n'] +##teamcity[testFinished name='Captures do not leave block with an exception' duration="{duration}"] +##teamcity[testStarted name='Captures outlive section end'] +##teamcity[testFailed name='Captures outlive section end' message='Message.tests.cpp:|n...............................................................................|n|nMessage.tests.cpp:|nexpression failed with message:|n "a := 1"|n REQUIRE( false )|nwith expansion:|n false|n'] +##teamcity[testFinished name='Captures outlive section end' duration="{duration}"] ##teamcity[testStarted name='CaseInsensitiveEqualsTo is case insensitive'] ##teamcity[testFinished name='CaseInsensitiveEqualsTo is case insensitive' duration="{duration}"] ##teamcity[testStarted name='CaseInsensitiveLess is case insensitive'] @@ -269,6 +285,8 @@ ##teamcity[testFinished name='Combining templated matchers' duration="{duration}"] ##teamcity[testStarted name='Commas in various macros are allowed'] ##teamcity[testFinished name='Commas in various macros are allowed' duration="{duration}"] +##teamcity[testStarted name='Comparing (and stringifying) volatile pointers works'] +##teamcity[testFinished name='Comparing (and stringifying) volatile pointers works' duration="{duration}"] ##teamcity[testStarted name='Comparing function pointers'] ##teamcity[testFinished name='Comparing function pointers' duration="{duration}"] ##teamcity[testStarted name='Comparison ops'] @@ -360,6 +378,8 @@ ##teamcity[testFinished name='Exception message can be matched' duration="{duration}"] ##teamcity[testStarted name='Exception messages can be tested for'] ##teamcity[testFinished name='Exception messages can be tested for' duration="{duration}"] +##teamcity[testStarted name='Exception thrown inside stringify does not fail the test'] +##teamcity[testFinished name='Exception thrown inside stringify does not fail the test' duration="{duration}"] ##teamcity[testStarted name='Exceptions matchers'] ##teamcity[testFinished name='Exceptions matchers' duration="{duration}"] ##teamcity[testStarted name='Expected exceptions that don|'t throw or unexpected exceptions fail the test'] @@ -370,6 +390,10 @@ ##teamcity[testStarted name='FAIL aborts the test'] ##teamcity[testFailed name='FAIL aborts the test' message='Message.tests.cpp:|n...............................................................................|n|nMessage.tests.cpp:|nexplicit failure with message:|n "This is a failure"'] ##teamcity[testFinished name='FAIL aborts the test' duration="{duration}"] +##teamcity[testStarted name='FAIL can be nested in assertion'] +##teamcity[testIgnored name='FAIL can be nested in assertion' message='AssertionHandler.tests.cpp:|n...............................................................................|n|nAssertionHandler.tests.cpp:|nexplicit failure with message:|n "Throw a Catch::TestFailureException"- failure ignore as test marked as |'ok to fail|'|n'] +##teamcity[testIgnored name='FAIL can be nested in assertion' message='AssertionHandler.tests.cpp:|nunexpected exception with message:|n "{ nested assertion failed }"|n CHECK_NOTHROW( do_fail() )|nwith expansion:|n do_fail()|n- failure ignore as test marked as |'ok to fail|'|n'] +##teamcity[testFinished name='FAIL can be nested in assertion' duration="{duration}"] ##teamcity[testStarted name='FAIL does not require an argument'] ##teamcity[testFailed name='FAIL does not require an argument' message='Message.tests.cpp:|n...............................................................................|n|nMessage.tests.cpp:|nexplicit failure'] ##teamcity[testFinished name='FAIL does not require an argument' duration="{duration}"] @@ -431,8 +455,8 @@ ##teamcity[testFinished name='Inequality checks that should succeed' duration="{duration}"] ##teamcity[testStarted name='JsonWriter'] ##teamcity[testFinished name='JsonWriter' duration="{duration}"] -##teamcity[testStarted name='JsonWriter escapes charaters in strings properly'] -##teamcity[testFinished name='JsonWriter escapes charaters in strings properly' duration="{duration}"] +##teamcity[testStarted name='JsonWriter escapes characters in strings properly'] +##teamcity[testFinished name='JsonWriter escapes characters in strings properly' duration="{duration}"] ##teamcity[testStarted name='Lambdas in assertions'] ##teamcity[testFinished name='Lambdas in assertions' duration="{duration}"] ##teamcity[testStarted name='Less-than inequalities with different epsilons'] @@ -566,6 +590,16 @@ ##teamcity[testFinished name='Scenario: This is a really long scenario name to see how the list command deals with wrapping' duration="{duration}"] ##teamcity[testStarted name='Scenario: Vector resizing affects size and capacity'] ##teamcity[testFinished name='Scenario: Vector resizing affects size and capacity' duration="{duration}"] +##teamcity[testStarted name='Scoped message applies to all assertions in scope'] +##teamcity[testFailed name='Scoped message applies to all assertions in scope' message='Message.tests.cpp:|n...............................................................................|n|nMessage.tests.cpp:|nexpression failed with message:|n "This will be reported multiple times"|n CHECK( false )|nwith expansion:|n false|n'] +##teamcity[testFailed name='Scoped message applies to all assertions in scope' message='Message.tests.cpp:|nexpression failed with message:|n "This will be reported multiple times"|n CHECK( false )|nwith expansion:|n false|n'] +##teamcity[testFinished name='Scoped message applies to all assertions in scope' duration="{duration}"] +##teamcity[testStarted name='Scoped messages do not leave block with an exception'] +##teamcity[testFailed name='Scoped messages do not leave block with an exception' message='Message.tests.cpp:|n...............................................................................|n|nMessage.tests.cpp:|nexpression failed with message:|n "Should be in scope at the end"|n REQUIRE( false )|nwith expansion:|n false|n'] +##teamcity[testFinished name='Scoped messages do not leave block with an exception' duration="{duration}"] +##teamcity[testStarted name='Scoped messages outlive section end'] +##teamcity[testFailed name='Scoped messages outlive section end' message='Message.tests.cpp:|n...............................................................................|n|nMessage.tests.cpp:|nexpression failed with message:|n "Should survive a section end"|n REQUIRE( false )|nwith expansion:|n false|n'] +##teamcity[testFinished name='Scoped messages outlive section end' duration="{duration}"] ##teamcity[testStarted name='Sends stuff to stdout and stderr'] ##teamcity[testStdOut name='Sends stuff to stdout and stderr' out='A string sent directly to stdout|n'] ##teamcity[testStdErr name='Sends stuff to stdout and stderr' out='A string sent directly to stderr|nA string sent to stderr via clog|n'] @@ -634,6 +668,14 @@ ##teamcity[testFinished name='TemplateTest: vectors can be sized and resized - std::string' duration="{duration}"] ##teamcity[testStarted name='TemplateTest: vectors can be sized and resized - std::tuple'] ##teamcity[testFinished name='TemplateTest: vectors can be sized and resized - std::tuple' duration="{duration}"] +##teamcity[testStarted name='TemplateTestSig: compiles with a single int parameter - 1'] +##teamcity[testFinished name='TemplateTestSig: compiles with a single int parameter - 1' duration="{duration}"] +##teamcity[testStarted name='TemplateTestSig: compiles with a single int parameter - 3'] +##teamcity[testFinished name='TemplateTestSig: compiles with a single int parameter - 3' duration="{duration}"] +##teamcity[testStarted name='TemplateTestSig: compiles with a single int parameter - 6'] +##teamcity[testFinished name='TemplateTestSig: compiles with a single int parameter - 6' duration="{duration}"] +##teamcity[testStarted name='TemplateTestSig: compiles with two type parameters - int,int'] +##teamcity[testFinished name='TemplateTestSig: compiles with two type parameters - int,int' duration="{duration}"] ##teamcity[testStarted name='TemplateTestSig: vectors can be sized and resized - (std::tuple), 6'] ##teamcity[testFinished name='TemplateTestSig: vectors can be sized and resized - (std::tuple), 6' duration="{duration}"] ##teamcity[testStarted name='TemplateTestSig: vectors can be sized and resized - float,4'] @@ -743,7 +785,7 @@ ##teamcity[testFailed name='When unchecked exceptions are thrown from functions they are always failures' message='Exception.tests.cpp:|n...............................................................................|n|nException.tests.cpp:|nunexpected exception with message:|n "expected exception"|n CHECK( thisThrows() == 0 )|nwith expansion:|n thisThrows() == 0|n'] ##teamcity[testFinished name='When unchecked exceptions are thrown from functions they are always failures' duration="{duration}"] ##teamcity[testStarted name='When unchecked exceptions are thrown from sections they are always failures'] -##teamcity[testFailed name='When unchecked exceptions are thrown from sections they are always failures' message='-------------------------------------------------------------------------------|nsection name|n-------------------------------------------------------------------------------|nException.tests.cpp:|n...............................................................................|n|nException.tests.cpp:|nunexpected exception with message:|n "unexpected exception"'] +##teamcity[testFailed name='When unchecked exceptions are thrown from sections they are always failures' message='-------------------------------------------------------------------------------|nsection name|n-------------------------------------------------------------------------------|nException.tests.cpp:|n...............................................................................|n|nException.tests.cpp:|nunexpected exception with message:|n "unexpected exception"|n {Unknown expression after the reported line}|nwith expansion:|n {Unknown expression after the reported line}|n'] ##teamcity[testFinished name='When unchecked exceptions are thrown from sections they are always failures' duration="{duration}"] ##teamcity[testStarted name='When unchecked exceptions are thrown, but caught, they do not affect the test'] ##teamcity[testFinished name='When unchecked exceptions are thrown, but caught, they do not affect the test' duration="{duration}"] @@ -960,6 +1002,8 @@ ##teamcity[testFinished name='stringify( vectors )' duration="{duration}"] ##teamcity[testStarted name='strlen3'] ##teamcity[testFinished name='strlen3' duration="{duration}"] +##teamcity[testStarted name='system_clock timepoint with non-default duration'] +##teamcity[testFinished name='system_clock timepoint with non-default duration' duration="{duration}"] ##teamcity[testStarted name='tables'] ##teamcity[testFinished name='tables' duration="{duration}"] ##teamcity[testStarted name='tags with dots in later positions are not parsed as hidden'] diff --git a/tests/SelfTest/Baselines/xml.sw.approved.txt b/tests/SelfTest/Baselines/xml.sw.approved.txt index aa6bd6a..142d662 100644 --- a/tests/SelfTest/Baselines/xml.sw.approved.txt +++ b/tests/SelfTest/Baselines/xml.sw.approved.txt @@ -668,19 +668,51 @@ Nor would this - - failure to init - + + + {Unknown expression after the reported line} + + + {Unknown expression after the reported line} + + + failure to init + + + + + + + + bf.e == 1 + + + 1 == 1 + + + + + 1 == bf.e + + + 1 == 1 + +
- - answer := 42 - - - expected exception - + + + {Unknown expression after the reported line} + + + {Unknown expression after the reported line} + + + expected exception + +
@@ -2703,6 +2735,58 @@ Approx( 1.23399996757507324 ) + + + + i > 10 + + + 2 > 10 + + + + + foo( 2 ) == 2 + + + foo( 2 ) == 2 + + + { nested assertion failed } + + + + + true + + + true + + + + + + + + i > 10 + + + 2 > 10 + + + + + foo( 2 ) == 2 + + + foo( 2 ) == 2 + + + { nested assertion failed } + + + + @@ -3074,6 +3158,48 @@ Approx( 1.23399996757507324 )
+ + + a := 1 + + + + false + + + false + + + + + +
+ + a := 1 + + + + true + + + true + + + +
+ + a := 1 + + + + false + + + false + + + +
@@ -3755,6 +3881,65 @@ C + + + + !(ptr) + + + !0 + + + + + ptr == ptr + + + 0 == 0 + + + + + !(ptr != ptr) + + + !(0 != 0) + + + + + !(ptr < ptr) + + + !(0 < 0) + + + + + ptr <= ptr + + + 0 <= 0 + + + + + !(ptr > ptr) + + + !(0 > 0) + + + + + ptr >= ptr + + + 0 >= 0 + + + + @@ -4995,6 +5180,19 @@ Approx( 1.30000000000000004 )
+ + + + tos == tos + + + { stringification failed with an exception: "Invalid" } +== +{ stringification failed with an exception: "Invalid" } + + + + @@ -5069,6 +5267,23 @@ Approx( 1.30000000000000004 )
+ + + Throw a Catch::TestFailureException + + + + do_fail() + + + do_fail() + + + { nested assertion failed } + + + + @@ -6701,6 +6916,44 @@ Approx( 1.30000000000000004 ) +
+
+ + + gen.get() == 1 + + + 1 == 1 + + + + + gen.next() + + + true + + + + + gen.get() == 3 + + + 3 == 3 + + + + + !(gen.next()) + + + !false + + + +
+ +
@@ -9158,7 +9411,7 @@ Approx( 3.14150000000000018 )
- +
@@ -12877,6 +13130,73 @@ All available test cases:
+ + + This will be reported multiple times + + + + false + + + false + + + + This will be reported multiple times + + + + false + + + false + + + + + + + Should be in scope at the end + + + + false + + + false + + + + + +
+ + Should survive a section end + + + + true + + + true + + + +
+ + Should survive a section end + + + + false + + + false + + + +
@@ -14483,6 +14803,18 @@ Message from section two
+ + + + + + + + + + + + @@ -18337,9 +18669,17 @@ Approx( 1.23999999999999999 )
- - unexpected exception - + + + {Unknown expression after the reported line} + + + {Unknown expression after the reported line} + + + unexpected exception + +
@@ -21094,6 +21434,19 @@ Approx( -1.95996398454005449 )
+ + + + tp1 == tp2 + + + {iso8601-timestamp} +== +{iso8601-timestamp} + + + + @@ -21971,6 +22324,6 @@ Approx( -1.95996398454005449 ) - - + + diff --git a/tests/SelfTest/Baselines/xml.sw.multi.approved.txt b/tests/SelfTest/Baselines/xml.sw.multi.approved.txt index 7f3b498..6256233 100644 --- a/tests/SelfTest/Baselines/xml.sw.multi.approved.txt +++ b/tests/SelfTest/Baselines/xml.sw.multi.approved.txt @@ -668,19 +668,51 @@ Nor would this
- - failure to init - + + + {Unknown expression after the reported line} + + + {Unknown expression after the reported line} + + + failure to init + + + + + + + + bf.e == 1 + + + 1 == 1 + + + + + 1 == bf.e + + + 1 == 1 + +
- - answer := 42 - - - expected exception - + + + {Unknown expression after the reported line} + + + {Unknown expression after the reported line} + + + expected exception + +
@@ -2703,6 +2735,58 @@ Approx( 1.23399996757507324 ) + + + + i > 10 + + + 2 > 10 + + + + + foo( 2 ) == 2 + + + foo( 2 ) == 2 + + + { nested assertion failed } + + + + + true + + + true + + + + + + + + i > 10 + + + 2 > 10 + + + + + foo( 2 ) == 2 + + + foo( 2 ) == 2 + + + { nested assertion failed } + + + + @@ -3074,6 +3158,48 @@ Approx( 1.23399996757507324 )
+ + + a := 1 + + + + false + + + false + + + + + +
+ + a := 1 + + + + true + + + true + + + +
+ + a := 1 + + + + false + + + false + + + +
@@ -3755,6 +3881,65 @@ C + + + + !(ptr) + + + !0 + + + + + ptr == ptr + + + 0 == 0 + + + + + !(ptr != ptr) + + + !(0 != 0) + + + + + !(ptr < ptr) + + + !(0 < 0) + + + + + ptr <= ptr + + + 0 <= 0 + + + + + !(ptr > ptr) + + + !(0 > 0) + + + + + ptr >= ptr + + + 0 >= 0 + + + + @@ -4995,6 +5180,19 @@ Approx( 1.30000000000000004 )
+ + + + tos == tos + + + { stringification failed with an exception: "Invalid" } +== +{ stringification failed with an exception: "Invalid" } + + + + @@ -5069,6 +5267,23 @@ Approx( 1.30000000000000004 ) + + + Throw a Catch::TestFailureException + + + + do_fail() + + + do_fail() + + + { nested assertion failed } + + + + @@ -6701,6 +6916,44 @@ Approx( 1.30000000000000004 ) +
+
+ + + gen.get() == 1 + + + 1 == 1 + + + + + gen.next() + + + true + + + + + gen.get() == 3 + + + 3 == 3 + + + + + !(gen.next()) + + + !false + + + +
+ +
@@ -9158,7 +9411,7 @@ Approx( 3.14150000000000018 )
- +
@@ -12877,6 +13130,73 @@ All available test cases:
+ + + This will be reported multiple times + + + + false + + + false + + + + This will be reported multiple times + + + + false + + + false + + + + + + + Should be in scope at the end + + + + false + + + false + + + + + +
+ + Should survive a section end + + + + true + + + true + + + +
+ + Should survive a section end + + + + false + + + false + + + +
@@ -14483,6 +14803,18 @@ Message from section two
+ + + + + + + + + + + + @@ -18337,9 +18669,17 @@ Approx( 1.23999999999999999 )
- - unexpected exception - + + + {Unknown expression after the reported line} + + + {Unknown expression after the reported line} + + + unexpected exception + +
@@ -21093,6 +21433,19 @@ Approx( -1.95996398454005449 )
+ + + + tp1 == tp2 + + + {iso8601-timestamp} +== +{iso8601-timestamp} + + + + @@ -21970,6 +22323,6 @@ Approx( -1.95996398454005449 ) - - + + diff --git a/tests/SelfTest/IntrospectiveTests/AssertionHandler.tests.cpp b/tests/SelfTest/IntrospectiveTests/AssertionHandler.tests.cpp index ab09607..15966a0 100644 --- a/tests/SelfTest/IntrospectiveTests/AssertionHandler.tests.cpp +++ b/tests/SelfTest/IntrospectiveTests/AssertionHandler.tests.cpp @@ -15,3 +15,28 @@ TEST_CASE( "Incomplete AssertionHandler", "[assertion-handler][!shouldfail]" ) { "Dummy", Catch::ResultDisposition::Normal ); } + + +static int foo( int i ) { + REQUIRE( i > 10 ); + return 42; +} + +TEST_CASE( "Assertions can be nested - CHECK", "[assertions][!shouldfail]" ) { + CHECK( foo( 2 ) == 2 ); + // We should hit this, because CHECK continues on failure + CHECK( true ); +} + +TEST_CASE( "Assertions can be nested - REQUIRE", "[assertions][!shouldfail]" ) { + REQUIRE( foo( 2 ) == 2 ); + // We should not hit this, because REQUIRE does not continue on failure + CHECK( true ); +} + +static void do_fail() { FAIL( "Throw a Catch::TestFailureException" ); } + +TEST_CASE( "FAIL can be nested in assertion", "[assertions][!shouldfail]" ) { + // Fails, but the error message makes sense. + CHECK_NOTHROW( do_fail() ); +} diff --git a/tests/SelfTest/IntrospectiveTests/Clara.tests.cpp b/tests/SelfTest/IntrospectiveTests/Clara.tests.cpp index 53023b5..0fadad6 100644 --- a/tests/SelfTest/IntrospectiveTests/Clara.tests.cpp +++ b/tests/SelfTest/IntrospectiveTests/Clara.tests.cpp @@ -25,19 +25,19 @@ TEST_CASE("is_unary_function", "[clara][compilation]") { auto nullary1 = []() {}; auto nullary2 = []() {return 42;}; - STATIC_REQUIRE(Catch::Clara::Detail::is_unary_function::value); - STATIC_REQUIRE(Catch::Clara::Detail::is_unary_function::value); - STATIC_REQUIRE(Catch::Clara::Detail::is_unary_function::value); - STATIC_REQUIRE(Catch::Clara::Detail::is_unary_function::value); - STATIC_REQUIRE(Catch::Clara::Detail::is_unary_function::value); - STATIC_REQUIRE(Catch::Clara::Detail::is_unary_function::value); - - STATIC_REQUIRE_FALSE(Catch::Clara::Detail::is_unary_function::value); - STATIC_REQUIRE_FALSE(Catch::Clara::Detail::is_unary_function::value); - STATIC_REQUIRE_FALSE(Catch::Clara::Detail::is_unary_function::value); - STATIC_REQUIRE_FALSE(Catch::Clara::Detail::is_unary_function::value); - STATIC_REQUIRE_FALSE(Catch::Clara::Detail::is_unary_function::value); - STATIC_REQUIRE_FALSE(Catch::Clara::Detail::is_unary_function::value); + STATIC_REQUIRE(Catch::Clara::Detail::is_unary_function_v); + STATIC_REQUIRE(Catch::Clara::Detail::is_unary_function_v); + STATIC_REQUIRE(Catch::Clara::Detail::is_unary_function_v); + STATIC_REQUIRE(Catch::Clara::Detail::is_unary_function_v); + STATIC_REQUIRE(Catch::Clara::Detail::is_unary_function_v); + STATIC_REQUIRE(Catch::Clara::Detail::is_unary_function_v); + + STATIC_REQUIRE_FALSE(Catch::Clara::Detail::is_unary_function_v); + STATIC_REQUIRE_FALSE(Catch::Clara::Detail::is_unary_function_v); + STATIC_REQUIRE_FALSE(Catch::Clara::Detail::is_unary_function_v); + STATIC_REQUIRE_FALSE(Catch::Clara::Detail::is_unary_function_v); + STATIC_REQUIRE_FALSE(Catch::Clara::Detail::is_unary_function_v); + STATIC_REQUIRE_FALSE(Catch::Clara::Detail::is_unary_function_v); } diff --git a/tests/SelfTest/IntrospectiveTests/GeneratorsImpl.tests.cpp b/tests/SelfTest/IntrospectiveTests/GeneratorsImpl.tests.cpp index 14c9011..c044547 100644 --- a/tests/SelfTest/IntrospectiveTests/GeneratorsImpl.tests.cpp +++ b/tests/SelfTest/IntrospectiveTests/GeneratorsImpl.tests.cpp @@ -85,6 +85,19 @@ TEST_CASE("Generators internals", "[generators][internals]") { filter([](int) { return false; }, values({ 1, 2, 3 })), Catch::GeneratorException); } + + // Non-trivial usage + SECTION("Out-of-line predicates are copied into the generator") { + auto evilNumber = Catch::Detail::make_unique(2); + auto gen = [&]{ + const auto predicate = [&](int i) { return i != *evilNumber; }; + return filter(predicate, values({ 2, 1, 2, 3, 2, 2 })); + }(); + REQUIRE(gen.get() == 1); + REQUIRE(gen.next()); + REQUIRE(gen.get() == 3); + REQUIRE_FALSE(gen.next()); + } } SECTION("Take generator") { SECTION("Take less") { diff --git a/tests/SelfTest/IntrospectiveTests/Integer.tests.cpp b/tests/SelfTest/IntrospectiveTests/Integer.tests.cpp index 8955f40..4ce2ae6 100644 --- a/tests/SelfTest/IntrospectiveTests/Integer.tests.cpp +++ b/tests/SelfTest/IntrospectiveTests/Integer.tests.cpp @@ -22,7 +22,7 @@ namespace { ExtendedMultResult{ upper_result, lower_result } ); } - // Simple (and slow) implmentation of extended multiplication for tests + // Simple (and slow) implementation of extended multiplication for tests constexpr Catch::Detail::ExtendedMultResult extendedMultNaive( std::uint64_t lhs, std::uint64_t rhs ) { // This is a simple long multiplication, where we split lhs and rhs diff --git a/tests/SelfTest/IntrospectiveTests/Json.tests.cpp b/tests/SelfTest/IntrospectiveTests/Json.tests.cpp index 8204e3c..fae1c84 100644 --- a/tests/SelfTest/IntrospectiveTests/Json.tests.cpp +++ b/tests/SelfTest/IntrospectiveTests/Json.tests.cpp @@ -7,6 +7,8 @@ // SPDX-License-Identifier: BSL-1.0 #include +#include +#include #include #include @@ -17,7 +19,6 @@ namespace { static std::ostream& operator<<( std::ostream& os, Custom const& ) { return os << "custom"; } -} // namespace TEST_CASE( "JsonWriter", "[JSON][JsonWriter]" ) { @@ -111,7 +112,7 @@ TEST_CASE( "JsonWriter", "[JSON][JsonWriter]" ) { } } -TEST_CASE( "JsonWriter escapes charaters in strings properly", "[JsonWriter]" ) { +TEST_CASE( "JsonWriter escapes characters in strings properly", "[JsonWriter]" ) { std::stringstream sstream; SECTION( "Quote in a string is escaped" ) { Catch::JsonValueWriter{ sstream }.write( "\"" ); @@ -150,3 +151,28 @@ TEST_CASE( "JsonWriter escapes charaters in strings properly", "[JsonWriter]" ) REQUIRE( sstream.str() == "\"\\\\/\\t\\r\\n\"" ); } } + +TEST_CASE( "JsonWriter benchmarks", "[JsonWriter][!benchmark]" ) { + const auto input_length = GENERATE( as{}, 10, 100, 10'000 ); + std::string test_input( input_length, 'a' ); + BENCHMARK_ADVANCED( "write string, no-escaping, len=" + + std::to_string( input_length ) )( + Catch::Benchmark::Chronometer meter ) { + std::stringstream sstream; + meter.measure( [&]( int ) { + Catch::JsonValueWriter( sstream ).write( test_input ); + } ); + }; + + std::string escape_input( input_length, '\b' ); + BENCHMARK_ADVANCED( "write string, all-escaped, len=" + + std::to_string( input_length ) )( + Catch::Benchmark::Chronometer meter ) { + std::stringstream sstream; + meter.measure( [&]( int ) { + Catch::JsonValueWriter( sstream ).write( escape_input ); + } ); + }; +} + +} // namespace diff --git a/tests/SelfTest/IntrospectiveTests/RandomNumberGeneration.tests.cpp b/tests/SelfTest/IntrospectiveTests/RandomNumberGeneration.tests.cpp index 8932321..5166070 100644 --- a/tests/SelfTest/IntrospectiveTests/RandomNumberGeneration.tests.cpp +++ b/tests/SelfTest/IntrospectiveTests/RandomNumberGeneration.tests.cpp @@ -151,7 +151,7 @@ TEMPLATE_TEST_CASE( "uniform_integer_distribution can handle unit ranges", Catch::SimplePcg32 pcg( seed ); // We check unitary ranges of 3 different values, min for type, max for type, - // some value inbetween just to make sure + // some value in between just to make sure SECTION("lowest value") { constexpr auto lowest = std::numeric_limits::min(); Catch::uniform_integer_distribution dist( lowest, lowest ); @@ -180,7 +180,7 @@ TEST_CASE( "uniform_integer_distribution can handle boolean unit ranges", Catch::SimplePcg32 pcg( seed ); // We check unitary ranges of 3 different values, min for type, max for - // type, some value inbetween just to make sure + // type, some value in between just to make sure SECTION( "lowest value" ) { Catch::uniform_integer_distribution dist( false, false ); CheckReturnValue( dist, pcg, false ); diff --git a/tests/SelfTest/IntrospectiveTests/ToString.tests.cpp b/tests/SelfTest/IntrospectiveTests/ToString.tests.cpp index e190460..01d989a 100644 --- a/tests/SelfTest/IntrospectiveTests/ToString.tests.cpp +++ b/tests/SelfTest/IntrospectiveTests/ToString.tests.cpp @@ -6,11 +6,13 @@ // SPDX-License-Identifier: BSL-1.0 +#include +#include +#include +#include #include #include #include -#include -#include #include @@ -103,18 +105,62 @@ TEST_CASE( "#2944 - Stringifying dates before 1970 should not crash", "[.approva using Catch::Matchers::Equals; using Days = std::chrono::duration>; using SysDays = std::chrono::time_point; - Catch::StringMaker sm; + using SM = Catch::StringMaker; // Check simple date first const SysDays post1970{ Days{ 1 } }; - auto converted_post = sm.convert( post1970 ); + auto converted_post = SM::convert( post1970 ); REQUIRE( converted_post == "1970-01-02T00:00:00Z" ); const SysDays pre1970{ Days{ -1 } }; - auto converted_pre = sm.convert( pre1970 ); + auto converted_pre = SM::convert( pre1970 ); REQUIRE_THAT( converted_pre, Equals( "1969-12-31T00:00:00Z" ) || Equals( "gmtime from provided timepoint has failed. This " "happens e.g. with pre-1970 dates using Microsoft libc" ) ); } + +namespace { + struct ThrowsOnStringification { + friend bool operator==( ThrowsOnStringification, + ThrowsOnStringification ) { + return true; + } + }; +} + +template <> +struct Catch::StringMaker { + static std::string convert(ThrowsOnStringification) { + throw std::runtime_error( "Invalid" ); + } +}; + +TEST_CASE( "Exception thrown inside stringify does not fail the test", "[toString]" ) { + ThrowsOnStringification tos; + CHECK( tos == tos ); +} + +TEST_CASE( "string escaping benchmark", "[toString][!benchmark]" ) { + const auto input_length = GENERATE( as{}, 10, 100, 10'000, 100'000 ); + std::string test_input( input_length, 'a' ); + BENCHMARK( "no-escape string, no-escaping, len=" + + std::to_string( input_length ) ) { + return Catch::Detail::convertIntoString( test_input, false ); + }; + BENCHMARK( "no-escape string, escaping, len=" + + std::to_string( input_length ) ) { + return Catch::Detail::convertIntoString( test_input, true ); + }; + + std::string escape_input( input_length, '\r' ); + BENCHMARK( "full escape string, no-escaping, len=" + + std::to_string( input_length ) ) { + return Catch::Detail::convertIntoString( escape_input, false ); + }; + BENCHMARK( "full escape string, escaping, len=" + + std::to_string( input_length ) ) { + return Catch::Detail::convertIntoString( escape_input, true ); + }; +} diff --git a/tests/SelfTest/IntrospectiveTests/Xml.tests.cpp b/tests/SelfTest/IntrospectiveTests/Xml.tests.cpp index b5982b8..a53bae3 100644 --- a/tests/SelfTest/IntrospectiveTests/Xml.tests.cpp +++ b/tests/SelfTest/IntrospectiveTests/Xml.tests.cpp @@ -7,13 +7,16 @@ // SPDX-License-Identifier: BSL-1.0 #include -#include - +#include +#include #include +#include #include #include +namespace { + static std::string encode( std::string const& str, Catch::XmlEncode::ForWhat forWhat = Catch::XmlEncode::ForTextNodes ) { Catch::ReusableStringStream oss; oss << Catch::XmlEncode( str, forWhat ); @@ -181,3 +184,20 @@ TEST_CASE("XmlWriter escapes attributes properly", "[XML][XmlWriter][approvals]" REQUIRE_THAT(stream.str(), ContainsSubstring(R"(some-attribute="Special chars need escaping: < > ' " &")")); } + +TEST_CASE( "XmlWriter benchmarks", "[XML][XmlWriter][!benchmark]" ) { + const auto input_length = GENERATE( as{}, 10, 100, 10'000 ); + std::string test_input( input_length, 'a' ); + BENCHMARK_ADVANCED( "write string, no-escaping, len=" + + std::to_string( input_length ) ) { + return encode( test_input ); + }; + + std::string escape_input( input_length, '\b' ); + BENCHMARK_ADVANCED( "write string, all-escaped, len=" + + std::to_string( input_length ) ) { + return encode( escape_input ); + }; +} + +} // namespace diff --git a/tests/SelfTest/TestRegistrations.cpp b/tests/SelfTest/TestRegistrations.cpp index d7a6966..1a7be39 100644 --- a/tests/SelfTest/TestRegistrations.cpp +++ b/tests/SelfTest/TestRegistrations.cpp @@ -54,6 +54,7 @@ class ValidatingTestListener : public Catch::EventListenerBase { ValidatingTestListener(Catch::IConfig const* config) : EventListenerBase(config) { m_preferences.shouldReportAllAssertions = true; + m_preferences.shouldReportAllAssertionStarts = true; } void testRunStarting( Catch::TestRunInfo const& ) override { diff --git a/tests/SelfTest/UsageTests/Compilation.tests.cpp b/tests/SelfTest/UsageTests/Compilation.tests.cpp index 7a6a1f2..d881335 100644 --- a/tests/SelfTest/UsageTests/Compilation.tests.cpp +++ b/tests/SelfTest/UsageTests/Compilation.tests.cpp @@ -144,6 +144,23 @@ TEST_CASE("#1027: Bitfields can be captured") { REQUIRE(0 == y.v); } +TEST_CASE( "#3001: Enum-based bitfields can be captured" ) { + enum E { + ZERO = 0, + ONE = 1, + TWO = 2, + }; + + struct BF { + E e : 2; + }; + + BF bf{}; + bf.e = ONE; + REQUIRE( bf.e == 1 ); + REQUIRE( 1 == bf.e ); +} + // Comparison operators can return non-booleans. // This is unusual, but should be supported. TEST_CASE("#1147") { diff --git a/tests/SelfTest/UsageTests/Message.tests.cpp b/tests/SelfTest/UsageTests/Message.tests.cpp index 7626e00..07d3ee7 100644 --- a/tests/SelfTest/UsageTests/Message.tests.cpp +++ b/tests/SelfTest/UsageTests/Message.tests.cpp @@ -310,3 +310,53 @@ TEST_CASE( "INFO and UNSCOPED_INFO can stream multiple arguments", << " parts." ); FAIL( "Show infos!" ); } + +TEST_CASE( "Scoped messages do not leave block with an exception", "[messages][info][.failing]" ) { + INFO( "Should be in scope at the end" ); + { INFO( "This should go out of scope immediately" ); } + + try { + INFO( "Should not be in scope at the end" ); + throw std::runtime_error( "ex" ); + } catch (std::exception const&) {} + + REQUIRE( false ); +} + +TEST_CASE( "Captures do not leave block with an exception", + "[messages][capture][.failing]" ) { + int a = 1, b = 2, c = 3; + + CAPTURE( a ); + { CAPTURE( b ); } + + try { + CAPTURE( c ); + throw std::runtime_error( "ex" ); + } catch ( std::exception const& ) {} + + REQUIRE( false ); +} + +TEST_CASE( "Scoped messages outlive section end", + "[messages][info][.failing]" ) { + INFO( "Should survive a section end" ); + SECTION( "Dummy section" ) { CHECK( true ); } + + REQUIRE( false ); +} + +TEST_CASE( "Captures outlive section end", "[messages][info][.failing]" ) { + int a = 1; + CAPTURE( a ); + SECTION( "Dummy section" ) { CHECK( true ); } + + REQUIRE( false ); +} + +TEST_CASE( "Scoped message applies to all assertions in scope", + "[messages][info][.failing]" ) { + INFO( "This will be reported multiple times" ); + CHECK( false ); + CHECK( false ); +} diff --git a/tests/SelfTest/UsageTests/Misc.tests.cpp b/tests/SelfTest/UsageTests/Misc.tests.cpp index 3697f06..dec51ab 100644 --- a/tests/SelfTest/UsageTests/Misc.tests.cpp +++ b/tests/SelfTest/UsageTests/Misc.tests.cpp @@ -56,7 +56,7 @@ namespace { CATCH_INTERNAL_START_WARNINGS_SUPPRESSION CATCH_INTERNAL_SUPPRESS_GLOBALS_WARNINGS - static AutoTestReg autoTestReg; + static const AutoTestReg autoTestReg; CATCH_INTERNAL_STOP_WARNINGS_SUPPRESSION template @@ -334,6 +334,10 @@ TEMPLATE_TEST_CASE( "TemplateTest: vectors can be sized and resized", "[vector][ } } +TEMPLATE_TEST_CASE_SIG("TemplateTestSig: compiles with a single int parameter", "[template][singleint]", ((int V), V), 1, 3, 6) {} + +TEMPLATE_TEST_CASE_SIG("TemplateTestSig: compiles with two type parameters", "[template][onlytypes]", ((typename U, typename V), U, V), (int,int)) {} + TEMPLATE_TEST_CASE_SIG("TemplateTestSig: vectors can be sized and resized", "[vector][template][nttp]", ((typename TestType, int V), TestType, V), (int,5), (float,4), (std::string,15), ((std::tuple), 6)) { std::vector v(V); @@ -558,3 +562,15 @@ TEST_CASE("Validate SEH behavior - no crash for stack unwinding", "[approvals][! } #endif // _MSC_VER + +TEST_CASE( "Comparing (and stringifying) volatile pointers works", + "[volatile]" ) { + volatile int* ptr = nullptr; + REQUIRE_FALSE( ptr ); + REQUIRE( ptr == ptr ); + REQUIRE_FALSE( ptr != ptr ); + REQUIRE_FALSE( ptr < ptr ); + REQUIRE( ptr <= ptr ); + REQUIRE_FALSE( ptr > ptr ); + REQUIRE( ptr >= ptr ); +} diff --git a/tests/SelfTest/UsageTests/ToStringChrono.tests.cpp b/tests/SelfTest/UsageTests/ToStringChrono.tests.cpp index 744b899..931b4b2 100644 --- a/tests/SelfTest/UsageTests/ToStringChrono.tests.cpp +++ b/tests/SelfTest/UsageTests/ToStringChrono.tests.cpp @@ -49,3 +49,9 @@ TEST_CASE("Stringifying std::chrono::time_point", "[toString][chrono][!no auto later2 = now2 + std::chrono::minutes(2); REQUIRE(now2 != later2); } + +TEST_CASE( "system_clock timepoint with non-default duration", "[toString][chrono]" ) { + std::chrono::time_point + tp1, tp2; + CHECK( tp1 == tp2 ); +} diff --git a/tests/TestScripts/ConfigureTestsCommon.py b/tests/TestScripts/ConfigureTestsCommon.py index 13b1d92..66896a8 100644 --- a/tests/TestScripts/ConfigureTestsCommon.py +++ b/tests/TestScripts/ConfigureTestsCommon.py @@ -15,7 +15,7 @@ def configure_and_build(source_path: str, project_path: str, options: List[Tuple[str, str]]): base_configure_cmd = ['cmake', '-B{}'.format(project_path), - '-H{}'.format(source_path), + '-S{}'.format(source_path), '-DCMAKE_BUILD_TYPE=Debug', '-DCATCH_DEVELOPMENT_BUILD=ON'] for option, value in options: diff --git a/tests/TestScripts/DiscoverTests/CMakeLists.txt b/tests/TestScripts/DiscoverTests/CMakeLists.txt index f0b49f4..1bcefbb 100644 --- a/tests/TestScripts/DiscoverTests/CMakeLists.txt +++ b/tests/TestScripts/DiscoverTests/CMakeLists.txt @@ -1,24 +1,18 @@ cmake_minimum_required(VERSION 3.16) +project(discover-tests-test LANGUAGES CXX) -project(discover-tests-test - LANGUAGES CXX -) - -add_executable(tests - register-tests.cpp -) - +add_executable(tests register-tests.cpp) add_subdirectory(${CATCH2_PATH} catch2-build) target_link_libraries(tests PRIVATE Catch2::Catch2WithMain) enable_testing() include(Catch) set(extra_args) -if (CMAKE_VERSION GREATER_EQUAL 3.27) - list(APPEND extra_args - DL_PATHS "${CMAKE_CURRENT_LIST_DIR};${CMAKE_CURRENT_LIST_DIR}/.." - ) -endif () +if(CMAKE_VERSION GREATER_EQUAL 3.27) + list(APPEND extra_args + DL_PATHS "${CMAKE_CURRENT_LIST_DIR};${CMAKE_CURRENT_LIST_DIR}/.." + ) +endif() catch_discover_tests( tests ADD_TAGS_AS_LABELS diff --git a/tests/TestScripts/DiscoverTests/VerifyRegistration.py b/tests/TestScripts/DiscoverTests/VerifyRegistration.py index 33150ca..7d9862c 100644 --- a/tests/TestScripts/DiscoverTests/VerifyRegistration.py +++ b/tests/TestScripts/DiscoverTests/VerifyRegistration.py @@ -17,7 +17,7 @@ TestInfo = namedtuple('TestInfo', ['name', 'tags']) -cmake_version_regex = re.compile('cmake version (\d+)\.(\d+)\.(\d+)') +cmake_version_regex = re.compile(r'cmake version (\d+)\.(\d+)\.(\d+)') def get_cmake_version(): result = subprocess.run(['cmake', '--version'], diff --git a/tests/TestScripts/testBazelExitGuardFile.py b/tests/TestScripts/testBazelExitGuardFile.py new file mode 100755 index 0000000..b4d31b5 --- /dev/null +++ b/tests/TestScripts/testBazelExitGuardFile.py @@ -0,0 +1,88 @@ +#!/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 os +import sys +import subprocess + +def generate_path_suffix() -> str: + return os.urandom(16).hex()[:16] + + +def run_common(cmd, env = None): + cmd_env = env if env is not None else os.environ.copy() + print('Running:', cmd) + + try: + ret = subprocess.run( + cmd, + stdout=subprocess.PIPE, + stderr=subprocess.PIPE, + check=True, + universal_newlines=True, + env=cmd_env + ) + except subprocess.SubprocessError as ex: + print('Could not run "{}"'.format(cmd)) + print("Return code: {}".format(ex.returncode)) + print("stdout: {}".format(ex.stdout)) + print("stderr: {}".format(ex.stderr)) + raise + + +def test_bazel_env_vars(bin_path, guard_path): + env = os.environ.copy() + env["TEST_PREMATURE_EXIT_FILE"] = guard_path + env["BAZEL_TEST"] = '1' + run_common([bin_path], env) + +def test_cli_parameter(bin_path, guard_path): + cmd = [ + bin_path, + '--premature-exit-guard-file', + guard_path + ] + run_common(cmd) + +def test_no_crash(bin_path, guard_path): + cmd = [ + bin_path, + '--premature-exit-guard-file', + guard_path, + # Disable the quick-exit test + '~[quick_exit]' + ] + run_common(cmd) + +checks = { + 'bazel': (test_bazel_env_vars, True), + 'cli': (test_cli_parameter, True), + 'no-crash': (test_no_crash, False), +} + + +bin_path = os.path.abspath(sys.argv[1]) +output_dir = os.path.abspath(sys.argv[2]) +test_kind = sys.argv[3] +guard_file_path = os.path.join(output_dir, f"guard_file.{generate_path_suffix()}") +print(f'Guard file path: "{guard_file_path}"') + +check_func, file_should_exist = checks[test_kind] +check_func(bin_path, guard_file_path) + +assert os.path.exists(guard_file_path) == file_should_exist +# With randomly generated file suffix, we should not run into name conflicts. +# However, we try to cleanup anyway, to avoid having infinity files in +# long living build directories. +if file_should_exist: + try: + os.remove(guard_file_path) + except Exception as ex: + print(f'Could not remove file {guard_file_path} because: {ex}') + diff --git a/tests/TestScripts/testBazelSharding.py b/tests/TestScripts/testBazelSharding.py index d4a9aee..2359446 100755 --- a/tests/TestScripts/testBazelSharding.py +++ b/tests/TestScripts/testBazelSharding.py @@ -46,10 +46,15 @@ env["TEST_TOTAL_SHARDS"] = "2" env["TEST_SHARD_STATUS_FILE"] = info_file_path +cmd = [ + bin_path, + # We rely on the test order being deterministic + '--order', 'decl' +] try: ret = subprocess.run( - bin_path, + cmd, stdout=subprocess.PIPE, stderr=subprocess.PIPE, check=True, diff --git a/tools/misc/CMakeLists.txt b/tools/misc/CMakeLists.txt index 59811df..39e816a 100644 --- a/tools/misc/CMakeLists.txt +++ b/tools/misc/CMakeLists.txt @@ -1,11 +1,8 @@ cmake_minimum_required(VERSION 3.16) - -project(CatchCoverageHelper) +project(CatchCoverageHelper LANGUAGES CXX) add_executable(CoverageHelper coverage-helper.cpp) -set_property(TARGET CoverageHelper PROPERTY CXX_STANDARD 11) -set_property(TARGET CoverageHelper PROPERTY CXX_STANDARD_REQUIRED ON) -set_property(TARGET CoverageHelper PROPERTY CXX_EXTENSIONS OFF) -if (MSVC) - target_compile_options( CoverageHelper PRIVATE /W4 /w44265 /WX /w44061 /w44062 ) +target_compile_features(CoverageHelper PRIVATE cxx_std_11) +if(MSVC) + target_compile_options(CoverageHelper PRIVATE /W4 /w44265 /WX /w44061 /w44062) endif() diff --git a/tools/misc/appveyorBuildConfigurationScript.bat b/tools/misc/appveyorBuildConfigurationScript.bat index 727f829..75b3955 100644 --- a/tools/misc/appveyorBuildConfigurationScript.bat +++ b/tools/misc/appveyorBuildConfigurationScript.bat @@ -8,14 +8,14 @@ SETLOCAL EnableDelayedExpansion if "%CONFIGURATION%"=="Debug" ( if "%coverage%"=="1" ( @REM # coverage needs to build the special helper as well as the main - cmake -Htools/misc -Bbuild-misc -A%PLATFORM% || exit /b !ERRORLEVEL! + cmake -Stools/misc -Bbuild-misc -A%PLATFORM% || exit /b !ERRORLEVEL! cmake --build build-misc || exit /b !ERRORLEVEL! - cmake -H. -BBuild -A%PLATFORM% -DCATCH_TEST_USE_WMAIN=%wmain% -DMEMORYCHECK_COMMAND=build-misc\Debug\CoverageHelper.exe -DMEMORYCHECK_COMMAND_OPTIONS=--sep-- -DMEMORYCHECK_TYPE=Valgrind -DCATCH_BUILD_EXAMPLES=%examples% -DCATCH_BUILD_EXTRA_TESTS=%examples% -DCATCH_ENABLE_CONFIGURE_TESTS=%configure_tests% -DCATCH_DEVELOPMENT_BUILD=ON || exit /b !ERRORLEVEL! + cmake -S. -BBuild -A%PLATFORM% -DCATCH_TEST_USE_WMAIN=%wmain% -DMEMORYCHECK_COMMAND=build-misc\Debug\CoverageHelper.exe -DMEMORYCHECK_COMMAND_OPTIONS=--sep-- -DMEMORYCHECK_TYPE=Valgrind -DCATCH_BUILD_EXAMPLES=%examples% -DCATCH_BUILD_EXTRA_TESTS=%examples% -DCATCH_ENABLE_CONFIGURE_TESTS=%configure_tests% -DCATCH_DEVELOPMENT_BUILD=ON || exit /b !ERRORLEVEL! ) else ( @REM # We know that coverage is 0 - cmake -H. -BBuild -A%PLATFORM% -DCATCH_TEST_USE_WMAIN=%wmain% -DCATCH_BUILD_EXAMPLES=%examples% -DCATCH_BUILD_EXTRA_TESTS=%examples% -DCATCH_BUILD_SURROGATES=%surrogates% -DCATCH_DEVELOPMENT_BUILD=ON -DCATCH_ENABLE_CONFIGURE_TESTS=%configure_tests% || exit /b !ERRORLEVEL! + cmake -S. -BBuild -A%PLATFORM% -DCATCH_TEST_USE_WMAIN=%wmain% -DCATCH_BUILD_EXAMPLES=%examples% -DCATCH_BUILD_EXTRA_TESTS=%examples% -DCATCH_BUILD_SURROGATES=%surrogates% -DCATCH_DEVELOPMENT_BUILD=ON -DCATCH_ENABLE_CONFIGURE_TESTS=%configure_tests% || exit /b !ERRORLEVEL! ) ) if "%CONFIGURATION%"=="Release" ( - cmake -H. -BBuild -A%PLATFORM% -DCATCH_TEST_USE_WMAIN=%wmain% -DCATCH_DEVELOPMENT_BUILD=ON || exit /b !ERRORLEVEL! + cmake -S. -BBuild -A%PLATFORM% -DCATCH_TEST_USE_WMAIN=%wmain% -DCATCH_DEVELOPMENT_BUILD=ON || exit /b !ERRORLEVEL! ) diff --git a/tools/misc/installOpenCppCoverage.ps1 b/tools/misc/installOpenCppCoverage.ps1 index 215fe20..795b7c8 100644 --- a/tools/misc/installOpenCppCoverage.ps1 +++ b/tools/misc/installOpenCppCoverage.ps1 @@ -4,7 +4,7 @@ $installerPath = [System.IO.Path]::Combine($Env:USERPROFILE, "Downloads", "OpenC if(-Not (Test-Path $installerPath)) { Write-Host -ForegroundColor White ("Downloading OpenCppCoverage from: " + $downloadUrl) - Start-FileDownload $downloadUrl -FileName $installerPath + Start-BitsTransfer $downloadUrl -Destination $installerPath } Write-Host -ForegroundColor White "About to install OpenCppCoverage..." diff --git a/tools/scripts/approvalTests.py b/tools/scripts/approvalTests.py index 4146b64..144edc0 100755 --- a/tools/scripts/approvalTests.py +++ b/tools/scripts/approvalTests.py @@ -121,7 +121,7 @@ def filterLine(line, isCompact): # strip source line numbers # Note that this parser assumes an already normalized filepath from above, # and might break terribly if it is moved around before the normalization. - line = filelocParser.sub('\g:', line) + line = filelocParser.sub(r'\g:', line) line = lineNumberParser.sub(" ", line) @@ -130,7 +130,7 @@ def filterLine(line, isCompact): line = line.replace(': PASSED', ': passed') # strip out the test order number in TAP to avoid massive diffs for every change - line = tapTestNumParser.sub("\g<1> {test-number} -", line) + line = tapTestNumParser.sub(r"\g<1> {test-number} -", line) # strip Catch2 version number line = versionParser.sub("", line) @@ -148,7 +148,7 @@ def filterLine(line, isCompact): line = junitDurationsParser.sub(' time="{duration}"', line) line = durationParser.sub(' duration="{duration}"', line) line = timestampsParser.sub('{iso8601-timestamp}', line) - line = specialCaseParser.sub('file:\g<1>', line) + line = specialCaseParser.sub(r'file:\g<1>', line) line = sinceEpochParser.sub('{since-epoch-report}', line) return line diff --git a/tools/scripts/checkConvenienceHeaders.py b/tools/scripts/checkConvenienceHeaders.py index 41b52ce..0695752 100755 --- a/tools/scripts/checkConvenienceHeaders.py +++ b/tools/scripts/checkConvenienceHeaders.py @@ -21,11 +21,11 @@ import re def normalized_path(path): - """Replaces \ in paths on Windows with /""" + r"""Replaces \ in paths on Windows with /""" return path.replace('\\', '/') def normalized_paths(paths): - """Replaces \ with / in every path""" + r"""Replaces \ with / in every path""" return [normalized_path(path) for path in paths] source_path = catchPath + '/src/catch2' diff --git a/tools/scripts/releaseCommon.py b/tools/scripts/releaseCommon.py index 81efa76..920fb98 100644 --- a/tools/scripts/releaseCommon.py +++ b/tools/scripts/releaseCommon.py @@ -114,7 +114,7 @@ def updateVersionDefine(version): def updateVersionPlaceholder(filename, version): with open(filename, 'rb') as file: lines = file.readlines() - placeholderRegex = re.compile(b'Catch[0-9]? X.Y.Z') + placeholderRegex = re.compile(b'Catch[0-9]? v?X.Y.Z') replacement = 'Catch2 {}.{}.{}'.format(version.majorVersion, version.minorVersion, version.patchNumber).encode('ascii') with open(filename, 'wb') as file: for line in lines: